class BaremetalComputer: """Baremetal computer class. First, this class also provide a easy API to initialize the cobbler baremetal computers in mongodb, e.g., mac and power info, Second, this class have an API through which user can get the detail information to provision a cobbler baremetal computer """ def __init__(self): coll_name = "inventory" self.yaml_file = config_file("/cloudmesh_mac.yaml") self.db_client = DBHelper(coll_name) self.bm_status = BaremetalStatus() def get_default_query(self): """ query helper function. :return: the default query field. """ return { "cm_type": "inventory", "cm_kind": "server", "cm_attribute": "network", "cm_key": "server", } def get_full_query(self, query_elem=None): """ merge the default query and user defined query. :return: the full query dict """ result = self.get_default_query() if query_elem: result.update(query_elem) return result def read_data_from_yaml(self): """ read mac address and bmc configuration information from **mac.yaml** file. """ data = read_yaml_config(self.yaml_file) result = None if data: result = {} data = data["inventory"] for cluster in data: cluster_data = data[cluster] if "bmc" in cluster_data and "common" in cluster_data["bmc"]: # process the common bmc data in cluster common_bmc_data = cluster_data["bmc"]["common"] host_range = common_bmc_data.pop("range", None) hosts = expand_hostlist(host_range) mac_data = cluster_data["macaddr"] for host in mac_data: if host in hosts: temp_common_bmc_data = deepcopy(common_bmc_data) if "bmc" in mac_data[host]: # bmc config in individual host have a high # priority than common config temp_common_bmc_data.update(mac_data[host]["bmc"]) mac_data[host]["bmc"] = temp_common_bmc_data result[cluster] = mac_data return result def insert_mac_data_to_inventory(self): """ Insert the mac address information including power config into inventory. This API should be called **BEFORE** baremetal provision. Currently, this API is called by **fab mongo.inventory** """ # insert a document of baremetal computers list self.insert_blank_baremetal_list() # insert mac data data = self.read_data_from_yaml() result = False if data and len(data) > 0: result = self.update_mac_address(data) return result def update_mac_address(self, mac_dict): """ update *inventory* db with mac address information. :param dict mac_dict: a dict with the following formation. *label_name* is the *cm_id* defined in inventory. *internal* or *public* is the type defined in inventory. {"cluster_name":{ "label_name": {"internal": {"name":"eth0", "macaddr": "aa:aa:aa:aa:aa:aa"}, "public": {"name":"eth1", "macaddr": "aa:aa:aa:aa:aa:ab"}, "bmc": {"user": "******", "pass": "******", "type": "type",},} } :return: True means all the mac address in mac_dict updated successfully; False means failed. """ result = True if mac_dict: for cluster in mac_dict: # cluster cluster_data = mac_dict[cluster] for host_id in cluster_data: # host host_data = cluster_data[host_id] for network_type in host_data: # network network_data = host_data[network_type] query_elem = { "cm_id": host_id, "type": network_type, "cm_cluster": cluster, } if network_type in ["bmc"]: # power config information update_elem = network_data else: update_elem = { "ifname": network_data["name"], "macaddr": network_data["macaddr"], } update_result = self.db_client.atom_update( self.get_full_query(query_elem), {"$set": update_elem}, False) if not update_result["result"]: result = False break if not result: break if not result: break return result def get_host_info(self, host_id, info_format="cobbler"): """ get the required host info for baremetal computer. :param string host_id: the unique name/id of a node in cloudmesh :param string info_format: the dest info format of general host info. To support a new formation, such as *xtest*, the API get_host_info_xtest MUST be provided. :return: a dict with the following formation if info_format is None, otherwise return the use specified formation conerted from the default one. { "id": "unique ID", "power": {"ipaddr": ipaddr, "power_user": user, "power_pass": pass, "power_type": type,}, "interfaces": [{"name": "eth0", "ipaddr": ipaddr, "macaddr": macaddr,}], } """ query_elem = {"cm_id": host_id} full_query_elem = self.get_full_query(query_elem) find_result = self.db_client.find(full_query_elem) result = None if find_result["result"]: result = {"id": host_id, "power": {}} data = find_result["data"] interface_list = [] cluster_id = None for record in data: if "macaddr" in record: # general network interface interface_list.append({ "name": record["ifname"], "ipaddr": record["ipaddr"], "macaddr": record["macaddr"], }) if record["type"] == "public": result["hostname"] = record["label"] cluster_id = record["cm_cluster"] elif "power_user" in record: # ipmi network interface power_key_list = [ "ipaddr", "power_user", "power_pass", "power_type", ] for key in power_key_list: result["power"][key] = record[key] # sort the inteface with ascending order result["interfaces"] = sorted(interface_list, key=lambda k: k["name"]) if cluster_id: # try to find name server for the servers in this cluster name_servers = self.get_cluster_name_server(cluster_id) if name_servers: result["name_servers"] = name_servers if info_format: getattr(self, "get_host_info_{0}".format(info_format))(result) return result def get_cluster_name_server(self, cluster_id): """find the name servers for a cluster :param string cluster_id: the unique ID of a cluster :return: None if not exist a name server for the cluster, otherwise a string represents the one or more name servers """ query_elem = { "cm_id": cluster_id, "cm_key": "nameserver", "cm_attribute": "variable" } full_query_elem = self.get_full_query(query_elem) find_result = self.db_client.find(full_query_elem) result = [] if find_result["result"]: data = find_result["data"] for record in data: result.append(record["cm_value"]) return None if len(result) < 1 else " ".join(result) def change_dict_key(self, data_dict, fields): """ change the key in dict from old_key to new_key. :param dict fields: the projection from old_key to new_key. {"old_key": "new_key"} """ for key in fields: if key in data_dict: data_dict[fields[key]] = data_dict.pop(key) def fill_dict_default_key(self, data_dict, fields): """ fill the dict with default key-value pair. :param dict fields: the default key-value pair. {"key": "default"} """ for key in fields: if key not in data_dict: data_dict[key] = fields[key] def get_host_info_cobbler(self, host_dict): """ convert general host info dict to the formation of cobbler host formation """ # section 1, general fields general_fields = { "id": "name", "name_servers": "name-servers", } self.change_dict_key(host_dict, general_fields) # section 2, power fields power_fields = { "ipaddr": "power-address", "power_user": "******", "power_pass": "******", "power_type": "power-type", } power_default = { "power-id": 2, } self.change_dict_key(host_dict["power"], power_fields) self.fill_dict_default_key(host_dict["power"], power_default) # section 3, interface fields interface_fields = { "ipaddr": "ip-address", "macaddr": "mac-address", } interface_default = { "netmask": "255.255.255.0", "static": True, } for one_interface in host_dict["interfaces"]: self.change_dict_key(one_interface, interface_fields) self.fill_dict_default_key(one_interface, interface_default) def insert_blank_baremetal_list(self): """insert a blank document of baremetal computers list into mongodb ONLY called ONCE by **fab mongo.inventory** """ elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } result = self.db_client.find_one(elem) flag_insert = True if result["result"] and result["data"]: flag_insert = False if not flag_insert: return True result = self.db_client.insert(elem) return result["result"] def enable_baremetal_computers(self, hosts): """add the list of *hosts* to be baremetal computers :param list hosts: the list of hosts with the formation ["host1", "host2",] :return: True means enabled successfully, otherwise False """ if hosts: query_elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } update_elem = {"$addToSet": {"data": {"$each": hosts}}} result = self.db_client.atom_update(query_elem, update_elem) return result["result"] return True def disable_baremetal_computers(self, hosts): """remove the list of *hosts* from baremetal computers :param list hosts: the list of hosts with the formation ["host1", "host2",] :return: True means disabled successfully, otherwise False """ if hosts: query_elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } update_elem = {"$pull": {"data": {"$in": hosts}}} result = self.db_client.atom_update(query_elem, update_elem) return result["result"] return True def get_baremetal_computers(self): """get the list of baremetal computers :return: the list of hosts with the formation ["host1", "host2",] or None if failed """ query_elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } result = self.db_client.find_one(query_elem) if result["result"]: return result["data"]["data"] if "data" in result["data"] else [] return None
class BaremetalStatus: """Baremetal computer Status. """ def __init__(self): """Init function of class BaremetalStatus """ self.db_client = DBHelper() def get_default_query(self): return {"cm_kind": "baremetal", "cm_type": "status_inventory"} def get_full_query(self, query_elem): elem = self.get_default_query() if query_elem: elem.update(query_elem) return elem def init_deploy_status(self, host): """Init the deploy status of host :param string host: the host name of baremetal computer """ return self.init_status(host, "deploy") def init_power_status(self, host, flag_on=True): """Init the Power ON/OFF status of the host :param string host: the host name of baremetal computer """ return self.init_status(host, "on" if flag_on else "off") def init_status(self, host, action): """init the status of baremetal computer :param string action: one of values in ["on", "off", "deploy"] :return: True means init status successfully, otherwise failed. """ query_elem = self.get_full_query({"cm_id": host}) # status cycle for deploy, OFF --> ON --> OFF # status cycle for power on, ON # status cycle for power off, OFF update_elem = {"transient": {"action": "{0}...".format(action), # deploy, on, off "status_1": "unknown", # status phase 1 "status_2": "unknown", # status phase 2 "status_3": "unknown", # status phase 3 }, } if action == "deploy": update_elem["status"] = "unknown" # unknown, deploying, deployed, failed result = self.db_client.atom_update(query_elem, {"$set": update_elem}) return result["result"] def update_deploy_command_result(self, host, result): """update the deploy result :param string host: the unique name of host :param boolean result: True means deploy command success, False means failed :return: a flag, True means update mongodb success, otherwise failed. """ query_elem = self.get_full_query({"cm_id": host}) update_elem = {"status": "deploying" if result else "failed", } result = self.db_client.atom_update(query_elem, {"$set": update_elem}) return result["result"] def update_power_status(self, host, status, flag_on): """update the status of power ON/OFF :param string host: the unique name of host :param string status: status of "ON" or "OFF" :param boolean flag_on: True means power on, False means power off :return: True means udpate successfully, otherwise failed """ query_elem = self.get_full_query({"cm_id": host}) hosts_status = self.get_status(host) result = False if hosts_status and hosts_status[0]["status"] == "deployed": host_status = hosts_status[0] trans_status = {} flag_update = False if flag_on and host_status["transient"]["action"] == "on...": result = True if status == "ON": trans_status["transient.action"] = "on" trans_status["transient.status_1"] = "ON" flag_update = True elif not flag_on and host_status["transient"]["action"] == "off...": result = True if status == "OFF": trans_status["transient.action"] = "off" trans_status["transient.status_1"] = "OFF" flag_update = True if flag_update: update_result = self.db_client.atom_update(query_elem, {"$set": trans_status}) result = update_result["result"] return result def update_deploy_status(self, host, status): """update the status of deploy based on the cycle OFF --> ON --> OFF :param string host: the unique name of host :param string status: status of "ON" or "OFF" :return: True means udpate successfully, otherwise failed """ query_elem = self.get_full_query({"cm_id": host}) hosts_status = self.get_status(host) result = False if hosts_status and hosts_status[0]["status"] == "deploying": host_status = hosts_status[0] if host_status["transient"]["action"] == "deploy...": result = True trans_status = {} flag_update = False # phase 1, unknown --> OFF if host_status["transient"]["status_1"] == "unknown": if status == "OFF": log.debug("host {0} deploy monitor, step 1 ".format(host)) trans_status["transient.status_1"] = "OFF" flag_update = True elif host_status["transient"]["status_1"] == "OFF": # phase 2, unknown --> ON if host_status["transient"]["status_2"] == "unknown": if status == "ON": log.debug("host {0} deploy monitor, step 2 ".format(host)) trans_status["transient.status_2"] = "ON" flag_update = True elif host_status["transient"]["status_2"] == "ON": # phase 3, unknown --> OFF if host_status["transient"]["status_3"] == "unknown": if status == "OFF": log.debug("host {0} deploy monitor, step 3 ".format(host)) trans_status["transient.status_3"] = "OFF" trans_status["transient.action"] = "deploy" trans_status["status"] = "deployed" flag_update = True if flag_update: update_result = self.db_client.atom_update(query_elem, {"$set": trans_status}) result = update_result["result"] return result def get_deploy_progress(self, host, host_status=None): """get the progress of deploy of host baremetal computer. :param string host: the unique ID of host :param dict host_status: the status document of host in mongodb :return: an integer number. -1 means error. 10 means before phase 1, 25, 50, 100 means the end of phase 1, 2, 3. """ result = -1 if not host_status: status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status: if host_status["status"] == "deploying": if host_status["transient"]["status_1"] == "unknown": result = 10 elif host_status["transient"]["status_1"] == "OFF": result = 25 if host_status["transient"]["status_2"] == "ON": result = 50 elif host_status["status"] == "deployed": result = 100 return result def get_power_progress(self, host, flag_on, host_status=None): """get the progress of power ON/OFF of host baremetal computer. :param string host: the unique ID of host :param boolean flag_on: True means power ON, False means OFF :param dict host_status: the status document of host in mongodb :return: an integer number. -1 means error. 10 means before phase 1, 100 means phase 1. """ result = -1 if not host_status: status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status: if host_status["status"] == "deployed": if host_status["transient"]["status_1"] == "unknown": result = 10 elif host_status["transient"]["status_1"] == "ON" if flag_on else "OFF": result = 100 return result def get_host_progress(self, host): """get the progress of host baremetal computer. :param string host: the unique ID of host :return: a dict of {"status": "deploy", "progress": 25, }, there are 5 status of host, namely deploy, poweron, poweroff, failed, unknown """ result = {"status": "unknown", "progress": -1, } status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status["status"] == "deployed": # maybe any status in deploy, poweron, poweroff action = host_status["transient"]["action"] if action.startswith("on"): result["status"] = "poweron" result["progress"] = self.get_power_progress(host, True, host_status) elif action.startswith("off"): result["status"] = "poweroff" result["progress"] = self.get_power_progress(host, False, host_status) elif action.startswith("deploy"): result["status"] = "deploy" result["progress"] = self.get_deploy_progress(host, host_status) elif host_status["status"] == "deploying": result["status"] = "deploy" result["progress"] = self.get_deploy_progress(host, host_status) elif host_status["status"] == "failed": result["status"] = "failed" return result def get_status(self, host=None): """get the status of single or all baremetal computer(s) :param string host: the unique ID of host, None means get status of all hosts :return: a list of dict with the following formation [{"cm_id": "cm_id", "status":"status", "transient":{"action": "action", "status_1":"status_1", "status_2":"status_2", "status_3":"status_3"}}] """ query_elem = self.get_full_query({"cm_id": host} if host else {}) result = self.db_client.find(query_elem) return result["data"] if result["result"] else None def get_status_short(self, hosts=None): """get the short status of baremetal for hosts :param list hosts: a list of host or None means all hosts :return: a dict with the formation {"host1":"deployed", "host2": "deploying", "host3": "failed", "host4": "unknown", } """ status_list = self.get_status() valid_hosts_status = [status for status in status_list if status["cm_id"] in hosts] if hosts is not None else status_list result = {} for host in valid_hosts_status: result[host["cm_id"]] = host["status"] return result def get_status_summary(self, hosts=None): """get the summary status of baremetal for hosts :param list hosts: a list of host or None means all hosts :return: a dict with the formation {"deployed": 1, "deploying":2, "failed":2, "total": 5} """ status_list = self.get_status() valid_hosts_status = [status for status in status_list if status["cm_id"] in hosts] if hosts is not None else status_list result = {"deployed": 0, "deploying": 0, "failed": 0, "total": 0} for host in valid_hosts_status: result["total"] += 1 result[host["status"]] += 1 return result
class BaremetalStatus: """Baremetal computer Status. """ def __init__(self): """Init function of class BaremetalStatus """ self.db_client = DBHelper() def get_default_query(self): return {"cm_kind": "baremetal", "cm_type": "status_inventory"} def get_full_query(self, query_elem): elem = self.get_default_query() if query_elem: elem.update(query_elem) return elem def init_deploy_status(self, host): """Init the deploy status of host :param string host: the host name of baremetal computer """ return self.init_status(host, "deploy") def init_power_status(self, host, flag_on=True): """Init the Power ON/OFF status of the host :param string host: the host name of baremetal computer """ return self.init_status(host, "on" if flag_on else "off") def init_status(self, host, action): """init the status of baremetal computer :param string action: one of values in ["on", "off", "deploy"] :return: True means init status successfully, otherwise failed. """ query_elem = self.get_full_query({"cm_id": host}) # status cycle for deploy, OFF --> ON --> OFF # status cycle for power on, ON # status cycle for power off, OFF update_elem = { "transient": { "action": "{0}...".format(action), # deploy, on, off "status_1": "unknown", # status phase 1 "status_2": "unknown", # status phase 2 "status_3": "unknown", # status phase 3 }, } if action == "deploy": # unknown, deploying, deployed, failed update_elem["status"] = "unknown" result = self.db_client.atom_update(query_elem, {"$set": update_elem}) return result["result"] def update_deploy_command_result(self, host, result): """update the deploy result :param string host: the unique name of host :param boolean result: True means deploy command success, False means failed :return: a flag, True means update mongodb success, otherwise failed. """ query_elem = self.get_full_query({"cm_id": host}) update_elem = { "status": "deploying" if result else "failed", } result = self.db_client.atom_update(query_elem, {"$set": update_elem}) return result["result"] def update_power_status(self, host, status, flag_on): """update the status of power ON/OFF :param string host: the unique name of host :param string status: status of "ON" or "OFF" :param boolean flag_on: True means power on, False means power off :return: True means udpate successfully, otherwise failed """ query_elem = self.get_full_query({"cm_id": host}) hosts_status = self.get_status(host) result = False if hosts_status and hosts_status[0]["status"] == "deployed": host_status = hosts_status[0] trans_status = {} flag_update = False if flag_on and host_status["transient"]["action"] == "on...": result = True if status == "ON": trans_status["transient.action"] = "on" trans_status["transient.status_1"] = "ON" flag_update = True elif not flag_on and host_status["transient"]["action"] == "off...": result = True if status == "OFF": trans_status["transient.action"] = "off" trans_status["transient.status_1"] = "OFF" flag_update = True if flag_update: update_result = self.db_client.atom_update( query_elem, {"$set": trans_status}) result = update_result["result"] return result def update_deploy_status(self, host, status): """update the status of deploy based on the cycle OFF --> ON --> OFF :param string host: the unique name of host :param string status: status of "ON" or "OFF" :return: True means udpate successfully, otherwise failed """ query_elem = self.get_full_query({"cm_id": host}) hosts_status = self.get_status(host) result = False if hosts_status and hosts_status[0]["status"] == "deploying": host_status = hosts_status[0] if host_status["transient"]["action"] == "deploy...": result = True trans_status = {} flag_update = False # phase 1, unknown --> OFF if host_status["transient"]["status_1"] == "unknown": if status == "OFF": log.debug( "host {0} deploy monitor, step 1 ".format(host)) trans_status["transient.status_1"] = "OFF" flag_update = True elif host_status["transient"]["status_1"] == "OFF": # phase 2, unknown --> ON if host_status["transient"]["status_2"] == "unknown": if status == "ON": log.debug( "host {0} deploy monitor, step 2 ".format( host)) trans_status["transient.status_2"] = "ON" flag_update = True elif host_status["transient"]["status_2"] == "ON": # phase 3, unknown --> OFF if host_status["transient"]["status_3"] == "unknown": if status == "OFF": log.debug( "host {0} deploy monitor, step 3 ".format( host)) trans_status["transient.status_3"] = "OFF" trans_status["transient.action"] = "deploy" trans_status["status"] = "deployed" flag_update = True if flag_update: update_result = self.db_client.atom_update( query_elem, {"$set": trans_status}) result = update_result["result"] return result def get_deploy_progress(self, host, host_status=None): """get the progress of deploy of host baremetal computer. :param string host: the unique ID of host :param dict host_status: the status document of host in mongodb :return: an integer number. -1 means error. 10 means before phase 1, 25, 50, 100 means the end of phase 1, 2, 3. """ result = -1 if not host_status: status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status: if host_status["status"] == "deploying": if host_status["transient"]["status_1"] == "unknown": result = 10 elif host_status["transient"]["status_1"] == "OFF": result = 25 if host_status["transient"]["status_2"] == "ON": result = 50 elif host_status["status"] == "deployed": result = 100 return result def get_power_progress(self, host, flag_on, host_status=None): """get the progress of power ON/OFF of host baremetal computer. :param string host: the unique ID of host :param boolean flag_on: True means power ON, False means OFF :param dict host_status: the status document of host in mongodb :return: an integer number. -1 means error. 10 means before phase 1, 100 means phase 1. """ result = -1 if not host_status: status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status: if host_status["status"] == "deployed": if host_status["transient"]["status_1"] == "unknown": result = 10 elif host_status["transient"][ "status_1"] == "ON" if flag_on else "OFF": result = 100 return result def get_host_progress(self, host): """get the progress of host baremetal computer. :param string host: the unique ID of host :return: a dict of {"status": "deploy", "progress": 25, }, there are 5 status of host, namely deploy, poweron, poweroff, failed, unknown """ result = { "status": "unknown", "progress": -1, } status_list = self.get_status(host) if status_list: host_status = status_list[0] if host_status["status"] == "deployed": # maybe any status in deploy, poweron, poweroff action = host_status["transient"]["action"] if action.startswith("on"): result["status"] = "poweron" result["progress"] = self.get_power_progress( host, True, host_status) elif action.startswith("off"): result["status"] = "poweroff" result["progress"] = self.get_power_progress( host, False, host_status) elif action.startswith("deploy"): result["status"] = "deploy" result["progress"] = self.get_deploy_progress( host, host_status) elif host_status["status"] == "deploying": result["status"] = "deploy" result["progress"] = self.get_deploy_progress( host, host_status) elif host_status["status"] == "failed": result["status"] = "failed" return result def get_status(self, host=None): """get the status of single or all baremetal computer(s) :param string host: the unique ID of host, None means get status of all hosts :return: a list of dict with the following formation [{"cm_id": "cm_id", "status":"status", "transient":{"action": "action", "status_1":"status_1", "status_2":"status_2", "status_3":"status_3"}}] """ query_elem = self.get_full_query({"cm_id": host} if host else {}) result = self.db_client.find(query_elem) return result["data"] if result["result"] else None def get_status_short(self, hosts=None): """get the short status of baremetal for hosts :param list hosts: a list of host or None means all hosts :return: a dict with the formation {"host1":"deployed", "host2": "deploying", "host3": "failed", "host4": "unknown", } """ status_list = self.get_status() valid_hosts_status = [ status for status in status_list if status["cm_id"] in hosts ] if hosts is not None else status_list result = {} for host in valid_hosts_status: result[host["cm_id"]] = host["status"] return result def get_status_summary(self, hosts=None): """get the summary status of baremetal for hosts :param list hosts: a list of host or None means all hosts :return: a dict with the formation {"deployed": 1, "deploying":2, "failed":2, "total": 5} """ status_list = self.get_status() valid_hosts_status = [ status for status in status_list if status["cm_id"] in hosts ] if hosts is not None else status_list result = {"deployed": 0, "deploying": 0, "failed": 0, "total": 0} for host in valid_hosts_status: result["total"] += 1 result[host["status"]] += 1 return result
class BaremetalComputer: """Baremetal computer class. First, this class also provide a easy API to initialize the cobbler baremetal computers in mongodb, e.g., mac and power info, Second, this class have an API through which user can get the detail information to provision a cobbler baremetal computer """ def __init__(self): coll_name = "inventory" self.yaml_file = config_file("/cloudmesh_mac.yaml") self.db_client = DBHelper(coll_name) self.bm_status = BaremetalStatus() def get_default_query(self): """ query helper function. :return: the default query field. """ return {"cm_type": "inventory", "cm_kind": "server", "cm_attribute": "network", "cm_key": "server", } def get_full_query(self, query_elem=None): """ merge the default query and user defined query. :return: the full query dict """ result = self.get_default_query() if query_elem: result.update(query_elem) return result def read_data_from_yaml(self): """ read mac address and bmc configuration information from **mac.yaml** file. """ data = read_yaml_config(self.yaml_file) result = None if data: result = {} data = data["inventory"] for cluster in data: cluster_data = data[cluster] if "bmc" in cluster_data and "common" in cluster_data["bmc"]: # process the common bmc data in cluster common_bmc_data = cluster_data["bmc"]["common"] host_range = common_bmc_data.pop("range", None) hosts = expand_hostlist(host_range) mac_data = cluster_data["macaddr"] for host in mac_data: if host in hosts: temp_common_bmc_data = deepcopy(common_bmc_data) if "bmc" in mac_data[host]: # bmc config in individual host have a high # priority than common config temp_common_bmc_data.update(mac_data[host]["bmc"]) mac_data[host]["bmc"] = temp_common_bmc_data result[cluster] = mac_data return result def insert_mac_data_to_inventory(self): """ Insert the mac address information including power config into inventory. This API should be called **BEFORE** baremetal provision. Currently, this API is called by **fab mongo.inventory** """ # insert a document of baremetal computers list self.insert_blank_baremetal_list() # insert mac data data = self.read_data_from_yaml() result = False if data and len(data) > 0: result = self.update_mac_address(data) return result def update_mac_address(self, mac_dict): """ update *inventory* db with mac address information. :param dict mac_dict: a dict with the following formation. *label_name* is the *cm_id* defined in inventory. *internal* or *public* is the type defined in inventory. {"cluster_name":{ "label_name": {"internal": {"name":"eth0", "macaddr": "aa:aa:aa:aa:aa:aa"}, "public": {"name":"eth1", "macaddr": "aa:aa:aa:aa:aa:ab"}, "bmc": {"user": "******", "pass": "******", "type": "type",},} } :return: True means all the mac address in mac_dict updated successfully; False means failed. """ result = True if mac_dict: for cluster in mac_dict: # cluster cluster_data = mac_dict[cluster] for host_id in cluster_data: # host host_data = cluster_data[host_id] for network_type in host_data: # network network_data = host_data[network_type] query_elem = { "cm_id": host_id, "type": network_type, "cm_cluster": cluster, } if network_type in ["bmc"]: # power config information update_elem = network_data else: update_elem = {"ifname": network_data["name"], "macaddr": network_data["macaddr"], } update_result = self.db_client.atom_update(self.get_full_query(query_elem), {"$set": update_elem}, False) if not update_result["result"]: result = False break if not result: break if not result: break return result def get_host_info(self, host_id, info_format="cobbler"): """ get the required host info for baremetal computer. :param string host_id: the unique name/id of a node in cloudmesh :param string info_format: the dest info format of general host info. To support a new formation, such as *xtest*, the API get_host_info_xtest MUST be provided. :return: a dict with the following formation if info_format is None, otherwise return the use specified formation conerted from the default one. { "id": "unique ID", "power": {"ipaddr": ipaddr, "power_user": user, "power_pass": pass, "power_type": type,}, "interfaces": [{"name": "eth0", "ipaddr": ipaddr, "macaddr": macaddr,}], } """ query_elem = {"cm_id": host_id} full_query_elem = self.get_full_query(query_elem) find_result = self.db_client.find(full_query_elem) result = None if find_result["result"]: result = {"id": host_id, "power": {}} data = find_result["data"] interface_list = [] cluster_id = None for record in data: if "macaddr" in record: # general network interface interface_list.append({"name": record["ifname"], "ipaddr": record["ipaddr"], "macaddr": record["macaddr"], }) if record["type"] == "public": result["hostname"] = record["label"] cluster_id = record["cm_cluster"] elif "power_user" in record: # ipmi network interface power_key_list = [ "ipaddr", "power_user", "power_pass", "power_type", ] for key in power_key_list: result["power"][key] = record[key] # sort the inteface with ascending order result["interfaces"] = sorted( interface_list, key=lambda k: k["name"]) if cluster_id: # try to find name server for the servers in this cluster name_servers = self.get_cluster_name_server(cluster_id) if name_servers: result["name_servers"] = name_servers if info_format: getattr(self, "get_host_info_{0}".format(info_format))(result) return result def get_cluster_name_server(self, cluster_id): """find the name servers for a cluster :param string cluster_id: the unique ID of a cluster :return: None if not exist a name server for the cluster, otherwise a string represents the one or more name servers """ query_elem = { "cm_id": cluster_id, "cm_key": "nameserver", "cm_attribute": "variable"} full_query_elem = self.get_full_query(query_elem) find_result = self.db_client.find(full_query_elem) result = [] if find_result["result"]: data = find_result["data"] for record in data: result.append(record["cm_value"]) return None if len(result) < 1 else " ".join(result) def change_dict_key(self, data_dict, fields): """ change the key in dict from old_key to new_key. :param dict fields: the projection from old_key to new_key. {"old_key": "new_key"} """ for key in fields: if key in data_dict: data_dict[fields[key]] = data_dict.pop(key) def fill_dict_default_key(self, data_dict, fields): """ fill the dict with default key-value pair. :param dict fields: the default key-value pair. {"key": "default"} """ for key in fields: if key not in data_dict: data_dict[key] = fields[key] def get_host_info_cobbler(self, host_dict): """ convert general host info dict to the formation of cobbler host formation """ # section 1, general fields general_fields = {"id": "name", "name_servers": "name-servers", } self.change_dict_key(host_dict, general_fields) # section 2, power fields power_fields = {"ipaddr": "power-address", "power_user": "******", "power_pass": "******", "power_type": "power-type", } power_default = {"power-id": 2, } self.change_dict_key(host_dict["power"], power_fields) self.fill_dict_default_key(host_dict["power"], power_default) # section 3, interface fields interface_fields = {"ipaddr": "ip-address", "macaddr": "mac-address", } interface_default = {"netmask": "255.255.255.0", "static": True, } for one_interface in host_dict["interfaces"]: self.change_dict_key(one_interface, interface_fields) self.fill_dict_default_key(one_interface, interface_default) def insert_blank_baremetal_list(self): """insert a blank document of baremetal computers list into mongodb ONLY called ONCE by **fab mongo.inventory** """ elem = {"cm_kind": "baremetal", "cm_type": "bm_list_inventory", } result = self.db_client.find_one(elem) flag_insert = True if result["result"] and result["data"]: flag_insert = False if not flag_insert: return True result = self.db_client.insert(elem) return result["result"] def enable_baremetal_computers(self, hosts): """add the list of *hosts* to be baremetal computers :param list hosts: the list of hosts with the formation ["host1", "host2",] :return: True means enabled successfully, otherwise False """ if hosts: query_elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } update_elem = {"$addToSet": {"data": {"$each": hosts}}} result = self.db_client.atom_update(query_elem, update_elem) return result["result"] return True def disable_baremetal_computers(self, hosts): """remove the list of *hosts* from baremetal computers :param list hosts: the list of hosts with the formation ["host1", "host2",] :return: True means disabled successfully, otherwise False """ if hosts: query_elem = { "cm_kind": "baremetal", "cm_type": "bm_list_inventory", } update_elem = {"$pull": {"data": {"$in": hosts}}} result = self.db_client.atom_update(query_elem, update_elem) return result["result"] return True def get_baremetal_computers(self): """get the list of baremetal computers :return: the list of hosts with the formation ["host1", "host2",] or None if failed """ query_elem = {"cm_kind": "baremetal", "cm_type": "bm_list_inventory", } result = self.db_client.find_one(query_elem) if result["result"]: return result["data"]["data"] if "data" in result["data"] else [] return None