Ejemplo n.º 1
0
    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._hardwareProfileDbApi = HardwareProfileDbApi()
        self._cm = ConfigManager()
        self._san = san.San()
Ejemplo n.º 2
0
    def __init__(self):
        super(AddHostManager, self).__init__()

        # Now do the class specific variable initialization
        self._addHostLock = threading.RLock()
        self._nodeDbApi = NodeDbApi()
        self._sessions = ObjectStoreManager.get(namespace='add-host-manager')
Ejemplo n.º 3
0
    def __init__(self):
        super(AddHostManager, self).__init__()

        # Now do the class specific variable initialization
        self._addHostLock = threading.RLock()
        self._sessions = {}
        self._nodeDbApi = NodeDbApi()
Ejemplo n.º 4
0
    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._hardwareProfileDbApi = HardwareProfileDbApi()
        self._cm = ConfigManager()
        self._san = san.San()
        self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager()
Ejemplo n.º 5
0
 def __init__(self):
     super(SoftwareProfileManager, self).__init__()
     self._sp_db_api = SoftwareProfileDbApi()
     self._node_db_api = NodeDbApi()
     self._component_db_api = ComponentDbApi()
     self._global_param_db_api = GlobalParameterDbApi()
     self._kit_db_api = KitDbApi()
     self._config_manager = ConfigManager()
Ejemplo n.º 6
0
    def __init__(self):
        super(HardwareProfileManager, self).__init__()

        self._hpDbApi = HardwareProfileDbApi()
        self._spDbApi = SoftwareProfileDbApi()
        self._networkDbApi = NetworkDbApi()
        self._globalParameterDbApi = GlobalParameterDbApi()
        self._nodeDbApi = NodeDbApi()
Ejemplo n.º 7
0
    def __init__(self):
        super(AddHostManager, self).__init__()

        # Now do the class specific variable initialization
        self._addHostLock = threading.RLock()
        self._logger = logging.getLogger(ADD_HOST_NAMESPACE)
        self._nodeDbApi = NodeDbApi()
        self._sessions = ObjectStoreManager.get(namespace='add-host-manager',
                                                expire=86400)
Ejemplo n.º 8
0
def test_getNode_with_options(dbm):
    options = dict(hardwareprofile=True)

    with dbm.session() as session:
        result = NodeDbApi().getNode(session, 'compute-01.private', options)

    assert isinstance(result, Node)

    assert result.getHardwareProfile().getName()
Ejemplo n.º 9
0
 def __init__(self):
     super(SoftwareProfileManager, self).__init__()
     self._sp_db_api = SoftwareProfileDbApi()
     self._node_db_api = NodeDbApi()
     self._component_db_api = ComponentDbApi()
     self._global_param_db_api = GlobalParameterDbApi()
     self._kit_db_api = KitDbApi()
     self._config_manager = ConfigManager()
     self._logger = logging.getLogger(SOFTWARE_PROFILE_NAMESPACE)
Ejemplo n.º 10
0
    def __init__(self):
        super(HardwareProfileManager, self).__init__()

        self._hpDbApi = HardwareProfileDbApi()
        self._spDbApi = SoftwareProfileDbApi()
        self._networkDbApi = NetworkDbApi()
        self._globalParameterDbApi = GlobalParameterDbApi()
        self._nodeDbApi = NodeDbApi()
        self._logger = logging.getLogger(HARDWARE_PROFILE_NAMESPACE)
Ejemplo n.º 11
0
    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._cm = ConfigManager()
        self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager(
            self._cm)
        self._nodesDbHandler = NodesDbHandler()
        self._addHostManager = AddHostManager()
        self._logger = logging.getLogger(NODE_NAMESPACE)
Ejemplo n.º 12
0
    def post_add_host(self, hardware_profile_name, software_profile_name,
                      add_host_session, *args, **kwargs):
        """
        Post add host processing on the installer node.

        Arguments:
            hardwareProfileName     the name of the hardware profile
                                    that hosts are being added to
            softwareProfileName     the name of the software profile
                                    that hosts are being added to
            newNodeIdList           list of new node id's just added.

        """
        logger.debug('post_add_host: {}, {}, {}, {}, {}'.format(
            hardware_profile_name, software_profile_name, add_host_session,
            args, kwargs))

        component_installers = self._get_enabled_component_installers(
            self._get_all_component_installers())

        #
        # This needs to be here because of circular imports :(
        #
        from tortuga.db.nodeDbApi import NodeDbApi
        nodes = NodeDbApi().getNodesByAddHostSession(add_host_session)

        self._run_action_with_node_list(component_installers,
                                        hardware_profile_name,
                                        software_profile_name, nodes,
                                        'add_host', *args, **kwargs)
Ejemplo n.º 13
0
def test_getNodeList(dbm):
    with dbm.session() as session:
        result = NodeDbApi().getNodeList(session)

    assert isinstance(result, TortugaObjectList)

    assert isinstance(result[0], Node)
Ejemplo n.º 14
0
def test_getNodesByNameFilter(dbm):
    with dbm.session() as session:
        result = NodeDbApi().getNodesByNameFilter(session, 'compute-*')

    assert isinstance(result, TortugaObjectList)

    assert result[0].getName().startswith('compute-')
Ejemplo n.º 15
0
class HardwareProfileManager(TortugaObjectManager, Singleton):
    def __init__(self):
        super(HardwareProfileManager, self).__init__()

        self._hpDbApi = HardwareProfileDbApi()
        self._spDbApi = SoftwareProfileDbApi()
        self._networkDbApi = NetworkDbApi()
        self._globalParameterDbApi = GlobalParameterDbApi()
        self._nodeDbApi = NodeDbApi()

    def getHardwareProfileList(self, optionDict=None, tags=None):
        """
        Return all of the hardwareprofiles with referenced components
        in this hardwareprofile
        """

        return self._hpDbApi.getHardwareProfileList(optionDict=optionDict,
                                                    tags=tags)

    def setIdleSoftwareProfile(self,
                               hardwareProfileName,
                               softwareProfileName=None):
        """Set idle software profile"""

        return self._hpDbApi.setIdleSoftwareProfile(hardwareProfileName,
                                                    softwareProfileName)

    def getHardwareProfile(self, name, optionDict=None):
        return self._hpDbApi.getHardwareProfile(name, optionDict or {})

    def getHardwareProfileById(self, id_, optionDict=None):
        return self._hpDbApi.getHardwareProfileById(id_, optionDict or {})

    def addAdmin(self, hardwareProfileName, adminUsername):
        """
        Add an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                HardwareProfileNotFound
        """

        return self._hpDbApi.addAdmin(hardwareProfileName, adminUsername)

    def deleteAdmin(self, hardwareProfileName, adminUsername):
        """
        Remove an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                HardwareProfileNotFound
        """

        return self._hpDbApi.deleteAdmin(hardwareProfileName, adminUsername)

    def updateHardwareProfile(self, hardwareProfileObject):
        """
        Update a hardware profile in the database that matches the passed
        in hardware profile object.  The ID is used as the primary matching
        criteria.

            Returns:
                None
            Throws:
                TortugaException
                HardwareProfileNotFound
                InvalidArgument

        """

        self.getLogger().debug('Updating hardware profile [%s]' %
                               (hardwareProfileObject.getName()))

        try:
            # First get the object from the db we are updating...
            existingProfile = self.\
                getHardwareProfileById(hardwareProfileObject.getId())

            if hardwareProfileObject.getInstallType() and \
                hardwareProfileObject.getInstallType() != \
                    existingProfile.getInstallType():
                raise InvalidArgument(
                    'Hardware profile installation type cannot be'
                    ' changed' % (hardwareProfileObject.getName()))

            self._hpDbApi.updateHardwareProfile(hardwareProfileObject)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise TortugaException(exception=ex)

    def createHardwareProfile(self, hwProfileSpec, settingsDict=None):
        settingsDict = settingsDict or {}

        bUseDefaults = settingsDict['bUseDefaults'] \
            if 'bUseDefaults' in settingsDict else False

        osInfo = settingsDict['osInfo'] \
            if settingsDict and 'osInfo' in settingsDict else None

        validation.validateProfileName(hwProfileSpec.getName())

        if hwProfileSpec.getDescription() is None or \
                hwProfileSpec.getDescription() == '**DEFAULT**':
            hwProfileSpec.setDescription('%s Nodes' %
                                         (hwProfileSpec.getName()))

        installerNode = self._nodeDbApi.getNode(ConfigManager().getInstaller(),
                                                {'softwareprofile': True})

        if bUseDefaults:
            if not hwProfileSpec.getNetworks():
                # No <network>...</network> entries found in the template,
                # use the default provisioning interface from the primary
                # installer.

                # Find first provisioning network and use it
                for nic in installerNode.getNics():
                    network = nic.getNetwork()
                    if network.getType() == 'provision':
                        # for now set the default interface to be index 0
                        # with the same device
                        networkDevice = fixNetworkDeviceName(
                            nic.getNetworkDevice().getName())

                        network.setNetworkDevice(
                            NetworkDevice(name=networkDevice))

                        hwProfileSpec.getNetworks().append(network)

                        break
                else:
                    raise NetworkNotFound(
                        'Unable to find provisioning network')
            else:
                # Ensure network device is defined
                installerNic = None

                for network in hwProfileSpec.getNetworks():
                    for installerNic in installerNode.getNics():
                        installerNetwork = installerNic.getNetwork()

                        if network.getId() and \
                           network.getId() == installerNetwork.getId():
                            break
                        elif network.getAddress() and \
                            network.getAddress() == \
                            installerNetwork.getAddress() and \
                            network.getNetmask() and \
                            network.getNetmask() == \
                                installerNetwork.getNetmask():
                            break
                    else:
                        # Unable to find network matching specification in
                        # template.

                        raise NetworkNotFound(
                            'Unable to find provisioning network [%s]' %
                            (network))

                    networkDevice = fixNetworkDeviceName(
                        installerNic.getNetworkDevice().getName())

                    network.setNetworkDevice(NetworkDevice(name=networkDevice))

        if hwProfileSpec.getIdleSoftwareProfile():
            # <idleSoftwareProfileId>...</idleSoftwareProfileId> is always
            # contained within the output of get-hardwareprofile.  If the
            # command-line option '--idleSoftwareProfile' is specified, it
            # overrides the
            # <idleSoftwareProfileId>...</idleSoftwareProfileId> element
            idleSoftwareProfile = self._spDbApi.getSoftwareProfile(
                hwProfileSpec.getIdleSoftwareProfile().getName())

            hwProfileSpec.setIdleSoftwareProfileId(idleSoftwareProfile.getId())

        if not osInfo:
            osInfo = installerNode.getSoftwareProfile().getOsInfo()

        osObjFactory = osUtility.getOsObjectFactory(osInfo.getName())

        if not hwProfileSpec.getKernel():
            hwProfileSpec.setKernel(
                osObjFactory.getOsSysManager().getKernel(osInfo))

        if not hwProfileSpec.getInitrd():
            hwProfileSpec.setInitrd(
                osObjFactory.getOsSysManager().getInitrd(osInfo))

        self._hpDbApi.addHardwareProfile(hwProfileSpec)

        # Iterate over all networks in the newly defined hardware profile
        # and build assocations to provisioning NICs
        if bUseDefaults:
            for network in \
                [network for network in hwProfileSpec.getNetworks()
                 if network.getType() == 'provision']:
                # Get provisioning nic for network
                try:
                    provisioningNic = self.getProvisioningNicForNetwork(
                        network.getAddress(), network.getNetmask())
                except NicNotFound:
                    # There is currently no provisioning NIC defined for
                    # this network.  This is not a fatal error.
                    continue

                self.setProvisioningNic(hwProfileSpec.getName(),
                                        provisioningNic.getId())

    def deleteHardwareProfile(self, name):
        """Delete hardwareprofile by name."""

        self._hpDbApi.deleteHardwareProfile(name)

        self.getLogger().info('Deleted hardware profile [%s]' % (name))

    def updateSoftwareOverrideAllowed(self, hardwareProfileName, flag):
        self._hpDbApi.updateSoftwareOverrideAllowed(hardwareProfileName, flag)

    def getHypervisorNodes(self, hardwareProfileName):
        return self._hpDbApi.getHypervisorNodes(hardwareProfileName)

    def setProvisioningNic(self, hardwareProfileName, nicId):
        return self._hpDbApi.setProvisioningNic(hardwareProfileName, nicId)

    def getProvisioningNicForNetwork(self, network, netmask):
        return self._hpDbApi.getProvisioningNicForNetwork(network, netmask)

    def copyHardwareProfile(self, srcHardwareProfileName,
                            dstHardwareProfileName):
        validation.validateProfileName(dstHardwareProfileName)

        self.getLogger().info('Copying hardware profile [%s] to [%s]' %
                              (srcHardwareProfileName, dstHardwareProfileName))

        return self._hpDbApi.copyHardwareProfile(srcHardwareProfileName,
                                                 dstHardwareProfileName)

    def getNodeList(self, hardwareProfileName):
        return self._hpDbApi.getNodeList(hardwareProfileName)
Ejemplo n.º 16
0
class NodeManager(TortugaObjectManager):     \
        # pylint: disable=too-many-public-methods

    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._hardwareProfileDbApi = HardwareProfileDbApi()
        self._cm = ConfigManager()
        self._san = san.San()

    def __validateHostName(self, hostname: str, name_format: str) -> NoReturn:
        """
        Raises:
            ConfigurationError
        """

        bWildcardNameFormat = (name_format == '*')

        if hostname and not bWildcardNameFormat:
            # Host name specified, but hardware profile does not
            # allow setting the host name
            raise ConfigurationError(
                'Hardware profile does not allow setting host names'
                ' of imported nodes')
        elif not hostname and bWildcardNameFormat:
            # Host name not specified but hardware profile expects it
            raise ConfigurationError(
                'Hardware profile requires host names to be set')

    def createNewNode(self,
                      session: Session,
                      addNodeRequest: dict,
                      dbHardwareProfile: HardwareProfiles,
                      dbSoftwareProfile: Optional[SoftwareProfiles] = None,
                      validateIp: bool = True,
                      bGenerateIp: bool = True,
                      dns_zone: Optional[str] = None) -> Nodes:
        """
        Convert the addNodeRequest into a Nodes object

        Raises:
            NicNotFound
        """

        self.getLogger().debug(
            'createNewNode(): session=[%s], addNodeRequest=[%s],'
            ' dbHardwareProfile=[%s], dbSoftwareProfile=[%s],'
            ' validateIp=[%s], bGenerateIp=[%s]' %
            (id(session), addNodeRequest, dbHardwareProfile.name,
             dbSoftwareProfile.name if dbSoftwareProfile else '(none)',
             validateIp, bGenerateIp))

        # This is where the Nodes() object is first created.
        node = Nodes()

        # Set the default node state
        node.state = 'Discovered'

        if 'rack' in addNodeRequest:
            node.rack = addNodeRequest['rack']

        node.addHostSession = addNodeRequest['addHostSession']

        hostname = addNodeRequest['name'] \
            if 'name' in addNodeRequest else None

        # Ensure no conflicting options (ie. specifying host name for
        # hardware profile in which host names are generated)
        self.__validateHostName(hostname, dbHardwareProfile.nameFormat)

        node.name = hostname

        # Complete initialization of new node record
        nic_defs = addNodeRequest['nics'] \
            if 'nics' in addNodeRequest else []

        AddHostServerLocal().initializeNode(session,
                                            node,
                                            dbHardwareProfile,
                                            dbSoftwareProfile,
                                            nic_defs,
                                            bValidateIp=validateIp,
                                            bGenerateIp=bGenerateIp,
                                            dns_zone=dns_zone)

        # Set hardware profile of new node
        node.hardwareProfileId = dbHardwareProfile.id

        # Set software profile of new node; if the software profile is None,
        # attempt to set the software profile to the idle software profile
        # of the associated hardware profile. This may also be None, in
        # which case the software profile is undefined.
        node.softwareprofile = dbSoftwareProfile \
            if dbSoftwareProfile else dbHardwareProfile.idlesoftwareprofile

        node.isIdle = dbSoftwareProfile.isIdle \
            if dbSoftwareProfile else True

        # Return the new node
        return node

    def getNode(self, name, optionDict=None):
        """Get node by name"""

        optionDict_ = optionDict.copy() if optionDict else {}

        optionDict_.update({'hardwareprofile': True})

        node = self._nodeDbApi.getNode(name, optionDict_)

        hwprofile = self._hardwareProfileDbApi.getHardwareProfile(
            node.getHardwareProfile().getName(), {'resourceadapter': True})

        adapter_name = hwprofile.getResourceAdapter().getName() \
            if hwprofile.getResourceAdapter() else 'default'

        # Query vcpus from resource adapter
        ResourceAdapterClass = resourceAdapterFactory.getResourceAdapterClass(
            adapter_name)

        # Update Node object
        node.setVcpus(ResourceAdapterClass().get_node_vcpus(node.getName()))

        return node

    def getNodeById(self, nodeId, optionDict=None):
        """
        Get node by node id

        Raises:
            NodeNotFound
        """
        return self._nodeDbApi.getNodeById(int(nodeId), optionDict)

    def getNodeByIp(self, ip):
        """
        Get node by IP address

        Raises:
            NodeNotFound
        """

        return self._nodeDbApi.getNodeByIp(ip)

    def getNodeList(self, tags=None):
        """Return all nodes"""
        return self._nodeDbApi.getNodeList(tags=tags)

    def updateNode(self, nodeName, updateNodeRequest):
        self.getLogger().debug('updateNode(): name=[{0}]'.format(nodeName))

        session = DbManager().openSession()

        try:
            node = NodesDbHandler().getNode(session, nodeName)

            if 'nics' in updateNodeRequest:
                nic = updateNodeRequest['nics'][0]

                if 'ip' in nic:
                    node.nics[0].ip = nic['ip']
                    node.nics[0].boot = True

            # Call resource adapter
            NodesDbHandler().updateNode(session, node, updateNodeRequest)

            run_post_install = False

            if 'state' in updateNodeRequest:
                run_post_install = node.state == 'Allocated' and \
                    updateNodeRequest['state'] == 'Provisioned'

                node.state = updateNodeRequest['state']

            session.commit()

            if run_post_install:
                self.getLogger().debug(
                    'updateNode(): run-post-install for node [{0}]'.format(
                        node.name))

                self.__scheduleUpdate()
        except Exception:
            session.rollback()

            self.getLogger().exception(
                'Exception updating node [{0}]'.format(nodeName))
        finally:
            DbManager().closeSession()

    def updateNodeStatus(self, nodeName, state=None, bootFrom=None):
        """Update node status

        If neither 'state' nor 'bootFrom' are not None, this operation will
        update only the 'lastUpdated' timestamp.

        Returns:
            bool indicating whether state and/or bootFrom differed from
            current value
        """

        value = 'None' if bootFrom is None else \
            '1 (disk)' if int(bootFrom) == 1 else '0 (network)'

        self.getLogger().debug(
            'updateNodeStatus(): node=[%s], state=[%s], bootFrom=[%s]' %
            (nodeName, state, value))

        session = DbManager().openSession()

        try:
            node = NodesDbHandler().getNode(session, nodeName)

            result = self._updateNodeStatus(node,
                                            state=state,
                                            bootFrom=bootFrom)

            session.commit()

            return result
        finally:
            DbManager().closeSession()

    def _updateNodeStatus(self, dbNode, state=None, bootFrom=None):
        """
        Internal method which takes a 'Nodes' object instead of a node
        name.
        """

        result = NodesDbHandler().updateNodeStatus(dbNode, state, bootFrom)

        # Only change local boot configuration if the hardware profile is
        # not marked as 'remote' and we're not acting on the installer node.
        if dbNode.softwareprofile and \
                dbNode.softwareprofile.type != 'installer' and \
                dbNode.hardwareprofile.location not in \
                ('remote', 'remote-vpn'):
            osUtility.getOsObjectFactory().getOsBootHostManager().\
                writePXEFile(dbNode, localboot=bootFrom)

        return result

    def __process_nodeErrorDict(self, nodeErrorDict):
        result = {}
        nodes_deleted = []

        for key, nodeList in nodeErrorDict.items():
            result[key] = [dbNode.name for dbNode in nodeList]

            if key == 'NodesDeleted':
                for node in nodeList:
                    node_deleted = {
                        'name': node.name,
                        'hardwareprofile': node.hardwareprofile.name,
                        'addHostSession': node.addHostSession,
                    }

                    if node.softwareprofile:
                        node_deleted['softwareprofile'] = \
                            node.softwareprofile.name

                    nodes_deleted.append(node_deleted)

        return result, nodes_deleted

    def deleteNode(self, nodespec):
        """
        Delete node by nodespec

        Raises:
            NodeNotFound
        """

        installer_hostname = socket.getfqdn().split('.', 1)[0]

        session = DbManager().openSession()

        try:
            nodes = []

            for node in self.__expand_nodespec(session, nodespec):
                if node.name.split('.', 1)[0] == installer_hostname:
                    self.getLogger().info(
                        'Ignoring request to delete installer node'
                        ' ([{0}])'.format(node.name))

                    continue

                nodes.append(node)

            if not nodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            self.__preDeleteHost(nodes)

            nodeErrorDict = NodesDbHandler().deleteNode(session, nodes)

            # REALLY!?!? Convert a list of Nodes objects into a list of
            # node names so we can report the list back to the end-user.
            # This needs to be FIXED!

            result, nodes_deleted = self.__process_nodeErrorDict(nodeErrorDict)

            session.commit()

            # ============================================================
            # Perform actions *after* node deletion(s) have been committed
            # to database.
            # ============================================================

            self.__postDeleteHost(nodes_deleted)

            addHostSessions = set(
                [tmpnode['addHostSession'] for tmpnode in nodes_deleted])

            if addHostSessions:
                AddHostManager().delete_sessions(addHostSessions)

            bhm = osUtility.getOsObjectFactory().getOsBootHostManager()

            for nodeName in result['NodesDeleted']:
                # Remove the Puppet cert
                bhm.deletePuppetNodeCert(nodeName)

                bhm.nodeCleanup(nodeName)

                self.getLogger().info('Node [%s] deleted' % (nodeName))

            # Schedule a cluster update
            self.__scheduleUpdate()

            return result
        except TortugaException:
            session.rollback()

            raise
        except Exception:
            session.rollback()

            self.getLogger().exception('Exception in NodeManager.deleteNode()')

            raise
        finally:
            DbManager().closeSession()

    def __process_delete_node_result(self, nodeErrorDict):
        # REALLY!?!? Convert a list of Nodes objects into a list of
        # node names so we can report the list back to the end-user.
        # This needs to be FIXED!

        result = {}
        nodes_deleted = []

        for key, nodeList in nodeErrorDict.items():
            result[key] = [dbNode.name for dbNode in nodeList]

            if key == 'NodesDeleted':
                for node in nodeList:
                    node_deleted = {
                        'name': node.name,
                        'hardwareprofile': node.hardwareprofile.name,
                    }

                    if node.softwareprofile:
                        node_deleted['softwareprofile'] = \
                            node.softwareprofile.name

                    nodes_deleted.append(node_deleted)

        return result, nodes_deleted

    def __preDeleteHost(self, nodes):
        self.getLogger().debug('__preDeleteHost(): nodes=[%s]' %
                               (' '.join([node.name for node in nodes])))

        if not nodes:
            self.getLogger().debug('No nodes deleted in this operation')

            return

        kitmgr = KitActionsManager()

        for node in nodes:
            kitmgr.pre_delete_host(
                node.hardwareprofile.name,
                node.softwareprofile.name if node.softwareprofile else None,
                nodes=[node.name])

    def __postDeleteHost(self, nodes_deleted):
        # 'nodes_deleted' is a list of dicts of the following format:
        #
        # {
        #     'name': 'compute-01',
        #     'softwareprofile': 'Compute',
        #     'hardwareprofile': 'LocalIron',
        # }
        #
        # if the node does not have an associated software profile, the
        # dict does not contain the key 'softwareprofile'.

        self.getLogger().debug('__postDeleteHost(): nodes_deleted=[%s]' %
                               (nodes_deleted))

        if not nodes_deleted:
            self.getLogger().debug('No nodes deleted in this operation')

            return

        kitmgr = KitActionsManager()

        for node_dict in nodes_deleted:
            kitmgr.post_delete_host(node_dict['hardwareprofile'],
                                    node_dict['softwareprofile'] if
                                    'softwareprofile' in node_dict else None,
                                    nodes=[node_dict['name']])

    def __scheduleUpdate(self):
        tortugaSubprocess.executeCommand(
            os.path.join(self._cm.getRoot(), 'bin/schedule-update'))

    def getInstallerNode(self, optionDict=None):
        return self._nodeDbApi.getNode(self._cm.getInstaller(),
                                       optionDict=optionDict)

    def getProvisioningInfo(self, nodeName):
        return self._nodeDbApi.getProvisioningInfo(nodeName)

    def getKickstartFile(self, node, hardwareprofile, softwareprofile):
        """
        Generate kickstart file for specified node

        Raises:
            OsNotSupported
        """

        osFamilyName = softwareprofile.os.family.name

        try:
            osSupportModule = __import__('tortuga.os.%s.osSupport' %
                                         (osFamilyName),
                                         fromlist=['OSSupport'])
        except ImportError:
            raise OsNotSupported('Operating system family [%s] not supported' %
                                 (osFamilyName))

        OSSupport = osSupportModule.OSSupport

        tmpOsFamilyInfo = OsFamilyInfo(softwareprofile.os.family.name,
                                       softwareprofile.os.family.version,
                                       softwareprofile.os.family.arch)

        return OSSupport(tmpOsFamilyInfo).getKickstartFileContents(
            node, hardwareprofile, softwareprofile)

    def __transferNodeCommon(self, session, dbDstSoftwareProfile, results):         \
            # pylint: disable=no-self-use

        # Aggregate list of transferred nodes based on hardware profile
        # to call resource adapter minimal number of times.

        hwProfileMap = {}

        for transferResultDict in results:
            dbNode = transferResultDict['node']
            dbHardwareProfile = dbNode.hardwareprofile

            if dbHardwareProfile not in hwProfileMap:
                hwProfileMap[dbHardwareProfile] = [transferResultDict]
            else:
                hwProfileMap[dbHardwareProfile].append(transferResultDict)

        session.commit()

        nodeTransferDict = {}

        # Kill two birds with one stone... do the resource adapter
        # action as well as populate the nodeTransferDict. This saves
        # having to iterate twice on the same result data.
        for dbHardwareProfile, nodesDict in hwProfileMap.items():
            adapter = resourceAdapterFactory.getApi(
                dbHardwareProfile.resourceadapter.name)

            dbNodeTuples = []

            for nodeDict in nodesDict:
                dbNode = nodeDict['node']
                dbSrcSoftwareProfile = nodeDict['prev_softwareprofile']

                if dbSrcSoftwareProfile.name not in nodeTransferDict:
                    nodeTransferDict[dbSrcSoftwareProfile.name] = {
                        'added': [],
                        'removed': [dbNode],
                    }
                else:
                    nodeTransferDict[dbSrcSoftwareProfile.name]['removed'].\
                        append(dbNode)

                if dbDstSoftwareProfile.name not in nodeTransferDict:
                    nodeTransferDict[dbDstSoftwareProfile.name] = {
                        'added': [dbNode],
                        'removed': [],
                    }
                else:
                    nodeTransferDict[dbDstSoftwareProfile.name]['added'].\
                        append(dbNode)

                # The destination software profile is available through
                # node relationship.
                dbNodeTuples.append((dbNode, dbSrcSoftwareProfile))

            adapter.transferNode(dbNodeTuples, dbDstSoftwareProfile)

            session.commit()

        # Now call the 'refresh' action to all participatory components
        KitActionsManager().refresh(nodeTransferDict)

        return results

    def transferNode(self, nodespec, dstSoftwareProfileName, bForce=False):
        """
        Transfer nodes defined by 'nodespec' to 'dstSoftwareProfile'

        Raises:
            NodeNotFound
            SoftwareProfileNotFound
            NodeTransferNotValid
        """

        session = DbManager().openSession()

        try:
            nodes = self.__expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            dbDstSoftwareProfile = SoftwareProfilesDbHandler().\
                getSoftwareProfile(session, dstSoftwareProfileName)

            results = NodesDbHandler().transferNode(session,
                                                    nodes,
                                                    dbDstSoftwareProfile,
                                                    bForce=bForce)

            return self.__transferNodeCommon(session, dbDstSoftwareProfile,
                                             results)
        finally:
            DbManager().closeSession()

    def transferNodes(self,
                      srcSoftwareProfileName,
                      dstSoftwareProfileName,
                      count,
                      bForce=False):
        """
        Transfer 'count' nodes from 'srcSoftwareProfile' to
        'dstSoftwareProfile'

        Raises:
            SoftwareProfileNotFound
        """

        session = DbManager().openSession()

        try:
            # It is not necessary to specify a source software profile. If
            # not specified, pick any eligible nodes in the hardware profile
            # mapped to the destination software profile. Don't ask me who
            # uses this capability, but it's here if you need it...

            dbSrcSoftwareProfile = SoftwareProfilesDbHandler().\
                getSoftwareProfile(
                    session, srcSoftwareProfileName) \
                if srcSoftwareProfileName else None

            dbDstSoftwareProfile = SoftwareProfilesDbHandler().\
                getSoftwareProfile(session, dstSoftwareProfileName)

            results = NodesDbHandler().transferNodes(session,
                                                     dbSrcSoftwareProfile,
                                                     dbDstSoftwareProfile,
                                                     int(float(count)),
                                                     bForce=bForce)

            return self.__transferNodeCommon(session, dbDstSoftwareProfile,
                                             results)
        finally:
            DbManager().closeSession()

    def idleNode(self, nodespec):
        """
        Raises:
            NodeNotFound
        """

        session = DbManager().openSession()

        try:
            nodes = self.__expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            result = NodesDbHandler().idleNode(session, nodes)

            # Convert list of Nodes to list of node names for providing
            # user feedback.

            result_dict = {}
            for key, dbNodes in result.items():
                result_dict[key] = [dbNode.name for dbNode in dbNodes]

            session.commit()

            # Remove Puppet certificate(s) for idled node(s)
            for node_name in result_dict['success']:
                # Remove Puppet certificate for idled node
                bhm = osUtility.getOsObjectFactory().getOsBootHostManager()
                bhm.deletePuppetNodeCert(node_name)

            # Schedule a cluster update
            self.__scheduleUpdate()

            return result_dict
        except TortugaException as ex:
            session.rollback()

            raise
        except Exception as ex:
            session.rollback()

            self.getLogger().exception('[%s] %s' %
                                       (self.__class__.__name__, ex))

            raise
        finally:
            DbManager().closeSession()

    def __process_activateNode_results(self, tmp_results, dstswprofilename):
        results = {}

        for key, values in tmp_results.items():
            # With the exception of the "ProfileMappingNotAllowed" dict
            # item, all items in the dict are lists of nodes.
            if key != 'ProfileMappingNotAllowed':
                results[key] = [dbNode.name for dbNode in values]
            else:
                results[key] = \
                    [(value[0].name, value[1], value[2])
                     for value in values]

        if tmp_results['success']:
            # Iterate over activated nodes, creating dict keyed on
            # 'addHostSession'
            addHostSessions = {}

            for node in tmp_results['success']:
                if node.addHostSession not in addHostSessions:
                    addHostSessions[node.addHostSession] = []

                addHostSessions[node.addHostSession] = \
                    node.hardwareprofile.name

            # For each 'addHostSession', call postAddHost()
            for addHostSession, hwprofile in addHostSessions.items():
                AddHostManager().postAddHost(hwprofile, dstswprofilename,
                                             addHostSession)

        return results

    def activateNode(self, nodespec, softwareProfileName):
        """
        Raises:
            SoftwareProfileNotFound
            NodeNotFound
            TortugaException
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = SoftwareProfilesDbHandler().\
                getSoftwareProfile(session, softwareProfileName) \
                if softwareProfileName else None

            dbNodes = self.__expand_nodespec(session, nodespec)

            if not dbNodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            tmp_results = NodesDbHandler().activateNode(
                session, dbNodes, dbSoftwareProfile)

            results = self.__process_activateNode_results(
                tmp_results, softwareProfileName)

            session.commit()

            # Schedule a cluster update
            self.__scheduleUpdate()

            return results
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def startupNode(self, nodespec, remainingNodeList=None, bootMethod='n'):
        """
        Raises:
            NodeNotFound
        """

        return self._nodeDbApi.startupNode(nodespec,
                                           remainingNodeList=remainingNodeList
                                           or [],
                                           bootMethod=bootMethod)

    def shutdownNode(self, nodespec, bSoftShutdown=False):
        """
        Raises:
            NodeNotFound
        """

        return self._nodeDbApi.shutdownNode(nodespec, bSoftShutdown)

    def build_node_filterspec(self, nodespec):
        filter_spec = []

        for nodespec_token in nodespec.split(','):
            # Convert shell-style wildcards into SQL wildcards
            if '*' in nodespec_token or '?' in nodespec_token:
                filter_spec.append(
                    nodespec_token.replace('*', '%').replace('?', '_'))

                continue

            if '.' not in nodespec_token:
                filter_spec.append(nodespec_token)
                filter_spec.append(nodespec_token + '.%')

                continue

            # Add nodespec "AS IS"
            filter_spec.append(nodespec_token)

        return filter_spec

    def __expand_nodespec(self, session, nodespec):         \
            # pylint: disable=no-self-use

        # Expand wildcards in nodespec. Each token in the nodespec can
        # be wildcard that expands into one or more nodes.

        return NodesDbHandler().getNodesByNameFilter(
            session, self.build_node_filterspec(nodespec))

    def rebootNode(self, nodespec, bSoftReset=False, bReinstall=False):
        """
        Raises:
            NodeNotFound
        """

        session = DbManager().openSession()

        try:
            nodes = self.__expand_nodespec(session, nodespec)
            if not nodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            bhm = osUtility.getOsObjectFactory().getOsBootHostManager()

            if bReinstall:
                for dbNode in nodes:
                    bhm.setNodeForNetworkBoot(dbNode)

            results = NodesDbHandler().rebootNode(session, nodes, bSoftReset)

            session.commit()

            return results
        finally:
            DbManager().closeSession()

    def checkpointNode(self, nodeName):
        return self._nodeDbApi.checkpointNode(nodeName)

    def revertNodeToCheckpoint(self, nodeName):
        return self._nodeDbApi.revertNodeToCheckpoint(nodeName)

    def migrateNode(self, nodeName, remainingNodeList, liveMigrate):
        return self._nodeDbApi.migrateNode(nodeName, remainingNodeList,
                                           liveMigrate)

    def evacuateChildren(self, nodeName):
        self._nodeDbApi.evacuateChildren(nodeName)

    def getChildrenList(self, nodeName):
        return self._nodeDbApi.getChildrenList(nodeName)

    def setParentNode(self, nodeName, parentNodeName):
        self._nodeDbApi.setParentNode(nodeName, parentNodeName)

    def addStorageVolume(self, nodeName, volume, isDirect="DEFAULT"):
        """
        Raises:
            VolumeDoesNotExist
            UnsupportedOperation
        """

        node = self.getNode(nodeName, {'hardwareprofile': True})

        # Only allow persistent volumes to be attached...
        vol = self._san.getVolume(volume)
        if vol is None:
            raise VolumeDoesNotExist('Volume [%s] does not exist' % (volume))

        if not vol.getPersistent():
            raise UnsupportedOperation(
                'Only persistent volumes can be attached')

        api = resourceAdapterFactory.getApi(
            node.getHardwareProfile().getResourceAdapter().getName())

        if isDirect == "DEFAULT":
            return api.addVolumeToNode(node, volume)

        return api.addVolumeToNode(node, volume, isDirect)

    def removeStorageVolume(self, nodeName, volume):
        """
        Raises:
            VolumeDoesNotExist
            UnsupportedOperation
        """

        node = self.getNode(nodeName, {'hardwareprofile': True})

        api = resourceAdapterFactory.getApi(
            node.getHardwareProfile().getResourceAdapter().getName())

        vol = self._san.getVolume(volume)

        if vol is None:
            raise VolumeDoesNotExist('The volume [%s] does not exist' %
                                     (volume))

        if not vol.getPersistent():
            raise UnsupportedOperation(
                'Only persistent volumes can be detached')

        return api.removeVolumeFromNode(node, volume)

    def getStorageVolumes(self, nodeName):
        return self._san.getNodeVolumes(self.getNode(nodeName).getName())

    def getNodesByNodeState(self, state):
        return self._nodeDbApi.getNodesByNodeState(state)

    def getNodesByNameFilter(self, _filter):
        return self._nodeDbApi.getNodesByNameFilter(_filter)
Ejemplo n.º 17
0
class SoftwareProfileManager(TortugaObjectManager, Singleton):

    BASE_KIT_NAME = 'base'

    def __init__(self):
        super(SoftwareProfileManager, self).__init__()
        self._sp_db_api = SoftwareProfileDbApi()
        self._node_db_api = NodeDbApi()
        self._component_db_api = ComponentDbApi()
        self._global_param_db_api = GlobalParameterDbApi()
        self._kit_db_api = KitDbApi()
        self._config_manager = ConfigManager()

    def getSoftwareProfileList(self, tags=None):
        """Return all of the softwareprofiles with referenced components
        in this softwareprofile
        """

        return self._sp_db_api.getSoftwareProfileList(tags=tags)

    def getIdleSoftwareProfileList(self):
        """ Return all of the idle softwareprofiles """
        return self._sp_db_api.getIdleSoftwareProfileList()

    def setIdleState(self, softwareProfileName, state):
        """ Sets the  idle state of a softwareprofile """
        return self._sp_db_api.setIdleState(softwareProfileName, state)

    def addAdmin(self, softwareProfileName, adminUsername):
        """
        Add an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                SoftwareProfileNotFound
        """
        return self._sp_db_api.addAdmin(softwareProfileName, adminUsername)

    def deleteAdmin(self, softwareProfileName, adminUsername):
        """
        Remove an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                SoftwareProfileNotFound
        """
        return self._sp_db_api.deleteAdmin(softwareProfileName, adminUsername)

    def updateSoftwareProfile(self, softwareProfileObject):
        try:
            self.getLogger().debug('Updating software profile: %s' %
                                   (softwareProfileObject.getName()))

            # First get the object from the db we are updating...
            existingProfile = self.\
                getSoftwareProfileById(softwareProfileObject.getId())

            # Set parameters that we will not allow updating
            softwareProfileObject.setOsInfo(existingProfile.getOsInfo())
            softwareProfileObject.setOsId(existingProfile.getOsId())
            softwareProfileObject.setIsIdle(existingProfile.getIsIdle())
            softwareProfileObject.setType(existingProfile.getType())

            self._sp_db_api.updateSoftwareProfile(softwareProfileObject)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise TortugaException(exception=ex)

    def getSoftwareProfile(self, name, optionDict=None):
        return self._sp_db_api.getSoftwareProfile(name, optionDict or {})

    def getSoftwareProfileById(self, id_, optionDict=None):
        return self._sp_db_api.getSoftwareProfileById(id_,
                                                      optionDict=optionDict
                                                      or {})

    def _getCoreComponentForOsInfo(self, osInfo):
        # Find core component

        # Find the version of the 'core' component
        import tortuga.kit.kitApi
        _kitApi = tortuga.kit.kitApi.KitApi()

        baseKit = None

        for baseKit in _kitApi.getKitList():
            if not baseKit.getName() == self.BASE_KIT_NAME:
                continue

            break
        else:
            raise KitNotFound('Kit [%s] not found.' % (self.BASE_KIT_NAME))

        baseComp = None

        for baseComp in baseKit.getComponentList():
            if baseComp.getName() != 'core':
                continue

            break
        else:
            raise ComponentNotFound('Component [%s] not found in kit [%s]' %
                                    ('core', baseKit.getName()))

        comp = osUtility.getOsObjectFactory().getComponentManager().\
            getBestMatchComponent(
                baseComp.getName(), baseComp.getVersion(), osInfo,
                baseKit.getId())

        comp.setKit(baseKit)

        return comp

    def _getOsInfo(self, bOsMediaRequired):
        if not bOsMediaRequired:
            # As a placeholder, use the same OS as the installer

            # Find installer node entry
            node = self._node_db_api.getNode(ConfigManager().getInstaller(),
                                             {'softwareprofile': True})

            return node.getSoftwareProfile().getOsInfo()

        # Use available operating system kit; raise exception if
        # multiple available

        os_kits = _get_os_kits()
        if not os_kits:
            raise KitNotFound('No operating system kit installed')

        if len(os_kits) > 1:
            raise KitNotFound(
                'Multiple OS kits defined; use --os option to specify'
                ' operating system')

        kit = kitApiFactory.getKitApi().getKit(os_kits[0].getName(),
                                               os_kits[0].getVersion(), '0')

        components = kit.getComponentList()

        if not components:
            raise ComponentNotFound('Malformed operating system kit [%s]' %
                                    (os_kits))

        osinfo_list = components[0].getOsInfoList()
        if len(osinfo_list) > 1:
            raise ComponentNotFound(
                'Multiple operating system components for kit [%s];'
                ' use --os argument to specify operating system' %
                (os_kits[0]))

        return osinfo_list[0]

    def createSoftwareProfile(self, swProfileSpec, settingsDict=None):
        """
        Exceptions:
            ConfigurationError
            NetworkNotFound
            ComponentNotFound
            KitNotFound
            OSError
        """

        # Parse 'settingsDict'
        if settingsDict:
            # ... bOsMediaRequired; default is True
            bOsMediaRequired = settingsDict['bOsMediaRequired'] \
                if 'bOsMediaRequired' in settingsDict else True

            # ... unmanagedProfile; default is False
            unmanagedProfile = settingsDict['unmanagedProfile'] \
                if 'unmanagedProfile' in settingsDict else False

        # Validate software profile name
        validation.validateProfileName(swProfileSpec.getName())

        # Insert default description for software profile
        if not swProfileSpec.getDescription() or \
                swProfileSpec.getDescription() == '**DEFAULT**':
            swProfileSpec.setDescription('%s Nodes' %
                                         (swProfileSpec.getName()))

        self.getLogger().debug('Creating software profile [%s]' %
                               (swProfileSpec))

        osInfo = swProfileSpec.getOsInfo() \
            if swProfileSpec.getOsInfo() else self._getOsInfo(bOsMediaRequired)

        # If we're creating an unmanaged software profile (no
        # DHCP/PXE/kickstart/OS) just create it now and we're done
        if unmanagedProfile:
            self._sp_db_api.addSoftwareProfile(swProfileSpec)
        else:
            if bOsMediaRequired and swProfileSpec.getOsInfo():
                try:
                    kitApiFactory.getKitApi().getKit(
                        swProfileSpec.getOsInfo().getName(),
                        swProfileSpec.getOsInfo().getVersion(), '0')
                except KitNotFound:
                    self._logger.error('OS kit for [%s] not found' %
                                       (swProfileSpec.getOsInfo()))

                    raise
            else:
                swProfileSpec.setOsInfo(osInfo)

            # Get component manager for appropriate OS family
            osConfig = osHelper.getOsInfo(osInfo.getName(),
                                          osInfo.getVersion(),
                                          osInfo.getArch())

            osObjFactory = osUtility.getOsObjectFactory(
                osConfig.getOsFamilyInfo().getName())
            compManager = osObjFactory.getComponentManager()

            # Need to be fancy with components
            spComponents = swProfileSpec.getComponents()
            swProfileSpec.setComponents(TortugaObjectList())

            bFoundOsComponent = False
            bFoundCoreComponent = False
            components = []

            # Iterate over components, adding them to the software profile
            for c in spComponents:
                cobj = compManager.getBestMatchComponent(
                    c.getName(), c.getVersion(), osInfo,
                    c.getKit().getId())

                k = cobj.getKit()

                if k.getIsOs():
                    # This component is a member of the OS kit, set the flag
                    bFoundOsComponent = True
                else:
                    if c.getName() == 'core':
                        # Found the 'core' component, set the flag
                        bFoundCoreComponent = True

                components.append(cobj)

            # If the operating system is undefined for this software
            # profile, use the same OS as the installer.
            if bOsMediaRequired and not bFoundOsComponent:
                # Find OS component
                osCompName = '%s-%s-%s' % (
                    osInfo.getName(), osInfo.getVersion(), osInfo.getArch())

                self.getLogger().debug('Automatically adding OS component [%s]'
                                       ' (not specified in template)' %
                                       (osCompName))

                try:
                    osComponent = self._component_db_api.getComponent(
                        osCompName, osInfo.getVersion(), osInfo, {'kit': True})

                    components.append(osComponent)
                except ComponentNotFound:
                    # Cannot find OS component, don't freak out
                    pass

            # Ensure 'core' component is enabled
            if not bFoundCoreComponent:
                # Attempt to automatically add the core component, only
                # if one exists for this OS

                try:
                    comp = self._getCoreComponentForOsInfo(osInfo)

                    self.getLogger().debug(
                        'Automatically adding [core] component'
                        ' (not specified in template)')

                    components.append(comp)
                except ComponentNotFound:
                    pass

                # Initialize values for kernel, kernelParams, and initrd
                if not swProfileSpec.getKernel():
                    swProfileSpec.setKernel(
                        osObjFactory.getOsSysManager().getKernel(osInfo))

                if not swProfileSpec.getInitrd():
                    swProfileSpec.setInitrd(
                        osObjFactory.getOsSysManager().getInitrd(osInfo))

            # Add the software profile
            self._sp_db_api.addSoftwareProfile(swProfileSpec)

            # Enable components in one fell swoop
            for comp in components:
                self.getLogger().debug('Enabling component [%s]' %
                                       (comp.getName()))

                if comp.getKit().getIsOs():
                    # Don't use enableComponent() on OS kit
                    self._component_db_api.\
                        addComponentToSoftwareProfile(
                            comp.getId(), swProfileSpec.getId())

                    continue

                self.enableComponent(swProfileSpec.getName(),
                                     comp.getKit().getName(),
                                     comp.getKit().getVersion(),
                                     comp.getKit().getIteration(),
                                     comp.getName(), comp.getVersion())

            self.getLogger().debug(
                'Software profile [%s] created successfully' %
                (swProfileSpec.getName()))

    def _getComponent(self, kit, compName, compVersion):         \
            # pylint: disable=no-self-use

        # Iterate over component list, looking for a match
        comp = None

        for comp in kit.getComponentList():
            if comp.getName() == compName and \
                    comp.getVersion() == compVersion:
                break
        else:
            raise ComponentNotFound("Component [%s-%s] not found in kit [%s]" %
                                    (compName, compVersion, kit))

        return comp

    def _get_kit_by_component(self, comp_name, comp_version=None):
        """
        Gets a kit by compoent name/version.
        :param comp_name:    the name of the component
        :param comp_version: the version of the component

        :raises KitNotFound:
        :raises ComponentNotFound:

        """
        kit_list = self._kit_db_api.getKitList()
        kits = [
            kit for kit in kit_list for component in kit.getComponentList()
            if component.getName() == comp_name and (
                comp_version is None or component.getVersion() == comp_version)
        ]
        if not kits:
            raise KitNotFound('Kit containing component [%s] not found' %
                              (comp_name))

        if len(kits) > 1:
            raise ComponentNotFound(
                'Kit name must be specified, multiple kits contain '
                'component: {}'.format(comp_name))

        return kits[0]

    def enableComponent(self,
                        software_profile_name,
                        kit_name,
                        kit_version,
                        kit_iteration,
                        comp_name,
                        comp_version=None):
        """
        Enable a component on a software profile.

        :param software_profile_name: the name of the software profile
        :param kit_name:              the name of the kit
        :param kit_version:           the version of the kit
        :param kit_iteration:         the iteration of the kit
        :param comp_name:             the name of the component
        :param comp_version:          the version of the component

        :raises KitNotFound:
        :raises SoftwareProfileNotFound:
        :raises ComponentNotFound:

        """
        kit, comp_version = self._get_kit_and_component_version(
            kit_name, kit_version, kit_iteration, comp_name, comp_version)

        software_profile = self.getSoftwareProfile(software_profile_name,
                                                   {'os': True})

        if kit.getIsOs():
            best_match_component = self._enable_os_kit_component(
                kit, comp_name, comp_version, software_profile)
        else:
            best_match_component = self._enable_kit_component(
                kit, comp_name, comp_version, software_profile)

        if not best_match_component:
            self.getLogger().info(
                'Component not enabled: {}'.format(comp_name))
        else:
            self.getLogger().info(
                'Enabled component on software profile: {} -> {}'.format(
                    best_match_component, software_profile))

    def _get_kit_and_component_version(self,
                                       kit_name,
                                       kit_version,
                                       kit_iteration,
                                       comp_name,
                                       comp_version=None):
        """
        Gets a Kit instance and component version.

        :param kit_name:      the name of the kit
        :param kit_version:   the version of the kit
        :param kit_iteration: the iteration of the kit
        :param comp_name:     the component name
        :param comp_version:  the component version (optional)

        :return: a tuple, consisting of (Kit, component_version)

        """
        kit = None
        if kit_name is None:
            kit = self._get_kit_by_component(comp_name,
                                             comp_version=comp_version)
            #
            # Get component version if required
            #
            if comp_version is None:
                for component in kit.getComponentList():
                    if component.getName() == comp_name:
                        comp_version = component.getVersion()
                        break
        elif kit_version is None or kit_iteration is None:
            kits_found = 0
            for k in self._kit_db_api.getKitList():
                if k.getName() == kit_name and \
                        (kit_version is None or
                            k.getVersion() == kit_version) and \
                        (kit_iteration is None or
                            k.getIteration() == kit_iteration):
                    kit = k
                    kits_found += 1

            if kits_found > 1:
                if kit_version is not None:
                    raise KitNotFound('Multiple kits found: {}-{}'.format(
                        kit_name, kit_version))
                else:
                    raise KitNotFound(
                        'Multiple kits found {}'.format(kit_name))
        else:
            kit = self._kit_db_api.getKit(kit_name, kit_version, kit_iteration)

        return kit, comp_version

    def _enable_kit_component(self, kit, comp_name, comp_version,
                              software_profile):
        """
        Enables a regular kit component on a specific software profile.

        :param kit:              the Kit instance, whose component is being
                                 enabled
        :param comp_name:        the name of the component to enable
        :param comp_version:     the version of the component to enable
        :param software_profile: the software profile on which the component
                                 will be enabled

        :return:                 the Component instance that was enabled

        """
        kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration())

        load_kits()
        installer = get_kit_installer(kit_spec)()
        comp_installer = installer.get_component_installer(comp_name)
        if not comp_installer.is_enableable(software_profile):
            self.getLogger().warning('Component cannot be enabled: {}'.format(
                comp_installer.spec))
            return None
        comp_installer.run_action('pre_enable', software_profile.getName())

        best_match_component = self._add_component_to_software_profile(
            kit, comp_name, comp_version, software_profile)

        comp_installer.run_action('enable', software_profile.getName())
        comp_installer.run_action('post_enable', software_profile.getName())

        return best_match_component

    def _enable_os_kit_component(self, kit, comp_name, comp_version,
                                 software_profile):
        """
        Enables an OS kit component on a specific software profile.

        :param kit:              the OS Kit instance, whose component is being
                                 enabled
        :param comp_name:        the name of the component to enable
        :param comp_version:     the version of the component to enable
        :param software_profile: the software profile on which the component
                                 will be enabled

        :return:                 the Component instance that was enabled

        """
        return self._add_component_to_software_profile(kit, comp_name,
                                                       comp_version,
                                                       software_profile)

    def _add_component_to_software_profile(self, kit, comp_name, comp_version,
                                           software_profile):
        """
        Adds a kit to a software profile. This is a data-only operation,
        as no pre/post enable actions are called.

        :param kit:              the OS Kit instance, whose component is being
                                 added
        :param comp_name:        the name of the component to add
        :param comp_version:     the version of the component to add
        :param software_profile: the software profile to which the component
                                 will be added

        :return:                 the Component instance that was added

        """
        os_obj_factory = osUtility.getOsObjectFactory(
            software_profile.getOsInfo().getOsFamilyInfo().getName())
        comp_manager = os_obj_factory.getComponentManager()
        best_match_component = comp_manager.getBestMatchComponent(
            comp_name, comp_version, software_profile.getOsInfo(), kit.getId())
        self._component_db_api.addComponentToSoftwareProfile(
            best_match_component.getId(), software_profile.getId())

        return best_match_component

    def disableComponent(self,
                         software_profile_name,
                         kit_name,
                         kit_version,
                         kit_iteration,
                         comp_name,
                         comp_version=None):
        """
        Disables a component on a software profile.

        :param software_profile_name: the name of the software profile
        :param kit_name:              the name of the kit
        :param kit_version:           the version of the kit
        :param kit_iteration:         the iteration of the kit
        :param comp_name:             the name of the component
        :param comp_version:          the version of the component

        :raises KitNotFound:
        :raises SoftwareProfileNotFound:
        :raises ComponentNotFound:

        """
        kit, comp_version = self._get_kit_and_component_version(
            kit_name, kit_version, kit_iteration, comp_name)

        software_profile = self.getSoftwareProfile(software_profile_name,
                                                   {'os': True})

        if kit.getIsOs():
            best_match_component = self._disable_os_kit_component(
                kit, comp_name, comp_version, software_profile)
        else:
            best_match_component = self._disable_kit_component(
                kit, comp_name, comp_version, software_profile)

        self.getLogger().info(
            'Disabled component on software profile: {} -> {}'.format(
                best_match_component, software_profile))

    def _disable_kit_component(self, kit, comp_name, comp_version,
                               software_profile):
        """
        Disables a regular kit component on a specific software profile.

        :param kit:              the Kit instance, whose component is being
                                 disabled
        :param comp_name:        the name of the component to disable
        :param comp_version:     the version of the component to disable
        :param software_profile: the software profile on which the component
                                 will be disable

        :return:                 the Component instance that was disabled

        """
        kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration())

        load_kits()
        installer = get_kit_installer(kit_spec)()
        comp_installer = installer.get_component_installer(comp_name)
        comp_installer.run_action('pre_disable', software_profile.getName())
        comp_installer.run_action('disable', software_profile.getName())

        best_match_component = \
            self._remove_component_from_software_profile(
                kit, comp_name, comp_version, software_profile)

        comp_installer.run_action('post_disable', software_profile.getName())

        return best_match_component

    def _disable_os_kit_component(self, kit, comp_name, comp_version,
                                  software_profile):
        """
        Enables an OS kit component on a specific software profile.

        :param kit:              the OS Kit instance, whose component is being
                                 disabled
        :param comp_name:        the name of the component to disable
        :param comp_version:     the version of the component to disable
        :param software_profile: the software profile on which the component
                                 will be disabled

        :return:                 the Component instance that was disabled

        """
        return self._remove_component_from_software_profile(
            kit, comp_name, comp_version, software_profile)

    def _remove_component_from_software_profile(self, kit, comp_name,
                                                comp_version,
                                                software_profile):
        """
        Removes a kit to a software profile. This is a data-only operation,
        as no pre/post disable actions are called.

        :param kit:              the OS Kit instance, whose component is being
                                 removed
        :param comp_name:        the name of the component to remove
        :param comp_version:     the version of the component to remove
        :param software_profile: the software profile to which the component
                                 will be removed

        :return:                 the Component instance that was removed

        """
        os_obj_factory = osUtility.getOsObjectFactory(
            software_profile.getOsInfo().getOsFamilyInfo().getName())
        comp_manager = os_obj_factory.getComponentManager()
        best_match_component = comp_manager.getBestMatchComponent(
            comp_name, comp_version, software_profile.getOsInfo(), kit.getId())
        self._component_db_api.deleteComponentFromSoftwareProfile(
            best_match_component.getId(), software_profile.getId())

        return best_match_component

    def deleteSoftwareProfile(self, name):
        """
        Delete software profile by name

        Raises:
            SoftwareProfileNotFound
        """

        self._sp_db_api.deleteSoftwareProfile(name)

        # Remove all flags for software profile
        swProfileFlagPath = os.path.join(self._config_manager.getRoot(),
                                         'var/run/actions/%s' % (name))
        if os.path.exists(swProfileFlagPath):
            shutil.rmtree(swProfileFlagPath)

        self.getLogger().info('Deleted software profile [%s]' % (name))

    def getNodeList(self, softwareProfileName):
        return self._sp_db_api.getNodeList(softwareProfileName)

    def getEnabledComponentList(self, name):
        """ Get the list of enabled components """
        return self._sp_db_api.getEnabledComponentList(name)

    def getPackageList(self, softwareProfileName):
        """ Get list of packages. """
        return self._sp_db_api.getPackageList(softwareProfileName)

    def getPartitionList(self, softwareProfileName):
        """ Get list of partitions. """
        return self._sp_db_api.getPartitionList(softwareProfileName)

    def getProvisioningInfo(self, nodeName):
        return self._sp_db_api.getProvisioningInfo(nodeName)

    def addUsableHardwareProfileToSoftwareProfile(self, hardwareProfileName,
                                                  softwareProfileName):
        self._logger.info(
            'Mapping hardware profile [%s] to software profile [%s]' %
            (hardwareProfileName, softwareProfileName))

        return self._sp_db_api.\
            addUsableHardwareProfileToSoftwareProfile(hardwareProfileName,
                                                      softwareProfileName)

    def deleteUsableHardwareProfileFromSoftwareProfile(self,
                                                       hardwareProfileName,
                                                       softwareProfileName):
        return self._sp_db_api.\
            deleteUsableHardwareProfileFromSoftwareProfile(
                hardwareProfileName, softwareProfileName)

    def copySoftwareProfile(self, srcSoftwareProfileName,
                            dstSoftwareProfileName):
        # Validate software profile name
        validation.validateProfileName(dstSoftwareProfileName)

        self._logger.info('Copying software profile [%s] to [%s]' %
                          (srcSoftwareProfileName, dstSoftwareProfileName))

        softwareProfile = self._sp_db_api.\
            copySoftwareProfile(srcSoftwareProfileName,
                                dstSoftwareProfileName)

        return softwareProfile

    def getUsableNodes(self, softwareProfileName):
        return self._sp_db_api.getUsableNodes(softwareProfileName)
Ejemplo n.º 18
0
def test_getNodeByIp(dbm):
    with dbm.session() as session:
        with pytest.raises(NodeNotFound):
            result = NodeDbApi().getNodeByIp(session, '127.0.0.1')
Ejemplo n.º 19
0
def test_getNodeById(dbm):
    with dbm.session() as session:
        result = NodeDbApi().getNodeById(session, 1)

    assert isinstance(result, Node)
Ejemplo n.º 20
0
class NodeManager(TortugaObjectManager):     \
        # pylint: disable=too-many-public-methods

    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._cm = ConfigManager()
        self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager(
            self._cm)
        self._syncApi = SyncApi()
        self._nodesDbHandler = NodesDbHandler()
        self._addHostManager = AddHostManager()
        self._logger = logging.getLogger(NODE_NAMESPACE)

    def __validateHostName(self, hostname: str, name_format: str) -> None:
        """
        Raises:
            ConfigurationError
        """

        bWildcardNameFormat = (name_format == '*')

        if hostname and not bWildcardNameFormat:
            # Host name specified, but hardware profile does not
            # allow setting the host name
            raise ConfigurationError(
                'Hardware profile does not allow setting host names'
                ' of imported nodes')
        elif not hostname and bWildcardNameFormat:
            # Host name not specified but hardware profile expects it
            raise ConfigurationError(
                'Hardware profile requires host names to be set')

    def createNewNode(self,
                      session: Session,
                      addNodeRequest: dict,
                      dbHardwareProfile: HardwareProfileModel,
                      dbSoftwareProfile: Optional[SoftwareProfileModel] = None,
                      validateIp: bool = True,
                      bGenerateIp: bool = True,
                      dns_zone: Optional[str] = None) -> NodeModel:
        """
        Convert the addNodeRequest into a Nodes object

        Raises:
            NicNotFound
        """

        self._logger.debug(
            'createNewNode(): session=[%s], addNodeRequest=[%s],'
            ' dbHardwareProfile=[%s], dbSoftwareProfile=[%s],'
            ' validateIp=[%s], bGenerateIp=[%s]' %
            (id(session), addNodeRequest, dbHardwareProfile.name,
             dbSoftwareProfile.name if dbSoftwareProfile else '(none)',
             validateIp, bGenerateIp))

        hostname = addNodeRequest['name'] \
            if 'name' in addNodeRequest else None

        # Ensure no conflicting options (ie. specifying host name for
        # hardware profile in which host names are generated)
        self.__validateHostName(hostname, dbHardwareProfile.nameFormat)

        node = NodeModel(name=hostname)

        if 'rack' in addNodeRequest:
            node.rack = addNodeRequest['rack']

        node.addHostSession = addNodeRequest['addHostSession']

        # Complete initialization of new node record
        nic_defs = addNodeRequest['nics'] \
            if 'nics' in addNodeRequest else []

        AddHostServerLocal().initializeNode(session,
                                            node,
                                            dbHardwareProfile,
                                            dbSoftwareProfile,
                                            nic_defs,
                                            bValidateIp=validateIp,
                                            bGenerateIp=bGenerateIp,
                                            dns_zone=dns_zone)

        # Set hardware profile of new node
        node.hardwareprofile = dbHardwareProfile

        node.softwareprofile = dbSoftwareProfile

        # Return the new node
        return node

    def getNode(self, session: Session, name, optionDict: OptionDict = None) \
            -> Node:
        """
        Get node by name

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNode(
                session, name, optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeById(self,
                    session: Session,
                    nodeId: int,
                    optionDict: OptionDict = None) -> Node:
        """
        Get node by node id

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNodeById(
                session,
                int(nodeId),
                optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeByIp(self,
                    session: Session,
                    ip: str,
                    optionDict: Dict[str, bool] = None) -> Node:
        """
        Get node by IP address

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNodeByIp(
                session, ip, optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeList(self,
                    session,
                    tags=None,
                    optionDict: Optional[OptionDict] = None) -> List[Node]:
        """
        Return all nodes

        """
        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodeList(
                session,
                tags=tags,
                optionDict=get_default_relations(optionDict)))

    def __populate_nodes(self, session: Session,
                         nodes: List[Node]) -> List[Node]:
        """
        Expand non-database fields in Node objects

        """
        class SoftwareProfileMetadataCache(defaultdict):
            def __missing__(self, key):
                metadata = \
                    SoftwareProfileManager().get_software_profile_metadata(
                        session, key
                    )

                self[key] = metadata

                return metadata

        swprofile_map = SoftwareProfileMetadataCache()

        for node in nodes:
            if not node.getSoftwareProfile():
                continue

            node.getSoftwareProfile().setMetadata(
                swprofile_map[node.getSoftwareProfile().getName()])

        return nodes

    def updateNode(self, session: Session, nodeName: str,
                   updateNodeRequest: dict) -> None:
        """
        Calls updateNode() method of resource adapter
        """

        self._logger.debug('updateNode(): name=[{0}]'.format(nodeName))

        try:
            node = self._nodesDbHandler.getNode(session, nodeName)

            if 'nics' in updateNodeRequest:
                nic = updateNodeRequest['nics'][0]

                if 'ip' in nic:
                    node.nics[0].ip = nic['ip']
                    node.nics[0].boot = True

            # Call resource adapter
            # self._nodesDbHandler.updateNode(session, node, updateNodeRequest)

            adapter = self.__getResourceAdapter(node.hardwareprofile)

            adapter.updateNode(session, node, updateNodeRequest)

            run_post_install = False

            #
            # Capture previous state and node data as dict for firing the
            # event later on
            #
            previous_state = node.state
            node_dict = Node.getFromDbDict(node.__dict__).getCleanDict()

            if 'state' in updateNodeRequest:
                run_post_install = \
                    node.state == state.NODE_STATE_ALLOCATED and \
                    updateNodeRequest['state'] == state.NODE_STATE_PROVISIONED

                node.state = updateNodeRequest['state']
                node_dict['state'] = updateNodeRequest['state']

            session.commit()

            #
            # If the node state has changed, then fire the node state changed
            # event
            #
            if node_dict['state'] != previous_state:
                NodeStateChanged.fire(node=node_dict,
                                      previous_state=previous_state)

            if run_post_install:
                self._logger.debug(
                    'updateNode(): run-post-install for node [{0}]'.format(
                        node.name))

                self.__scheduleUpdate()
        except Exception:
            session.rollback()

            raise

    def updateNodeStatus(self,
                         session: Session,
                         nodeName: str,
                         node_state: Optional[str] = None,
                         bootFrom: int = None):
        """Update node status

        If neither 'state' nor 'bootFrom' are not None, this operation will
        update only the 'lastUpdated' timestamp.

        Returns:
            bool indicating whether state and/or bootFrom differed from
            current value
        """

        value = 'None' if bootFrom is None else \
            '1 (disk)' if int(bootFrom) == 1 else '0 (network)'

        self._logger.debug(
            'updateNodeStatus(): node=[%s], node_state=[{%s}],'
            ' bootFrom=[{%s}]', nodeName, node_state, value)

        dbNode = self._nodesDbHandler.getNode(session, nodeName)

        #
        # Capture previous state and node data in dict form for the
        # event later on
        #
        previous_state = dbNode.state
        node_dict = Node.getFromDbDict(dbNode.__dict__).getCleanDict()

        # Bitfield representing node changes (0 = state change,
        # 1 = bootFrom # change)
        changed = 0

        if node_state is not None and node_state != dbNode.state:
            # 'state' changed
            changed |= 1

        if bootFrom is not None and bootFrom != dbNode.bootFrom:
            # 'bootFrom' changed
            changed |= 2

        if changed:
            # Create custom log message
            msg = 'Node [%s] state change:' % (dbNode.name)

            if changed & 1:
                msg += ' state: [%s] -> [%s]' % (dbNode.state, node_state)

                dbNode.state = node_state
                node_dict['state'] = node_state

            if changed & 2:
                msg += ' bootFrom: [%d] -> [%d]' % (dbNode.bootFrom, bootFrom)

                dbNode.bootFrom = bootFrom

            self._logger.info(msg)
        else:
            self._logger.info('Updated timestamp for node [%s]' %
                              (dbNode.name))

        dbNode.lastUpdate = time.strftime('%Y-%m-%d %H:%M:%S',
                                          time.localtime(time.time()))

        result = bool(changed)

        # Only change local boot configuration if the hardware profile is
        # not marked as 'remote' and we're not acting on the installer
        # node.
        if dbNode.softwareprofile and \
                dbNode.softwareprofile.type != 'installer' and \
                dbNode.hardwareprofile.location != 'remote':
            # update local boot configuration for on-premise nodes
            self._bhm.writePXEFile(session, dbNode, localboot=bootFrom)

        session.commit()

        #
        # If the node state has changed, fire the node state changed
        # event
        #
        if state and (previous_state != state):
            NodeStateChanged.fire(node=node_dict,
                                  previous_state=previous_state)

        return result

    def __process_nodeErrorDict(self, nodeErrorDict):
        result = {}
        nodes_deleted = []

        for key, nodeList in nodeErrorDict.items():
            result[key] = [dbNode.name for dbNode in nodeList]

            if key == 'NodesDeleted':
                for node in nodeList:
                    node_deleted = {
                        'name': node.name,
                        'hardwareprofile': node.hardwareprofile.name,
                        'addHostSession': node.addHostSession,
                    }

                    if node.softwareprofile:
                        node_deleted['softwareprofile'] = \
                            node.softwareprofile.name

                    nodes_deleted.append(node_deleted)

        return result, nodes_deleted

    def deleteNode(self, session, nodespec: str, force: bool = False):
        """
        Delete node by nodespec

        Raises:
            NodeNotFound
        """

        kitmgr = KitActionsManager()
        kitmgr.session = session

        try:
            nodes = self._nodesDbHandler.expand_nodespec(
                session, nodespec, include_installer=False)
            if not nodes:
                raise NodeNotFound('No nodes matching nodespec [%s]' %
                                   (nodespec))

            self.__validate_delete_nodes_request(nodes, force)

            self.__preDeleteHost(kitmgr, nodes)

            nodeErrorDict = self.__delete_node(session, nodes)

            # REALLY!?!? Convert a list of Nodes objects into a list of
            # node names so we can report the list back to the end-user.
            # This needs to be FIXED!

            result, nodes_deleted = self.__process_nodeErrorDict(nodeErrorDict)

            session.commit()

            # ============================================================
            # Perform actions *after* node deletion(s) have been committed
            # to database.
            # ============================================================

            self.__postDeleteHost(kitmgr, nodes_deleted)

            addHostSessions = set(
                [tmpnode['addHostSession'] for tmpnode in nodes_deleted])

            if addHostSessions:
                self._addHostManager.delete_sessions(addHostSessions)

            for nodeName in result['NodesDeleted']:
                # Remove the Puppet cert
                self._bhm.deletePuppetNodeCert(nodeName)

                self._bhm.nodeCleanup(nodeName)

                self._logger.info('Node [%s] deleted' % (nodeName))

            # Schedule a cluster update
            self.__scheduleUpdate()

            return result
        except Exception:
            session.rollback()

            raise

    def __validate_delete_nodes_request(self, nodes: List[NodeModel],
                                        force: bool):
        """
        Raises:
            DeleteNodeFailed
        """

        swprofile_distribution: Dict[SoftwareProfileModel, int] = {}

        for node in nodes:
            if node.softwareprofile not in swprofile_distribution:
                swprofile_distribution[node.softwareprofile] = 0

            swprofile_distribution[node.softwareprofile] += 1

        errors: List[str] = []

        for software_profile, num_nodes_deleted in \
                swprofile_distribution.items():
            if software_profile.lockedState == 'HardLocked':
                errors.append(
                    f'Nodes cannot be deleted from hard locked software'
                    ' profile [{software_profile.name}]')

                continue

            if software_profile.minNodes and \
                    len(software_profile.nodes) - num_nodes_deleted < \
                        software_profile.minNodes:
                if force and software_profile.lockedState == 'SoftLocked':
                    # allow deletion of nodes when force is set and profile
                    # is soft locked
                    continue

                # do not allow number of software profile nodes to drop
                # below configured minimum
                errors.append(
                    'Software profile [{}] requires minimum of {} nodes;'
                    ' denied request to delete {} node(s)'.format(
                        software_profile.name, software_profile.minNodes,
                        num_nodes_deleted))

                continue

            if software_profile.lockedState == 'SoftLocked' and not force:
                errors.append(
                    'Nodes cannot be deleted from soft locked software'
                    f' profile [{software_profile.name}]')

        if errors:
            raise OperationFailed('\n'.join(errors))

    def __delete_node(self, session: Session, dbNodes: List[NodeModel]) \
            -> Dict[str, List[NodeModel]]:
        """
        Raises:
            DeleteNodeFailed
        """

        result: Dict[str, list] = {
            'NodesDeleted': [],
            'DeleteNodeFailed': [],
            'SoftwareProfileLocked': [],
            'SoftwareProfileHardLocked': [],
        }

        nodes: Dict[HardwareProfileModel, List[NodeModel]] = {}
        events_to_fire: List[dict] = []

        #
        # Mark node states as deleted in the database
        #
        for dbNode in dbNodes:
            #
            # Capture previous state and node data as a dict for firing
            # the event later on
            #
            event_data = {
                'previous_state': dbNode.state,
                'node': Node.getFromDbDict(dbNode.__dict__).getCleanDict()
            }

            dbNode.state = state.NODE_STATE_DELETED
            event_data['node']['state'] = 'Deleted'

            if dbNode.hardwareprofile not in nodes:
                nodes[dbNode.hardwareprofile] = [dbNode]
            else:
                nodes[dbNode.hardwareprofile].append(dbNode)

        session.commit()

        #
        # Fire node state change events
        #
        for event in events_to_fire:
            NodeStateChanged.fire(node=event['node'],
                                  previous_state=event['previous_state'])

        #
        # Call resource adapter with batch(es) of node lists keyed on
        # hardware profile.
        #
        for hwprofile, hwprofile_nodes in nodes.items():
            # Get the ResourceAdapter
            adapter = self.__get_resource_adapter(session, hwprofile)

            # Call the resource adapter
            adapter.deleteNode(hwprofile_nodes)

            # Iterate over all nodes in hardware profile, completing the
            # delete operation.
            for dbNode in hwprofile_nodes:
                for tag in dbNode.tags:
                    if len(tag.nodes) == 1 and \
                            not tag.softwareprofiles and \
                            not tag.hardwareprofiles:
                        session.delete(tag)

                # Delete the Node
                self._logger.debug('Deleting node [%s]' % (dbNode.name))

                session.delete(dbNode)

                result['NodesDeleted'].append(dbNode)

        return result

    def __get_resource_adapter(self, session: Session,
                               hardwareProfile: HardwareProfileModel):
        """
        Raises:
            OperationFailed
        """

        if not hardwareProfile.resourceadapter:
            raise OperationFailed(
                'Hardware profile [%s] does not have an associated'
                ' resource adapter' % (hardwareProfile.name))

        adapter = resourceAdapterFactory.get_api(
            hardwareProfile.resourceadapter.name)

        adapter.session = session

        return adapter

    def __process_delete_node_result(self, nodeErrorDict):
        # REALLY!?!? Convert a list of Nodes objects into a list of
        # node names so we can report the list back to the end-user.
        # This needs to be FIXED!

        result = {}
        nodes_deleted = []

        for key, nodeList in nodeErrorDict.items():
            result[key] = [dbNode.name for dbNode in nodeList]

            if key == 'NodesDeleted':
                for node in nodeList:
                    node_deleted = {
                        'name': node.name,
                        'hardwareprofile': node.hardwareprofile.name,
                    }

                    if node.softwareprofile:
                        node_deleted['softwareprofile'] = \
                            node.softwareprofile.name

                    nodes_deleted.append(node_deleted)

        return result, nodes_deleted

    def __preDeleteHost(self, kitmgr: KitActionsManager, nodes):
        self._logger.debug('__preDeleteHost(): nodes=[%s]' %
                           (' '.join([node.name for node in nodes])))

        for node in nodes:
            kitmgr.pre_delete_host(
                node.hardwareprofile.name,
                node.softwareprofile.name if node.softwareprofile else None,
                nodes=[node.name])

    def __postDeleteHost(self, kitmgr, nodes_deleted):
        # 'nodes_deleted' is a list of dicts of the following format:
        #
        # {
        #     'name': 'compute-01',
        #     'softwareprofile': 'Compute',
        #     'hardwareprofile': 'LocalIron',
        # }
        #
        # if the node does not have an associated software profile, the
        # dict does not contain the key 'softwareprofile'.

        self._logger.debug('__postDeleteHost(): nodes_deleted=[%s]' %
                           (nodes_deleted))

        if not nodes_deleted:
            self._logger.debug('No nodes deleted in this operation')

            return

        for node_dict in nodes_deleted:
            kitmgr.post_delete_host(node_dict['hardwareprofile'],
                                    node_dict['softwareprofile'] if
                                    'softwareprofile' in node_dict else None,
                                    nodes=[node_dict['name']])

    def __scheduleUpdate(self):
        self._syncApi.scheduleClusterUpdate()

    def getInstallerNode(self,
                         session,
                         optionDict: Optional[OptionDict] = None):
        return self._nodeDbApi.getNode(
            session,
            self._cm.getInstaller(),
            optionDict=get_default_relations(optionDict))

    def getProvisioningInfo(self, session: Session, nodeName):
        return self._nodeDbApi.getProvisioningInfo(session, nodeName)

    def startupNode(self,
                    session,
                    nodespec: str,
                    remainingNodeList: List[NodeModel] = None,
                    bootMethod: str = 'n') -> None:
        """
        Raises:
            NodeNotFound
        """

        try:
            nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No matching nodes for nodespec [%s]' %
                                   (nodespec))

            # Break list of nodes into dict keyed on hardware profile
            nodes_dict = self.__processNodeList(nodes)

            for dbHardwareProfile, detailsDict in nodes_dict.items():
                # Get the ResourceAdapter
                adapter = self.__getResourceAdapter(dbHardwareProfile)

                # Call startup action extension
                adapter.startupNode(detailsDict['nodes'],
                                    remainingNodeList=remainingNodeList or [],
                                    tmpBootMethod=bootMethod)

            session.commit()
        except TortugaException:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self._logger.exception(str(ex))
            raise

    def shutdownNode(self, session, nodespec: str, bSoftShutdown: bool = False) \
            -> None:
        """
        Raises:
            NodeNotFound
        """

        try:
            nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No matching nodes for nodespec [%s]' %
                                   (nodespec))

            d = self.__processNodeList(nodes)

            for dbHardwareProfile, detailsDict in d.items():
                # Get the ResourceAdapter
                adapter = self.__getResourceAdapter(dbHardwareProfile)

                # Call shutdown action extension
                adapter.shutdownNode(detailsDict['nodes'], bSoftShutdown)

            session.commit()
        except TortugaException:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self._logger.exception(str(ex))
            raise

    def rebootNode(self,
                   session,
                   nodespec: str,
                   bSoftReset: bool = False,
                   bReinstall: bool = False) -> None:
        """
        Raises:
            NodeNotFound
        """

        nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)
        if not nodes:
            raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec))

        if bReinstall:
            for dbNode in nodes:
                self._bhm.setNodeForNetworkBoot(session, dbNode)

        for dbHardwareProfile, detailsDict in \
                self.__processNodeList(nodes).items():
            # iterate over hardware profile/nodes dict to reboot each
            # node
            adapter = self.__getResourceAdapter(dbHardwareProfile)

            # Call reboot action extension
            adapter.rebootNode(detailsDict['nodes'], bSoftReset)

        session.commit()

    def getNodesByNodeState(self, session, node_state: str,
                            optionDict: Optional[OptionDict] = None) \
            -> TortugaObjectList:
        """
        Get nodes by state
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByNodeState(
                session,
                node_state,
                optionDict=get_default_relations(optionDict)))

    def getNodesByNameFilter(self, session, nodespec: str,
                             optionDict: OptionDict = None,
                             include_installer: Optional[bool] = True) \
            -> TortugaObjectList:
        """
        Return TortugaObjectList of Node objects matching nodespec
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByNameFilter(
                session,
                nodespec,
                optionDict=get_default_relations(optionDict),
                include_installer=include_installer))

    def getNodesByAddHostSession(self, session, addHostSession: str,
                                 optionDict: OptionDict = None) \
            -> TortugaObjectList:
        """
        Return TortugaObjectList of Node objects matching add host session
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByAddHostSession(
                session,
                addHostSession,
                optionDict=get_default_relations(optionDict)))

    def __processNodeList(self, dbNodes: List[NodeModel]) \
            -> Dict[HardwareProfileModel, Dict[str, list]]:
        """
        Returns dict indexed by hardware profile, each with a list of
        nodes in the hardware profile
        """

        d: Dict[HardwareProfileModel, Dict[str, list]] = {}

        for dbNode in dbNodes:
            if dbNode.hardwareprofile not in d:
                d[dbNode.hardwareprofile] = {
                    'nodes': [],
                }

            d[dbNode.hardwareprofile]['nodes'].append(dbNode)

        return d

    def __getResourceAdapter(self, hardwareProfile: HardwareProfileModel):
        """
        Raises:
            OperationFailed
        """

        if not hardwareProfile.resourceadapter:
            raise OperationFailed(
                'Hardware profile [%s] does not have an associated'
                ' resource adapter' % (hardwareProfile.name))

        return resourceAdapterFactory.get_api(
            hardwareProfile.resourceadapter.name) \
            if hardwareProfile.resourceadapter else None
Ejemplo n.º 21
0
class AddHostManager(TortugaObjectManager, Singleton):
    def __init__(self):
        super(AddHostManager, self).__init__()

        # Now do the class specific variable initialization
        self._addHostLock = threading.RLock()
        self._sessions = {}
        self._nodeDbApi = NodeDbApi()

    def addHosts(self, session, addHostSession, addHostRequest):
        """
        Raises:
            HardwareProfileNotFound
        """

        self.getLogger().debug('addHosts()')

        softwareProfilesDbHandler = SoftwareProfilesDbHandler()
        hardwareProfilesDbHandler = HardwareProfilesDbHandler()

        dbHardwareProfile = hardwareProfilesDbHandler.getHardwareProfile(
            session, addHostRequest['hardwareProfile'])

        if not dbHardwareProfile.resourceadapter:
            errmsg = ('Resource adapter not defined for hardware'
                      ' profile [%s]' % (dbHardwareProfile.name))

            self.getLogger().error(errmsg)

            raise ResourceAdapterNotFound(errmsg)

        softwareProfileName = addHostRequest['softwareProfile'] \
            if 'softwareProfile' in addHostRequest else None

        dbSoftwareProfile = softwareProfilesDbHandler.\
            getSoftwareProfile(session, softwareProfileName) \
            if softwareProfileName else None

        # Look up and/or create tags as necessary
        tags = get_tags(session, addHostRequest['tags']) \
            if 'tags' in addHostRequest else []

        ResourceAdapterClass = resourceAdapterFactory.getResourceAdapterClass(
            dbHardwareProfile.resourceadapter.name)

        resourceAdapter = ResourceAdapterClass(addHostSession=addHostSession)

        # Call the start() method of the resource adapter
        newNodes = resourceAdapter.start(addHostRequest,
                                         session,
                                         dbHardwareProfile,
                                         dbSoftwareProfile=dbSoftwareProfile)

        # Apply tags to new nodes
        for node in newNodes:
            node.tags = tags

        # Commit new node(s) to database
        session.commit()

        # Only perform post-add operations if we actually added a node
        if newNodes:
            if dbSoftwareProfile and not dbSoftwareProfile.isIdle:
                self.getLogger().info(
                    'Node(s) added to software profile [%s] and'
                    ' hardware profile [%s]' %
                    (dbSoftwareProfile.name if dbSoftwareProfile else 'None',
                     dbHardwareProfile.name))

                newNodeNames = [tmpNode.name for tmpNode in newNodes]

                resourceAdapter.hookAction('add', newNodeNames)

                self.postAddHost(dbHardwareProfile.name, softwareProfileName,
                                 addHostSession)

                resourceAdapter.hookAction('start', newNodeNames)

        self.getLogger().debug('Add host workflow complete')

    def postAddHost(self, hardwareProfileName, softwareProfileName,
                    addHostSession):
        """Perform post add host operations"""

        self.getLogger().debug(
            'postAddHost(): hardwareProfileName=[%s]'
            ' softwareProfileName=[%s] addHostSession=[%s]' %
            (hardwareProfileName, softwareProfileName, addHostSession))

        mgr = KitActionsManager()

        mgr.post_add_host(hardwareProfileName, softwareProfileName,
                          addHostSession)

        # Always go over the web service for this call.
        SyncWsApi().scheduleClusterUpdate(updateReason='Node(s) added')

    def updateStatus(self, addHostSession, msg):
        self._addHostLock.acquire()

        try:
            if addHostSession not in self._sessions:
                self.getLogger().warn(
                    'updateStatus(): unknown session ID [%s]' %
                    (addHostSession))

                return

            addHostStatus = self._sessions[addHostSession]['status']

            addHostStatus.getMessageList().append(msg)
        finally:
            self._addHostLock.release()

    def getStatus(self, session: str, startMessage: int,
                  getNodes: bool) -> AddHostStatus:
        """
        Raises:
            NotFound
        """
        with self._addHostLock:
            nodeList = self._nodeDbApi.getNodesByAddHostSession(session) \
                if getNodes else TortugaObjectList()

            # Lock and copy for data consistency
            if session not in self._sessions:
                raise NotFound('Invalid add host session ID [%s]' % (session))

            sessionDict = self._sessions.get(session)

            statusCopy = AddHostStatus()

            # Copy simple data
            for key in sessionDict['status'].getKeys():
                statusCopy.set(key, sessionDict['status'].get(key))

            # Get slice of status messages
            messages = sessionDict['status'].getMessageList()[startMessage:]

            statusCopy.setMessageList(messages)

            if nodeList:
                statusCopy.getNodeList().extend(nodeList)

            return statusCopy

    def createNewSession(self) -> str:
        self.getLogger().debug('createNewSession()')

        with self._addHostLock:
            # Create new add nodes session
            session_id = str(uuid.uuid4())

            self._sessions[session_id] = {
                'status': AddHostStatus(),
            }

            return session_id

    def delete_session(self, session_id):
        """TODO: currently a no-op"""

    def delete_sessions(self, session_ids):
        """Bulk session deletion

        Currently only called when deleting nodes
        """

        self.getLogger().debug('delete_sessions()')

        with self._addHostLock:
            for session_id in session_ids:
                if session_id in self._sessions:
                    self.getLogger().debug(
                        'Deleting session [{0}]'.format(session_id))

                    del self._sessions[session_id]

    def update_session(self, session_id, running=None):
        self.getLogger().debug(
            'Updating add host session [%s] (status: running=%s)' %
            (session_id, str(running)))

        with self._addHostLock:
            self._sessions[session_id]['status'].setIsRunning(running)
Ejemplo n.º 22
0
class UctagCli(TortugaCli):
    def __init__(self, *args, **kwargs):
        self._hwp_api = HardwareProfileDbApi()
        self._node_api = NodeDbApi()
        self._swp_api = SoftwareProfileDbApi()
        super().__init__(*args, **kwargs)

    def parseArgs(self, usage: Optional[str] = None):
        subparsers = self.getParser().add_subparsers(help='sub-command help',
                                                     dest='subparser_name')

        add_subparser = subparsers.add_parser('add')
        add_subparser.add_argument('--node', dest='nodespec')
        add_subparser.add_argument('--software-profile', metavar='NAME')
        add_subparser.add_argument('--hardware-profile', metavar='NAME')
        add_subparser.add_argument('--tags',
                                   action='append',
                                   dest='tags',
                                   metavar='key=value[,key=value]')
        add_subparser.set_defaults(func=self.add_tag)

        remove_subparser = subparsers.add_parser('remove')
        remove_subparser.add_argument('--node', dest='nodespec')
        remove_subparser.add_argument('--software-profile', metavar='NAME')
        remove_subparser.add_argument('--hardware-profile', metavar='NAME')
        remove_subparser.add_argument('--tags',
                                      action='append',
                                      dest='tags',
                                      metavar='key[,key]')
        remove_subparser.set_defaults(func=self.remove_tag)

        list_subparser = subparsers.add_parser('list')
        list_subparser.add_argument('--all-resources', action='store_true')
        list_subparser.add_argument('--nodes', action='store_true')
        list_subparser.add_argument('--software-profiles', action='store_true')
        list_subparser.add_argument('--hardware-profiles', action='store_true')
        list_subparser.set_defaults(func=self.list_tag)

        return super().parseArgs(usage=usage)

    def runCommand(self):
        args = self.parseArgs()

        with DbManager().session() as session:
            args.func(session, args)

    def add_tag(self, session: Session, args):
        if not args.nodespec and not args.software_profile and \
                not args.hardware_profile:
            sys.stderr.write('Error: must specify --nodes'
                             '/--software-profile/--hardware-profile\n')
            sys.stderr.flush()
            sys.exit(1)

        tags = parse_tags(args.tags)

        if args.nodespec:
            nodes = self._node_api.getNodesByNameFilter(session, args.nodespec)
            for node in nodes:
                node_tags = node.getTags()
                node_tags.update(tags)
                self._node_api.set_tags(session,
                                        node_id=node.getId(),
                                        tags=node_tags)
                print(node.getName(), node.getTags())

        if args.software_profile:
            for name in args.software_profile.split(','):
                swp = self._swp_api.getSoftwareProfile(session, name)
                swp_tags = swp.getTags()
                swp_tags.update(tags)
                swp.setTags(swp_tags)
                self._swp_api.updateSoftwareProfile(session, swp)

        if args.hardware_profile:
            for name in args.hardware_profile.split(','):
                hwp = self._hwp_api.getHardwareProfile(session, name)
                hwp_tags = hwp.getTags()
                hwp_tags.update(tags)
                hwp.setTags(hwp_tags)
                self._hwp_api.updateHardwareProfile(session, hwp)

        session.commit()

    def remove_tag(self, session: Session, args):
        if not args.nodespec and not args.software_profile and \
                not args.hardware_profile:
            sys.stderr.write('Error: must specify --nodes'
                             '/--software-profile/--hardware-profile\n')
            sys.stderr.flush()
            sys.exit(1)

        tag_keys = []
        for tag_string in args.tags:
            tag_keys.extend(tag_string.split(','))

        if args.nodespec:
            nodes = self._node_api.getNodesByNameFilter(session, args.nodespec)
            for node in nodes:
                node_tags = node.getTags()
                for key in tag_keys:
                    if key in node_tags.keys():
                        node_tags.pop(key)
                self._node_api.set_tags(session,
                                        node_id=node.getId(),
                                        tags=node_tags)
                print(node.getName(), node.getTags())

        if args.software_profile:
            for name in args.software_profile.split(','):
                swp = self._swp_api.getSoftwareProfile(session, name)
                swp_tags = swp.getTags()
                for key in tag_keys:
                    if key in swp_tags.keys():
                        swp_tags.pop(key)
                swp.setTags(swp_tags)
                self._swp_api.updateSoftwareProfile(session, swp)

        if args.hardware_profile:
            for name in args.hardware_profile.split(','):
                hwp = self._hwp_api.getHardwareProfile(session, name)
                hwp_tags = hwp.getTags()
                for key in tag_keys:
                    if key in hwp_tags.keys():
                        hwp_tags.pop(key)
                hwp.setTags(hwp_tags)
                self._hwp_api.updateHardwareProfile(session, hwp)

        session.commit()

    def list_tag(self, session: Session, args):
        report = TagReport()

        if args.all_resources or args.nodes:
            for node in self._node_api.getNodeList(session):
                for key, value in node.getTags().items():
                    report.add_node(key, value, node)

        if args.all_resources or args.software_profiles:
            for swp in self._swp_api.getSoftwareProfileList(session):
                for key, value in swp.getTags().items():
                    report.add_swp(key, value, swp)

        if args.all_resources or args.hardware_profiles:
            for hwp in self._hwp_api.getHardwareProfileList(session):
                for key, value in hwp.getTags().items():
                    report.add_hwp(key, value, hwp)

        for key, values in report.keys.items():
            for value, types in values.items():
                print('{} = {}:'.format(key, value))
                for type_, names in types.items():
                    print('  {}:'.format(type_))
                    for name in names:
                        print('    - {}'.format(name))
Ejemplo n.º 23
0
class AddHostManager(TagsDbApiMixin, TortugaObjectManager):
    tag_model = NodeTag

    def __init__(self):
        super(AddHostManager, self).__init__()

        # Now do the class specific variable initialization
        self._addHostLock = threading.RLock()
        self._nodeDbApi = NodeDbApi()
        self._sessions = ObjectStoreManager.get(namespace='add-host-manager')

    def addHosts(self, session: Session, addHostRequest: dict) -> None:
        """
        Raises:
            HardwareProfileNotFound
            ResourceAdapterNotFound
        """

        self.getLogger().debug('addHosts()')

        dbHardwareProfile = \
            HardwareProfilesDbHandler().getHardwareProfile(
                session, addHostRequest['hardwareProfile'])

        if not dbHardwareProfile.resourceadapter:
            errmsg = ('Resource adapter not defined for hardware'
                      ' profile [%s]' % (dbHardwareProfile.name))

            self.getLogger().error(errmsg)

            raise ResourceAdapterNotFound(errmsg)

        softwareProfileName = addHostRequest['softwareProfile'] \
            if 'softwareProfile' in addHostRequest else None

        dbSoftwareProfile = \
            SoftwareProfilesDbHandler().getSoftwareProfile(
                session, softwareProfileName) \
            if softwareProfileName else None

        ResourceAdapterClass = \
            resourceAdapterFactory.get_resourceadapter_class(
                dbHardwareProfile.resourceadapter.name)

        resourceAdapter = ResourceAdapterClass(
            addHostSession=addHostRequest['addHostSession'])

        resourceAdapter.session = session

        # Call the start() method of the resource adapter
        newNodes = resourceAdapter.start(addHostRequest,
                                         session,
                                         dbHardwareProfile,
                                         dbSoftwareProfile=dbSoftwareProfile)

        session.add_all(newNodes)
        session.flush()

        if 'tags' in addHostRequest and addHostRequest['tags']:
            for node in newNodes:
                self._set_tags(node, addHostRequest['tags'])

        # Commit new node(s) to database
        session.commit()

        # Only perform post-add operations if we actually added a node
        if newNodes:
            if dbSoftwareProfile and not dbSoftwareProfile.isIdle:
                self.getLogger().info(
                    'Node(s) added to software profile [%s] and'
                    ' hardware profile [%s]' %
                    (dbSoftwareProfile.name if dbSoftwareProfile else 'None',
                     dbHardwareProfile.name))

                newNodeNames = [tmpNode.name for tmpNode in newNodes]

                resourceAdapter.hookAction('add', newNodeNames)

                self.postAddHost(session, dbHardwareProfile.name,
                                 softwareProfileName,
                                 addHostRequest['addHostSession'])

                resourceAdapter.hookAction('start', newNodeNames)

        self.getLogger().debug('Add host workflow complete')

    def postAddHost(self, session: Session, hardwareProfileName: str,
                    softwareProfileName: Optional[str],
                    addHostSession: str) -> None:
        """
        Perform post add host operations
        """

        self.getLogger().debug(
            'postAddHost(): hardwareProfileName=[%s]'
            ' softwareProfileName=[%s] addHostSession=[%s]' %
            (hardwareProfileName, softwareProfileName, addHostSession))

        # this query is redundant; in the calling method, we already have
        # a list of Node (db) objects
        from tortuga.node.nodeApi import NodeApi
        nodes = NodeApi().getNodesByAddHostSession(session, addHostSession)

        mgr = KitActionsManager()
        mgr.session = session

        mgr.post_add_host(hardwareProfileName, softwareProfileName, nodes)

        # Always go over the web service for this call.
        SyncWsApi().scheduleClusterUpdate(updateReason='Node(s) added')

    def updateStatus(self, addHostSession: str, msg: str) -> None:
        self._addHostLock.acquire()

        try:
            if not self._sessions.exists(addHostSession):
                self.getLogger().warning(
                    'updateStatus(): unknown session ID [%s]' %
                    (addHostSession))

                return

            addHostStatus = AddHostStatus.getFromDict(
                self._sessions.get(addHostSession)['status'])

            addHostStatus.getMessageList().append(msg)
        finally:
            self._addHostLock.release()

    def getStatus(self, db_session: Session, session: str, startMessage: int,
                  getNodes: bool) -> AddHostStatus:
        """
        Raises:
            NotFound
        """
        with self._addHostLock:
            nodeList = self._nodeDbApi.getNodesByAddHostSession(
                db_session, session) if getNodes else TortugaObjectList()

            # Lock and copy for data consistency
            if not self._sessions.exists(session):
                raise NotFound('Invalid add host session ID [%s]' % (session))

            session = self._sessions.get(session)

            status_copy = AddHostStatus()

            # Copy simple data
            status = AddHostStatus.getFromDict(session['status'])
            for key in status.getKeys():
                status_copy.set(key, status.get(key))

            # Get slice of status messages
            messages = status.getMessageList()[startMessage:]

            status_copy.setMessageList(messages)

            if nodeList:
                status_copy.getNodeList().extend(nodeList)

            return status_copy

    def createNewSession(self) -> str:
        self.getLogger().debug('createNewSession()')

        with self._addHostLock:
            # Create new add nodes session
            session_id = str(uuid.uuid4())

            self._sessions.set(session_id,
                               {'status': AddHostStatus().getCleanDict()})

            return session_id

    def delete_session(self, session_id: str) -> None:
        """TODO: currently a no-op"""

    def delete_sessions(self, session_ids: List[str]) -> None:
        """Bulk session deletion

        Currently only called when deleting nodes
        """

        self.getLogger().debug('delete_sessions()')

        with self._addHostLock:
            for session_id in session_ids:
                if self._sessions.exists(session_id):
                    self.getLogger().debug(
                        'Deleting session [{0}]'.format(session_id))

                    self._sessions.delete(session_id)

    def update_session(self, session_id: str, running: Optional[bool] = None):
        self.getLogger().debug(
            'Updating add host session [%s] (status: running=%s)' %
            (session_id, str(running)))

        with self._addHostLock:
            session = self._sessions.get(session_id)
            status = AddHostStatus.getFromDict(session['status'])
            session['status'] = status.getCleanDict()
            self._sessions.set(session_id, session)
Ejemplo n.º 24
0
class SoftwareProfileManager(TortugaObjectManager):     \
        # pylint: disable=too-many-public-methods

    BASE_KIT_NAME = 'base'

    def __init__(self):
        super(SoftwareProfileManager, self).__init__()
        self._sp_db_api = SoftwareProfileDbApi()
        self._node_db_api = NodeDbApi()
        self._component_db_api = ComponentDbApi()
        self._global_param_db_api = GlobalParameterDbApi()
        self._kit_db_api = KitDbApi()
        self._config_manager = ConfigManager()
        self._logger = logging.getLogger(SOFTWARE_PROFILE_NAMESPACE)

    def getSoftwareProfileList(self, session: Session, tags=None):
        """Return all of the softwareprofiles with referenced components
        in this softwareprofile
        """

        results = self._sp_db_api.getSoftwareProfileList(session, tags=tags)

        for software_profile_obj in results:
            # load any available software profile metadata
            software_profile_obj.setMetadata(
                self.get_software_profile_metadata(
                    session, software_profile_obj.getName()))

        return results

    def addAdmin(self, session: Session, softwareProfileName, adminUsername):
        """
        Add an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                SoftwareProfileNotFound
        """
        return self._sp_db_api.addAdmin(session, softwareProfileName,
                                        adminUsername)

    def deleteAdmin(self, session: Session, softwareProfileName,
                    adminUsername):
        """
        Remove an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                SoftwareProfileNotFound
        """
        return self._sp_db_api.deleteAdmin(session, softwareProfileName,
                                           adminUsername)

    def updateSoftwareProfile(self, session: Session, softwareProfileObject):
        self._logger.debug('Updating software profile: %s' %
                           (softwareProfileObject.getName()))
        #
        # First get the object from the db we are updating
        #
        existing_swp = self.getSoftwareProfileById(
            session, softwareProfileObject.getId())
        #
        # Set parameters that we will not allow updating
        #
        softwareProfileObject.setOsInfo(existing_swp.getOsInfo())
        softwareProfileObject.setOsId(existing_swp.getOsId())
        softwareProfileObject.setType(existing_swp.getType())
        #
        # Do the DB update
        #
        self._sp_db_api.updateSoftwareProfile(session, softwareProfileObject)
        #
        # Get the new version
        #
        new_swp = self.getSoftwareProfileById(session,
                                              softwareProfileObject.getId())
        #
        # If the tags have changed, fire the tags changed event
        #
        if existing_swp.getTags() != new_swp.getTags():
            SoftwareProfileTagsChanged.fire(
                softwareprofile_id=str(new_swp.getId()),
                softwareprofile_name=new_swp.getName(),
                tags=new_swp.getTags(),
                previous_tags=existing_swp.getTags())

    def getSoftwareProfile(
            self,
            session: Session,
            name: str,
            optionDict: Optional[Dict[str, bool]] = None) -> SoftwareProfile:
        """
        Retrieve software profile by name

        """
        software_profile_obj: SoftwareProfile = \
            self._sp_db_api.getSoftwareProfile(
                session, name, optionDict=optionDict)

        # load any available software profile metadata
        software_profile_obj.setMetadata(
            self.get_software_profile_metadata(session, name))

        return software_profile_obj

    def getSoftwareProfileById(
            self,
            session: Session,
            id_: int,
            optionDict: Optional[Dict[str, bool]] = None) -> SoftwareProfile:
        """
        Retrieve software profile by id

        """
        software_profile_obj: SoftwareProfile = \
            self._sp_db_api.getSoftwareProfileById(
                session, id_, optionDict=optionDict)

        # load any available software profile metadata
        software_profile_obj.setMetadata(
            self.get_software_profile_metadata(session,
                                               software_profile_obj.getName()))

        return software_profile_obj

    def _getCoreComponentForOsInfo(self, session: Session, osInfo):
        # Find core component

        baseKit = None

        for baseKit in self._kit_db_api.getKitList(session):
            if not baseKit.getName() == self.BASE_KIT_NAME:
                continue

            break
        else:
            raise KitNotFound('Kit [%s] not found.' % (self.BASE_KIT_NAME))

        baseComp = None

        for baseComp in baseKit.getComponentList():
            if baseComp.getName() != 'core':
                continue

            break
        else:
            raise ComponentNotFound('Component [%s] not found in kit [%s]' %
                                    ('core', baseKit.getName()))

        comp = self._component_db_api.getBestMatchComponent(
            session, baseComp.getName(), baseComp.getVersion(), osInfo,
            baseKit.getId())

        comp.setKit(baseKit)

        return comp

    def _getOsInfo(self, session: Session, bOsMediaRequired: bool):
        if not bOsMediaRequired:
            # As a placeholder, use the same OS as the installer

            # Find installer node entry
            node = self._node_db_api.getNode(session,
                                             ConfigManager().getInstaller(),
                                             {'softwareprofile': True})

            return node.getSoftwareProfile().getOsInfo()

        # Use available operating system kit; raise exception if
        # multiple available

        os_kits = self._kit_db_api.getKitList(session, os_kits_only=True)
        if not os_kits:
            raise KitNotFound('No operating system kit installed')

        if len(os_kits) > 1:
            raise KitNotFound(
                'Multiple OS kits defined; use --os option to specify'
                ' operating system')

        kit = self._kit_db_api.getKit(session, os_kits[0].getName(),
                                      os_kits[0].getVersion(), '0')

        components = kit.getComponentList()

        if not components:
            raise ComponentNotFound('Malformed operating system kit [%s]' %
                                    (os_kits))

        osinfo_list = components[0].getOsInfoList()
        if len(osinfo_list) > 1:
            raise ComponentNotFound(
                'Multiple operating system components for kit [%s];'
                ' use --os argument to specify operating system' %
                (os_kits[0]))

        return osinfo_list[0]

    def createSoftwareProfile(self,
                              session: Session,
                              swProfileSpec,
                              settingsDict=None):
        """
        Exceptions:
            ConfigurationError
            NetworkNotFound
            ComponentNotFound
            KitNotFound
            OSError
        """

        if settingsDict == None:
            settingsDict = {}

        bOsMediaRequired = settingsDict.get('bOsMediaRequired', True)
        unmanagedProfile = settingsDict.get('unmanagedProfile', False)

        # Validate software profile name
        validation.validateProfileName(swProfileSpec.getName())

        # Insert default description for software profile
        if swProfileSpec.getDescription() is None:
            swProfileSpec.setDescription('%s Nodes' %
                                         (swProfileSpec.getName()))

        self._logger.debug('Creating software profile [%s]' % (swProfileSpec))

        osInfo = swProfileSpec.getOsInfo() \
            if swProfileSpec.getOsInfo() else self._getOsInfo(
                session, bOsMediaRequired)

        # If we're creating an unmanaged software profile (no
        # DHCP/PXE/kickstart/OS) just create it now and we're done
        if unmanagedProfile:
            self._sp_db_api.addSoftwareProfile(session, swProfileSpec)

        else:
            if bOsMediaRequired and swProfileSpec.getOsInfo():
                try:
                    self._kit_db_api.getKit(
                        session,
                        swProfileSpec.getOsInfo().getName(),
                        swProfileSpec.getOsInfo().getVersion(), '0')
                except KitNotFound:
                    self._logger.error('OS kit for [%s] not found' %
                                       (swProfileSpec.getOsInfo()))

                    raise
            else:
                swProfileSpec.setOsInfo(osInfo)

            # Get component manager for appropriate OS family
            osConfig = osHelper.getOsInfo(osInfo.getName(),
                                          osInfo.getVersion(),
                                          osInfo.getArch())

            osObjFactory = osUtility.getOsObjectFactory(
                osConfig.getOsFamilyInfo().getName())

            # Need to be fancy with components
            spComponents = swProfileSpec.getComponents()
            swProfileSpec.setComponents(TortugaObjectList())

            bFoundOsComponent = False
            bFoundCoreComponent = False
            components = []

            # Iterate over components, adding them to the software profile
            for c in spComponents:
                cobj = self._component_db_api.getBestMatchComponent(
                    session, c.getName(), c.getVersion(), osInfo,
                    c.getKit().getId())

                k = cobj.getKit()

                if k.getIsOs():
                    # This component is a member of the OS kit, set the flag
                    bFoundOsComponent = True
                elif k.getName() == 'base' and c.getName() == 'core':
                    # Found the 'core' component in 'base' kit
                    bFoundCoreComponent = True

                components.append(cobj)

            # If the operating system is undefined for this software
            # profile, use the same OS as the installer.
            if bOsMediaRequired and not bFoundOsComponent:
                # Find OS component
                osCompName = '%s-%s-%s' % (
                    osInfo.getName(), osInfo.getVersion(), osInfo.getArch())

                self._logger.debug('Automatically adding OS component [%s]'
                                   ' (not specified in template)' %
                                   (osCompName))

                try:
                    osComponent = self._component_db_api.getComponent(
                        session, osCompName, osInfo.getVersion(), osInfo,
                        {'kit': True})

                    components.append(osComponent)
                except ComponentNotFound:
                    # Cannot find OS component, don't freak out
                    pass

            # Ensure 'core' component is enabled
            if not bFoundCoreComponent:
                # Attempt to automatically add the core component, only
                # if one exists for this OS

                try:
                    comp = self._getCoreComponentForOsInfo(session, osInfo)

                    self._logger.debug('Automatically adding [core] component'
                                       ' (not specified in template)')

                    components.append(comp)
                except ComponentNotFound:
                    self._logger.warning(
                        'OS [{}] does not have a compatible \'core\''
                        ' component'.format(osInfo))

                # Initialize values for kernel, kernelParams, and initrd
                if not swProfileSpec.getKernel():
                    swProfileSpec.setKernel(
                        osObjFactory.getOsSysManager().getKernel(osInfo))

                if not swProfileSpec.getInitrd():
                    swProfileSpec.setInitrd(
                        osObjFactory.getOsSysManager().getInitrd(osInfo))

            # Add the software profile
            self._sp_db_api.addSoftwareProfile(session, swProfileSpec)

            # Enable components in one fell swoop
            for comp in components:
                self._logger.debug('Enabling component [%s]' %
                                   (comp.getName()))

                if comp.getKit().getIsOs():
                    # Don't use enableComponent() on OS kit
                    self._component_db_api.addComponentToSoftwareProfile(
                        session, comp.getId(), swProfileSpec.getId())

                    continue

                self.enableComponent(session, swProfileSpec.getName(),
                                     comp.getKit().getName(),
                                     comp.getKit().getVersion(),
                                     comp.getKit().getIteration(),
                                     comp.getName(), comp.getVersion())

        #
        # Fire the tags changed event for all creates that have tags
        #
        # Get the latest version from the db in case the create method
        # added some embellishments
        #
        swp = self.getSoftwareProfile(session, swProfileSpec.getName())
        if swp.getTags():
            SoftwareProfileTagsChanged.fire(softwareprofile_id=str(
                swp.getId()),
                                            softwareprofile_name=swp.getName(),
                                            tags=swp.getTags(),
                                            previous_tags={})

    def _getComponent(self, kit, compName, compVersion):         \
            # pylint: disable=no-self-use

        # Iterate over component list, looking for a match
        comp = None

        for comp in kit.getComponentList():
            if comp.getName() == compName and \
                    comp.getVersion() == compVersion:
                break
        else:
            raise ComponentNotFound("Component [%s-%s] not found in kit [%s]" %
                                    (compName, compVersion, kit))

        return comp

    def _get_kit_by_component(self,
                              session: Session,
                              comp_name,
                              comp_version=None):
        """
        Gets a kit by compoent name/version.
        :param comp_name:    the name of the component
        :param comp_version: the version of the component

        :raises KitNotFound:
        :raises ComponentNotFound:

        """
        kit_list = self._kit_db_api.getKitList(session)
        kits = [
            kit for kit in kit_list for component in kit.getComponentList()
            if component.getName() == comp_name and (
                comp_version is None or component.getVersion() == comp_version)
        ]
        if not kits:
            raise KitNotFound('Kit containing component [%s] not found' %
                              (comp_name))

        if len(kits) > 1:
            raise ComponentNotFound(
                'Kit name must be specified, multiple kits contain '
                'component: {}'.format(comp_name))

        return kits[0]

    def enableComponent(self,
                        session: Session,
                        software_profile_name: str,
                        kit_name: str,
                        kit_version: str,
                        kit_iteration: str,
                        comp_name: str,
                        comp_version: Optional[str] = None):
        """
        Enable a component on a software profile.

        :param software_profile_name: the name of the software profile
        :param kit_name:              the name of the kit
        :param kit_version:           the version of the kit
        :param kit_iteration:         the iteration of the kit
        :param comp_name:             the name of the component
        :param comp_version:          the version of the component

        :raises KitNotFound:
        :raises SoftwareProfileNotFound:
        :raises ComponentNotFound:

        """
        kit, comp_version = self._get_kit_and_component_version(
            session, kit_name, kit_version, kit_iteration, comp_name,
            comp_version)

        software_profile = self.getSoftwareProfile(session,
                                                   software_profile_name,
                                                   {'os': True})

        if kit.getIsOs():
            best_match_component = self._enable_os_kit_component(
                session, kit, comp_name, comp_version, software_profile)
        else:
            best_match_component = self._enable_kit_component(
                session, kit, comp_name, comp_version, software_profile)

        if not best_match_component:
            self._logger.info('Component not enabled: {}'.format(comp_name))
        else:
            self._logger.info(
                'Enabled component on software profile: {} -> {}'.format(
                    best_match_component, software_profile))

    def _get_kit_and_component_version(self,
                                       session: Session,
                                       kit_name,
                                       kit_version,
                                       kit_iteration,
                                       comp_name,
                                       comp_version=None):
        """
        Gets a Kit instance and component version.

        :param kit_name:      the name of the kit
        :param kit_version:   the version of the kit
        :param kit_iteration: the iteration of the kit
        :param comp_name:     the component name
        :param comp_version:  the component version (optional)

        :return: a tuple, consisting of (Kit, component_version)

        """
        kit = None
        if kit_name is None:
            kit = self._get_kit_by_component(session,
                                             comp_name,
                                             comp_version=comp_version)
            #
            # Get component version if required
            #
            if comp_version is None:
                for component in kit.getComponentList():
                    if component.getName() == comp_name:
                        comp_version = component.getVersion()
                        break
        elif kit_version is None or kit_iteration is None:
            kits_found = 0
            for k in self._kit_db_api.getKitList(session):
                if k.getName() == kit_name and \
                        (kit_version is None or
                         k.getVersion() == kit_version) and \
                        (kit_iteration is None or
                         k.getIteration() == kit_iteration):
                    kit = k
                    kits_found += 1

            if kits_found > 1:
                if kit_version is not None:
                    raise KitNotFound('Multiple kits found: {}-{}'.format(
                        kit_name, kit_version))
                else:
                    raise KitNotFound(
                        'Multiple kits found {}'.format(kit_name))
        else:
            kit = self._kit_db_api.getKit(session, kit_name, kit_version,
                                          kit_iteration)

        if kit is None:
            raise KitNotFound('Kit [%s] not found' %
                              (Kit(kit_name, kit_version, kit_iteration)))

        return kit, comp_version

    def _enable_kit_component(self, session: Session, kit, comp_name,
                              comp_version, software_profile):
        """
        Enables a regular kit component on a specific software profile.

        :param kit:              the Kit instance, whose component is being
                                 enabled
        :param comp_name:        the name of the component to enable
        :param comp_version:     the version of the component to enable
        :param software_profile: the software profile on which the component
                                 will be enabled

        :return:                 the Component instance that was enabled

        """
        kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration())

        installer = get_kit_installer(kit_spec)()
        installer.session = session
        comp_installer = installer.get_component_installer(comp_name)

        if comp_installer is None:
            raise ComponentNotFound('Component [%s] not found in kit [%s]' %
                                    (comp_name, kit))

        if not comp_installer.is_enableable(software_profile):
            self._logger.warning('Component cannot be enabled: {}'.format(
                comp_installer.spec))
            return None
        comp_installer.run_action('pre_enable', software_profile.getName())

        best_match_component = self._add_component_to_software_profile(
            session, kit, comp_name, comp_version, software_profile)

        comp_installer.run_action('enable', software_profile.getName())
        comp_installer.run_action('post_enable', software_profile.getName())

        return best_match_component

    def _enable_os_kit_component(self, session: Session, kit, comp_name,
                                 comp_version, software_profile):
        """
        Enables an OS kit component on a specific software profile.

        :param kit:              the OS Kit instance, whose component is being
                                 enabled
        :param comp_name:        the name of the component to enable
        :param comp_version:     the version of the component to enable
        :param software_profile: the software profile on which the component
                                 will be enabled

        :return:                 the Component instance that was enabled

        """
        return self._add_component_to_software_profile(session, kit, comp_name,
                                                       comp_version,
                                                       software_profile)

    def _add_component_to_software_profile(self, session: Session, kit,
                                           comp_name, comp_version,
                                           software_profile):
        """
        Adds a kit to a software profile. This is a data-only operation,
        as no pre/post enable actions are called.

        :param kit:              the OS Kit instance, whose component is being
                                 added
        :param comp_name:        the name of the component to add
        :param comp_version:     the version of the component to add
        :param software_profile: the software profile to which the component
                                 will be added

        :return:                 the Component instance that was added

        """
        best_match_component = \
            self._component_db_api.getBestMatchComponent(
                session,
                comp_name, comp_version, software_profile.getOsInfo(),
                kit.getId())

        self._component_db_api.addComponentToSoftwareProfile(
            session, best_match_component.getId(), software_profile.getId())

        return best_match_component

    def disableComponent(self,
                         session: Session,
                         software_profile_name,
                         kit_name,
                         kit_version,
                         kit_iteration,
                         comp_name,
                         comp_version=None):         \
            # pylint: disable=unused-argument
        """
        Disables a component on a software profile.

        :param software_profile_name: the name of the software profile
        :param kit_name:              the name of the kit
        :param kit_version:           the version of the kit
        :param kit_iteration:         the iteration of the kit
        :param comp_name:             the name of the component
        :param comp_version:          the version of the component

        :raises KitNotFound:
        :raises SoftwareProfileNotFound:
        :raises ComponentNotFound:

        """
        kit, comp_version = self._get_kit_and_component_version(
            session, kit_name, kit_version, kit_iteration, comp_name)

        software_profile = self.getSoftwareProfile(session,
                                                   software_profile_name,
                                                   {'os': True})

        if kit.getIsOs():
            best_match_component = self._disable_os_kit_component(
                session, kit, comp_name, comp_version, software_profile)
        else:
            best_match_component = self._disable_kit_component(
                session, kit, comp_name, comp_version, software_profile)

        self._logger.info(
            'Disabled component on software profile: {} -> {}'.format(
                best_match_component, software_profile))

    def _disable_kit_component(self, session, kit, comp_name, comp_version,
                               software_profile):
        """
        Disables a regular kit component on a specific software profile.

        :param kit:              the Kit instance, whose component is being
                                 disabled
        :param comp_name:        the name of the component to disable
        :param comp_version:     the version of the component to disable
        :param software_profile: the software profile on which the component
                                 will be disable

        :return:                 the Component instance that was disabled

        """
        kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration())

        installer = get_kit_installer(kit_spec)()
        installer.session = session

        comp_installer = installer.get_component_installer(comp_name)

        if comp_installer is None:
            raise ComponentNotFound('Component [%s] not found in kit [%s]' %
                                    (comp_name, kit))

        comp_installer.run_action('pre_disable', software_profile.getName())
        comp_installer.run_action('disable', software_profile.getName())

        best_match_component = \
            self._remove_component_from_software_profile(
                session, kit, comp_name, comp_version, software_profile)

        comp_installer.run_action('post_disable', software_profile.getName())

        return best_match_component

    def _disable_os_kit_component(self, session, kit, comp_name, comp_version,
                                  software_profile):
        """
        Enables an OS kit component on a specific software profile.

        :param kit:              the OS Kit instance, whose component is being
                                 disabled
        :param comp_name:        the name of the component to disable
        :param comp_version:     the version of the component to disable
        :param software_profile: the software profile on which the component
                                 will be disabled

        :return:                 the Component instance that was disabled

        """
        return self._remove_component_from_software_profile(
            session, kit, comp_name, comp_version, software_profile)

    def _remove_component_from_software_profile(self, session: Session, kit,
                                                comp_name, comp_version,
                                                software_profile):
        """
        Removes a kit to a software profile. This is a data-only operation,
        as no pre/post disable actions are called.

        :param kit:              the OS Kit instance, whose component is being
                                 removed
        :param comp_name:        the name of the component to remove
        :param comp_version:     the version of the component to remove
        :param software_profile: the software profile to which the component
                                 will be removed

        :return:                 the Component instance that was removed

        """
        best_match_component = self._component_db_api.getBestMatchComponent(
            session, comp_name, comp_version, software_profile.getOsInfo(),
            kit.getId())

        self._component_db_api.deleteComponentFromSoftwareProfile(
            session, best_match_component.getId(), software_profile.getId())

        return best_match_component

    def deleteSoftwareProfile(self, session: Session, name):
        """
        Delete software profile by name

        Raises:
            SoftwareProfileNotFound
        """

        self._sp_db_api.deleteSoftwareProfile(session, name)

        # Remove all flags for software profile
        swProfileFlagPath = os.path.join(self._config_manager.getRoot(),
                                         'var/run/actions/%s' % (name))
        if os.path.exists(swProfileFlagPath):
            shutil.rmtree(swProfileFlagPath)

        self._logger.info('Deleted software profile [%s]' % (name))

    def getNodeList(self, session: Session, softwareProfileName):
        return self._sp_db_api.getNodeList(session, softwareProfileName)

    def getEnabledComponentList(self, session: Session, name):
        """ Get the list of enabled components """
        return self._sp_db_api.getEnabledComponentList(session, name)

    def getPartitionList(self, session: Session, softwareProfileName):
        """ Get list of partitions. """
        return self._sp_db_api.getPartitionList(session, softwareProfileName)

    def addUsableHardwareProfileToSoftwareProfile(
            self, session: Session, hardwareProfileName: str,
            softwareProfileName: str) -> None:
        """
        Map software profile to hardware profile
        """

        self._logger.info(
            'Mapping hardware profile [%s] to software profile [%s]',
            hardwareProfileName, softwareProfileName)

        self._sp_db_api.addUsableHardwareProfileToSoftwareProfile(
            session, hardwareProfileName, softwareProfileName)

    def deleteUsableHardwareProfileFromSoftwareProfile(self, session: Session,
                                                       hardwareProfileName,
                                                       softwareProfileName):
        return self._sp_db_api.deleteUsableHardwareProfileFromSoftwareProfile(
            session, hardwareProfileName, softwareProfileName)

    def copySoftwareProfile(self, session: Session, srcSoftwareProfileName,
                            dstSoftwareProfileName):
        validation.validateProfileName(dstSoftwareProfileName)
        self._logger.info('Copying software profile [%s] to [%s]',
                          srcSoftwareProfileName, dstSoftwareProfileName)
        self._sp_db_api.copySoftwareProfile(session, srcSoftwareProfileName,
                                            dstSoftwareProfileName)
        #
        # Fire the tags changed event for all copies that have tags
        #
        swp = self.getSoftwareProfile(session, dstSoftwareProfileName)
        if swp.getTags():
            SoftwareProfileTagsChanged.fire(softwareprofile_id=str(
                swp.getId()),
                                            softwareprofile_name=swp.getName(),
                                            tags=swp.getTags(),
                                            previous_tags={})

    def getUsableNodes(self, session: Session, softwareProfileName):
        return self._sp_db_api.getUsableNodes(session, softwareProfileName)

    def get_software_profile_metadata(self, session: Session,
                                      name: str) -> Dict[str, str]:
        """
        Call action_get_metadata() method for all kits
        """

        self._logger.debug('Retrieving metadata for software profile [%s]',
                           name)

        metadata: Dict[str, str] = {}

        for kit in self._kit_db_api.getKitList(session):
            if kit.getIsOs():
                # ignore OS kits
                continue

            kit_installer = get_kit_installer(
                (kit.getName(), kit.getVersion(), kit.getIteration()))()
            kit_installer.session = session

            # we are only interested in software profile metadata
            item = kit_installer.action_get_metadata(
                software_profile_name=name)

            if item:
                metadata.update(item)

        return metadata
Ejemplo n.º 25
0
class NodeManager(TortugaObjectManager):     \
        # pylint: disable=too-many-public-methods

    def __init__(self):
        super(NodeManager, self).__init__()

        self._nodeDbApi = NodeDbApi()
        self._cm = ConfigManager()
        self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager(
            self._cm)
        self._syncApi = SyncApi()
        self._nodesDbHandler = NodesDbHandler()
        self._addHostManager = AddHostManager()
        self._logger = logging.getLogger(NODE_NAMESPACE)

    def __validateHostName(self, hostname: str, name_format: str) -> None:
        """
        Raises:
            ConfigurationError
        """

        bWildcardNameFormat = (name_format == '*')

        if hostname and not bWildcardNameFormat:
            # Host name specified, but hardware profile does not
            # allow setting the host name
            raise ConfigurationError(
                'Hardware profile does not allow setting host names'
                ' of imported nodes')
        elif not hostname and bWildcardNameFormat:
            # Host name not specified but hardware profile expects it
            raise ConfigurationError(
                'Hardware profile requires host names to be set')

    def createNewNode(self,
                      session: Session,
                      addNodeRequest: dict,
                      dbHardwareProfile: HardwareProfileModel,
                      dbSoftwareProfile: Optional[SoftwareProfileModel] = None,
                      validateIp: bool = True,
                      bGenerateIp: bool = True,
                      dns_zone: Optional[str] = None) -> NodeModel:
        """
        Convert the addNodeRequest into a Nodes object

        Raises:
            NicNotFound
        """

        self._logger.debug(
            'createNewNode(): session=[%s], addNodeRequest=[%s],'
            ' dbHardwareProfile=[%s], dbSoftwareProfile=[%s],'
            ' validateIp=[%s], bGenerateIp=[%s]' %
            (id(session), addNodeRequest, dbHardwareProfile.name,
             dbSoftwareProfile.name if dbSoftwareProfile else '(none)',
             validateIp, bGenerateIp))

        hostname = addNodeRequest['name'] \
            if 'name' in addNodeRequest else None

        # Ensure no conflicting options (ie. specifying host name for
        # hardware profile in which host names are generated)
        self.__validateHostName(hostname, dbHardwareProfile.nameFormat)

        node: Node = NodeModel(name=hostname)

        if 'rack' in addNodeRequest:
            node.rack = addNodeRequest['rack']

        node.addHostSession = addNodeRequest['addHostSession']

        # Complete initialization of new node record
        nic_defs = addNodeRequest['nics'] \
            if 'nics' in addNodeRequest else []

        AddHostServerLocal().initializeNode(session,
                                            node,
                                            dbHardwareProfile,
                                            dbSoftwareProfile,
                                            nic_defs,
                                            bValidateIp=validateIp,
                                            bGenerateIp=bGenerateIp,
                                            dns_zone=dns_zone)

        node.hardwareprofile = dbHardwareProfile
        node.softwareprofile = dbSoftwareProfile

        #
        # Fire the tags changed event for all creates that have tags...
        # we have to convert this to a node object because... our API
        # is inconsistent!
        #
        n = Node.getFromDbDict(node.__dict__)
        if n.getTags():
            NodeTagsChanged.fire(node=n.getCleanDict(), previous_tags={})

        # Return the new node
        return node

    def getNode(self, session: Session, name, optionDict: OptionDict = None) \
            -> Node:
        """
        Get node by name

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNode(
                session, name, optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeById(self,
                    session: Session,
                    nodeId: int,
                    optionDict: OptionDict = None) -> Node:
        """
        Get node by node id

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNodeById(
                session,
                int(nodeId),
                optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeByIp(self,
                    session: Session,
                    ip: str,
                    optionDict: Dict[str, bool] = None) -> Node:
        """
        Get node by IP address

        Raises:
            NodeNotFound
        """

        return self.__populate_nodes(session, [
            self._nodeDbApi.getNodeByIp(
                session, ip, optionDict=get_default_relations(optionDict))
        ])[0]

    def getNodeList(self,
                    session,
                    tags=None,
                    optionDict: Optional[OptionDict] = None) -> List[Node]:
        """
        Return all nodes

        """
        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodeList(
                session,
                tags=tags,
                optionDict=get_default_relations(optionDict)))

    def __populate_nodes(self, session: Session, nodes: List[Node]) \
            -> List[Node]:
        """
        Expand non-database fields in Node objects

        """
        class SoftwareProfileMetadataCache(defaultdict):
            def __missing__(self, key):
                metadata = \
                    SoftwareProfileManager().get_software_profile_metadata(
                        session, key
                    )

                self[key] = metadata

                return metadata

        swprofile_map = SoftwareProfileMetadataCache()

        for node in nodes:
            if not node.getSoftwareProfile():
                continue

            node.getSoftwareProfile().setMetadata(
                swprofile_map[node.getSoftwareProfile().getName()])

        return nodes

    def updateNode(self, session: Session, nodeName: str,
                   updateNodeRequest: dict) -> None:
        """
        Calls updateNode() method of resource adapter
        """

        self._logger.debug('updateNode(): name=[{0}]'.format(nodeName))

        try:
            #
            # Get the old version for comparison later
            #
            node_old: Node = self.getNode(session, nodeName)

            db_node = self._nodesDbHandler.getNode(session, nodeName)
            if 'nics' in updateNodeRequest:
                nic = updateNodeRequest['nics'][0]
                if 'ip' in nic:
                    db_node.nics[0].ip = nic['ip']
                    db_node.nics[0].boot = True

            adapter = self.__getResourceAdapter(session,
                                                db_node.hardwareprofile)
            adapter.updateNode(session, db_node, updateNodeRequest)
            run_post_install = False

            if 'state' in updateNodeRequest:
                run_post_install = \
                    db_node.state == state.NODE_STATE_ALLOCATED and \
                    updateNodeRequest['state'] == state.NODE_STATE_PROVISIONED
                db_node.state = updateNodeRequest['state']

            session.commit()

            #
            # Fire events as required
            #
            # Get the current/new state of the node from the DB
            #
            node: Node = self.getNode(session, nodeName)
            if node.getState() != node_old.getState():
                NodeStateChanged.fire(node=node.getCleanDict(),
                                      previous_state=node_old.getState())
            if node.getTags() != node_old.getTags():
                NodeTagsChanged.fire(node=node.getCleanDict(),
                                     previous_tags=node_old.getTags())

            if run_post_install:
                self._logger.debug(
                    'updateNode(): run-post-install for node [{0}]'.format(
                        db_node.name))
                self.__scheduleUpdate()

        except Exception:
            session.rollback()
            raise

    def updateNodeStatus(self,
                         session: Session,
                         nodeName: str,
                         node_state: Optional[str] = None,
                         bootFrom: int = None):
        """Update node status

        If neither 'state' nor 'bootFrom' are not None, this operation will
        update only the 'lastUpdated' timestamp.

        Returns:
            bool indicating whether state and/or bootFrom differed from
            current value
        """

        value = 'None' if bootFrom is None else \
            '1 (disk)' if int(bootFrom) == 1 else '0 (network)'

        self._logger.debug(
            'updateNodeStatus(): node=[%s], node_state=[{%s}],'
            ' bootFrom=[{%s}]', nodeName, node_state, value)

        dbNode = self._nodesDbHandler.getNode(session, nodeName)

        #
        # Capture previous state and node data in dict form for the
        # event later on
        #
        previous_state = dbNode.state
        node_dict = Node.getFromDbDict(dbNode.__dict__).getCleanDict()

        # Bitfield representing node changes (0 = state change,
        # 1 = bootFrom # change)
        changed = 0

        if node_state is not None and node_state != dbNode.state:
            # 'state' changed
            changed |= 1

        if bootFrom is not None and bootFrom != dbNode.bootFrom:
            # 'bootFrom' changed
            changed |= 2

        if changed:
            # Create custom log message
            msg = 'Node [%s] state change:' % (dbNode.name)

            if changed & 1:
                msg += ' state: [%s] -> [%s]' % (dbNode.state, node_state)

                dbNode.state = node_state
                node_dict['state'] = node_state

            if changed & 2:
                msg += ' bootFrom: [%d] -> [%d]' % (dbNode.bootFrom, bootFrom)

                dbNode.bootFrom = bootFrom

            self._logger.info(msg)
        else:
            self._logger.info('Updated timestamp for node [%s]' %
                              (dbNode.name))

        dbNode.lastUpdate = time.strftime('%Y-%m-%d %H:%M:%S',
                                          time.gmtime(time.time()))

        result = bool(changed)

        # Only change local boot configuration if the hardware profile is
        # not marked as 'remote' and we're not acting on the installer
        # node.
        if dbNode.softwareprofile and \
                dbNode.softwareprofile.type != 'installer' and \
                dbNode.hardwareprofile.location != 'remote':
            # update local boot configuration for on-premise nodes
            self._bhm.writePXEFile(session, dbNode, localboot=bootFrom)

        session.commit()

        #
        # If the node state has changed, fire the node state changed
        # event
        #
        if state and (previous_state != state):
            NodeStateChanged.fire(node=node_dict,
                                  previous_state=previous_state)

        return result

    def deleteNode(self, session: Session, nodespec: str, force: bool = False):
        """
        Delete nodes by node spec

        :param Session session: a database session
        :param str nodespec:    a node spec
        :param bool force:      whether or not this is a force operation

        """
        try:
            nodes = self.__get_nodes_for_deletion(session, nodespec)

            kitmgr = KitActionsManager()
            kitmgr.session = session
            self.__delete_nodes(session, kitmgr, nodes)

        except Exception:
            session.rollback()
            raise

    def __get_nodes_for_deletion(self, session: Session,
                                 nodespec: str) -> List[NodeModel]:
        """
        Gets a list of nodes from a node spec.

        :param Session session: a database session
        :param str nodespec:    a node spec

        :raise NodeNotFound:

        """
        nodes = self._nodesDbHandler.expand_nodespec(session,
                                                     nodespec,
                                                     include_installer=False)
        if not nodes:
            raise NodeNotFound(
                'No nodes matching nodespec [{}]'.format(nodespec))

        return nodes

    def __delete_nodes(self, session: Session, kitmgr: KitActionsManager,
                       nodes: List[NodeModel]) -> None:
        """
        :raises DeleteNodeFailed:
        """

        hwprofile_nodes = self.__pre_delete_nodes(kitmgr, nodes)

        # commit node state changes to database
        session.commit()

        for hwprofile, node_data_dicts in hwprofile_nodes.items():
            # build list of NodeModels
            node_objs: List[NodeModel] = [
                node_data_dict['node'] for node_data_dict in node_data_dicts
            ]

            # Call resource adapter deleteNode() entry point
            self.__get_resource_adapter(session,
                                        hwprofile).deleteNode(node_objs)

            # Perform delete node action for each node in hwprofile
            for node_data_dict in node_data_dicts:
                # get JSON object for node record
                node_dict = NodeSchema(
                    only=('hardwareprofile', 'softwareprofile', 'name',
                          'state'),
                    exclude=('softwareprofile.metadata', )).dump(
                        node_data_dict['node']).data

                # Delete the Node
                self._logger.debug('Deleting node [%s]', node_dict['name'])

                #
                # Fire node state change events
                #
                NodeStateChanged.fire(
                    node=node_dict,
                    previous_state=node_data_dict['previous_state'])

                session.delete(node_data_dict['node'])

                # Commit the actual deletion of this node to the DB.  This is required
                # as the post_delete hooks may use a different DB session and we have
                # already commited some changes for this node.
                session.commit()

                self.__post_delete(kitmgr, node_dict)

                self._logger.info('Node [%s] deleted', node_dict['name'])

            # clean up add host session cache
            self._addHostManager.delete_sessions(
                set([
                    node.addHostSession for node in node_objs
                    if node.addHostSession
                ]))

        self.__scheduleUpdate()

    def __pre_delete_nodes(self, kitmgr: KitActionsManager,
                           nodes: List[NodeModel]) \
            -> DefaultDict[HardwareProfileModel, List[NodeModel]]:
        """Collect nodes being deleted, call pre-delete kit action,
        mark them for deletion, and return dict containing nodes
        keyed by hardware profile.
        """

        hwprofile_nodes = defaultdict(list)

        #
        # Mark node states as deleted in the database
        #
        for node in nodes:
            # call pre-delete host kit action
            kitmgr.pre_delete_host(node.hardwareprofile.name,
                                   get_node_swprofile_name(node),
                                   nodes=[node.name])

            #
            # Capture previous state and node data as a dict for firing
            # the event later on
            #
            hwprofile_nodes[node.hardwareprofile].append({
                'node':
                node,
                'previous_state':
                node.state
            })

            # mark node deleted
            node.state = state.NODE_STATE_DELETED

        return hwprofile_nodes

    def __get_resource_adapter(self, session: Session,
                               hardwareProfile: HardwareProfileModel):
        """
        Raises:
            OperationFailed
        """

        if not hardwareProfile.resourceadapter:
            raise OperationFailed(
                'Hardware profile [%s] does not have an associated'
                ' resource adapter' % (hardwareProfile.name))

        adapter = resourceAdapterFactory.get_api(
            hardwareProfile.resourceadapter.name)

        adapter.session = session

        return adapter

    def __process_delete_node_result(self, nodeErrorDict):
        # REALLY!?!? Convert a list of Nodes objects into a list of
        # node names so we can report the list back to the end-user.
        # This needs to be FIXED!

        result = {}
        nodes_deleted = []

        for key, nodeList in nodeErrorDict.items():
            result[key] = [dbNode.name for dbNode in nodeList]

            if key == 'NodesDeleted':
                for node in nodeList:
                    node_deleted = {
                        'name': node.name,
                        'hardwareprofile': node.hardwareprofile.name,
                    }

                    if node.softwareprofile:
                        node_deleted['softwareprofile'] = \
                            node.softwareprofile.name

                    nodes_deleted.append(node_deleted)

        return result, nodes_deleted

    def __post_delete(self, kitmgr: KitActionsManager, node: dict):
        """Call post-delete kit action for deleted node and clean up node
        state files (ie. Puppet certificate, etc.).

        'node' is a JSON object representing the deleted node.
        """

        kitmgr.post_delete_host(node['hardwareprofile']['name'],
                                node['softwareprofile']['name']
                                if node['softwareprofile'] else None,
                                nodes=[node['name']])

        # remove Puppet cert, etc.
        self.__cleanup_node_state_files(node)

    def __cleanup_node_state_files(self, node_dict: dict):
        # Remove the Puppet cert
        self._bhm.deletePuppetNodeCert(node_dict['name'])

        self._bhm.nodeCleanup(node_dict['name'])

    def __scheduleUpdate(self):
        self._syncApi.scheduleClusterUpdate()

    def getInstallerNode(self,
                         session,
                         optionDict: Optional[OptionDict] = None):
        return self._nodeDbApi.getNode(
            session,
            self._cm.getInstaller(),
            optionDict=get_default_relations(optionDict))

    def getProvisioningInfo(self, session: Session, nodeName):
        return self._nodeDbApi.getProvisioningInfo(session, nodeName)

    def startupNode(self,
                    session,
                    nodespec: str,
                    remainingNodeList: List[NodeModel] = None,
                    bootMethod: str = 'n') -> None:
        """
        Raises:
            NodeNotFound
        """

        try:
            nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No matching nodes for nodespec [%s]' %
                                   (nodespec))

            # Break list of nodes into dict keyed on hardware profile
            nodes_dict = self.__processNodeList(nodes)

            for dbHardwareProfile, detailsDict in nodes_dict.items():
                # Get the ResourceAdapter
                adapter = self.__getResourceAdapter(session, dbHardwareProfile)

                # Call startup action extension
                adapter.startupNode(detailsDict['nodes'],
                                    remainingNodeList=remainingNodeList or [],
                                    tmpBootMethod=bootMethod)

            session.commit()
        except TortugaException:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self._logger.exception(str(ex))
            raise

    def shutdownNode(self,
                     session,
                     nodespec: str,
                     bSoftShutdown: bool = False) -> None:
        """
        Raises:
            NodeNotFound
        """

        try:
            nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)

            if not nodes:
                raise NodeNotFound('No matching nodes for nodespec [%s]' %
                                   (nodespec))

            d = self.__processNodeList(nodes)

            for dbHardwareProfile, detailsDict in d.items():
                # Get the ResourceAdapter
                adapter = self.__getResourceAdapter(session, dbHardwareProfile)

                # Call shutdown action extension
                adapter.shutdownNode(detailsDict['nodes'], bSoftShutdown)

            session.commit()
        except TortugaException:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self._logger.exception(str(ex))
            raise

    def rebootNode(self,
                   session,
                   nodespec: str,
                   bSoftReset: bool = False,
                   bReinstall: bool = False) -> None:
        """
        Raises:
            NodeNotFound
        """

        nodes = self._nodesDbHandler.expand_nodespec(session, nodespec)
        if not nodes:
            raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec))

        if bReinstall:
            for dbNode in nodes:
                self._bhm.setNodeForNetworkBoot(session, dbNode)

        for dbHardwareProfile, detailsDict in \
                self.__processNodeList(nodes).items():
            # iterate over hardware profile/nodes dict to reboot each
            # node
            adapter = self.__getResourceAdapter(session, dbHardwareProfile)

            # Call reboot action extension
            adapter.rebootNode(detailsDict['nodes'], bSoftReset)

        session.commit()

    def getNodesByNodeState(self, session, node_state: str,
                            optionDict: Optional[OptionDict] = None) \
            -> TortugaObjectList:
        """
        Get nodes by state
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByNodeState(
                session,
                node_state,
                optionDict=get_default_relations(optionDict)))

    def getNodesByNameFilter(self, session, nodespec: str,
                             optionDict: OptionDict = None,
                             include_installer: Optional[bool] = True) \
            -> TortugaObjectList:
        """
        Return TortugaObjectList of Node objects matching nodespec
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByNameFilter(
                session,
                nodespec,
                optionDict=get_default_relations(optionDict),
                include_installer=include_installer))

    def getNodesByAddHostSession(self, session, addHostSession: str,
                                 optionDict: OptionDict = None) \
            -> TortugaObjectList:
        """
        Return TortugaObjectList of Node objects matching add host session
        """

        return self.__populate_nodes(
            session,
            self._nodeDbApi.getNodesByAddHostSession(
                session,
                addHostSession,
                optionDict=get_default_relations(optionDict)))

    def __processNodeList(self, dbNodes: List[NodeModel]) \
            -> Dict[HardwareProfileModel, Dict[str, list]]:
        """
        Returns dict indexed by hardware profile, each with a list of
        nodes in the hardware profile
        """

        d: Dict[HardwareProfileModel, Dict[str, list]] = {}

        for dbNode in dbNodes:
            if dbNode.hardwareprofile not in d:
                d[dbNode.hardwareprofile] = {
                    'nodes': [],
                }

            d[dbNode.hardwareprofile]['nodes'].append(dbNode)

        return d

    def __getResourceAdapter(self, session: Session,
                             hardwareProfile: HardwareProfileModel) \
            -> Optional[ResourceAdapter]:
        """
        Raises:
            OperationFailed
        """

        if not hardwareProfile.resourceadapter:
            raise OperationFailed(
                'Hardware profile [%s] does not have an associated'
                ' resource adapter' % (hardwareProfile.name))

        adapter = resourceAdapterFactory.get_api(
            hardwareProfile.resourceadapter.name) \
            if hardwareProfile.resourceadapter else None

        if not adapter:
            return None

        adapter.session = session

        return adapter
Ejemplo n.º 26
0
 def __init__(self, *args, **kwargs):
     self._hwp_api = HardwareProfileDbApi()
     self._node_api = NodeDbApi()
     self._swp_api = SoftwareProfileDbApi()
     super().__init__(*args, **kwargs)
Ejemplo n.º 27
0
def test_getNodeByAddHostSession(dbm):
    with dbm.session() as session:
        result = NodeDbApi().getNodesByAddHostSession(session, 'xxxx')

    assert not result
Ejemplo n.º 28
0
def test_getNode(dbm):
    with dbm.session() as session:
        result = NodeDbApi().getNode(session, 'compute-01.private')

    assert isinstance(result, Node)
Ejemplo n.º 29
0
class HardwareProfileManager(TortugaObjectManager):
    def __init__(self):
        super(HardwareProfileManager, self).__init__()

        self._hpDbApi = HardwareProfileDbApi()
        self._spDbApi = SoftwareProfileDbApi()
        self._networkDbApi = NetworkDbApi()
        self._globalParameterDbApi = GlobalParameterDbApi()
        self._nodeDbApi = NodeDbApi()
        self._logger = logging.getLogger(HARDWARE_PROFILE_NAMESPACE)

    def getHardwareProfileList(self,
                               session: Session,
                               optionDict: Optional[Union[Dict[str, str],
                                                          None]] = None,
                               tags: Optional[Tags] = None):
        """
        Return all of the hardwareprofiles with referenced components
        in this hardwareprofile
        """

        return self._hpDbApi.getHardwareProfileList(session,
                                                    optionDict=optionDict,
                                                    tags=tags)

    def getHardwareProfile(self,
                           session: Session,
                           name: str,
                           optionDict: Optional[Union[dict, None]] = None):
        return self._hpDbApi.getHardwareProfile(session, name, optionDict)

    def getHardwareProfileById(self, session: Session, id_, optionDict=None):
        return self._hpDbApi.getHardwareProfileById(session, id_, optionDict)

    def addAdmin(self, session: Session, hardwareProfileName: str,
                 adminUsername: str):
        """
        Add an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                HardwareProfileNotFound
        """

        return self._hpDbApi.addAdmin(session, hardwareProfileName,
                                      adminUsername)

    def deleteAdmin(self, session: Session, hardwareProfileName: str,
                    adminUsername: str):
        """
        Remove an admin as an authorized user.

            Returns:
                None
            Throws:
                TortugaException
                AdminNotFound
                HardwareProfileNotFound
        """

        return self._hpDbApi.deleteAdmin(session, hardwareProfileName,
                                         adminUsername)

    def updateHardwareProfile(self, session: Session,
                              hardwareProfileObject: Any):
        """
        Update a hardware profile in the database that matches the passed
        in hardware profile object.  The ID is used as the primary matching
        criteria.

            Returns:
                None
            Throws:
                TortugaException
                HardwareProfileNotFound
                InvalidArgument

        """

        self._logger.debug('Updating hardware profile [%s]' %
                           (hardwareProfileObject.getName()))

        existing_hwp = self.getHardwareProfileById(
            session, hardwareProfileObject.getId())
        if hardwareProfileObject.getInstallType() and \
            hardwareProfileObject.getInstallType() != \
                existing_hwp.getInstallType():
            raise InvalidArgument(
                'Hardware profile installation type cannot be'
                ' changed' % (hardwareProfileObject.getName()))
        self._hpDbApi.updateHardwareProfile(session, hardwareProfileObject)

        #
        # Get the new version from the DB
        #
        new_hwp = self.getHardwareProfileById(session,
                                              hardwareProfileObject.getId())
        #
        # If the tags have changed, fire the tags changed event
        #
        if existing_hwp.getTags() != new_hwp.getTags():
            HardwareProfileTagsChanged.fire(
                hardwareprofile_id=str(new_hwp.getId()),
                hardwareprofile_name=new_hwp.getName(),
                tags=new_hwp.getTags(),
                previous_tags=existing_hwp.getTags())

    def createHardwareProfile(self,
                              session: Session,
                              hwProfileSpec: HardwareProfile,
                              settingsDict: Optional[Union[dict,
                                                           None]] = None):
        bUseDefaults = settingsDict['defaults'] \
            if settingsDict and 'defaults' in settingsDict else False

        osInfo = settingsDict['osInfo'] \
            if settingsDict and \
            settingsDict and 'osInfo' in settingsDict else None

        validation.validateProfileName(hwProfileSpec.getName())

        if hwProfileSpec.getDescription() is None:
            hwProfileSpec.setDescription('%s Nodes' %
                                         (hwProfileSpec.getName()))

        installerNode = self._nodeDbApi.getNode(session,
                                                ConfigManager().getInstaller(),
                                                {'softwareprofile': True})

        if bUseDefaults:
            if not hwProfileSpec.getNetworks():
                # No <network>...</network> entries found in the template,
                # use the default provisioning interface from the primary
                # installer.

                # Find first provisioning network and use it
                for nic in installerNode.getNics():
                    network = nic.getNetwork()
                    if network.getType() == 'provision':
                        # for now set the default interface to be index 0
                        # with the same device
                        networkDevice = fixNetworkDeviceName(
                            nic.getNetworkDevice().getName())

                        network.setNetworkDevice(
                            NetworkDevice(name=networkDevice))

                        hwProfileSpec.getNetworks().append(network)

                        break
                else:
                    raise NetworkNotFound(
                        'Unable to find provisioning network')
            else:
                # Ensure network device is defined
                installerNic = None

                for network in hwProfileSpec.getNetworks():
                    for installerNic in installerNode.getNics():
                        installerNetwork = installerNic.getNetwork()

                        if network.getId() and \
                           network.getId() == installerNetwork.getId():
                            break
                        elif network.getAddress() and \
                            network.getAddress() == \
                            installerNetwork.getAddress() and \
                            network.getNetmask() and \
                            network.getNetmask() == \
                                installerNetwork.getNetmask():
                            break
                    else:
                        # Unable to find network matching specification in
                        # template.

                        raise NetworkNotFound(
                            'Unable to find provisioning network [%s]' %
                            (network))

                    networkDevice = fixNetworkDeviceName(
                        installerNic.getNetworkDevice().getName())

                    network.setNetworkDevice(NetworkDevice(name=networkDevice))

        if not osInfo:
            osInfo = installerNode.getSoftwareProfile().getOsInfo()

        osObjFactory = osUtility.getOsObjectFactory(osInfo.getName())

        if not hwProfileSpec.getKernel():
            hwProfileSpec.setKernel(
                osObjFactory.getOsSysManager().getKernel(osInfo))

        if not hwProfileSpec.getInitrd():
            hwProfileSpec.setInitrd(
                osObjFactory.getOsSysManager().getInitrd(osInfo))

        self._hpDbApi.addHardwareProfile(session, hwProfileSpec)

        # Iterate over all networks in the newly defined hardware profile
        # and build assocations to provisioning NICs
        if bUseDefaults:
            for network in \
                [network for network in hwProfileSpec.getNetworks()
                 if network.getType() == 'provision']:
                # Get provisioning nic for network
                try:
                    provisioningNic = self.getProvisioningNicForNetwork(
                        session, network.getAddress(), network.getNetmask())
                except NicNotFound:
                    # There is currently no provisioning NIC defined for
                    # this network.  This is not a fatal error.
                    continue

                self.setProvisioningNic(session, hwProfileSpec.getName(),
                                        provisioningNic.getId())

        #
        # Fire the tags changed event for all creates that have tags
        #
        # Get the latest version from the db in case the create method
        # added some embellishments
        #
        hwp = self.getHardwareProfile(session, hwProfileSpec.getName())
        if hwp.getTags():
            HardwareProfileTagsChanged.fire(hardwareprofile_id=str(
                hwp.getId()),
                                            hardwareprofile_name=hwp.getName(),
                                            tags=hwp.getTags(),
                                            previous_tags={})

    def deleteHardwareProfile(self, session: Session, name: str) -> None:
        """
        Delete hardwareprofile by name.
        """

        self._hpDbApi.deleteHardwareProfile(session, name)

        self._logger.info('Deleted hardware profile [%s]' % (name))

    def updateSoftwareOverrideAllowed(self, session: Session,
                                      hardwareProfileName: str,
                                      flag: bool) -> None:
        self._hpDbApi.updateSoftwareOverrideAllowed(session,
                                                    hardwareProfileName, flag)

    def setProvisioningNic(self, session: Session, hardwareProfileName: str,
                           nicId: int):
        return self._hpDbApi.setProvisioningNic(session, hardwareProfileName,
                                                nicId)

    def getProvisioningNicForNetwork(self, session: Session, network: str,
                                     netmask: str):
        return self._hpDbApi.getProvisioningNicForNetwork(
            session, network, netmask)

    def copyHardwareProfile(self, session: Session,
                            srcHardwareProfileName: str,
                            dstHardwareProfileName: str):
        validation.validateProfileName(dstHardwareProfileName)
        self._logger.info('Copying hardware profile [%s] to [%s]' %
                          (srcHardwareProfileName, dstHardwareProfileName))
        self._hpDbApi.copyHardwareProfile(session, srcHardwareProfileName,
                                          dstHardwareProfileName)
        #
        # Fire the tags changed event for all copies that have tags
        #
        hwp = self.getHardwareProfile(session, dstHardwareProfileName)
        if hwp.getTags():
            HardwareProfileTagsChanged.fire(hardwareprofile_id=str(
                hwp.getId()),
                                            hardwareprofile_name=hwp.getName(),
                                            tags=hwp.getTags(),
                                            previous_tags={})

    def getNodeList(self, session: Session, hardwareProfileName: str):
        return self._hpDbApi.getNodeList(session, hardwareProfileName)