예제 #1
0
class NodeDbApi(TortugaDbApi):
    """
    Nodes DB API class.
    """
    def __init__(self):
        TortugaDbApi.__init__(self)

        self._nodesDbHandler = NodesDbHandler()
        self._globalParameterDbApi = GlobalParameterDbApi()

    def getNode(self,
                name: str,
                optionDict: Optional[Union[dict, None]] = None):
        """
        Get node from the db.

            Returns:
                node
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbNode = self._nodesDbHandler.getNode(session, name)

            self.loadRelations(dbNode, optionDict)

            self.loadRelations(dbNode, {
                'softwareprofile': True,
                'hardwareprofile': True,
                'tags': True,
            })

            return Node.getFromDbDict(dbNode.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getNodesByAddHostSession(self, ahSession):
        """
        Get node(s) from db based their addhost session
        """

        session = DbManager().openSession()

        try:
            return self.__convert_nodes_to_TortugaObjectList(
                self._nodesDbHandler.getNodesByAddHostSession(
                    session, ahSession))
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getNodesByNameFilter(self, _filter):
        """
        Get node(s) from db based on the name filter
        """

        session = DbManager().openSession()

        try:
            dbNodes = self._nodesDbHandler.getNodesByNameFilter(
                session, _filter)

            return self.getTortugaObjectList(Node, dbNodes)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getNodeById(self,
                    nodeId: int,
                    optionDict: Optional[Union[dict, None]] = None):

        session = DbManager().openSession()

        try:
            dbNode = self._nodesDbHandler.getNodeById(session, nodeId)

            self.loadRelations(dbNode, optionDict)

            return Node.getFromDbDict(dbNode.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getNodeByIp(self, ip):
        session = DbManager().openSession()

        try:
            node = self._nodesDbHandler.getNodeByIp(session, ip)

            return Node.getFromDbDict(node.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def __convert_nodes_to_TortugaObjectList(
            self,
            nodes,
            relations: Optional[Union[dict,
                                      None]] = None) -> TortugaObjectList:
        nodeList = TortugaObjectList()

        relations = relations or dict(softwareprofile=True,
                                      hardwareprofile=True)

        for t in nodes:
            self.loadRelations(t, relations)

            # Always load 'tags' relation
            self.loadRelations(t, {'tags': True})

            node = Node.getFromDbDict(t.__dict__)

            nodeList.append(node)

        return nodeList

    def getNodeList(self, tags=None):
        """
        Get list of all available nodes from the db.

            Returns:
                [node]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            return self.__convert_nodes_to_TortugaObjectList(
                self._nodesDbHandler.getNodeList(session, tags=tags))
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getProvisioningInfo(self, nodeName):
        """
        Get the provisioing information for a given provisioned address

            Returns:
                [provisioningInformation structure]
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            provisioningInfo = ProvisioningInfo()

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

            if dbNode.softwareprofile:
                self.loadRelations(dbNode.softwareprofile, {
                    'partitions': True,
                    'packages': True,
                })

                for component in dbNode.softwareprofile.components:
                    self.loadRelations(
                        component, {
                            'kit': True,
                            'os': True,
                            'family': True,
                            'os_components': True,
                            'osfamily_components': True,
                        })

            self.loadRelation(dbNode, 'hardwareprofile')

            provisioningInfo.setNode(Node.getFromDbDict(dbNode.__dict__))

            globalParameters = self._globalParameterDbApi.getParameterList()

            # TODO: this is a terrible hack until something better comes
            # along.

            p = Parameter()
            p.setName('Installer')

            hostName = socket.gethostname().split('.', 1)[0]

            if '.' in dbNode.name:
                nodeDomain = dbNode.name.split('.', 1)[1]

                priInstaller = hostName + '.%s' % (nodeDomain)
            else:
                priInstaller = hostName

            p.setValue(priInstaller)

            globalParameters.append(p)

            provisioningInfo.setGlobalParameters(globalParameters)

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

    def startupNode(self, nodespec, remainingNodeList=None, bootMethod='n'):
        """
        Start Node
        """

        session = DbManager().openSession()

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

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

            self._nodesDbHandler.startupNode(
                session,
                dbNodes,
                remainingNodeList=remainingNodeList or [],
                bootMethod=bootMethod)

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

    def shutdownNode(self, nodespec, bSoftShutdown=False):
        """
        Shutdown Node

        Raises:
            NodeNotFound
        """

        session = DbManager().openSession()

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

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

            self._nodesDbHandler.shutdownNode(session, dbNodes, bSoftShutdown)

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

    def __expand_nodespec(self, session, nodespec):
        # Expand wildcards in nodespec. Each token in the nodespec can
        # be wildcard that expands into one or more nodes.

        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

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

        return self._nodesDbHandler.getNodesByNameFilter(session, filter_spec)

    def evacuateChildren(self, nodeName):
        """
        Evacuate Children of node
        """

        session = DbManager().openSession()

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

            self._nodesDbHandler.evacuateChildren(session, dbNode)

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

    def getChildrenList(self, nodeName):
        """
        Get children of node

        Raises:
            NodeNotFound
        """

        session = DbManager().openSession()

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

            return self.getTortugaObjectList(Node, dbNode.children)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def checkpointNode(self, nodeName):
        """
        Checkpoint Node
        """

        session = DbManager().openSession()

        try:
            self._nodesDbHandler.checkpointNode(session, nodeName)
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def revertNodeToCheckpoint(self, nodeName):
        """
        Revert Node to Checkpoint
        """

        session = DbManager().openSession()

        try:
            self._nodesDbHandler.revertNodeToCheckpoint(session, nodeName)

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

    def migrateNode(self, nodeName, remainingNodeList, liveMigrate):
        """
        Migrate Node
        """

        session = DbManager().openSession()

        try:
            self._nodesDbHandler.migrateNode(session, nodeName,
                                             remainingNodeList, liveMigrate)

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

    def setParentNode(self, nodeName, parentNodeName):
        '''
        Raises:
            NodeNotFound
        '''

        session = DbManager().openSession()

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

            # Setting the parent to 'None' is equivalent to unsetting it
            dbNode.parentnode = self._nodesDbHandler.getNode(
                session, parentNodeName) if parentNodeName else None

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

    def getNodesByNodeState(self, state):
        session = DbManager().openSession()

        try:
            return self.getTortugaObjectList(
                Node, self._nodesDbHandler.getNodesByNodeState(session, state))
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % (ex))
            raise
        finally:
            DbManager().closeSession()
예제 #2
0
class HardwareProfileDbApi(TortugaDbApi):
    """
    HardwareProfile DB API class.
    """
    def __init__(self):
        TortugaDbApi.__init__(self)

        self._hardwareProfilesDbHandler = HardwareProfilesDbHandler()
        self._nodesDbHandler = NodesDbHandler()
        self._globalParametersDbHandler = GlobalParametersDbHandler()
        self._adminsDbHandler = AdminsDbHandler()
        self._nicsDbHandler = NicsDbHandler()
        self._resourceAdaptersDbHandler = ResourceAdaptersDbHandler()
        self._networkDevicesDbHandler = NetworkDevicesDbHandler()
        self._networksDbHandler = NetworksDbHandler()

    def getHardwareProfile(self,
                           name: str,
                           optionDict: Optional[Union[dict, None]] = None):
        """
        Get hardwareProfile from the db.

            Returns:
                hardwareProfile
            Throws:
                HardwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, name)

            self.loadRelations(dbHardwareProfile, optionDict)
            self.loadRelations(dbHardwareProfile, dict(tags=True))

            return HardwareProfile.getFromDbDict(dbHardwareProfile.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getHardwareProfileById(self,
                               hardwareProfileId: int,
                               optionDict: Optional[Union[dict, None]] = None):
        """
        Get hardwareProfile from the db.

            Returns:
                hardwareProfile
            Throws:
                HardwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfileById(session, hardwareProfileId)

            self.loadRelations(dbHardwareProfile, optionDict)

            return HardwareProfile.getFromDbDict(dbHardwareProfile.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getHardwareProfileList(self,
                               optionDict: Optional[Union[dict, None]] = None,
                               tags: Optional[Union[dict, None]] = None):
        """
        Get list of all available hardwareProfiles from the db.

            Returns:
                [hardwareProfile]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            dbHardwareProfileList = self._hardwareProfilesDbHandler.\
                getHardwareProfileList(session, tags=tags)

            hardwareProfileList = TortugaObjectList()

            for dbHardwareProfile in dbHardwareProfileList:
                # For now expand networks
                self.loadRelation(dbHardwareProfile, 'hardwareprofilenetworks')

                self.loadRelations(dbHardwareProfile, optionDict)

                self.loadRelations(dbHardwareProfile, dict(tags=True))

                hardwareProfileList.append(
                    HardwareProfile.getFromDbDict(dbHardwareProfile.__dict__))

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

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

        Returns:
            -none-

        Raises:
            SoftwareProfileNotFound
            SoftwareProfileNotIdle
        """

        session = DbManager().openSession()

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

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            self._hardwareProfilesDbHandler.setIdleSoftwareProfile(
                dbHardwareProfile, dbSoftwareProfile)

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

    def addHardwareProfile(self, hardwareProfile, session=None):
        """
        Insert hardwareProfile into the db.

            Returns:
                (none)
            Throws:
                HardwareProfileAlreadyExists
                DbError
        """

        # Keep local 'session' instance.  If 'session' is None,
        # ensure transaction is committed before returning to the
        # caller, otherwise the caller is responsible.  On exceptions,
        # the rollback is performed regardless.

        _session = session

        if _session is None:
            _session = DbManager().openSession()

        try:
            try:
                self._hardwareProfilesDbHandler.getHardwareProfile(
                    _session, hardwareProfile.getName())

                raise HardwareProfileAlreadyExists(
                    'Hardware profile [%s] already exists' % (hardwareProfile))
            except HardwareProfileNotFound as ex:
                pass

            dbHardwareProfile = self.__populateHardwareProfile(
                _session, hardwareProfile)

            _session.add(dbHardwareProfile)

            if session is None:
                _session.commit()

            self.getLogger().info('Added hardware profile [%s]' %
                                  (dbHardwareProfile.name))
        except TortugaException as ex:
            _session.rollback()
            raise
        except Exception as ex:
            _session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            if session is None:
                DbManager().closeSession()

    def deleteHardwareProfile(self, name):
        """
        Delete hardwareProfile from the db.

            Returns:
                None
            Throws:
                HardwareProfileNotFound
                DbError
                TortugaException
        """

        session = DbManager().openSession()

        try:
            hwProfile = self._hardwareProfilesDbHandler.getHardwareProfile(
                session, name)

            if hwProfile.nodes:
                raise TortugaException(
                    'Unable to remove hardware profile with associated'
                    ' nodes')

            # First delete the mappings
            hwProfile.mappedsoftwareprofiles = []

            self.getLogger().debug(
                'Marking hardware profile [%s] for deletion' % (name))

            session.delete(hwProfile)

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

    def copyHardwareProfile(self, srcHardwareProfileName,
                            dstHardwareProfileName):
        session = DbManager().openSession()

        try:
            srcHardwareProfile = self.getHardwareProfile(
                srcHardwareProfileName, {
                    'admins': True,
                    'hardwareprofilenetworks': True,
                    'nics': True,
                    'resourceadapter': True,
                })

            dstHardwareProfile = \
                self.getHardwareProfile(srcHardwareProfileName)

            dstHardwareProfile.setName(dstHardwareProfileName)

            newDescription = 'Copy of %s' % (
                dstHardwareProfile.getDescription())

            dstHardwareProfile.setDescription(newDescription)

            dstHardwareProfile.setNetworks(srcHardwareProfile.getNetworks())

            dstHardwareProfile.setProvisioningNics(
                srcHardwareProfile.getProvisioningNics())

            dstHardwareProfile.setResourceAdapter(
                srcHardwareProfile.getResourceAdapter())

            self.addHardwareProfile(dstHardwareProfile, session)

            session.commit()
        finally:
            DbManager().closeSession()

    def getNode(self, name):
        """
        Get node from the db.

            Returns:
                node
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            return self._nodesDbHandler.getNode(session, name)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def deleteNode(self, name):
        """
        Delete node from the db.

            Returns:
                None
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._nodesDbHandler.deleteNode(session, name)

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

    def addAdmin(self, hardwareProfileName, adminUsername):
        """
        Add an admin to this hardware profile

        Raises:
            AdminAlreadyExists
        """

        session = DbManager().openSession()

        try:
            dbAdmin = self._adminsDbHandler.getAdmin(session, adminUsername)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            if dbAdmin in dbHardwareProfile.admins:
                raise AdminAlreadyExists(
                    'The admin %s is already associated with %s.' %
                    (adminUsername, hardwareProfileName))

            dbHardwareProfile.admins.append(dbAdmin)

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

    def deleteAdmin(self, hardwareProfileName, adminUsername):
        """
        Delete an admin from a hardware profile

        Raises:
            AdminNotFound
        """

        session = DbManager().openSession()

        try:
            dbAdmin = self._adminsDbHandler.getAdmin(session, adminUsername)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            if dbAdmin not in dbHardwareProfile.admins:
                raise AdminNotFound('Admin user [%s] not associated with %s.' %
                                    (adminUsername, hardwareProfileName))

            dbHardwareProfile.admins.remove(dbAdmin)

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

    def updateHardwareProfile(self, hardwareProfileObject):
        """ Update Hardware Profile Object """

        session = DbManager().openSession()

        try:
            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfileById(session,
                                       hardwareProfileObject.getId())

            self.__populateHardwareProfile(session, hardwareProfileObject,
                                           dbHardwareProfile)

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

    def __getInstallerNode(self, session):
        return self._nodesDbHandler.getNode(session,
                                            ConfigManager().getInstaller())

    def __get_provisioning_nics(self, session):
        return self.__getInstallerNode(session).nics

    def __get_all_networks(self, session):
        return self._networksDbHandler.getNetworkList(session)

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

        return session.query(NetworkDevices).all()

    def __populateHardwareProfile(self,
                                  session,
                                  hardwareProfile,
                                  dbHardwareProfile=None):
        """
        Helper function for creating / updating HardwareProfiles. If
        'dbHardwareProfile' is specified, this is an update (vs. add) operation

        Raises:
            NicNotFound
        """

        # Preload provisioning nics and networks
        prov_nics = self.__get_provisioning_nics(session)
        all_networks = self.__get_all_networks(session)

        networkdevices = self.__get_network_devices(session)

        # Validate hw profile
        if hardwareProfile.getName() is None:
            raise ConfigurationError('Hardware profile requires name.')

        if hardwareProfile.getNameFormat() is None:
            raise ConfigurationError(
                'Hardware profile requires name format field.')

        # Handle the special case of a hardware profile not having an
        # associated idle software profile (ie. Installer hardware
        # profile)
        idleSoftwareProfileId = hardwareProfile.getIdleSoftwareProfileId() \
            if hardwareProfile.getIdleSoftwareProfileId else None

        if dbHardwareProfile is None:
            dbHardwareProfile = HardwareProfiles()

        dbHardwareProfile.name = hardwareProfile.getName()
        dbHardwareProfile.description = hardwareProfile.getDescription()
        dbHardwareProfile.nameFormat = hardwareProfile.getNameFormat()

        if hardwareProfile.getInstallType() is None:
            if hardwareProfile.getLocation() == 'remote':
                dbHardwareProfile.installType = 'bootstrap'
            else:
                raise ConfigurationError(
                    'Hardware profile must have valid install type.')
        else:
            dbHardwareProfile.installType = hardwareProfile.\
                getInstallType()

        if hardwareProfile.getLocation() != 'remote':
            dbHardwareProfile.kernel = hardwareProfile.getKernel()
            dbHardwareProfile.kernelParams = hardwareProfile.\
                getKernelParams()
            dbHardwareProfile.initrd = hardwareProfile.getInitrd()
            dbHardwareProfile.localBootParams = hardwareProfile.\
                getLocalBootParams()

        dbHardwareProfile.softwareOverrideAllowed = hardwareProfile.\
            getSoftwareOverrideAllowed()
        dbHardwareProfile.idleSoftwareProfileId = idleSoftwareProfileId
        dbHardwareProfile.location = hardwareProfile.getLocation()

        dbHardwareProfile.hypervisorSoftwareProfileId = hardwareProfile.\
            getHypervisorSoftwareProfileId()
        dbHardwareProfile.maxUnits = hardwareProfile.getMaxUnits()
        dbHardwareProfile.bcastEnabled = hardwareProfile.getBcastEnabled()
        dbHardwareProfile.mcastEnabled = hardwareProfile.getMcastEnabled()
        dbHardwareProfile.cost = hardwareProfile.getCost()

        # Add resource adapter
        resourceAdapter = hardwareProfile.getResourceAdapter()
        if resourceAdapter:
            dbHardwareProfile.resourceAdapter = self.\
                _resourceAdaptersDbHandler.getResourceAdapter(
                    session, resourceAdapter.getName())

            dbHardwareProfile.resourceAdapterId = dbHardwareProfile.\
                resourceAdapter.id

        # Add networks
        networks = []
        for network in hardwareProfile.getNetworks():
            for prov_network in all_networks:
                if prov_network.address == network.getAddress():
                    dbNetwork = prov_network

                    break
            else:
                raise NetworkNotFound('Network [%s] does not exist' %
                                      (network.getAddress()))

            # Lookup network device
            for network_device in networkdevices:
                if network.getNetworkDevice().getName() == network_device.name:
                    dbNetworkDevice = network_device

                    break
            else:
                dbNetworkDevice = NetworkDevices()
                dbNetworkDevice.name = network.getNetworkDevice().getName()

            # Now check if we have this one already...
            for dbHardwareProfileNetwork in dbHardwareProfile.\
                    hardwareprofilenetworks:
                if dbHardwareProfileNetwork.\
                    networkDeviceId == dbNetworkDevice.id and \
                        dbHardwareProfileNetwork.networkId == dbNetwork.id:
                    break
            else:
                dbHardwareProfileNetwork = HardwareProfileNetworks()

                if dbNetwork.id is not None:
                    dbHardwareProfileNetwork.networkId = dbNetwork.id
                else:
                    dbHardwareProfileNetwork.network = dbNetwork

                dbHardwareProfileNetwork.hardwareProfileId = \
                    dbHardwareProfile.id

                if dbNetworkDevice.id is not None:
                    dbHardwareProfileNetwork.networkDeviceId = \
                        dbNetworkDevice.id
                else:
                    dbHardwareProfileNetwork.networkdevice = dbNetworkDevice

                dbHardwareProfile.hardwareprofilenetworks.append(
                    dbHardwareProfileNetwork)

            networks.append(dbHardwareProfileNetwork)

        # Now remove all old networks
        for dbNetwork in dbHardwareProfile.hardwareprofilenetworks:
            for network in networks:
                if network.networkDeviceId == dbNetwork.networkDeviceId \
                        and network.networkId == dbNetwork.networkId:
                    # Its a keeper
                    break
            else:
                # No match...delete time
                session.delete(dbNetwork)

        # Add provisioning Nics
        if hardwareProfile.getProvisioningNics():
            # Only one provisioning nic is possible
            nic = hardwareProfile.getProvisioningNics()[0]

            for prov_nic in prov_nics:
                if nic.getIp() == prov_nic.ip:
                    dbNic = prov_nic

                    break
            else:
                raise NicNotFound('Provisioning NIC with IP [%s] not found' %
                                  (nic.getIp()))

            if dbNic not in dbHardwareProfile.nics:
                dbHardwareProfile.nics.append(dbNic)

        return dbHardwareProfile

    def getHypervisorNodes(self, hardwareProfileName):
        """
        Get list of nodes that belong to hypervisorSoftwareProfileId
        assigned to the given hardware profile name.
        """

        session = DbManager().openSession()

        try:
            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            if not dbHardwareProfile.hypervisor:
                return TortugaObjectList()

            return self.getTortugaObjectList(
                Node, dbHardwareProfile.hypervisor.nodes)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def setProvisioningNic(self, hardwareProfileName, nicId):
        session = DbManager().openSession()

        try:
            dbNic = self._nicsDbHandler.getNicById(session, nicId)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            dbHardwareProfile.nics.append(dbNic)

            session.commit()
        except sqlalchemy.exc.IntegrityError as ex:
            # Entry for this hwProfile/nicId already exists, ignore
            self.getLogger().debug(
                'setProvisioningNic(): entry already exists for'
                ' hwProfile=%s, nicId=%d' % (hardwareProfileName, nicId))
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getProvisioningNicForNetwork(self, network, netmask):
        """
        Raises:
            NicNotFound
        """

        session = DbManager().openSession()

        try:
            installer_node = self.__getInstallerNode(session)

            nics = [
                dbNic for dbNic in installer_node.hardwareprofile.nics
                if dbNic.network.address == network
                and dbNic.network.netmask == netmask
            ]

            if not nics:
                raise NicNotFound(
                    'Unable to find provisioning NIC for network [%s]'
                    ' netmask [%s]' % (network, netmask))

            return tortuga.objects.nic.Nic.getFromDbDict(nics[0].__dict__)
        except TortugaException as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()
예제 #3
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
예제 #4
0
class HardwareProfileDbApi(TagsDbApiMixin, TortugaDbApi):
    """
    HardwareProfile DB API class.

    """
    tag_model = HardwareProfileTag

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

        self._hardwareProfilesDbHandler = HardwareProfilesDbHandler()
        self._nodesDbHandler = NodesDbHandler()
        self._globalParametersDbHandler = GlobalParametersDbHandler()
        self._adminsDbHandler = AdminsDbHandler()
        self._nicsDbHandler = NicsDbHandler()
        self._resourceAdaptersDbHandler = ResourceAdaptersDbHandler()
        self._networkDevicesDbHandler = NetworkDevicesDbHandler()
        self._networksDbHandler = NetworksDbHandler()

    def getHardwareProfile(self, session: Session, name: str,
                           optionDict: Optional[OptionDict] = None) \
            -> HardwareProfile:
        """
        Get hardwareProfile from the db.

            Returns:
                hardwareProfile
            Throws:
                HardwareProfileNotFound
                DbError
        """

        try:
            dbHardwareProfile = \
                self._hardwareProfilesDbHandler.getHardwareProfile(
                    session, name)

            self.loadRelations(
                dbHardwareProfile, get_default_relations(optionDict))

            return HardwareProfile.getFromDbDict(
                dbHardwareProfile.__dict__)
        except TortugaException:
            raise
        except Exception as ex:
            self._logger.exception(str(ex))
            raise

    def getHardwareProfileById(
            self, session: Session, hardwareProfileId: int,
            optionDict: Optional[OptionDict] = None) -> HardwareProfile:
        """
        Get hardwareProfile from the db.

            Returns:
                hardwareProfile
            Throws:
                HardwareProfileNotFound
                DbError
        """

        try:
            dbHardwareProfile = \
                self._hardwareProfilesDbHandler.getHardwareProfileById(
                    session, hardwareProfileId)

            self.loadRelations(
                dbHardwareProfile, get_default_relations(optionDict))

            return HardwareProfile.getFromDbDict(dbHardwareProfile.__dict__)
        except TortugaException:
            raise
        except Exception as ex:
            self._logger.exception(str(ex))
            raise

    def getHardwareProfileList(
            self, session: Session, optionDict: Optional[OptionDict] = None,
            tags: Optional[Tags] = None) -> TortugaObjectList:
        """
        Get list of all available hardwareProfiles from the db.

            Returns:
                [hardwareProfile]
            Throws:
                DbError
        """

        try:
            dbHardwareProfileList = \
                self._hardwareProfilesDbHandler.getHardwareProfileList(
                    session, tags=tags)

            hardwareProfileList = TortugaObjectList()

            for dbHardwareProfile in dbHardwareProfileList:
                options = dict.copy(optionDict or {})
                options['hardwareprofilenetworks'] = True

                self.loadRelations(
                    dbHardwareProfile, get_default_relations(options))

                hardwareProfileList.append(
                    HardwareProfile.getFromDbDict(
                        dbHardwareProfile.__dict__))

            return hardwareProfileList
        except TortugaException:
            raise
        except Exception as ex:
            self._logger.exception(str(ex))
            raise

    def addHardwareProfile(
            self, session: Session,
            hardwareProfile: HardwareProfile) -> None:
        """
        Insert hardwareProfile into the db.

            Returns:
                (none)
            Throws:
                HardwareProfileAlreadyExists
                DbError
        """

        try:
            try:
                self._hardwareProfilesDbHandler.getHardwareProfile(
                    session, hardwareProfile.getName())

                raise HardwareProfileAlreadyExists(
                    'Hardware profile [%s] already exists' % (
                        hardwareProfile))
            except HardwareProfileNotFound as ex:
                pass

            dbHardwareProfile = self.__populateHardwareProfile(
                session, hardwareProfile)

            session.add(dbHardwareProfile)
            session.flush()
            self._set_tags(dbHardwareProfile, hardwareProfile.getTags())
            session.commit()

            self._logger.info(
                'Added hardware profile [%s]' % (dbHardwareProfile.name))
        except TortugaException:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self._logger.exception(str(ex))
            raise

    def deleteHardwareProfile(self, session: Session, name: str) -> None:
        """
        Delete hardwareProfile from the db.

            Returns:
                None
            Throws:
                HardwareProfileNotFound
                DbError
                TortugaException
        """

        try:
            hwProfile = self._hardwareProfilesDbHandler.getHardwareProfile(
                session, name)

            if hwProfile.nodes:
                raise TortugaException(
                    'Unable to delete hardware profile with associated nodes'
                )

            # First delete the mappings
            hwProfile.mappedsoftwareprofiles = []

            self._logger.debug(
                'Marking hardware profile [%s] for deletion' % (name))

            session.delete(hwProfile)

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

    def copyHardwareProfile(self, session: Session,
                            srcHardwareProfileName: str,
                            dstHardwareProfileName: str):
        #
        # Ensure the destination hardware profile doesn't already exist
        #
        try:
            self.getHardwareProfile(session, dstHardwareProfileName)
            raise TortugaException(
                'Destination hardware profile already exists: {}'.format(
                    dstHardwareProfileName)
            )
        except HardwareProfileNotFound:
            pass

        srcHardwareProfile = self.getHardwareProfile(
            session,
            srcHardwareProfileName, {
                'admins': True,
                'hardwareprofilenetworks': True,
                'nics': True,
                'resourceadapter': True,
            })

        dstHardwareProfile = \
            self.getHardwareProfile(session, srcHardwareProfileName)

        dstHardwareProfile.setName(dstHardwareProfileName)

        newDescription = 'Copy of %s' % (
            dstHardwareProfile.getDescription())

        dstHardwareProfile.setDescription(newDescription)

        dstHardwareProfile.setNetworks(srcHardwareProfile.getNetworks())

        dstHardwareProfile.setProvisioningNics(
            srcHardwareProfile.getProvisioningNics())

        dstHardwareProfile.setResourceAdapter(
            srcHardwareProfile.getResourceAdapter())

        self.addHardwareProfile(session, dstHardwareProfile)

    def addAdmin(
            self, session: Session, hardwareProfileName,
            adminUsername: str) -> None:
        """
        Add an admin to this hardware profile

        Raises:
            AdminAlreadyExists
        """

        try:
            dbAdmin = self._adminsDbHandler.getAdmin(
                session, adminUsername)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            if dbAdmin in dbHardwareProfile.admins:
                raise AdminAlreadyExists(
                    'The admin %s is already associated with %s.' % (
                        adminUsername, hardwareProfileName))

            dbHardwareProfile.admins.append(dbAdmin)

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

    def deleteAdmin(
            self, session: Session, hardwareProfileName: str,
            adminUsername: str) -> None:
        """
        Delete an admin from a hardware profile

        Raises:
            AdminNotFound
        """

        try:
            dbAdmin = self._adminsDbHandler.getAdmin(session, adminUsername)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            if dbAdmin not in dbHardwareProfile.admins:
                raise AdminNotFound(
                    'Admin user [%s] not associated with %s.' % (
                        adminUsername, hardwareProfileName))

            dbHardwareProfile.admins.remove(dbAdmin)

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

    def updateHardwareProfile(
            self, session: Session,
            hardwareProfileObject: HardwareProfile) -> None:
        """
        Update Hardware Profile Object
        """

        try:
            dbHardwareProfile = \
                self._hardwareProfilesDbHandler.getHardwareProfileById(
                    session, hardwareProfileObject.getId())

            self.__populateHardwareProfile(
                session, hardwareProfileObject, dbHardwareProfile)
            self._set_tags(dbHardwareProfile, hardwareProfileObject.getTags())
            session.commit()

        except TortugaException:
            session.rollback()
            raise

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

    def __getInstallerNode(self, session: Session) -> Node:
        return self._nodesDbHandler.getNode(
            session, ConfigManager().getInstaller())

    def __get_provisioning_nics(self, session: Session) -> List[Nic]:
        return self.__getInstallerNode(session).nics

    def __get_all_networks(self, session: Session) -> List[Network]:
        return self._networksDbHandler.getNetworkList(session)

    def __populateHardwareProfile(
            self, session: Session, hardwareProfile: HardwareProfile,
            dbHardwareProfile: Optional[HardwareProfileModel] = None) \
            -> HardwareProfileModel:
        """
        Helper function for creating / updating hardware profiles. If
        'dbHardwareProfile' is specified, this is an update (vs. add)
        operation

        Raises:
            NicNotFound
            ResourceAdapterNotFound
            InvalidArgument
            ConfigurationError

        """
        # Preload provisioning nics and networks
        prov_nics = self.__get_provisioning_nics(session)
        all_networks = self.__get_all_networks(session)

        # Validate hw profile
        if hardwareProfile.getName() is None:
            raise ConfigurationError('Hardware profile requires name.')

        if hardwareProfile.getNameFormat() is None:
            raise ConfigurationError(
                'Hardware profile requires name format field.')

        if dbHardwareProfile is None:
            dbHardwareProfile = HardwareProfileModel()

        dbHardwareProfile.name = hardwareProfile.getName()
        dbHardwareProfile.description = hardwareProfile.getDescription()
        dbHardwareProfile.nameFormat = hardwareProfile.getNameFormat()

        if hardwareProfile.getInstallType() is None:
            if hardwareProfile.getLocation() == 'remote':
                dbHardwareProfile.installType = 'bootstrap'
            else:
                dbHardwareProfile.installType = 'package'
        else:
            dbHardwareProfile.installType = hardwareProfile.\
                getInstallType()

        if hardwareProfile.getLocation() != 'remote':
            dbHardwareProfile.kernel = hardwareProfile.getKernel()
            dbHardwareProfile.kernelParams = \
                hardwareProfile.getKernelParams()
            dbHardwareProfile.initrd = hardwareProfile.getInitrd()
            dbHardwareProfile.localBootParams = \
                hardwareProfile.getLocalBootParams()

        dbHardwareProfile.softwareOverrideAllowed = hardwareProfile.\
            getSoftwareOverrideAllowed()

        dbHardwareProfile.location = hardwareProfile.getLocation()

        dbHardwareProfile.cost = hardwareProfile.getCost()

        # Add resource adapter
        resource_adapter_name = \
            hardwareProfile.getResourceAdapter().getName() \
            if hardwareProfile.getResourceAdapter() else 'default'

        dbHardwareProfile.resourceadapter = \
            self._resourceAdaptersDbHandler.getResourceAdapter(
                session, resource_adapter_name)

        if hardwareProfile.getDefaultResourceAdapterConfig():
            adapter_cfg = None

            self._logger.debug(
                'Setting default resource adapter config: {}'.format(
                    hardwareProfile.getDefaultResourceAdapterConfig())
            )

            for adapter_cfg in \
                    dbHardwareProfile.resourceadapter.resource_adapter_config:
                if adapter_cfg.name == \
                        hardwareProfile.getDefaultResourceAdapterConfig():
                    break
            else:
                raise InvalidArgument(
                    'Resource adapter configuration profile [{}] is'
                    ' invalid'.format(
                        hardwareProfile.getDefaultResourceAdapterConfig())
                )

            dbHardwareProfile.default_resource_adapter_config = adapter_cfg
        else:
            dbHardwareProfile.default_resource_adapter_config = None

        # Add networks
        networks = []
        for network in hardwareProfile.getNetworks():
            for prov_network in all_networks:
                if prov_network.address == network.getAddress():
                    dbNetwork = prov_network

                    break
            else:
                raise NetworkNotFound(
                    'Network [%s] does not exist' % (network.getAddress()))

            dbNetworkDevice = \
                self._networkDevicesDbHandler.createNetworkDeviceIfNotExists(
                    session, network.getNetworkDevice().getName())

            # Now check if we have this one already...
            for dbHardwareProfileNetwork in \
                    dbHardwareProfile.hardwareprofilenetworks:
                if dbHardwareProfileNetwork.networkDeviceId == \
                        dbNetworkDevice.id and \
                        dbHardwareProfileNetwork.networkId == dbNetwork.id:
                    break
            else:
                dbHardwareProfileNetwork = HardwareProfileNetwork()
                dbHardwareProfileNetwork.hardwareprofile = dbHardwareProfile

                if dbNetwork.id is not None:
                    dbHardwareProfileNetwork.networkId = dbNetwork.id
                else:
                    dbHardwareProfileNetwork.network = dbNetwork

                dbHardwareProfileNetwork.hardwareProfileId = \
                    dbHardwareProfile.id

                if dbNetworkDevice.id is not None:
                    dbHardwareProfileNetwork.networkDeviceId = \
                        dbNetworkDevice.id
                else:
                    dbHardwareProfileNetwork.networkdevice = dbNetworkDevice

                dbHardwareProfile.hardwareprofilenetworks.append(
                    dbHardwareProfileNetwork)

            networks.append(dbHardwareProfileNetwork)

        # Now remove all old networks
        for dbNetwork in dbHardwareProfile.hardwareprofilenetworks:
            for network in networks:
                if network.networkDeviceId == dbNetwork.networkDeviceId \
                        and network.networkId == dbNetwork.networkId:
                    # Its a keeper
                    break
            else:
                # No match...delete time
                session.delete(dbNetwork)

        # Add provisioning Nics
        if hardwareProfile.getProvisioningNics():
            # Only one provisioning nic is possible
            nic = hardwareProfile.getProvisioningNics()[0]

            for prov_nic in prov_nics:
                if nic.getIp() == prov_nic.ip:
                    dbNic = prov_nic

                    break
            else:
                raise NicNotFound(
                    'Provisioning NIC with IP [%s] not found' % nic.getIp())

            if dbNic not in dbHardwareProfile.nics:
                dbHardwareProfile.nics.append(dbNic)

        return dbHardwareProfile

    def setProvisioningNic(
            self, session: Session, hardwareProfileName: str,
            nicId: int) -> None:
        try:
            dbNic = self._nicsDbHandler.getNicById(session, nicId)

            dbHardwareProfile = self._hardwareProfilesDbHandler.\
                getHardwareProfile(session, hardwareProfileName)

            dbHardwareProfile.nics.append(dbNic)

            session.commit()
        except sqlalchemy.exc.IntegrityError as ex:
            # Entry for this hwProfile/nicId already exists, ignore
            self._logger.debug(
                'setProvisioningNic(): entry already exists for'
                ' hwProfile=%s, nicId=%d' % (hardwareProfileName, nicId))
        except TortugaException:
            raise
        except Exception as ex:
            self._logger.exception(str(ex))
            raise

    def getProvisioningNicForNetwork(
            self, session: Session, network: str, netmask: str) -> Nic:
        """
        Raises:
            NicNotFound
        """

        try:
            installer_node = self.__getInstallerNode(session)

            nics = [
                dbNic for dbNic in installer_node.hardwareprofile.nics
                if dbNic.network.address == network and
                dbNic.network.netmask == netmask]

            if not nics:
                raise NicNotFound(
                    'Unable to find provisioning NIC for network [%s]'
                    ' netmask [%s]' % (network, netmask))

            return tortuga.objects.nic.Nic.getFromDbDict(nics[0].__dict__)
        except TortugaException as ex:
            self._logger.exception(str(ex))
            raise
예제 #5
0
class SoftwareProfileDbApi(TortugaDbApi):
    """
    SoftwareProfile DB API class.
    """
    def __init__(self):
        TortugaDbApi.__init__(self)

        self._softwareProfilesDbHandler = SoftwareProfilesDbHandler()
        self._nodesDbHandler = NodesDbHandler()
        self._globalParametersDbHandler = GlobalParametersDbHandler()
        self._adminsDbHandler = AdminsDbHandler()
        self._osDbHandler = OperatingSystemsDbHandler()

    def getSoftwareProfile(self, name, optionDict=None):
        """
        Get softwareProfile from the db.

            Returns:
               softwareProfile
            Throws:
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, name)

            self.loadRelations(dbSoftwareProfile, optionDict or {})

            self.loadRelations(dbSoftwareProfile, dict(tags=True))

            return SoftwareProfile.getFromDbDict(dbSoftwareProfile.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getSoftwareProfileById(self, softwareProfileId, optionDict=None):
        """
        Get softwareProfile from the db.

            Returns:
               softwareProfile
            Throws:
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfileById(session, softwareProfileId)

            self.loadRelations(dbSoftwareProfile, optionDict or {})

            return SoftwareProfile.getFromDbDict(dbSoftwareProfile.__dict__)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getSoftwareProfileList(self, tags=None):
        """
        Get list of all available softwareProfiles from the db.

            Returns:
                [softwareProfile]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfileList = self._softwareProfilesDbHandler.\
                getSoftwareProfileList(session, tags=tags)

            softwareProfileList = TortugaObjectList()

            for dbSoftwareProfile in dbSoftwareProfileList:
                self.loadRelations(
                    dbSoftwareProfile, {
                        'components': True,
                        'partitions': True,
                        'hardwareprofiles': True,
                        'tags': True,
                    })

                softwareProfileList.append(
                    SoftwareProfile.getFromDbDict(dbSoftwareProfile.__dict__))

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

    def getIdleSoftwareProfileList(self):
        """
        Get list of all available idle softwareProfiles from the db.

            Returns:
                [idle softwareProfile]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfileList = self._softwareProfilesDbHandler.\
                getIdleSoftwareProfileList(session)

            return self.getTortugaObjectList(SoftwareProfile,
                                             dbSoftwareProfileList)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def setIdleState(self, softwareProfileName, state):
        """
        Set idle state of a software profile

            Returns:
                -none-
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, softwareProfileName)

            self.getLogger().debug(
                'Setting idle state [%s] on software profile [%s]' %
                (state, dbSoftwareProfile.name))

            dbSoftwareProfile.isIdle = state

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

    def addSoftwareProfile(self, softwareProfile, session=None):
        """
        Insert software profile into the db.

        Raises:
            SoftwareProfileAlreadyExists
            DbError
        """

        # Keep local 'session' instance.  If 'session' is None,
        # ensure transaction is committed before returning to the
        # caller, otherwise the caller is responsible.  On exceptions,
        # the rollback is performed regardless.
        _session = session

        if _session is None:
            _session = DbManager().openSession()

        try:
            try:
                dbSoftwareProfile = self._softwareProfilesDbHandler.\
                    getSoftwareProfile(
                        _session, softwareProfile.getName())

                raise SoftwareProfileAlreadyExists(
                    'Software profile [%s] already exists' % (softwareProfile))
            except SoftwareProfileNotFound as ex:
                pass

            dbSoftwareProfile = self.__populateSoftwareProfile(
                _session, softwareProfile)

            _session.query(func.max(SoftwareProfiles.id)).one()

            softwareProfile.setId(dbSoftwareProfile.id)

            if session is None:
                _session.commit()

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

            return dbSoftwareProfile
        except TortugaException as ex:
            _session.rollback()
            raise
        except Exception as ex:
            _session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            if session is None:
                DbManager().closeSession()

    def deleteSoftwareProfile(self, name):
        """
        Delete softwareProfile from the db.

            Returns:
                None
            Throws:
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            # This role of this lookup is twofold.  One, it validates
            # the existence of the software profile to be deleted and
            # second, gets the id for looking up any associated nodes
            dbSwProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, name)

            if dbSwProfile.nodes:
                # The software profile cannot be removed while
                # associated
                # nodes exist
                raise TortugaException('Unable to delete software profile with'
                                       ' associated nodes')

            if dbSwProfile.isIdle:
                # Ensure this software profile is not associated with
                # a hardware profile

                if dbSwProfile.hwprofileswithidle:
                    # This software profile is associated with one
                    # or more hardware profiles
                    raise TortugaException(
                        'Unable to delete software profile.'
                        '  This software profile is associated'
                        ' with one or more hardware profiles: [%s]' %
                        (' '.join([
                            hwprofile.name
                            for hwprofile in dbSwProfile.hwprofileswithidle
                        ])))

            # Proceed with software profile deletion
            self.getLogger().debug(
                'Marking software profile [%s] for deletion' % (name))

            session.delete(dbSwProfile)

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

    def getNode(self, name):
        """
        Get node from the db.

            Returns:
                node
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            return self._nodesDbHandler.getNode(session, name)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getAllEnabledComponentList(self):
        """
        Get a list of all enabled components in the system

            Returns:
                [ components ]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            handler = ComponentsDbHandler()

            dbComponents = handler.getEnabledComponentList(session)

            return self.getTortugaObjectList(Component, dbComponents)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getEnabledComponentList(self, name):
        """
        Get a list of enabled components from the db.

            Returns:
                node
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            componentList = TortugaObjectList()

            for c in self._softwareProfilesDbHandler.getSoftwareProfile(
                    session, name).components:
                self.loadRelation(c, 'kit')

                componentList.append(Component.getFromDbDict(c.__dict__))

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

    def getNodeList(self, softwareProfile):
        """
        Get list of nodes in 'softwareProfile'

            Returns:
                [node]
            Throws:
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, softwareProfile)

            nodeList = TortugaObjectList()

            for dbNode in dbSoftwareProfile.nodes:
                self.loadRelation(dbNode, 'hardwareprofile')

                nodeList.append(Node.getFromDbDict(dbNode.__dict__))

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

    def getNodeListByNodeStateAndSoftwareProfileName(self, nodeState,
                                                     softwareProfileName):
        """
        Return a list of Node objects based on the specified node state
        and software profile
        """

        session = DbManager().openSession()

        try:
            dbNodes = self._nodesDbHandler.\
                getNodeListByNodeStateAndSoftwareProfileName(
                    session, nodeState, softwareProfileName)

            return self.getTortugaObjectList(Node, dbNodes)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def deleteNode(self, name):
        """
        Delete node from the db.

            Returns:
                None
            Throws:
                NodeNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._nodesDbHandler.deleteNode(session, name)
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getPackageList(self, softwareProfileName):
        """
        Get a list of packages from the db.

            Returns:
                [package]
            Throws:
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbPackages = self._softwareProfilesDbHandler.\
                getSoftwareProfile(
                    session, softwareProfileName).packages

            return self.getTortugaObjectList(Package, dbPackages)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def getPartitionList(self, softwareProfileName):
        """
        Get a list of software profile partitions from the db.

            Returns:
                [partition]
            Throws:
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, softwareProfileName)
            return self.getTortugaObjectList(Partition,
                                             dbSoftwareProfile.partitions)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def addPackage(self, packageName, softwareProfileName):
        """
        Add software profile package.

            Returns:
                packageId
            Throws:
                PackageAlreadyExists
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._softwareProfilesDbHandler.addPackageToSoftwareProfile(
                session, packageName, softwareProfileName)

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

    def deletePackage(self, packageName, softwareProfileName):
        """
        Delete node from the db.

            Returns:
                None
            Throws:
                PackageNotFound
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._softwareProfilesDbHandler.\
                deletePackageFromSoftwareProfile(
                    session, packageName, softwareProfileName)
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def addPartition(self, partitionName, softwareProfileName):
        """
        Add software profile partition.

            Returns:
                partitionId
            Throws:
                PartitionAlreadyExists
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._softwareProfilesDbHandler.\
                addPartitionToSoftwareProfile(
                    session, partitionName, softwareProfileName)

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

    def deletePartition(self, partitionName, softwareProfileName):
        """
        Delete node from the db.

            Returns:
                None
            Throws:
                PartitionNotFound
                SoftwareProfileNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._softwareProfilesDbHandler.\
                deletePartitionFromSoftwareProfile(
                    session, partitionName, softwareProfileName)
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def addUsableHardwareProfileToSoftwareProfile(self, hardwareProfileName,
                                                  softwareProfileName):
        """
         Add hardwareProfile to softwareProfile

            Returns:
                SoftwareUsesHardwareId
            Throws:
                HardwareProfileNotFound
                SoftwareProfileNotFound
                SoftwareUsesHardwareAlreadyExists
                DbError
        """

        session = DbManager().openSession()

        try:
            swUsesHwId = self._softwareProfilesDbHandler.\
                addUsableHardwareProfileToSoftwareProfile(
                    session, hardwareProfileName, softwareProfileName)
            session.commit()
            return swUsesHwId
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def deleteUsableHardwareProfileFromSoftwareProfile(self,
                                                       hardwareProfileName,
                                                       softwareProfileName):
        """
        Delete hardwareProfile from softwareProfile

            Returns:
                None
            Throws:
                HardwareProfileNotFound
                SoftwareProfileNotFound
                SoftwareUsesHardwareNotFound
                DbError
        """

        session = DbManager().openSession()

        try:
            self._softwareProfilesDbHandler.\
                deleteUsableHardwareProfileFromSoftwareProfile(
                    session, hardwareProfileName, softwareProfileName)

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

    def addAdmin(self, softwareProfileName, adminUsername):
        """ Add an admin to this software profile """
        session = DbManager().openSession()

        try:
            dbAdmin = self._adminsDbHandler.getAdmin(session, adminUsername)

            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, softwareProfileName)

            if dbAdmin not in dbSoftwareProfile.admins:
                dbSoftwareProfile.admins.append(dbAdmin)
            else:
                raise AdminAlreadyExists(
                    'Admin [%s] already associated with %s' %
                    (adminUsername, softwareProfileName))
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def deleteAdmin(self, softwareProfileName, adminUsername):
        """ Delete an admin from a software profile """

        session = DbManager().openSession()
        try:
            dbAdmin = self._adminsDbHandler.getAdmin(session, adminUsername)

            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, softwareProfileName)

            if dbAdmin in dbSoftwareProfile.admins:
                dbSoftwareProfile.admins.remove(dbAdmin)
            else:
                raise AdminNotFound(
                    'Admin [%s] not associated with software profile'
                    ' [%s]' % (adminUsername, softwareProfileName))

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

    def updateSoftwareProfile(self, softwareProfileObject):
        """ Update a software profile """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfileById(
                    session, softwareProfileObject.getId())
            self.__populateSoftwareProfile(session, softwareProfileObject,
                                           dbSoftwareProfile)
            session.commit()
        except TortugaException as ex:
            session.rollback()
            raise
        except Exception as ex:
            session.rollback()
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()

    def __populateSoftwareProfile(self,
                                  session,
                                  softwareProfile,
                                  dbSoftwareProfile=None):
        """
        Helper function for creating/updating dbSoftwareProfile Object
        """

        # Validate object
        if softwareProfile.getName() is None:
            raise UpdateSoftwareProfileFailed('Software profile name required')

        if softwareProfile.getType() is None:
            raise UpdateSoftwareProfileFailed(
                'Software profile must have valid type')

        osInfo = softwareProfile.getOsInfo()
        if osInfo is None or osInfo.getName() is None:
            raise UpdateSoftwareProfileFailed(
                'Software profile must have valid operating system')

        if dbSoftwareProfile is None:
            dbSoftwareProfile = SoftwareProfiles()

        dbOs = self._osDbHandler.addOsIfNotFound(session, osInfo)

        dbSoftwareProfile.name = softwareProfile.getName()
        dbSoftwareProfile.description = softwareProfile.getDescription()
        dbSoftwareProfile.kernel = softwareProfile.getKernel()
        dbSoftwareProfile.kernelParams = softwareProfile.getKernelParams()
        dbSoftwareProfile.initrd = softwareProfile.getInitrd()
        dbSoftwareProfile.os = dbOs
        dbSoftwareProfile.type = softwareProfile.getType()
        dbSoftwareProfile.minNodes = softwareProfile.getMinNodes()
        dbSoftwareProfile.isIdle = softwareProfile.getIsIdle()

        # Add packages
        packages = {}
        for package in softwareProfile.getPackages():
            # This is a new package
            dbPackage = Packages()

            dbPackage.name = package.getName()
            if packages.get(dbPackage.name) is not None:
                # Duplicate package ...error
                raise UpdateSoftwareProfileFailed(
                    'Duplicate package [%s] found' % (dbPackage.name))

            packages[dbPackage.name] = dbPackage

        # Add partitions
        partitions = {}
        for partition in softwareProfile.getPartitions():
            # This is a new partition
            dbPartition = Partitions()

            dbPartition.name = partition.getName()
            dbPartition.device = partition.getDevice()
            dbPartition.mountPoint = partition.getMountPoint()
            dbPartition.fsType = partition.getFsType()
            dbPartition.size = partition.getSize()
            dbPartition.options = partition.getOptions()
            dbPartition.preserve = partition.getPreserve()
            dbPartition.bootLoader = partition.getBootLoader()
            dbPartition.diskSize = partition.getDiskSize()
            dbPartition.directAttachment = partition.getDirectAttachment()
            dbPartition.indirectAttachment = partition.\
                getIndirectAttachment()
            dbPartition.sanVolume = partition.getSanVolume()

            if not dbPartition.name:
                raise InvalidPartitionScheme(
                    'Invalid partition in software profile:'
                    ' missing or empty name')

            if not dbPartition.device:
                raise InvalidPartitionScheme(
                    'Invalid partition in software profile:'
                    ' missing or empty device')

            if not dbPartition.fsType:
                raise InvalidPartitionScheme(
                    'Invalid partition [%s/%s] in software profile:'
                    ' missing or empty fsType' %
                    (dbPartition.name, dbPartition.device))

            if dbPartition.size is None:
                raise InvalidPartitionScheme(
                    'Invalid partition [%s/%s] in software profile:'
                    ' missing size' % (dbPartition.name, dbPartition.device))

            if partitions.get(
                (dbPartition.name, dbPartition.device)) is not None:
                # Duplicate partition ...error
                raise UpdateSoftwareProfileFailed(
                    'Duplicate partition [%s/%s] found' %
                    (dbPartition.name, dbPartition.device))

            try:
                int(dbPartition.size)
            except ValueError:
                raise InvalidPartitionScheme(
                    'Invalid partition [%s/%s] in software profile:'
                    ' non-integer size' %
                    (dbPartition.name, dbPartition.device))

            try:
                if dbPartition.diskSize is not None:
                    int(dbPartition.diskSize)
            except ValueError:
                raise InvalidPartitionScheme(
                    'Invalid partition [%s/%s] in software profile:'
                    ' non-integer disk size' %
                    (dbPartition.name, dbPartition.device))

            bGrow = partition.getGrow()
            if bGrow is not None:
                dbPartition.grow = bGrow

            maxSize = partition.getMaxSize()
            if maxSize is not None:
                dbPartition.maxSize = maxSize

            partitions[(dbPartition.name, dbPartition.device)] = \
                dbPartition

        # Delete out the old ones
        dbSoftwareProfile.partitions = []
        dbSoftwareProfile.packages = []

        session.flush()

        dbSoftwareProfile.partitions = list(partitions.values())
        dbSoftwareProfile.packages = list(packages.values())

        session.add(dbSoftwareProfile)

        session.flush()

        return dbSoftwareProfile

    def copySoftwareProfile(self, srcSoftwareProfileName,
                            dstSoftwareProfileName):
        session = DbManager().openSession()

        srcSoftwareProfile = self.getSoftwareProfile(srcSoftwareProfileName, {
            'partitions': True,
            'packages': True,
            'components': True,
        })

        dstSoftwareProfile = self.getSoftwareProfile(srcSoftwareProfileName)
        dstSoftwareProfile.setName(dstSoftwareProfileName)
        newDescription = 'Copy of %s' % (dstSoftwareProfile.getDescription())
        dstSoftwareProfile.setDescription(newDescription)

        # partitions
        dstSoftwareProfile.setPartitions(srcSoftwareProfile.getPartitions())

        # packages
        dstSoftwareProfile.setPackages(srcSoftwareProfile.getPackages())

        # Finally add the software profile
        dstSoftwareProfile = self.addSoftwareProfile(dstSoftwareProfile,
                                                     session)

        # Enable components separately
        srcCompList = self.getEnabledComponentList(srcSoftwareProfileName)

        for srcComp in srcCompList:
            if srcComp.getKit().getIsOs() or srcComp.getName() == 'core':
                self._softwareProfilesDbHandler.\
                    addComponentToSoftwareProfileEx(
                        session, srcComp.getId(), dstSoftwareProfile)

        session.commit()

    def getUsableNodes(self, name):
        """
        Return list of nodes with same software/hardware profile mapping
        as the specified software profile.
        """

        session = DbManager().openSession()

        try:
            dbSoftwareProfile = self._softwareProfilesDbHandler.\
                getSoftwareProfile(session, name)

            nodes = [
                dbNode
                for dbHardwareProfile in dbSoftwareProfile.hardwareprofiles
                for dbNode in dbHardwareProfile.nodes
            ]

            return self.getTortugaObjectList(Node, nodes)
        except TortugaException as ex:
            raise
        except Exception as ex:
            self.getLogger().exception('%s' % ex)
            raise
        finally:
            DbManager().closeSession()
예제 #6
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