From 905b0231e93ce2409a45dd6c4f5f983689fdb790 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Wed, 1 Nov 2017 11:56:50 +0800 Subject: Add compass-deck RESTful API and DB Handlers for Compass Change-Id: I1ce411f279943764c286ea48dca9185d453cf254 Signed-off-by: Harry Huang --- compass-deck/apiclient/v1/__init__.py | 0 compass-deck/apiclient/v1/example.py | 305 ++++++++++++++++ compass-deck/apiclient/v1/restful.py | 655 ++++++++++++++++++++++++++++++++++ 3 files changed, 960 insertions(+) create mode 100644 compass-deck/apiclient/v1/__init__.py create mode 100755 compass-deck/apiclient/v1/example.py create mode 100644 compass-deck/apiclient/v1/restful.py (limited to 'compass-deck/apiclient/v1') diff --git a/compass-deck/apiclient/v1/__init__.py b/compass-deck/apiclient/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compass-deck/apiclient/v1/example.py b/compass-deck/apiclient/v1/example.py new file mode 100755 index 0000000..6f7a7f7 --- /dev/null +++ b/compass-deck/apiclient/v1/example.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Example code to deploy a cluster by compass client api.""" +import os +import re +import requests +import sys +import time + +from compass.apiclient.restful import Client + + +COMPASS_SERVER_URL = 'http://127.0.0.1/api' +SWITCH_IP = '10.145.81.220' +SWITCH_SNMP_VERSION = 'v2c' +SWITCH_SNMP_COMMUNITY = 'public' +# MACHINES_TO_ADD = ['00:11:20:30:40:01'] +CLUSTER_NAME = 'cluster2' +HOST_NAME_PREFIX = 'host' +SERVER_USERNAME = 'root' +SERVER_PASSWORD = 'root' +SERVICE_USERNAME = 'service' +SERVICE_PASSWORD = 'service' +CONSOLE_USERNAME = 'console' +CONSOLE_PASSWORD = 'console' +HA_VIP = '' +# NAMESERVERS = '192.168.10.6' +SEARCH_PATH = 'ods.com' +# GATEWAY = '192.168.10.6' +# PROXY = 'http://192.168.10.6:3128' +# NTP_SERVER = '192.168.10.6' +MANAGEMENT_IP_START = '192.168.10.130' +MANAGEMENT_IP_END = '192.168.10.254' +MANAGEMENT_IP_GATEWAY = '192.168.10.1' +MANAGEMENT_NETMASK = '255.255.255.0' +MANAGEMENT_NIC = 'eth0' +MANAGEMENT_PROMISC = 0 +TENANT_IP_START = '192.168.10.100' +TENANT_IP_END = '192.168.10.255' +TENANT_IP_GATEWAY = '192.168.10.1' +TENANT_NETMASK = '255.255.255.0' +TENANT_NIC = 'eth0' +TENANT_PROMISC = 0 +PUBLIC_IP_START = '12.234.32.100' +PUBLIC_IP_END = '12.234.32.255' +PUBLIC_IP_GATEWAY = '12.234.32.1' +PUBLIC_NETMASK = '255.255.255.0' +PUBLIC_NIC = 'eth1' +PUBLIC_PROMISC = 1 +STORAGE_IP_START = '172.16.100.100' +STORAGE_IP_END = '172.16.100.255' +STORAGE_NETMASK = '255.255.255.0' +STORAGE_IP_GATEWAY = '172.16.100.1' +STORAGE_NIC = 'eth0' +STORAGE_PROMISC = 0 +HOME_PERCENTAGE = 5 +TMP_PERCENTAGE = 5 +VAR_PERCENTAGE = 10 +# ROLES_LIST = [['os-dashboard']] + +PRESET_VALUES = { + 'NAMESERVERS': '192.168.10.1', + 'NTP_SERVER': '192.168.10.1', + 'GATEWAY': '192.168.10.1', + 'PROXY': 'http://192.168.10.1:3128', + 'ROLES_LIST': 'os-dashboard', + 'MACHINES_TO_ADD': '00:11:20:30:40:01', + 'BUILD_TIMEOUT': 60 +} +for v in PRESET_VALUES: + if v in os.environ.keys(): + PRESET_VALUES[v] = os.environ.get(v) + print (v + PRESET_VALUES[v] + " is set by env variables") + else: + print (PRESET_VALUES[v]) + +# get apiclient object. +client = Client(COMPASS_SERVER_URL) + + +# get all switches. +status, resp = client.get_switches() +print 'get all switches status: %s resp: %s' % (status, resp) + +# add a switch. +status, resp = client.add_switch( + SWITCH_IP, version=SWITCH_SNMP_VERSION, + community=SWITCH_SNMP_COMMUNITY) + +print 'add a switch status: %s resp: %s' % (status, resp) + +if status < 400: + switch = resp['switch'] +else: + status, resp = client.get_switches() + print 'get all switches status: %s resp: %s' % (status, resp) + switch = None + for switch in resp['switches']: + if switch['ip'] == SWITCH_IP: + break + +switch_id = switch['id'] +switch_ip = switch['ip'] + + +# if the switch is not in under_monitoring, wait for the poll switch task +# update the swich information and change the switch state. +while switch['state'] != 'under_monitoring': + print 'waiting for the switch into under_monitoring' + status, resp = client.get_switch(switch_id) + print 'get switch %s status: %s, resp: %s' % (switch_id, status, resp) + switch = resp['switch'] + time.sleep(10) + + +# get machines connected to the switch. +status, resp = client.get_machines(switch_id=switch_id) +print 'get all machines under switch %s status: %s, resp: %s' % ( + switch_id, status, resp) +machines = {} +MACHINES_TO_ADD = PRESET_VALUES['MACHINES_TO_ADD'].split() +for machine in resp['machines']: + mac = machine['mac'] + if mac in MACHINES_TO_ADD: + machines[machine['id']] = mac + +print 'machine to add: %s' % machines + +if set(machines.values()) != set(MACHINES_TO_ADD): + print 'only found macs %s while expected are %s' % ( + machines.values(), MACHINES_TO_ADD) + sys.exit(1) + + +# get adapters. +status, resp = client.get_adapters() +print 'get all adapters status: %s, resp: %s' % (status, resp) +adapter_ids = [] +for adapter in resp['adapters']: + adapter_ids.append(adapter['id']) + +adapter_id = adapter_ids[0] +print 'adpater for deploying a cluster: %s' % adapter_id + + +# add a cluster. +status, resp = client.add_cluster( + cluster_name=CLUSTER_NAME, adapter_id=adapter_id) +print 'add cluster %s status: %s, resp: %s' % (CLUSTER_NAME, status, resp) +cluster = resp['cluster'] +cluster_id = cluster['id'] + +# add hosts to the cluster. +status, resp = client.add_hosts( + cluster_id=cluster_id, + machine_ids=machines.keys()) +print 'add hosts to cluster %s status: %s, resp: %s' % ( + cluster_id, status, resp) +host_ids = [] +for host in resp['cluster_hosts']: + host_ids.append(host['id']) + +print 'added hosts: %s' % host_ids + + +# set cluster security +status, resp = client.set_security( + cluster_id, server_username=SERVER_USERNAME, + server_password=SERVER_PASSWORD, + service_username=SERVICE_USERNAME, + service_password=SERVICE_PASSWORD, + console_username=CONSOLE_USERNAME, + console_password=CONSOLE_PASSWORD) +print 'set security config to cluster %s status: %s, resp: %s' % ( + cluster_id, status, resp) + + +# set cluster networking +status, resp = client.set_networking( + cluster_id, + nameservers=PRESET_VALUES["NAMESERVERS"], + search_path=SEARCH_PATH, + gateway=PRESET_VALUES["GATEWAY"], + proxy=PRESET_VALUES["PROXY"], + ntp_server=PRESET_VALUES["NTP_SERVER"], + ha_vip=HA_VIP, + management_ip_start=MANAGEMENT_IP_START, + management_ip_end=MANAGEMENT_IP_END, + management_netmask=MANAGEMENT_NETMASK, + management_nic=MANAGEMENT_NIC, + management_gateway=MANAGEMENT_IP_GATEWAY, + management_promisc=MANAGEMENT_PROMISC, + tenant_ip_start=TENANT_IP_START, + tenant_ip_end=TENANT_IP_END, + tenant_netmask=TENANT_NETMASK, + tenant_nic=TENANT_NIC, + tenant_gateway=TENANT_IP_GATEWAY, + tenant_promisc=TENANT_PROMISC, + public_ip_start=PUBLIC_IP_START, + public_ip_end=PUBLIC_IP_END, + public_netmask=PUBLIC_NETMASK, + public_nic=PUBLIC_NIC, + public_gateway=PUBLIC_IP_GATEWAY, + public_promisc=PUBLIC_PROMISC, + storage_ip_start=STORAGE_IP_START, + storage_ip_end=STORAGE_IP_END, + storage_netmask=STORAGE_NETMASK, + storage_nic=STORAGE_NIC, + storage_gateway=STORAGE_IP_GATEWAY, + storage_promisc=STORAGE_PROMISC) +print 'set networking config to cluster %s status: %s, resp: %s' % ( + cluster_id, status, resp) + + +# set partiton of each host in cluster +status, resp = client.set_partition( + cluster_id, + home_percentage=HOME_PERCENTAGE, + tmp_percentage=TMP_PERCENTAGE, + var_percentage=VAR_PERCENTAGE) +print 'set partition config to cluster %s status: %s, resp: %s' % ( + cluster_id, status, resp) + + +# set each host config in cluster. +ROLES_LIST = [PRESET_VALUES['ROLES_LIST'].split()] +for host_id in host_ids: + if ROLES_LIST: + roles = ROLES_LIST.pop(0) + else: + roles = [] + status, resp = client.update_host_config( + host_id, hostname='%s%s' % (HOST_NAME_PREFIX, host_id), + roles=roles) + print 'set roles to host %s status: %s, resp: %s' % ( + host_id, status, resp) + + +# deploy cluster. +status, resp = client.deploy_hosts(cluster_id) +print 'deploy cluster %s status: %s, resp: %s' % (cluster_id, status, resp) + + +# get intalling progress. +BUILD_TIMEOUT = float(PRESET_VALUES['BUILD_TIMEOUT']) +timeout = time.time() + BUILD_TIMEOUT * 60 +while True: + status, resp = client.get_cluster_installing_progress(cluster_id) + print 'get cluster %s installing progress status: %s, resp: %s' % ( + cluster_id, status, resp) + progress = resp['progress'] + if ( + progress['state'] not in ['UNINITIALIZED', 'INSTALLING'] or + progress['percentage'] >= 1.0 + ): + break + if ( + time.time() > timeout + ): + raise Exception("Timeout! The system is not ready in time.") + + for host_id in host_ids: + status, resp = client.get_host_installing_progress(host_id) + print 'get host %s installing progress status: %s, resp: %s' % ( + host_id, status, resp) + + time.sleep(60) + + +status, resp = client.get_dashboard_links(cluster_id) +print 'get cluster %s dashboardlinks status: %s, resp: %s' % ( + cluster_id, status, resp) +dashboardlinks = resp['dashboardlinks'] +if not dashboardlinks.keys(): + raise Exception("Dashboard link is not found!") +for x in dashboardlinks.keys(): + if x in ("os-dashboard", "os-controller"): + dashboardurl = dashboardlinks.get(x) + if dashboardurl is None: + raise Exception("No dashboard link is found") + r = requests.get(dashboardurl, verify=False) + r.raise_for_status() + match = re.search( + r'(?m)(http://\d+\.\d+\.\d+\.\d+:5000/v2\.0)', r.text) + if match: + print 'dashboard login page can be downloaded' + break + print ( + 'dashboard login page failed to be downloaded\n' + 'the context is:\n%s\n') % r.text + raise Exception("os-dashboard is not properly installed!") diff --git a/compass-deck/apiclient/v1/restful.py b/compass-deck/apiclient/v1/restful.py new file mode 100644 index 0000000..3fb235c --- /dev/null +++ b/compass-deck/apiclient/v1/restful.py @@ -0,0 +1,655 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Compass api client library. + + .. moduleauthor:: Xiaodong Wang +""" +import json +import logging +import requests + + +class Client(object): + """wrapper for compass restful api. + + .. note:: + Every api client method returns (status as int, resp as dict). + If the api succeeds, the status is 2xx, the resp includes + {'status': 'OK'} and other keys depend on method. + If the api fails, the status is 4xx, the resp includes { + 'status': '...', 'message': '...'} + """ + + def __init__(self, url, headers=None, proxies=None, stream=None): + """Restful api client initialization. + + :param url: url to the compass web service. + :type url: str. + :param headers: http header sent in each restful request. + :type headers: dict of header name (str) to heade value (str). + :param proxies: the proxy address for each protocol. + :type proxies: dict of protocol (str) to proxy url (str). + :param stream: wether the restful response should be streamed. + :type stream: bool. + """ + self.url_ = url + self.session_ = requests.Session() + if headers: + self.session_.headers = headers + + if proxies is not None: + self.session_.proxies = proxies + + if stream is not None: + self.session_.stream = stream + + def __del__(self): + self.session_.close() + + @classmethod + def _get_response(cls, resp): + """decapsulate the resp to status code and python formatted data.""" + resp_obj = {} + try: + resp_obj = resp.json() + except Exception as error: + logging.error('failed to load object from %s: %s', + resp.url, resp.content) + logging.exception(error) + resp_obj['status'] = 'Json Parsing Failure' + resp_obj['message'] = resp.content + + return resp.status_code, resp_obj + + def _get(self, relative_url, params=None): + """encapsulate get method.""" + url = '%s%s' % (self.url_, relative_url) + if params: + resp = self.session_.get(url, params=params) + else: + resp = self.session_.get(url) + + return self._get_response(resp) + + def _post(self, relative_url, data=None): + """encapsulate post method.""" + url = '%s%s' % (self.url_, relative_url) + if data: + resp = self.session_.post(url, json.dumps(data)) + else: + resp = self.session_.post(url) + + return self._get_response(resp) + + def _put(self, relative_url, data=None): + """encapsulate put method.""" + url = '%s%s' % (self.url_, relative_url) + if data: + resp = self.session_.put(url, json.dumps(data)) + else: + resp = self.session_.put(url) + + return self._get_response(resp) + + def _delete(self, relative_url): + """encapsulate delete method.""" + url = '%s%s' % (self.url_, relative_url) + return self._get_response(self.session_.delete(url)) + + def get_switches(self, switch_ips=None, switch_networks=None, limit=None): + """List details for switches. + + .. note:: + The switches can be filtered by switch_ips, siwtch_networks and + limit. These params can be None or missing. If the param is None + or missing, that filter will be ignored. + + :param switch_ips: Filter switch(es) with IP(s). + :type switch_ips: list of str. Each is as 'xxx.xxx.xxx.xxx'. + :param switch_networks: Filter switche(es) with network(s). + :type switch_networks: list of str. Each is as 'xxx.xxx.xxx.xxx/xx'. + :param limit: int, The maximum number of switches to return. + :type limit: int. 0 means unlimited. + """ + params = {} + if switch_ips: + params['switchIp'] = switch_ips + + if switch_networks: + params['switchIpNetwork'] = switch_networks + + if limit: + params['limit'] = limit + return self._get('/switches', params=params) + + def get_switch(self, switch_id): + """Lists details for a specified switch. + + :param switch_id: switch id. + :type switch_id: int. + """ + return self._get('/switches/%s' % switch_id) + + def add_switch(self, switch_ip, version=None, community=None, + username=None, password=None, raw_data=None): + """Create a switch with specified details. + + .. note:: + It will trigger switch polling if successful. During + the polling, MAC address of the devices connected to the + switch will be learned by SNMP or SSH. + + :param switch_ip: the switch IP address. + :type switch_ip: str, as xxx.xxx.xxx.xxx. + :param version: SNMP version when using SNMP to poll switch. + :type version: str, one in ['v1', 'v2c', 'v3'] + :param community: SNMP community when using SNMP to poll switch. + :type community: str, usually 'public'. + :param username: SSH username when using SSH to poll switch. + :type username: str. + :param password: SSH password when using SSH to poll switch. + :type password: str. + """ + data = {} + if raw_data: + data = raw_data + else: + data['switch'] = {} + data['switch']['ip'] = switch_ip + data['switch']['credential'] = {} + if version: + data['switch']['credential']['version'] = version + + if community: + data['switch']['credential']['community'] = community + + if username: + data['switch']['credential']['username'] = username + + if password: + data['switch']['credential']['password'] = password + + return self._post('/switches', data=data) + + def update_switch(self, switch_id, ip_addr=None, + version=None, community=None, + username=None, password=None, + raw_data=None): + """Updates a switch with specified details. + + .. note:: + It will trigger switch polling if successful. During + the polling, MAC address of the devices connected to the + switch will be learned by SNMP or SSH. + + :param switch_id: switch id + :type switch_id: int. + :param ip_addr: the switch ip address. + :type ip_addr: str, as 'xxx.xxx.xxx.xxx' format. + :param version: SNMP version when using SNMP to poll switch. + :type version: str, one in ['v1', 'v2c', 'v3']. + :param community: SNMP community when using SNMP to poll switch. + :type community: str, usually be 'public'. + :param username: username when using SSH to poll switch. + :type username: str. + :param password: password when using SSH to poll switch. + """ + data = {} + if raw_data: + data = raw_data + else: + data['switch'] = {} + if ip_addr: + data['switch']['ip'] = ip_addr + + data['switch']['credential'] = {} + if version: + data['switch']['credential']['version'] = version + + if community: + data['switch']['credential']['community'] = community + + if username: + data['switch']['credential']['username'] = username + + if password: + data['switch']['credential']['password'] = password + + return self._put('/switches/%s' % switch_id, data=data) + + def delete_switch(self, switch_id): + """Not implemented in api.""" + return self._delete('/switches/%s' % switch_id) + + def get_machines(self, switch_id=None, vlan_id=None, + port=None, limit=None): + """Get the details of machines. + + .. note:: + The machines can be filtered by switch_id, vlan_id, port + and limit. These params can be None or missing. If the param + is None or missing, the filter will be ignored. + + :param switch_id: Return machine(s) connected to the switch. + :type switch_id: int. + :param vlan_id: Return machine(s) belonging to the vlan. + :type vlan_id: int. + :param port: Return machine(s) connect to the port. + :type port: int. + :param limit: the maximum number of machines will be returned. + :type limit: int. 0 means no limit. + """ + params = {} + if switch_id: + params['switchId'] = switch_id + + if vlan_id: + params['vlanId'] = vlan_id + + if port: + params['port'] = port + + if limit: + params['limit'] = limit + + return self._get('/machines', params=params) + + def get_machine(self, machine_id): + """Lists the details for a specified machine. + + :param machine_id: Return machine with the id. + :type machine_id: int. + """ + return self._get('/machines/%s' % machine_id) + + def get_clusters(self): + """Lists the details for all clusters.""" + return self._get('/clusters') + + def get_cluster(self, cluster_id): + """Lists the details of the specified cluster. + + :param cluster_id: cluster id. + :type cluster_id: int. + """ + return self._get('/clusters/%d' % cluster_id) + + def add_cluster(self, cluster_name, adapter_id, raw_data=None): + """Creates a cluster by specified name and given adapter id. + + :param cluster_name: cluster name. + :type cluster_name: str. + :param adapter_id: adapter id. + :type adapter_id: int. + """ + data = {} + if raw_data: + data = raw_data + else: + data['cluster'] = {} + data['cluster']['name'] = cluster_name + data['cluster']['adapter_id'] = adapter_id + return self._post('/clusters', data=data) + + def add_hosts(self, cluster_id, machine_ids, raw_data=None): + """add the specified machine(s) as the host(s) to the cluster. + + :param cluster_id: cluster id. + :type cluster_id: int. + :param machine_ids: machine ids to add to cluster. + :type machine_ids: list of int, each is the id of one machine. + """ + data = {} + if raw_data: + data = raw_data + else: + data['addHosts'] = machine_ids + return self._post('/clusters/%d/action' % cluster_id, data=data) + + def remove_hosts(self, cluster_id, host_ids, raw_data=None): + """remove the specified host(s) from the cluster. + + :param cluster_id: cluster id. + :type cluster_id: int. + :param host_ids: host ids to remove from cluster. + :type host_ids: list of int, each is the id of one host. + """ + data = {} + if raw_data: + data = raw_data + else: + data['removeHosts'] = host_ids + return self._post('/clusters/%s/action' % cluster_id, data=data) + + def replace_hosts(self, cluster_id, machine_ids, raw_data=None): + """replace the cluster hosts with the specified machine(s). + + :param cluster_id: int, The unique identifier of the cluster. + :type cluster_id: int. + :param machine_ids: the machine ids to replace the hosts in cluster. + :type machine_ids: list of int, each is the id of one machine. + """ + data = {} + if raw_data: + data = raw_data + else: + data['replaceAllHosts'] = machine_ids + return self._post('/clusters/%s/action' % cluster_id, data=data) + + def deploy_hosts(self, cluster_id, raw_data=None): + """Deploy the cluster. + + :param cluster_id: The unique identifier of the cluster + :type cluster_id: int. + """ + data = {} + if raw_data: + data = raw_data + else: + data['deploy'] = [] + return self._post('/clusters/%d/action' % cluster_id, data=data) + + @classmethod + def parse_security(cls, kwargs): + """parse the arguments to security data.""" + data = {} + for key, value in kwargs.items(): + if '_' not in key: + continue + key_name, key_value = key.split('_', 1) + data.setdefault( + '%s_credentials' % key_name, {})[key_value] = value + + return data + + def set_security(self, cluster_id, **kwargs): + """Update the cluster security configuration. + + :param cluster_id: cluster id. + :type cluster_id: int. + :param _username: username of the security name. + :type _username: str. + :param _password: passowrd of the security name. + :type _password: str. + + .. note:: + security_name should be one of ['server', 'service', 'console']. + """ + data = {} + data['security'] = self.parse_security(kwargs) + return self._put('/clusters/%d/security' % cluster_id, data=data) + + @classmethod + def parse_networking(cls, kwargs): + """parse arguments to network data.""" + data = {} + global_keys = [ + 'nameservers', 'search_path', 'gateway', + 'proxy', 'ntp_server', 'ha_vip'] + for key, value in kwargs.items(): + if key in global_keys: + data.setdefault('global', {})[key] = value + else: + if '_' not in key: + continue + + key_name, key_value = key.split('_', 1) + data.setdefault( + 'interfaces', {} + ).setdefault( + key_name, {} + )[key_value] = value + + return data + + def set_networking(self, cluster_id, **kwargs): + """Update the cluster network configuration. + + :param cluster_id: cluster id. + :type cluster_id: int. + :param nameservers: comma seperated nameserver ip address. + :type nameservers: str. + :param search_path: comma seperated dns name search path. + :type search_path: str. + :param gateway: gateway ip address for routing to outside. + :type gateway: str. + :param proxy: proxy url for downloading packages. + :type proxy: str. + :param ntp_server: ntp server ip address to sync timestamp. + :type ntp_server: str. + :param ha_vip: ha vip address to run ha proxy. + :type ha_vip: str. + :param _ip_start: start ip address to host's interface. + :type _ip_start: str. + :param _ip_end: end ip address to host's interface. + :type _ip_end: str. + :param _netmask: netmask to host's interface. + :type _netmask: str. + :param _nic: host physical interface name. + :type _nic: str. + :param _promisc: if the interface in promiscous mode. + :type _promisc: int, 0 or 1. + + .. note:: + interface should be one of ['management', 'tenant', + 'public', 'storage']. + """ + data = {} + data['networking'] = self.parse_networking(kwargs) + return self._put('/clusters/%d/networking' % cluster_id, data=data) + + @classmethod + def parse_partition(cls, kwargs): + """parse arguments to partition data.""" + data = {} + for key, value in kwargs.items(): + if key.endswith('_percentage'): + key_name = key[:-len('_percentage')] + data[key_name] = '%s%%' % value + elif key.endswitch('_mbytes'): + key_name = key[:-len('_mbytes')] + data[key_name] = str(value) + + return ';'.join([ + '/%s %s' % (key, value) for key, value in data.items() + ]) + + def set_partition(self, cluster_id, **kwargs): + """Update the cluster partition configuration. + + :param cluster_id: cluster id. + :type cluster_id: int. + :param _percentage: the partiton percentage. + :type _percentage: float between 0 to 100. + :param _mbytes: the partition mbytes. + :type _mbytes: int. + + .. note:: + partition should be one of ['home', 'var', 'tmp']. + """ + data = {} + data['partition'] = self.parse_partition(kwargs) + return self._put('/clusters/%s/partition' % cluster_id, data=data) + + def get_hosts(self, hostname=None, clustername=None): + """Lists the details of hosts. + + .. note:: + The hosts can be filtered by hostname, clustername. + These params can be None or missing. If the param + is None or missing, the filter will be ignored. + + :param hostname: The name of a host. + :type hostname: str. + :param clustername: The name of a cluster. + :type clustername: str. + """ + params = {} + if hostname: + params['hostname'] = hostname + + if clustername: + params['clustername'] = clustername + + return self._get('/clusterhosts', params=params) + + def get_host(self, host_id): + """Lists the details for the specified host. + + :param host_id: host id. + :type host_id: int. + """ + return self._get('/clusterhosts/%s' % host_id) + + def get_host_config(self, host_id): + """Lists the details of the config for the specified host. + + :param host_id: host id. + :type host_id: int. + """ + return self._get('/clusterhosts/%s/config' % host_id) + + def update_host_config(self, host_id, hostname=None, + roles=None, raw_data=None, **kwargs): + """Updates config for the host. + + :param host_id: host id. + :type host_id: int. + :param hostname: host name. + :type hostname: str. + :param security__username: username of the security name. + :type security__username: str. + :param security__password: passowrd of the security name. + :type security__password: str. + :param networking_nameservers: comma seperated nameserver ip address. + :type networking_nameservers: str. + :param networking_search_path: comma seperated dns name search path. + :type networking_search_path: str. + :param networking_gateway: gateway ip address for routing to outside. + :type networking_gateway: str. + :param networking_proxy: proxy url for downloading packages. + :type networking_proxy: str. + :param networking_ntp_server: ntp server ip address to sync timestamp. + :type networking_ntp_server: str. + :param networking__ip: ip address to host interface. + :type networking__ip: str. + :param networking__netmask: netmask to host's interface. + :type networking__netmask: str. + :param networking__nic: host physical interface name. + :type networking__nic: str. + :param networking__promisc: if the interface is promiscous. + :type networking__promisc: int, 0 or 1. + :param partition__percentage: the partiton percentage. + :type partition__percentage: float between 0 to 100. + :param partition__mbytes: the partition mbytes. + :type partition__mbytes: int. + :param roles: host assigned roles in the cluster. + :type roles: list of str. + """ + data = {} + if raw_data: + data = raw_data + else: + if hostname: + data['hostname'] = hostname + + sub_kwargs = {} + for key, value in kwargs.items(): + key_name, key_value = key.split('_', 1) + sub_kwargs.setdefault(key_name, {})[key_value] = value + + if 'security' in sub_kwargs: + data['security'] = self.parse_security(sub_kwargs['security']) + + if 'networking' in sub_kwargs: + data['networking'] = self.parse_networking( + sub_kwargs['networking']) + if 'partition' in sub_kwargs: + data['partition'] = self.parse_partition( + sub_kwargs['partition']) + + if roles: + data['roles'] = roles + + return self._put('/clusterhosts/%s/config' % host_id, data) + + def delete_from_host_config(self, host_id, delete_key): + """Deletes one key in config for the host. + + :param host_id: host id. + :type host_id: int. + :param delete_key: the key in host config to be deleted. + :type delete_key: str. + """ + return self._delete('/clusterhosts/%s/config/%s' % ( + host_id, delete_key)) + + def get_adapters(self, name=None): + """Lists details of adapters. + + .. note:: + the adapter can be filtered by name of name is given and not None. + + :param name: adapter name. + :type name: str. + """ + params = {} + if name: + params['name'] = name + + return self._get('/adapters', params=params) + + def get_adapter(self, adapter_id): + """Lists details for the specified adapter. + + :param adapter_id: adapter id. + :type adapter_id: int. + """ + return self._get('/adapters/%s' % adapter_id) + + def get_adapter_roles(self, adapter_id): + """Lists roles to assign to hosts for the specified adapter. + + :param adapter_id: adapter id. + :type adapter_id: int. + """ + return self._get('/adapters/%s/roles' % adapter_id) + + def get_host_installing_progress(self, host_id): + """Lists progress details for the specified host. + + :param host_id: host id. + :type host_id: int. + """ + return self._get('/clusterhosts/%s/progress' % host_id) + + def get_cluster_installing_progress(self, cluster_id): + """Lists progress details for the specified cluster. + + :param cluster_id: cluster id. + :param cluster_id: int. + """ + + return self._get('/clusters/%s/progress' % cluster_id) + + def get_dashboard_links(self, cluster_id): + """Lists links for dashboards of deployed cluster. + + :param cluster_id: cluster id. + :type cluster_id: int. + """ + params = {} + params['cluster_id'] = cluster_id + return self._get('/dashboardlinks', params) -- cgit 1.2.3-korg