Exemplo n.º 1
0
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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