def __init__(self): super(NodeManager, self).__init__() self._nodeDbApi = NodeDbApi() self._hardwareProfileDbApi = HardwareProfileDbApi() self._cm = ConfigManager() self._san = san.San()
def __init__(self): super(AddHostManager, self).__init__() # Now do the class specific variable initialization self._addHostLock = threading.RLock() self._nodeDbApi = NodeDbApi() self._sessions = ObjectStoreManager.get(namespace='add-host-manager')
def __init__(self): super(AddHostManager, self).__init__() # Now do the class specific variable initialization self._addHostLock = threading.RLock() self._sessions = {} self._nodeDbApi = NodeDbApi()
def __init__(self): super(NodeManager, self).__init__() self._nodeDbApi = NodeDbApi() self._hardwareProfileDbApi = HardwareProfileDbApi() self._cm = ConfigManager() self._san = san.San() self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager()
def __init__(self): super(SoftwareProfileManager, self).__init__() self._sp_db_api = SoftwareProfileDbApi() self._node_db_api = NodeDbApi() self._component_db_api = ComponentDbApi() self._global_param_db_api = GlobalParameterDbApi() self._kit_db_api = KitDbApi() self._config_manager = ConfigManager()
def __init__(self): super(HardwareProfileManager, self).__init__() self._hpDbApi = HardwareProfileDbApi() self._spDbApi = SoftwareProfileDbApi() self._networkDbApi = NetworkDbApi() self._globalParameterDbApi = GlobalParameterDbApi() self._nodeDbApi = NodeDbApi()
def __init__(self): super(AddHostManager, self).__init__() # Now do the class specific variable initialization self._addHostLock = threading.RLock() self._logger = logging.getLogger(ADD_HOST_NAMESPACE) self._nodeDbApi = NodeDbApi() self._sessions = ObjectStoreManager.get(namespace='add-host-manager', expire=86400)
def test_getNode_with_options(dbm): options = dict(hardwareprofile=True) with dbm.session() as session: result = NodeDbApi().getNode(session, 'compute-01.private', options) assert isinstance(result, Node) assert result.getHardwareProfile().getName()
def __init__(self): super(SoftwareProfileManager, self).__init__() self._sp_db_api = SoftwareProfileDbApi() self._node_db_api = NodeDbApi() self._component_db_api = ComponentDbApi() self._global_param_db_api = GlobalParameterDbApi() self._kit_db_api = KitDbApi() self._config_manager = ConfigManager() self._logger = logging.getLogger(SOFTWARE_PROFILE_NAMESPACE)
def __init__(self): super(HardwareProfileManager, self).__init__() self._hpDbApi = HardwareProfileDbApi() self._spDbApi = SoftwareProfileDbApi() self._networkDbApi = NetworkDbApi() self._globalParameterDbApi = GlobalParameterDbApi() self._nodeDbApi = NodeDbApi() self._logger = logging.getLogger(HARDWARE_PROFILE_NAMESPACE)
def __init__(self): super(NodeManager, self).__init__() self._nodeDbApi = NodeDbApi() self._cm = ConfigManager() self._bhm = osUtility.getOsObjectFactory().getOsBootHostManager( self._cm) self._nodesDbHandler = NodesDbHandler() self._addHostManager = AddHostManager() self._logger = logging.getLogger(NODE_NAMESPACE)
def post_add_host(self, hardware_profile_name, software_profile_name, add_host_session, *args, **kwargs): """ Post add host processing on the installer node. Arguments: hardwareProfileName the name of the hardware profile that hosts are being added to softwareProfileName the name of the software profile that hosts are being added to newNodeIdList list of new node id's just added. """ logger.debug('post_add_host: {}, {}, {}, {}, {}'.format( hardware_profile_name, software_profile_name, add_host_session, args, kwargs)) component_installers = self._get_enabled_component_installers( self._get_all_component_installers()) # # This needs to be here because of circular imports :( # from tortuga.db.nodeDbApi import NodeDbApi nodes = NodeDbApi().getNodesByAddHostSession(add_host_session) self._run_action_with_node_list(component_installers, hardware_profile_name, software_profile_name, nodes, 'add_host', *args, **kwargs)
def test_getNodeList(dbm): with dbm.session() as session: result = NodeDbApi().getNodeList(session) assert isinstance(result, TortugaObjectList) assert isinstance(result[0], Node)
def test_getNodesByNameFilter(dbm): with dbm.session() as session: result = NodeDbApi().getNodesByNameFilter(session, 'compute-*') assert isinstance(result, TortugaObjectList) assert result[0].getName().startswith('compute-')
class HardwareProfileManager(TortugaObjectManager, Singleton): def __init__(self): super(HardwareProfileManager, self).__init__() self._hpDbApi = HardwareProfileDbApi() self._spDbApi = SoftwareProfileDbApi() self._networkDbApi = NetworkDbApi() self._globalParameterDbApi = GlobalParameterDbApi() self._nodeDbApi = NodeDbApi() def getHardwareProfileList(self, optionDict=None, tags=None): """ Return all of the hardwareprofiles with referenced components in this hardwareprofile """ return self._hpDbApi.getHardwareProfileList(optionDict=optionDict, tags=tags) def setIdleSoftwareProfile(self, hardwareProfileName, softwareProfileName=None): """Set idle software profile""" return self._hpDbApi.setIdleSoftwareProfile(hardwareProfileName, softwareProfileName) def getHardwareProfile(self, name, optionDict=None): return self._hpDbApi.getHardwareProfile(name, optionDict or {}) def getHardwareProfileById(self, id_, optionDict=None): return self._hpDbApi.getHardwareProfileById(id_, optionDict or {}) def addAdmin(self, hardwareProfileName, adminUsername): """ Add an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound HardwareProfileNotFound """ return self._hpDbApi.addAdmin(hardwareProfileName, adminUsername) def deleteAdmin(self, hardwareProfileName, adminUsername): """ Remove an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound HardwareProfileNotFound """ return self._hpDbApi.deleteAdmin(hardwareProfileName, adminUsername) def updateHardwareProfile(self, hardwareProfileObject): """ Update a hardware profile in the database that matches the passed in hardware profile object. The ID is used as the primary matching criteria. Returns: None Throws: TortugaException HardwareProfileNotFound InvalidArgument """ self.getLogger().debug('Updating hardware profile [%s]' % (hardwareProfileObject.getName())) try: # First get the object from the db we are updating... existingProfile = self.\ getHardwareProfileById(hardwareProfileObject.getId()) if hardwareProfileObject.getInstallType() and \ hardwareProfileObject.getInstallType() != \ existingProfile.getInstallType(): raise InvalidArgument( 'Hardware profile installation type cannot be' ' changed' % (hardwareProfileObject.getName())) self._hpDbApi.updateHardwareProfile(hardwareProfileObject) except TortugaException as ex: raise except Exception as ex: self.getLogger().exception('%s' % ex) raise TortugaException(exception=ex) def createHardwareProfile(self, hwProfileSpec, settingsDict=None): settingsDict = settingsDict or {} bUseDefaults = settingsDict['bUseDefaults'] \ if 'bUseDefaults' in settingsDict else False osInfo = settingsDict['osInfo'] \ if settingsDict and 'osInfo' in settingsDict else None validation.validateProfileName(hwProfileSpec.getName()) if hwProfileSpec.getDescription() is None or \ hwProfileSpec.getDescription() == '**DEFAULT**': hwProfileSpec.setDescription('%s Nodes' % (hwProfileSpec.getName())) installerNode = self._nodeDbApi.getNode(ConfigManager().getInstaller(), {'softwareprofile': True}) if bUseDefaults: if not hwProfileSpec.getNetworks(): # No <network>...</network> entries found in the template, # use the default provisioning interface from the primary # installer. # Find first provisioning network and use it for nic in installerNode.getNics(): network = nic.getNetwork() if network.getType() == 'provision': # for now set the default interface to be index 0 # with the same device networkDevice = fixNetworkDeviceName( nic.getNetworkDevice().getName()) network.setNetworkDevice( NetworkDevice(name=networkDevice)) hwProfileSpec.getNetworks().append(network) break else: raise NetworkNotFound( 'Unable to find provisioning network') else: # Ensure network device is defined installerNic = None for network in hwProfileSpec.getNetworks(): for installerNic in installerNode.getNics(): installerNetwork = installerNic.getNetwork() if network.getId() and \ network.getId() == installerNetwork.getId(): break elif network.getAddress() and \ network.getAddress() == \ installerNetwork.getAddress() and \ network.getNetmask() and \ network.getNetmask() == \ installerNetwork.getNetmask(): break else: # Unable to find network matching specification in # template. raise NetworkNotFound( 'Unable to find provisioning network [%s]' % (network)) networkDevice = fixNetworkDeviceName( installerNic.getNetworkDevice().getName()) network.setNetworkDevice(NetworkDevice(name=networkDevice)) if hwProfileSpec.getIdleSoftwareProfile(): # <idleSoftwareProfileId>...</idleSoftwareProfileId> is always # contained within the output of get-hardwareprofile. If the # command-line option '--idleSoftwareProfile' is specified, it # overrides the # <idleSoftwareProfileId>...</idleSoftwareProfileId> element idleSoftwareProfile = self._spDbApi.getSoftwareProfile( hwProfileSpec.getIdleSoftwareProfile().getName()) hwProfileSpec.setIdleSoftwareProfileId(idleSoftwareProfile.getId()) if not osInfo: osInfo = installerNode.getSoftwareProfile().getOsInfo() osObjFactory = osUtility.getOsObjectFactory(osInfo.getName()) if not hwProfileSpec.getKernel(): hwProfileSpec.setKernel( osObjFactory.getOsSysManager().getKernel(osInfo)) if not hwProfileSpec.getInitrd(): hwProfileSpec.setInitrd( osObjFactory.getOsSysManager().getInitrd(osInfo)) self._hpDbApi.addHardwareProfile(hwProfileSpec) # Iterate over all networks in the newly defined hardware profile # and build assocations to provisioning NICs if bUseDefaults: for network in \ [network for network in hwProfileSpec.getNetworks() if network.getType() == 'provision']: # Get provisioning nic for network try: provisioningNic = self.getProvisioningNicForNetwork( network.getAddress(), network.getNetmask()) except NicNotFound: # There is currently no provisioning NIC defined for # this network. This is not a fatal error. continue self.setProvisioningNic(hwProfileSpec.getName(), provisioningNic.getId()) def deleteHardwareProfile(self, name): """Delete hardwareprofile by name.""" self._hpDbApi.deleteHardwareProfile(name) self.getLogger().info('Deleted hardware profile [%s]' % (name)) def updateSoftwareOverrideAllowed(self, hardwareProfileName, flag): self._hpDbApi.updateSoftwareOverrideAllowed(hardwareProfileName, flag) def getHypervisorNodes(self, hardwareProfileName): return self._hpDbApi.getHypervisorNodes(hardwareProfileName) def setProvisioningNic(self, hardwareProfileName, nicId): return self._hpDbApi.setProvisioningNic(hardwareProfileName, nicId) def getProvisioningNicForNetwork(self, network, netmask): return self._hpDbApi.getProvisioningNicForNetwork(network, netmask) def copyHardwareProfile(self, srcHardwareProfileName, dstHardwareProfileName): validation.validateProfileName(dstHardwareProfileName) self.getLogger().info('Copying hardware profile [%s] to [%s]' % (srcHardwareProfileName, dstHardwareProfileName)) return self._hpDbApi.copyHardwareProfile(srcHardwareProfileName, dstHardwareProfileName) def getNodeList(self, hardwareProfileName): return self._hpDbApi.getNodeList(hardwareProfileName)
class NodeManager(TortugaObjectManager): \ # pylint: disable=too-many-public-methods def __init__(self): super(NodeManager, self).__init__() self._nodeDbApi = NodeDbApi() self._hardwareProfileDbApi = HardwareProfileDbApi() self._cm = ConfigManager() self._san = san.San() def __validateHostName(self, hostname: str, name_format: str) -> NoReturn: """ Raises: ConfigurationError """ bWildcardNameFormat = (name_format == '*') if hostname and not bWildcardNameFormat: # Host name specified, but hardware profile does not # allow setting the host name raise ConfigurationError( 'Hardware profile does not allow setting host names' ' of imported nodes') elif not hostname and bWildcardNameFormat: # Host name not specified but hardware profile expects it raise ConfigurationError( 'Hardware profile requires host names to be set') def createNewNode(self, session: Session, addNodeRequest: dict, dbHardwareProfile: HardwareProfiles, dbSoftwareProfile: Optional[SoftwareProfiles] = None, validateIp: bool = True, bGenerateIp: bool = True, dns_zone: Optional[str] = None) -> Nodes: """ Convert the addNodeRequest into a Nodes object Raises: NicNotFound """ self.getLogger().debug( 'createNewNode(): session=[%s], addNodeRequest=[%s],' ' dbHardwareProfile=[%s], dbSoftwareProfile=[%s],' ' validateIp=[%s], bGenerateIp=[%s]' % (id(session), addNodeRequest, dbHardwareProfile.name, dbSoftwareProfile.name if dbSoftwareProfile else '(none)', validateIp, bGenerateIp)) # This is where the Nodes() object is first created. node = Nodes() # Set the default node state node.state = 'Discovered' if 'rack' in addNodeRequest: node.rack = addNodeRequest['rack'] node.addHostSession = addNodeRequest['addHostSession'] hostname = addNodeRequest['name'] \ if 'name' in addNodeRequest else None # Ensure no conflicting options (ie. specifying host name for # hardware profile in which host names are generated) self.__validateHostName(hostname, dbHardwareProfile.nameFormat) node.name = hostname # Complete initialization of new node record nic_defs = addNodeRequest['nics'] \ if 'nics' in addNodeRequest else [] AddHostServerLocal().initializeNode(session, node, dbHardwareProfile, dbSoftwareProfile, nic_defs, bValidateIp=validateIp, bGenerateIp=bGenerateIp, dns_zone=dns_zone) # Set hardware profile of new node node.hardwareProfileId = dbHardwareProfile.id # Set software profile of new node; if the software profile is None, # attempt to set the software profile to the idle software profile # of the associated hardware profile. This may also be None, in # which case the software profile is undefined. node.softwareprofile = dbSoftwareProfile \ if dbSoftwareProfile else dbHardwareProfile.idlesoftwareprofile node.isIdle = dbSoftwareProfile.isIdle \ if dbSoftwareProfile else True # Return the new node return node def getNode(self, name, optionDict=None): """Get node by name""" optionDict_ = optionDict.copy() if optionDict else {} optionDict_.update({'hardwareprofile': True}) node = self._nodeDbApi.getNode(name, optionDict_) hwprofile = self._hardwareProfileDbApi.getHardwareProfile( node.getHardwareProfile().getName(), {'resourceadapter': True}) adapter_name = hwprofile.getResourceAdapter().getName() \ if hwprofile.getResourceAdapter() else 'default' # Query vcpus from resource adapter ResourceAdapterClass = resourceAdapterFactory.getResourceAdapterClass( adapter_name) # Update Node object node.setVcpus(ResourceAdapterClass().get_node_vcpus(node.getName())) return node def getNodeById(self, nodeId, optionDict=None): """ Get node by node id Raises: NodeNotFound """ return self._nodeDbApi.getNodeById(int(nodeId), optionDict) def getNodeByIp(self, ip): """ Get node by IP address Raises: NodeNotFound """ return self._nodeDbApi.getNodeByIp(ip) def getNodeList(self, tags=None): """Return all nodes""" return self._nodeDbApi.getNodeList(tags=tags) def updateNode(self, nodeName, updateNodeRequest): self.getLogger().debug('updateNode(): name=[{0}]'.format(nodeName)) session = DbManager().openSession() try: node = NodesDbHandler().getNode(session, nodeName) if 'nics' in updateNodeRequest: nic = updateNodeRequest['nics'][0] if 'ip' in nic: node.nics[0].ip = nic['ip'] node.nics[0].boot = True # Call resource adapter NodesDbHandler().updateNode(session, node, updateNodeRequest) run_post_install = False if 'state' in updateNodeRequest: run_post_install = node.state == 'Allocated' and \ updateNodeRequest['state'] == 'Provisioned' node.state = updateNodeRequest['state'] session.commit() if run_post_install: self.getLogger().debug( 'updateNode(): run-post-install for node [{0}]'.format( node.name)) self.__scheduleUpdate() except Exception: session.rollback() self.getLogger().exception( 'Exception updating node [{0}]'.format(nodeName)) finally: DbManager().closeSession() def updateNodeStatus(self, nodeName, state=None, bootFrom=None): """Update node status If neither 'state' nor 'bootFrom' are not None, this operation will update only the 'lastUpdated' timestamp. Returns: bool indicating whether state and/or bootFrom differed from current value """ value = 'None' if bootFrom is None else \ '1 (disk)' if int(bootFrom) == 1 else '0 (network)' self.getLogger().debug( 'updateNodeStatus(): node=[%s], state=[%s], bootFrom=[%s]' % (nodeName, state, value)) session = DbManager().openSession() try: node = NodesDbHandler().getNode(session, nodeName) result = self._updateNodeStatus(node, state=state, bootFrom=bootFrom) session.commit() return result finally: DbManager().closeSession() def _updateNodeStatus(self, dbNode, state=None, bootFrom=None): """ Internal method which takes a 'Nodes' object instead of a node name. """ result = NodesDbHandler().updateNodeStatus(dbNode, state, bootFrom) # Only change local boot configuration if the hardware profile is # not marked as 'remote' and we're not acting on the installer node. if dbNode.softwareprofile and \ dbNode.softwareprofile.type != 'installer' and \ dbNode.hardwareprofile.location not in \ ('remote', 'remote-vpn'): osUtility.getOsObjectFactory().getOsBootHostManager().\ writePXEFile(dbNode, localboot=bootFrom) return result def __process_nodeErrorDict(self, nodeErrorDict): result = {} nodes_deleted = [] for key, nodeList in nodeErrorDict.items(): result[key] = [dbNode.name for dbNode in nodeList] if key == 'NodesDeleted': for node in nodeList: node_deleted = { 'name': node.name, 'hardwareprofile': node.hardwareprofile.name, 'addHostSession': node.addHostSession, } if node.softwareprofile: node_deleted['softwareprofile'] = \ node.softwareprofile.name nodes_deleted.append(node_deleted) return result, nodes_deleted def deleteNode(self, nodespec): """ Delete node by nodespec Raises: NodeNotFound """ installer_hostname = socket.getfqdn().split('.', 1)[0] session = DbManager().openSession() try: nodes = [] for node in self.__expand_nodespec(session, nodespec): if node.name.split('.', 1)[0] == installer_hostname: self.getLogger().info( 'Ignoring request to delete installer node' ' ([{0}])'.format(node.name)) continue nodes.append(node) if not nodes: raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec)) self.__preDeleteHost(nodes) nodeErrorDict = NodesDbHandler().deleteNode(session, nodes) # REALLY!?!? Convert a list of Nodes objects into a list of # node names so we can report the list back to the end-user. # This needs to be FIXED! result, nodes_deleted = self.__process_nodeErrorDict(nodeErrorDict) session.commit() # ============================================================ # Perform actions *after* node deletion(s) have been committed # to database. # ============================================================ self.__postDeleteHost(nodes_deleted) addHostSessions = set( [tmpnode['addHostSession'] for tmpnode in nodes_deleted]) if addHostSessions: AddHostManager().delete_sessions(addHostSessions) bhm = osUtility.getOsObjectFactory().getOsBootHostManager() for nodeName in result['NodesDeleted']: # Remove the Puppet cert bhm.deletePuppetNodeCert(nodeName) bhm.nodeCleanup(nodeName) self.getLogger().info('Node [%s] deleted' % (nodeName)) # Schedule a cluster update self.__scheduleUpdate() return result except TortugaException: session.rollback() raise except Exception: session.rollback() self.getLogger().exception('Exception in NodeManager.deleteNode()') raise finally: DbManager().closeSession() def __process_delete_node_result(self, nodeErrorDict): # REALLY!?!? Convert a list of Nodes objects into a list of # node names so we can report the list back to the end-user. # This needs to be FIXED! result = {} nodes_deleted = [] for key, nodeList in nodeErrorDict.items(): result[key] = [dbNode.name for dbNode in nodeList] if key == 'NodesDeleted': for node in nodeList: node_deleted = { 'name': node.name, 'hardwareprofile': node.hardwareprofile.name, } if node.softwareprofile: node_deleted['softwareprofile'] = \ node.softwareprofile.name nodes_deleted.append(node_deleted) return result, nodes_deleted def __preDeleteHost(self, nodes): self.getLogger().debug('__preDeleteHost(): nodes=[%s]' % (' '.join([node.name for node in nodes]))) if not nodes: self.getLogger().debug('No nodes deleted in this operation') return kitmgr = KitActionsManager() for node in nodes: kitmgr.pre_delete_host( node.hardwareprofile.name, node.softwareprofile.name if node.softwareprofile else None, nodes=[node.name]) def __postDeleteHost(self, nodes_deleted): # 'nodes_deleted' is a list of dicts of the following format: # # { # 'name': 'compute-01', # 'softwareprofile': 'Compute', # 'hardwareprofile': 'LocalIron', # } # # if the node does not have an associated software profile, the # dict does not contain the key 'softwareprofile'. self.getLogger().debug('__postDeleteHost(): nodes_deleted=[%s]' % (nodes_deleted)) if not nodes_deleted: self.getLogger().debug('No nodes deleted in this operation') return kitmgr = KitActionsManager() for node_dict in nodes_deleted: kitmgr.post_delete_host(node_dict['hardwareprofile'], node_dict['softwareprofile'] if 'softwareprofile' in node_dict else None, nodes=[node_dict['name']]) def __scheduleUpdate(self): tortugaSubprocess.executeCommand( os.path.join(self._cm.getRoot(), 'bin/schedule-update')) def getInstallerNode(self, optionDict=None): return self._nodeDbApi.getNode(self._cm.getInstaller(), optionDict=optionDict) def getProvisioningInfo(self, nodeName): return self._nodeDbApi.getProvisioningInfo(nodeName) def getKickstartFile(self, node, hardwareprofile, softwareprofile): """ Generate kickstart file for specified node Raises: OsNotSupported """ osFamilyName = softwareprofile.os.family.name try: osSupportModule = __import__('tortuga.os.%s.osSupport' % (osFamilyName), fromlist=['OSSupport']) except ImportError: raise OsNotSupported('Operating system family [%s] not supported' % (osFamilyName)) OSSupport = osSupportModule.OSSupport tmpOsFamilyInfo = OsFamilyInfo(softwareprofile.os.family.name, softwareprofile.os.family.version, softwareprofile.os.family.arch) return OSSupport(tmpOsFamilyInfo).getKickstartFileContents( node, hardwareprofile, softwareprofile) def __transferNodeCommon(self, session, dbDstSoftwareProfile, results): \ # pylint: disable=no-self-use # Aggregate list of transferred nodes based on hardware profile # to call resource adapter minimal number of times. hwProfileMap = {} for transferResultDict in results: dbNode = transferResultDict['node'] dbHardwareProfile = dbNode.hardwareprofile if dbHardwareProfile not in hwProfileMap: hwProfileMap[dbHardwareProfile] = [transferResultDict] else: hwProfileMap[dbHardwareProfile].append(transferResultDict) session.commit() nodeTransferDict = {} # Kill two birds with one stone... do the resource adapter # action as well as populate the nodeTransferDict. This saves # having to iterate twice on the same result data. for dbHardwareProfile, nodesDict in hwProfileMap.items(): adapter = resourceAdapterFactory.getApi( dbHardwareProfile.resourceadapter.name) dbNodeTuples = [] for nodeDict in nodesDict: dbNode = nodeDict['node'] dbSrcSoftwareProfile = nodeDict['prev_softwareprofile'] if dbSrcSoftwareProfile.name not in nodeTransferDict: nodeTransferDict[dbSrcSoftwareProfile.name] = { 'added': [], 'removed': [dbNode], } else: nodeTransferDict[dbSrcSoftwareProfile.name]['removed'].\ append(dbNode) if dbDstSoftwareProfile.name not in nodeTransferDict: nodeTransferDict[dbDstSoftwareProfile.name] = { 'added': [dbNode], 'removed': [], } else: nodeTransferDict[dbDstSoftwareProfile.name]['added'].\ append(dbNode) # The destination software profile is available through # node relationship. dbNodeTuples.append((dbNode, dbSrcSoftwareProfile)) adapter.transferNode(dbNodeTuples, dbDstSoftwareProfile) session.commit() # Now call the 'refresh' action to all participatory components KitActionsManager().refresh(nodeTransferDict) return results def transferNode(self, nodespec, dstSoftwareProfileName, bForce=False): """ Transfer nodes defined by 'nodespec' to 'dstSoftwareProfile' Raises: NodeNotFound SoftwareProfileNotFound NodeTransferNotValid """ session = DbManager().openSession() try: nodes = self.__expand_nodespec(session, nodespec) if not nodes: raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec)) dbDstSoftwareProfile = SoftwareProfilesDbHandler().\ getSoftwareProfile(session, dstSoftwareProfileName) results = NodesDbHandler().transferNode(session, nodes, dbDstSoftwareProfile, bForce=bForce) return self.__transferNodeCommon(session, dbDstSoftwareProfile, results) finally: DbManager().closeSession() def transferNodes(self, srcSoftwareProfileName, dstSoftwareProfileName, count, bForce=False): """ Transfer 'count' nodes from 'srcSoftwareProfile' to 'dstSoftwareProfile' Raises: SoftwareProfileNotFound """ session = DbManager().openSession() try: # It is not necessary to specify a source software profile. If # not specified, pick any eligible nodes in the hardware profile # mapped to the destination software profile. Don't ask me who # uses this capability, but it's here if you need it... dbSrcSoftwareProfile = SoftwareProfilesDbHandler().\ getSoftwareProfile( session, srcSoftwareProfileName) \ if srcSoftwareProfileName else None dbDstSoftwareProfile = SoftwareProfilesDbHandler().\ getSoftwareProfile(session, dstSoftwareProfileName) results = NodesDbHandler().transferNodes(session, dbSrcSoftwareProfile, dbDstSoftwareProfile, int(float(count)), bForce=bForce) return self.__transferNodeCommon(session, dbDstSoftwareProfile, results) finally: DbManager().closeSession() def idleNode(self, nodespec): """ Raises: NodeNotFound """ session = DbManager().openSession() try: nodes = self.__expand_nodespec(session, nodespec) if not nodes: raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec)) result = NodesDbHandler().idleNode(session, nodes) # Convert list of Nodes to list of node names for providing # user feedback. result_dict = {} for key, dbNodes in result.items(): result_dict[key] = [dbNode.name for dbNode in dbNodes] session.commit() # Remove Puppet certificate(s) for idled node(s) for node_name in result_dict['success']: # Remove Puppet certificate for idled node bhm = osUtility.getOsObjectFactory().getOsBootHostManager() bhm.deletePuppetNodeCert(node_name) # Schedule a cluster update self.__scheduleUpdate() return result_dict except TortugaException as ex: session.rollback() raise except Exception as ex: session.rollback() self.getLogger().exception('[%s] %s' % (self.__class__.__name__, ex)) raise finally: DbManager().closeSession() def __process_activateNode_results(self, tmp_results, dstswprofilename): results = {} for key, values in tmp_results.items(): # With the exception of the "ProfileMappingNotAllowed" dict # item, all items in the dict are lists of nodes. if key != 'ProfileMappingNotAllowed': results[key] = [dbNode.name for dbNode in values] else: results[key] = \ [(value[0].name, value[1], value[2]) for value in values] if tmp_results['success']: # Iterate over activated nodes, creating dict keyed on # 'addHostSession' addHostSessions = {} for node in tmp_results['success']: if node.addHostSession not in addHostSessions: addHostSessions[node.addHostSession] = [] addHostSessions[node.addHostSession] = \ node.hardwareprofile.name # For each 'addHostSession', call postAddHost() for addHostSession, hwprofile in addHostSessions.items(): AddHostManager().postAddHost(hwprofile, dstswprofilename, addHostSession) return results def activateNode(self, nodespec, softwareProfileName): """ Raises: SoftwareProfileNotFound NodeNotFound TortugaException """ session = DbManager().openSession() try: dbSoftwareProfile = SoftwareProfilesDbHandler().\ getSoftwareProfile(session, softwareProfileName) \ if softwareProfileName else None dbNodes = self.__expand_nodespec(session, nodespec) if not dbNodes: raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec)) tmp_results = NodesDbHandler().activateNode( session, dbNodes, dbSoftwareProfile) results = self.__process_activateNode_results( tmp_results, softwareProfileName) session.commit() # Schedule a cluster update self.__scheduleUpdate() return results except TortugaException as ex: session.rollback() raise except Exception as ex: session.rollback() self.getLogger().exception('%s' % ex) raise finally: DbManager().closeSession() def startupNode(self, nodespec, remainingNodeList=None, bootMethod='n'): """ Raises: NodeNotFound """ return self._nodeDbApi.startupNode(nodespec, remainingNodeList=remainingNodeList or [], bootMethod=bootMethod) def shutdownNode(self, nodespec, bSoftShutdown=False): """ Raises: NodeNotFound """ return self._nodeDbApi.shutdownNode(nodespec, bSoftShutdown) def build_node_filterspec(self, nodespec): filter_spec = [] for nodespec_token in nodespec.split(','): # Convert shell-style wildcards into SQL wildcards if '*' in nodespec_token or '?' in nodespec_token: filter_spec.append( nodespec_token.replace('*', '%').replace('?', '_')) continue if '.' not in nodespec_token: filter_spec.append(nodespec_token) filter_spec.append(nodespec_token + '.%') continue # Add nodespec "AS IS" filter_spec.append(nodespec_token) return filter_spec def __expand_nodespec(self, session, nodespec): \ # pylint: disable=no-self-use # Expand wildcards in nodespec. Each token in the nodespec can # be wildcard that expands into one or more nodes. return NodesDbHandler().getNodesByNameFilter( session, self.build_node_filterspec(nodespec)) def rebootNode(self, nodespec, bSoftReset=False, bReinstall=False): """ Raises: NodeNotFound """ session = DbManager().openSession() try: nodes = self.__expand_nodespec(session, nodespec) if not nodes: raise NodeNotFound('No nodes matching nodespec [%s]' % (nodespec)) bhm = osUtility.getOsObjectFactory().getOsBootHostManager() if bReinstall: for dbNode in nodes: bhm.setNodeForNetworkBoot(dbNode) results = NodesDbHandler().rebootNode(session, nodes, bSoftReset) session.commit() return results finally: DbManager().closeSession() def checkpointNode(self, nodeName): return self._nodeDbApi.checkpointNode(nodeName) def revertNodeToCheckpoint(self, nodeName): return self._nodeDbApi.revertNodeToCheckpoint(nodeName) def migrateNode(self, nodeName, remainingNodeList, liveMigrate): return self._nodeDbApi.migrateNode(nodeName, remainingNodeList, liveMigrate) def evacuateChildren(self, nodeName): self._nodeDbApi.evacuateChildren(nodeName) def getChildrenList(self, nodeName): return self._nodeDbApi.getChildrenList(nodeName) def setParentNode(self, nodeName, parentNodeName): self._nodeDbApi.setParentNode(nodeName, parentNodeName) def addStorageVolume(self, nodeName, volume, isDirect="DEFAULT"): """ Raises: VolumeDoesNotExist UnsupportedOperation """ node = self.getNode(nodeName, {'hardwareprofile': True}) # Only allow persistent volumes to be attached... vol = self._san.getVolume(volume) if vol is None: raise VolumeDoesNotExist('Volume [%s] does not exist' % (volume)) if not vol.getPersistent(): raise UnsupportedOperation( 'Only persistent volumes can be attached') api = resourceAdapterFactory.getApi( node.getHardwareProfile().getResourceAdapter().getName()) if isDirect == "DEFAULT": return api.addVolumeToNode(node, volume) return api.addVolumeToNode(node, volume, isDirect) def removeStorageVolume(self, nodeName, volume): """ Raises: VolumeDoesNotExist UnsupportedOperation """ node = self.getNode(nodeName, {'hardwareprofile': True}) api = resourceAdapterFactory.getApi( node.getHardwareProfile().getResourceAdapter().getName()) vol = self._san.getVolume(volume) if vol is None: raise VolumeDoesNotExist('The volume [%s] does not exist' % (volume)) if not vol.getPersistent(): raise UnsupportedOperation( 'Only persistent volumes can be detached') return api.removeVolumeFromNode(node, volume) def getStorageVolumes(self, nodeName): return self._san.getNodeVolumes(self.getNode(nodeName).getName()) def getNodesByNodeState(self, state): return self._nodeDbApi.getNodesByNodeState(state) def getNodesByNameFilter(self, _filter): return self._nodeDbApi.getNodesByNameFilter(_filter)
class SoftwareProfileManager(TortugaObjectManager, Singleton): BASE_KIT_NAME = 'base' def __init__(self): super(SoftwareProfileManager, self).__init__() self._sp_db_api = SoftwareProfileDbApi() self._node_db_api = NodeDbApi() self._component_db_api = ComponentDbApi() self._global_param_db_api = GlobalParameterDbApi() self._kit_db_api = KitDbApi() self._config_manager = ConfigManager() def getSoftwareProfileList(self, tags=None): """Return all of the softwareprofiles with referenced components in this softwareprofile """ return self._sp_db_api.getSoftwareProfileList(tags=tags) def getIdleSoftwareProfileList(self): """ Return all of the idle softwareprofiles """ return self._sp_db_api.getIdleSoftwareProfileList() def setIdleState(self, softwareProfileName, state): """ Sets the idle state of a softwareprofile """ return self._sp_db_api.setIdleState(softwareProfileName, state) def addAdmin(self, softwareProfileName, adminUsername): """ Add an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound SoftwareProfileNotFound """ return self._sp_db_api.addAdmin(softwareProfileName, adminUsername) def deleteAdmin(self, softwareProfileName, adminUsername): """ Remove an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound SoftwareProfileNotFound """ return self._sp_db_api.deleteAdmin(softwareProfileName, adminUsername) def updateSoftwareProfile(self, softwareProfileObject): try: self.getLogger().debug('Updating software profile: %s' % (softwareProfileObject.getName())) # First get the object from the db we are updating... existingProfile = self.\ getSoftwareProfileById(softwareProfileObject.getId()) # Set parameters that we will not allow updating softwareProfileObject.setOsInfo(existingProfile.getOsInfo()) softwareProfileObject.setOsId(existingProfile.getOsId()) softwareProfileObject.setIsIdle(existingProfile.getIsIdle()) softwareProfileObject.setType(existingProfile.getType()) self._sp_db_api.updateSoftwareProfile(softwareProfileObject) except TortugaException as ex: raise except Exception as ex: self.getLogger().exception('%s' % ex) raise TortugaException(exception=ex) def getSoftwareProfile(self, name, optionDict=None): return self._sp_db_api.getSoftwareProfile(name, optionDict or {}) def getSoftwareProfileById(self, id_, optionDict=None): return self._sp_db_api.getSoftwareProfileById(id_, optionDict=optionDict or {}) def _getCoreComponentForOsInfo(self, osInfo): # Find core component # Find the version of the 'core' component import tortuga.kit.kitApi _kitApi = tortuga.kit.kitApi.KitApi() baseKit = None for baseKit in _kitApi.getKitList(): if not baseKit.getName() == self.BASE_KIT_NAME: continue break else: raise KitNotFound('Kit [%s] not found.' % (self.BASE_KIT_NAME)) baseComp = None for baseComp in baseKit.getComponentList(): if baseComp.getName() != 'core': continue break else: raise ComponentNotFound('Component [%s] not found in kit [%s]' % ('core', baseKit.getName())) comp = osUtility.getOsObjectFactory().getComponentManager().\ getBestMatchComponent( baseComp.getName(), baseComp.getVersion(), osInfo, baseKit.getId()) comp.setKit(baseKit) return comp def _getOsInfo(self, bOsMediaRequired): if not bOsMediaRequired: # As a placeholder, use the same OS as the installer # Find installer node entry node = self._node_db_api.getNode(ConfigManager().getInstaller(), {'softwareprofile': True}) return node.getSoftwareProfile().getOsInfo() # Use available operating system kit; raise exception if # multiple available os_kits = _get_os_kits() if not os_kits: raise KitNotFound('No operating system kit installed') if len(os_kits) > 1: raise KitNotFound( 'Multiple OS kits defined; use --os option to specify' ' operating system') kit = kitApiFactory.getKitApi().getKit(os_kits[0].getName(), os_kits[0].getVersion(), '0') components = kit.getComponentList() if not components: raise ComponentNotFound('Malformed operating system kit [%s]' % (os_kits)) osinfo_list = components[0].getOsInfoList() if len(osinfo_list) > 1: raise ComponentNotFound( 'Multiple operating system components for kit [%s];' ' use --os argument to specify operating system' % (os_kits[0])) return osinfo_list[0] def createSoftwareProfile(self, swProfileSpec, settingsDict=None): """ Exceptions: ConfigurationError NetworkNotFound ComponentNotFound KitNotFound OSError """ # Parse 'settingsDict' if settingsDict: # ... bOsMediaRequired; default is True bOsMediaRequired = settingsDict['bOsMediaRequired'] \ if 'bOsMediaRequired' in settingsDict else True # ... unmanagedProfile; default is False unmanagedProfile = settingsDict['unmanagedProfile'] \ if 'unmanagedProfile' in settingsDict else False # Validate software profile name validation.validateProfileName(swProfileSpec.getName()) # Insert default description for software profile if not swProfileSpec.getDescription() or \ swProfileSpec.getDescription() == '**DEFAULT**': swProfileSpec.setDescription('%s Nodes' % (swProfileSpec.getName())) self.getLogger().debug('Creating software profile [%s]' % (swProfileSpec)) osInfo = swProfileSpec.getOsInfo() \ if swProfileSpec.getOsInfo() else self._getOsInfo(bOsMediaRequired) # If we're creating an unmanaged software profile (no # DHCP/PXE/kickstart/OS) just create it now and we're done if unmanagedProfile: self._sp_db_api.addSoftwareProfile(swProfileSpec) else: if bOsMediaRequired and swProfileSpec.getOsInfo(): try: kitApiFactory.getKitApi().getKit( swProfileSpec.getOsInfo().getName(), swProfileSpec.getOsInfo().getVersion(), '0') except KitNotFound: self._logger.error('OS kit for [%s] not found' % (swProfileSpec.getOsInfo())) raise else: swProfileSpec.setOsInfo(osInfo) # Get component manager for appropriate OS family osConfig = osHelper.getOsInfo(osInfo.getName(), osInfo.getVersion(), osInfo.getArch()) osObjFactory = osUtility.getOsObjectFactory( osConfig.getOsFamilyInfo().getName()) compManager = osObjFactory.getComponentManager() # Need to be fancy with components spComponents = swProfileSpec.getComponents() swProfileSpec.setComponents(TortugaObjectList()) bFoundOsComponent = False bFoundCoreComponent = False components = [] # Iterate over components, adding them to the software profile for c in spComponents: cobj = compManager.getBestMatchComponent( c.getName(), c.getVersion(), osInfo, c.getKit().getId()) k = cobj.getKit() if k.getIsOs(): # This component is a member of the OS kit, set the flag bFoundOsComponent = True else: if c.getName() == 'core': # Found the 'core' component, set the flag bFoundCoreComponent = True components.append(cobj) # If the operating system is undefined for this software # profile, use the same OS as the installer. if bOsMediaRequired and not bFoundOsComponent: # Find OS component osCompName = '%s-%s-%s' % ( osInfo.getName(), osInfo.getVersion(), osInfo.getArch()) self.getLogger().debug('Automatically adding OS component [%s]' ' (not specified in template)' % (osCompName)) try: osComponent = self._component_db_api.getComponent( osCompName, osInfo.getVersion(), osInfo, {'kit': True}) components.append(osComponent) except ComponentNotFound: # Cannot find OS component, don't freak out pass # Ensure 'core' component is enabled if not bFoundCoreComponent: # Attempt to automatically add the core component, only # if one exists for this OS try: comp = self._getCoreComponentForOsInfo(osInfo) self.getLogger().debug( 'Automatically adding [core] component' ' (not specified in template)') components.append(comp) except ComponentNotFound: pass # Initialize values for kernel, kernelParams, and initrd if not swProfileSpec.getKernel(): swProfileSpec.setKernel( osObjFactory.getOsSysManager().getKernel(osInfo)) if not swProfileSpec.getInitrd(): swProfileSpec.setInitrd( osObjFactory.getOsSysManager().getInitrd(osInfo)) # Add the software profile self._sp_db_api.addSoftwareProfile(swProfileSpec) # Enable components in one fell swoop for comp in components: self.getLogger().debug('Enabling component [%s]' % (comp.getName())) if comp.getKit().getIsOs(): # Don't use enableComponent() on OS kit self._component_db_api.\ addComponentToSoftwareProfile( comp.getId(), swProfileSpec.getId()) continue self.enableComponent(swProfileSpec.getName(), comp.getKit().getName(), comp.getKit().getVersion(), comp.getKit().getIteration(), comp.getName(), comp.getVersion()) self.getLogger().debug( 'Software profile [%s] created successfully' % (swProfileSpec.getName())) def _getComponent(self, kit, compName, compVersion): \ # pylint: disable=no-self-use # Iterate over component list, looking for a match comp = None for comp in kit.getComponentList(): if comp.getName() == compName and \ comp.getVersion() == compVersion: break else: raise ComponentNotFound("Component [%s-%s] not found in kit [%s]" % (compName, compVersion, kit)) return comp def _get_kit_by_component(self, comp_name, comp_version=None): """ Gets a kit by compoent name/version. :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises ComponentNotFound: """ kit_list = self._kit_db_api.getKitList() kits = [ kit for kit in kit_list for component in kit.getComponentList() if component.getName() == comp_name and ( comp_version is None or component.getVersion() == comp_version) ] if not kits: raise KitNotFound('Kit containing component [%s] not found' % (comp_name)) if len(kits) > 1: raise ComponentNotFound( 'Kit name must be specified, multiple kits contain ' 'component: {}'.format(comp_name)) return kits[0] def enableComponent(self, software_profile_name, kit_name, kit_version, kit_iteration, comp_name, comp_version=None): """ Enable a component on a software profile. :param software_profile_name: the name of the software profile :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises SoftwareProfileNotFound: :raises ComponentNotFound: """ kit, comp_version = self._get_kit_and_component_version( kit_name, kit_version, kit_iteration, comp_name, comp_version) software_profile = self.getSoftwareProfile(software_profile_name, {'os': True}) if kit.getIsOs(): best_match_component = self._enable_os_kit_component( kit, comp_name, comp_version, software_profile) else: best_match_component = self._enable_kit_component( kit, comp_name, comp_version, software_profile) if not best_match_component: self.getLogger().info( 'Component not enabled: {}'.format(comp_name)) else: self.getLogger().info( 'Enabled component on software profile: {} -> {}'.format( best_match_component, software_profile)) def _get_kit_and_component_version(self, kit_name, kit_version, kit_iteration, comp_name, comp_version=None): """ Gets a Kit instance and component version. :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the component name :param comp_version: the component version (optional) :return: a tuple, consisting of (Kit, component_version) """ kit = None if kit_name is None: kit = self._get_kit_by_component(comp_name, comp_version=comp_version) # # Get component version if required # if comp_version is None: for component in kit.getComponentList(): if component.getName() == comp_name: comp_version = component.getVersion() break elif kit_version is None or kit_iteration is None: kits_found = 0 for k in self._kit_db_api.getKitList(): if k.getName() == kit_name and \ (kit_version is None or k.getVersion() == kit_version) and \ (kit_iteration is None or k.getIteration() == kit_iteration): kit = k kits_found += 1 if kits_found > 1: if kit_version is not None: raise KitNotFound('Multiple kits found: {}-{}'.format( kit_name, kit_version)) else: raise KitNotFound( 'Multiple kits found {}'.format(kit_name)) else: kit = self._kit_db_api.getKit(kit_name, kit_version, kit_iteration) return kit, comp_version def _enable_kit_component(self, kit, comp_name, comp_version, software_profile): """ Enables a regular kit component on a specific software profile. :param kit: the Kit instance, whose component is being enabled :param comp_name: the name of the component to enable :param comp_version: the version of the component to enable :param software_profile: the software profile on which the component will be enabled :return: the Component instance that was enabled """ kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration()) load_kits() installer = get_kit_installer(kit_spec)() comp_installer = installer.get_component_installer(comp_name) if not comp_installer.is_enableable(software_profile): self.getLogger().warning('Component cannot be enabled: {}'.format( comp_installer.spec)) return None comp_installer.run_action('pre_enable', software_profile.getName()) best_match_component = self._add_component_to_software_profile( kit, comp_name, comp_version, software_profile) comp_installer.run_action('enable', software_profile.getName()) comp_installer.run_action('post_enable', software_profile.getName()) return best_match_component def _enable_os_kit_component(self, kit, comp_name, comp_version, software_profile): """ Enables an OS kit component on a specific software profile. :param kit: the OS Kit instance, whose component is being enabled :param comp_name: the name of the component to enable :param comp_version: the version of the component to enable :param software_profile: the software profile on which the component will be enabled :return: the Component instance that was enabled """ return self._add_component_to_software_profile(kit, comp_name, comp_version, software_profile) def _add_component_to_software_profile(self, kit, comp_name, comp_version, software_profile): """ Adds a kit to a software profile. This is a data-only operation, as no pre/post enable actions are called. :param kit: the OS Kit instance, whose component is being added :param comp_name: the name of the component to add :param comp_version: the version of the component to add :param software_profile: the software profile to which the component will be added :return: the Component instance that was added """ os_obj_factory = osUtility.getOsObjectFactory( software_profile.getOsInfo().getOsFamilyInfo().getName()) comp_manager = os_obj_factory.getComponentManager() best_match_component = comp_manager.getBestMatchComponent( comp_name, comp_version, software_profile.getOsInfo(), kit.getId()) self._component_db_api.addComponentToSoftwareProfile( best_match_component.getId(), software_profile.getId()) return best_match_component def disableComponent(self, software_profile_name, kit_name, kit_version, kit_iteration, comp_name, comp_version=None): """ Disables a component on a software profile. :param software_profile_name: the name of the software profile :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises SoftwareProfileNotFound: :raises ComponentNotFound: """ kit, comp_version = self._get_kit_and_component_version( kit_name, kit_version, kit_iteration, comp_name) software_profile = self.getSoftwareProfile(software_profile_name, {'os': True}) if kit.getIsOs(): best_match_component = self._disable_os_kit_component( kit, comp_name, comp_version, software_profile) else: best_match_component = self._disable_kit_component( kit, comp_name, comp_version, software_profile) self.getLogger().info( 'Disabled component on software profile: {} -> {}'.format( best_match_component, software_profile)) def _disable_kit_component(self, kit, comp_name, comp_version, software_profile): """ Disables a regular kit component on a specific software profile. :param kit: the Kit instance, whose component is being disabled :param comp_name: the name of the component to disable :param comp_version: the version of the component to disable :param software_profile: the software profile on which the component will be disable :return: the Component instance that was disabled """ kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration()) load_kits() installer = get_kit_installer(kit_spec)() comp_installer = installer.get_component_installer(comp_name) comp_installer.run_action('pre_disable', software_profile.getName()) comp_installer.run_action('disable', software_profile.getName()) best_match_component = \ self._remove_component_from_software_profile( kit, comp_name, comp_version, software_profile) comp_installer.run_action('post_disable', software_profile.getName()) return best_match_component def _disable_os_kit_component(self, kit, comp_name, comp_version, software_profile): """ Enables an OS kit component on a specific software profile. :param kit: the OS Kit instance, whose component is being disabled :param comp_name: the name of the component to disable :param comp_version: the version of the component to disable :param software_profile: the software profile on which the component will be disabled :return: the Component instance that was disabled """ return self._remove_component_from_software_profile( kit, comp_name, comp_version, software_profile) def _remove_component_from_software_profile(self, kit, comp_name, comp_version, software_profile): """ Removes a kit to a software profile. This is a data-only operation, as no pre/post disable actions are called. :param kit: the OS Kit instance, whose component is being removed :param comp_name: the name of the component to remove :param comp_version: the version of the component to remove :param software_profile: the software profile to which the component will be removed :return: the Component instance that was removed """ os_obj_factory = osUtility.getOsObjectFactory( software_profile.getOsInfo().getOsFamilyInfo().getName()) comp_manager = os_obj_factory.getComponentManager() best_match_component = comp_manager.getBestMatchComponent( comp_name, comp_version, software_profile.getOsInfo(), kit.getId()) self._component_db_api.deleteComponentFromSoftwareProfile( best_match_component.getId(), software_profile.getId()) return best_match_component def deleteSoftwareProfile(self, name): """ Delete software profile by name Raises: SoftwareProfileNotFound """ self._sp_db_api.deleteSoftwareProfile(name) # Remove all flags for software profile swProfileFlagPath = os.path.join(self._config_manager.getRoot(), 'var/run/actions/%s' % (name)) if os.path.exists(swProfileFlagPath): shutil.rmtree(swProfileFlagPath) self.getLogger().info('Deleted software profile [%s]' % (name)) def getNodeList(self, softwareProfileName): return self._sp_db_api.getNodeList(softwareProfileName) def getEnabledComponentList(self, name): """ Get the list of enabled components """ return self._sp_db_api.getEnabledComponentList(name) def getPackageList(self, softwareProfileName): """ Get list of packages. """ return self._sp_db_api.getPackageList(softwareProfileName) def getPartitionList(self, softwareProfileName): """ Get list of partitions. """ return self._sp_db_api.getPartitionList(softwareProfileName) def getProvisioningInfo(self, nodeName): return self._sp_db_api.getProvisioningInfo(nodeName) def addUsableHardwareProfileToSoftwareProfile(self, hardwareProfileName, softwareProfileName): self._logger.info( 'Mapping hardware profile [%s] to software profile [%s]' % (hardwareProfileName, softwareProfileName)) return self._sp_db_api.\ addUsableHardwareProfileToSoftwareProfile(hardwareProfileName, softwareProfileName) def deleteUsableHardwareProfileFromSoftwareProfile(self, hardwareProfileName, softwareProfileName): return self._sp_db_api.\ deleteUsableHardwareProfileFromSoftwareProfile( hardwareProfileName, softwareProfileName) def copySoftwareProfile(self, srcSoftwareProfileName, dstSoftwareProfileName): # Validate software profile name validation.validateProfileName(dstSoftwareProfileName) self._logger.info('Copying software profile [%s] to [%s]' % (srcSoftwareProfileName, dstSoftwareProfileName)) softwareProfile = self._sp_db_api.\ copySoftwareProfile(srcSoftwareProfileName, dstSoftwareProfileName) return softwareProfile def getUsableNodes(self, softwareProfileName): return self._sp_db_api.getUsableNodes(softwareProfileName)
def test_getNodeByIp(dbm): with dbm.session() as session: with pytest.raises(NodeNotFound): result = NodeDbApi().getNodeByIp(session, '127.0.0.1')
def test_getNodeById(dbm): with dbm.session() as session: result = NodeDbApi().getNodeById(session, 1) assert isinstance(result, Node)
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
class AddHostManager(TortugaObjectManager, Singleton): def __init__(self): super(AddHostManager, self).__init__() # Now do the class specific variable initialization self._addHostLock = threading.RLock() self._sessions = {} self._nodeDbApi = NodeDbApi() def addHosts(self, session, addHostSession, addHostRequest): """ Raises: HardwareProfileNotFound """ self.getLogger().debug('addHosts()') softwareProfilesDbHandler = SoftwareProfilesDbHandler() hardwareProfilesDbHandler = HardwareProfilesDbHandler() dbHardwareProfile = hardwareProfilesDbHandler.getHardwareProfile( session, addHostRequest['hardwareProfile']) if not dbHardwareProfile.resourceadapter: errmsg = ('Resource adapter not defined for hardware' ' profile [%s]' % (dbHardwareProfile.name)) self.getLogger().error(errmsg) raise ResourceAdapterNotFound(errmsg) softwareProfileName = addHostRequest['softwareProfile'] \ if 'softwareProfile' in addHostRequest else None dbSoftwareProfile = softwareProfilesDbHandler.\ getSoftwareProfile(session, softwareProfileName) \ if softwareProfileName else None # Look up and/or create tags as necessary tags = get_tags(session, addHostRequest['tags']) \ if 'tags' in addHostRequest else [] ResourceAdapterClass = resourceAdapterFactory.getResourceAdapterClass( dbHardwareProfile.resourceadapter.name) resourceAdapter = ResourceAdapterClass(addHostSession=addHostSession) # Call the start() method of the resource adapter newNodes = resourceAdapter.start(addHostRequest, session, dbHardwareProfile, dbSoftwareProfile=dbSoftwareProfile) # Apply tags to new nodes for node in newNodes: node.tags = tags # Commit new node(s) to database session.commit() # Only perform post-add operations if we actually added a node if newNodes: if dbSoftwareProfile and not dbSoftwareProfile.isIdle: self.getLogger().info( 'Node(s) added to software profile [%s] and' ' hardware profile [%s]' % (dbSoftwareProfile.name if dbSoftwareProfile else 'None', dbHardwareProfile.name)) newNodeNames = [tmpNode.name for tmpNode in newNodes] resourceAdapter.hookAction('add', newNodeNames) self.postAddHost(dbHardwareProfile.name, softwareProfileName, addHostSession) resourceAdapter.hookAction('start', newNodeNames) self.getLogger().debug('Add host workflow complete') def postAddHost(self, hardwareProfileName, softwareProfileName, addHostSession): """Perform post add host operations""" self.getLogger().debug( 'postAddHost(): hardwareProfileName=[%s]' ' softwareProfileName=[%s] addHostSession=[%s]' % (hardwareProfileName, softwareProfileName, addHostSession)) mgr = KitActionsManager() mgr.post_add_host(hardwareProfileName, softwareProfileName, addHostSession) # Always go over the web service for this call. SyncWsApi().scheduleClusterUpdate(updateReason='Node(s) added') def updateStatus(self, addHostSession, msg): self._addHostLock.acquire() try: if addHostSession not in self._sessions: self.getLogger().warn( 'updateStatus(): unknown session ID [%s]' % (addHostSession)) return addHostStatus = self._sessions[addHostSession]['status'] addHostStatus.getMessageList().append(msg) finally: self._addHostLock.release() def getStatus(self, session: str, startMessage: int, getNodes: bool) -> AddHostStatus: """ Raises: NotFound """ with self._addHostLock: nodeList = self._nodeDbApi.getNodesByAddHostSession(session) \ if getNodes else TortugaObjectList() # Lock and copy for data consistency if session not in self._sessions: raise NotFound('Invalid add host session ID [%s]' % (session)) sessionDict = self._sessions.get(session) statusCopy = AddHostStatus() # Copy simple data for key in sessionDict['status'].getKeys(): statusCopy.set(key, sessionDict['status'].get(key)) # Get slice of status messages messages = sessionDict['status'].getMessageList()[startMessage:] statusCopy.setMessageList(messages) if nodeList: statusCopy.getNodeList().extend(nodeList) return statusCopy def createNewSession(self) -> str: self.getLogger().debug('createNewSession()') with self._addHostLock: # Create new add nodes session session_id = str(uuid.uuid4()) self._sessions[session_id] = { 'status': AddHostStatus(), } return session_id def delete_session(self, session_id): """TODO: currently a no-op""" def delete_sessions(self, session_ids): """Bulk session deletion Currently only called when deleting nodes """ self.getLogger().debug('delete_sessions()') with self._addHostLock: for session_id in session_ids: if session_id in self._sessions: self.getLogger().debug( 'Deleting session [{0}]'.format(session_id)) del self._sessions[session_id] def update_session(self, session_id, running=None): self.getLogger().debug( 'Updating add host session [%s] (status: running=%s)' % (session_id, str(running))) with self._addHostLock: self._sessions[session_id]['status'].setIsRunning(running)
class UctagCli(TortugaCli): def __init__(self, *args, **kwargs): self._hwp_api = HardwareProfileDbApi() self._node_api = NodeDbApi() self._swp_api = SoftwareProfileDbApi() super().__init__(*args, **kwargs) def parseArgs(self, usage: Optional[str] = None): subparsers = self.getParser().add_subparsers(help='sub-command help', dest='subparser_name') add_subparser = subparsers.add_parser('add') add_subparser.add_argument('--node', dest='nodespec') add_subparser.add_argument('--software-profile', metavar='NAME') add_subparser.add_argument('--hardware-profile', metavar='NAME') add_subparser.add_argument('--tags', action='append', dest='tags', metavar='key=value[,key=value]') add_subparser.set_defaults(func=self.add_tag) remove_subparser = subparsers.add_parser('remove') remove_subparser.add_argument('--node', dest='nodespec') remove_subparser.add_argument('--software-profile', metavar='NAME') remove_subparser.add_argument('--hardware-profile', metavar='NAME') remove_subparser.add_argument('--tags', action='append', dest='tags', metavar='key[,key]') remove_subparser.set_defaults(func=self.remove_tag) list_subparser = subparsers.add_parser('list') list_subparser.add_argument('--all-resources', action='store_true') list_subparser.add_argument('--nodes', action='store_true') list_subparser.add_argument('--software-profiles', action='store_true') list_subparser.add_argument('--hardware-profiles', action='store_true') list_subparser.set_defaults(func=self.list_tag) return super().parseArgs(usage=usage) def runCommand(self): args = self.parseArgs() with DbManager().session() as session: args.func(session, args) def add_tag(self, session: Session, args): if not args.nodespec and not args.software_profile and \ not args.hardware_profile: sys.stderr.write('Error: must specify --nodes' '/--software-profile/--hardware-profile\n') sys.stderr.flush() sys.exit(1) tags = parse_tags(args.tags) if args.nodespec: nodes = self._node_api.getNodesByNameFilter(session, args.nodespec) for node in nodes: node_tags = node.getTags() node_tags.update(tags) self._node_api.set_tags(session, node_id=node.getId(), tags=node_tags) print(node.getName(), node.getTags()) if args.software_profile: for name in args.software_profile.split(','): swp = self._swp_api.getSoftwareProfile(session, name) swp_tags = swp.getTags() swp_tags.update(tags) swp.setTags(swp_tags) self._swp_api.updateSoftwareProfile(session, swp) if args.hardware_profile: for name in args.hardware_profile.split(','): hwp = self._hwp_api.getHardwareProfile(session, name) hwp_tags = hwp.getTags() hwp_tags.update(tags) hwp.setTags(hwp_tags) self._hwp_api.updateHardwareProfile(session, hwp) session.commit() def remove_tag(self, session: Session, args): if not args.nodespec and not args.software_profile and \ not args.hardware_profile: sys.stderr.write('Error: must specify --nodes' '/--software-profile/--hardware-profile\n') sys.stderr.flush() sys.exit(1) tag_keys = [] for tag_string in args.tags: tag_keys.extend(tag_string.split(',')) if args.nodespec: nodes = self._node_api.getNodesByNameFilter(session, args.nodespec) for node in nodes: node_tags = node.getTags() for key in tag_keys: if key in node_tags.keys(): node_tags.pop(key) self._node_api.set_tags(session, node_id=node.getId(), tags=node_tags) print(node.getName(), node.getTags()) if args.software_profile: for name in args.software_profile.split(','): swp = self._swp_api.getSoftwareProfile(session, name) swp_tags = swp.getTags() for key in tag_keys: if key in swp_tags.keys(): swp_tags.pop(key) swp.setTags(swp_tags) self._swp_api.updateSoftwareProfile(session, swp) if args.hardware_profile: for name in args.hardware_profile.split(','): hwp = self._hwp_api.getHardwareProfile(session, name) hwp_tags = hwp.getTags() for key in tag_keys: if key in hwp_tags.keys(): hwp_tags.pop(key) hwp.setTags(hwp_tags) self._hwp_api.updateHardwareProfile(session, hwp) session.commit() def list_tag(self, session: Session, args): report = TagReport() if args.all_resources or args.nodes: for node in self._node_api.getNodeList(session): for key, value in node.getTags().items(): report.add_node(key, value, node) if args.all_resources or args.software_profiles: for swp in self._swp_api.getSoftwareProfileList(session): for key, value in swp.getTags().items(): report.add_swp(key, value, swp) if args.all_resources or args.hardware_profiles: for hwp in self._hwp_api.getHardwareProfileList(session): for key, value in hwp.getTags().items(): report.add_hwp(key, value, hwp) for key, values in report.keys.items(): for value, types in values.items(): print('{} = {}:'.format(key, value)) for type_, names in types.items(): print(' {}:'.format(type_)) for name in names: print(' - {}'.format(name))
class AddHostManager(TagsDbApiMixin, TortugaObjectManager): tag_model = NodeTag def __init__(self): super(AddHostManager, self).__init__() # Now do the class specific variable initialization self._addHostLock = threading.RLock() self._nodeDbApi = NodeDbApi() self._sessions = ObjectStoreManager.get(namespace='add-host-manager') def addHosts(self, session: Session, addHostRequest: dict) -> None: """ Raises: HardwareProfileNotFound ResourceAdapterNotFound """ self.getLogger().debug('addHosts()') dbHardwareProfile = \ HardwareProfilesDbHandler().getHardwareProfile( session, addHostRequest['hardwareProfile']) if not dbHardwareProfile.resourceadapter: errmsg = ('Resource adapter not defined for hardware' ' profile [%s]' % (dbHardwareProfile.name)) self.getLogger().error(errmsg) raise ResourceAdapterNotFound(errmsg) softwareProfileName = addHostRequest['softwareProfile'] \ if 'softwareProfile' in addHostRequest else None dbSoftwareProfile = \ SoftwareProfilesDbHandler().getSoftwareProfile( session, softwareProfileName) \ if softwareProfileName else None ResourceAdapterClass = \ resourceAdapterFactory.get_resourceadapter_class( dbHardwareProfile.resourceadapter.name) resourceAdapter = ResourceAdapterClass( addHostSession=addHostRequest['addHostSession']) resourceAdapter.session = session # Call the start() method of the resource adapter newNodes = resourceAdapter.start(addHostRequest, session, dbHardwareProfile, dbSoftwareProfile=dbSoftwareProfile) session.add_all(newNodes) session.flush() if 'tags' in addHostRequest and addHostRequest['tags']: for node in newNodes: self._set_tags(node, addHostRequest['tags']) # Commit new node(s) to database session.commit() # Only perform post-add operations if we actually added a node if newNodes: if dbSoftwareProfile and not dbSoftwareProfile.isIdle: self.getLogger().info( 'Node(s) added to software profile [%s] and' ' hardware profile [%s]' % (dbSoftwareProfile.name if dbSoftwareProfile else 'None', dbHardwareProfile.name)) newNodeNames = [tmpNode.name for tmpNode in newNodes] resourceAdapter.hookAction('add', newNodeNames) self.postAddHost(session, dbHardwareProfile.name, softwareProfileName, addHostRequest['addHostSession']) resourceAdapter.hookAction('start', newNodeNames) self.getLogger().debug('Add host workflow complete') def postAddHost(self, session: Session, hardwareProfileName: str, softwareProfileName: Optional[str], addHostSession: str) -> None: """ Perform post add host operations """ self.getLogger().debug( 'postAddHost(): hardwareProfileName=[%s]' ' softwareProfileName=[%s] addHostSession=[%s]' % (hardwareProfileName, softwareProfileName, addHostSession)) # this query is redundant; in the calling method, we already have # a list of Node (db) objects from tortuga.node.nodeApi import NodeApi nodes = NodeApi().getNodesByAddHostSession(session, addHostSession) mgr = KitActionsManager() mgr.session = session mgr.post_add_host(hardwareProfileName, softwareProfileName, nodes) # Always go over the web service for this call. SyncWsApi().scheduleClusterUpdate(updateReason='Node(s) added') def updateStatus(self, addHostSession: str, msg: str) -> None: self._addHostLock.acquire() try: if not self._sessions.exists(addHostSession): self.getLogger().warning( 'updateStatus(): unknown session ID [%s]' % (addHostSession)) return addHostStatus = AddHostStatus.getFromDict( self._sessions.get(addHostSession)['status']) addHostStatus.getMessageList().append(msg) finally: self._addHostLock.release() def getStatus(self, db_session: Session, session: str, startMessage: int, getNodes: bool) -> AddHostStatus: """ Raises: NotFound """ with self._addHostLock: nodeList = self._nodeDbApi.getNodesByAddHostSession( db_session, session) if getNodes else TortugaObjectList() # Lock and copy for data consistency if not self._sessions.exists(session): raise NotFound('Invalid add host session ID [%s]' % (session)) session = self._sessions.get(session) status_copy = AddHostStatus() # Copy simple data status = AddHostStatus.getFromDict(session['status']) for key in status.getKeys(): status_copy.set(key, status.get(key)) # Get slice of status messages messages = status.getMessageList()[startMessage:] status_copy.setMessageList(messages) if nodeList: status_copy.getNodeList().extend(nodeList) return status_copy def createNewSession(self) -> str: self.getLogger().debug('createNewSession()') with self._addHostLock: # Create new add nodes session session_id = str(uuid.uuid4()) self._sessions.set(session_id, {'status': AddHostStatus().getCleanDict()}) return session_id def delete_session(self, session_id: str) -> None: """TODO: currently a no-op""" def delete_sessions(self, session_ids: List[str]) -> None: """Bulk session deletion Currently only called when deleting nodes """ self.getLogger().debug('delete_sessions()') with self._addHostLock: for session_id in session_ids: if self._sessions.exists(session_id): self.getLogger().debug( 'Deleting session [{0}]'.format(session_id)) self._sessions.delete(session_id) def update_session(self, session_id: str, running: Optional[bool] = None): self.getLogger().debug( 'Updating add host session [%s] (status: running=%s)' % (session_id, str(running))) with self._addHostLock: session = self._sessions.get(session_id) status = AddHostStatus.getFromDict(session['status']) session['status'] = status.getCleanDict() self._sessions.set(session_id, session)
class SoftwareProfileManager(TortugaObjectManager): \ # pylint: disable=too-many-public-methods BASE_KIT_NAME = 'base' def __init__(self): super(SoftwareProfileManager, self).__init__() self._sp_db_api = SoftwareProfileDbApi() self._node_db_api = NodeDbApi() self._component_db_api = ComponentDbApi() self._global_param_db_api = GlobalParameterDbApi() self._kit_db_api = KitDbApi() self._config_manager = ConfigManager() self._logger = logging.getLogger(SOFTWARE_PROFILE_NAMESPACE) def getSoftwareProfileList(self, session: Session, tags=None): """Return all of the softwareprofiles with referenced components in this softwareprofile """ results = self._sp_db_api.getSoftwareProfileList(session, tags=tags) for software_profile_obj in results: # load any available software profile metadata software_profile_obj.setMetadata( self.get_software_profile_metadata( session, software_profile_obj.getName())) return results def addAdmin(self, session: Session, softwareProfileName, adminUsername): """ Add an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound SoftwareProfileNotFound """ return self._sp_db_api.addAdmin(session, softwareProfileName, adminUsername) def deleteAdmin(self, session: Session, softwareProfileName, adminUsername): """ Remove an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound SoftwareProfileNotFound """ return self._sp_db_api.deleteAdmin(session, softwareProfileName, adminUsername) def updateSoftwareProfile(self, session: Session, softwareProfileObject): self._logger.debug('Updating software profile: %s' % (softwareProfileObject.getName())) # # First get the object from the db we are updating # existing_swp = self.getSoftwareProfileById( session, softwareProfileObject.getId()) # # Set parameters that we will not allow updating # softwareProfileObject.setOsInfo(existing_swp.getOsInfo()) softwareProfileObject.setOsId(existing_swp.getOsId()) softwareProfileObject.setType(existing_swp.getType()) # # Do the DB update # self._sp_db_api.updateSoftwareProfile(session, softwareProfileObject) # # Get the new version # new_swp = self.getSoftwareProfileById(session, softwareProfileObject.getId()) # # If the tags have changed, fire the tags changed event # if existing_swp.getTags() != new_swp.getTags(): SoftwareProfileTagsChanged.fire( softwareprofile_id=str(new_swp.getId()), softwareprofile_name=new_swp.getName(), tags=new_swp.getTags(), previous_tags=existing_swp.getTags()) def getSoftwareProfile( self, session: Session, name: str, optionDict: Optional[Dict[str, bool]] = None) -> SoftwareProfile: """ Retrieve software profile by name """ software_profile_obj: SoftwareProfile = \ self._sp_db_api.getSoftwareProfile( session, name, optionDict=optionDict) # load any available software profile metadata software_profile_obj.setMetadata( self.get_software_profile_metadata(session, name)) return software_profile_obj def getSoftwareProfileById( self, session: Session, id_: int, optionDict: Optional[Dict[str, bool]] = None) -> SoftwareProfile: """ Retrieve software profile by id """ software_profile_obj: SoftwareProfile = \ self._sp_db_api.getSoftwareProfileById( session, id_, optionDict=optionDict) # load any available software profile metadata software_profile_obj.setMetadata( self.get_software_profile_metadata(session, software_profile_obj.getName())) return software_profile_obj def _getCoreComponentForOsInfo(self, session: Session, osInfo): # Find core component baseKit = None for baseKit in self._kit_db_api.getKitList(session): if not baseKit.getName() == self.BASE_KIT_NAME: continue break else: raise KitNotFound('Kit [%s] not found.' % (self.BASE_KIT_NAME)) baseComp = None for baseComp in baseKit.getComponentList(): if baseComp.getName() != 'core': continue break else: raise ComponentNotFound('Component [%s] not found in kit [%s]' % ('core', baseKit.getName())) comp = self._component_db_api.getBestMatchComponent( session, baseComp.getName(), baseComp.getVersion(), osInfo, baseKit.getId()) comp.setKit(baseKit) return comp def _getOsInfo(self, session: Session, bOsMediaRequired: bool): if not bOsMediaRequired: # As a placeholder, use the same OS as the installer # Find installer node entry node = self._node_db_api.getNode(session, ConfigManager().getInstaller(), {'softwareprofile': True}) return node.getSoftwareProfile().getOsInfo() # Use available operating system kit; raise exception if # multiple available os_kits = self._kit_db_api.getKitList(session, os_kits_only=True) if not os_kits: raise KitNotFound('No operating system kit installed') if len(os_kits) > 1: raise KitNotFound( 'Multiple OS kits defined; use --os option to specify' ' operating system') kit = self._kit_db_api.getKit(session, os_kits[0].getName(), os_kits[0].getVersion(), '0') components = kit.getComponentList() if not components: raise ComponentNotFound('Malformed operating system kit [%s]' % (os_kits)) osinfo_list = components[0].getOsInfoList() if len(osinfo_list) > 1: raise ComponentNotFound( 'Multiple operating system components for kit [%s];' ' use --os argument to specify operating system' % (os_kits[0])) return osinfo_list[0] def createSoftwareProfile(self, session: Session, swProfileSpec, settingsDict=None): """ Exceptions: ConfigurationError NetworkNotFound ComponentNotFound KitNotFound OSError """ if settingsDict == None: settingsDict = {} bOsMediaRequired = settingsDict.get('bOsMediaRequired', True) unmanagedProfile = settingsDict.get('unmanagedProfile', False) # Validate software profile name validation.validateProfileName(swProfileSpec.getName()) # Insert default description for software profile if swProfileSpec.getDescription() is None: swProfileSpec.setDescription('%s Nodes' % (swProfileSpec.getName())) self._logger.debug('Creating software profile [%s]' % (swProfileSpec)) osInfo = swProfileSpec.getOsInfo() \ if swProfileSpec.getOsInfo() else self._getOsInfo( session, bOsMediaRequired) # If we're creating an unmanaged software profile (no # DHCP/PXE/kickstart/OS) just create it now and we're done if unmanagedProfile: self._sp_db_api.addSoftwareProfile(session, swProfileSpec) else: if bOsMediaRequired and swProfileSpec.getOsInfo(): try: self._kit_db_api.getKit( session, swProfileSpec.getOsInfo().getName(), swProfileSpec.getOsInfo().getVersion(), '0') except KitNotFound: self._logger.error('OS kit for [%s] not found' % (swProfileSpec.getOsInfo())) raise else: swProfileSpec.setOsInfo(osInfo) # Get component manager for appropriate OS family osConfig = osHelper.getOsInfo(osInfo.getName(), osInfo.getVersion(), osInfo.getArch()) osObjFactory = osUtility.getOsObjectFactory( osConfig.getOsFamilyInfo().getName()) # Need to be fancy with components spComponents = swProfileSpec.getComponents() swProfileSpec.setComponents(TortugaObjectList()) bFoundOsComponent = False bFoundCoreComponent = False components = [] # Iterate over components, adding them to the software profile for c in spComponents: cobj = self._component_db_api.getBestMatchComponent( session, c.getName(), c.getVersion(), osInfo, c.getKit().getId()) k = cobj.getKit() if k.getIsOs(): # This component is a member of the OS kit, set the flag bFoundOsComponent = True elif k.getName() == 'base' and c.getName() == 'core': # Found the 'core' component in 'base' kit bFoundCoreComponent = True components.append(cobj) # If the operating system is undefined for this software # profile, use the same OS as the installer. if bOsMediaRequired and not bFoundOsComponent: # Find OS component osCompName = '%s-%s-%s' % ( osInfo.getName(), osInfo.getVersion(), osInfo.getArch()) self._logger.debug('Automatically adding OS component [%s]' ' (not specified in template)' % (osCompName)) try: osComponent = self._component_db_api.getComponent( session, osCompName, osInfo.getVersion(), osInfo, {'kit': True}) components.append(osComponent) except ComponentNotFound: # Cannot find OS component, don't freak out pass # Ensure 'core' component is enabled if not bFoundCoreComponent: # Attempt to automatically add the core component, only # if one exists for this OS try: comp = self._getCoreComponentForOsInfo(session, osInfo) self._logger.debug('Automatically adding [core] component' ' (not specified in template)') components.append(comp) except ComponentNotFound: self._logger.warning( 'OS [{}] does not have a compatible \'core\'' ' component'.format(osInfo)) # Initialize values for kernel, kernelParams, and initrd if not swProfileSpec.getKernel(): swProfileSpec.setKernel( osObjFactory.getOsSysManager().getKernel(osInfo)) if not swProfileSpec.getInitrd(): swProfileSpec.setInitrd( osObjFactory.getOsSysManager().getInitrd(osInfo)) # Add the software profile self._sp_db_api.addSoftwareProfile(session, swProfileSpec) # Enable components in one fell swoop for comp in components: self._logger.debug('Enabling component [%s]' % (comp.getName())) if comp.getKit().getIsOs(): # Don't use enableComponent() on OS kit self._component_db_api.addComponentToSoftwareProfile( session, comp.getId(), swProfileSpec.getId()) continue self.enableComponent(session, swProfileSpec.getName(), comp.getKit().getName(), comp.getKit().getVersion(), comp.getKit().getIteration(), comp.getName(), comp.getVersion()) # # Fire the tags changed event for all creates that have tags # # Get the latest version from the db in case the create method # added some embellishments # swp = self.getSoftwareProfile(session, swProfileSpec.getName()) if swp.getTags(): SoftwareProfileTagsChanged.fire(softwareprofile_id=str( swp.getId()), softwareprofile_name=swp.getName(), tags=swp.getTags(), previous_tags={}) def _getComponent(self, kit, compName, compVersion): \ # pylint: disable=no-self-use # Iterate over component list, looking for a match comp = None for comp in kit.getComponentList(): if comp.getName() == compName and \ comp.getVersion() == compVersion: break else: raise ComponentNotFound("Component [%s-%s] not found in kit [%s]" % (compName, compVersion, kit)) return comp def _get_kit_by_component(self, session: Session, comp_name, comp_version=None): """ Gets a kit by compoent name/version. :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises ComponentNotFound: """ kit_list = self._kit_db_api.getKitList(session) kits = [ kit for kit in kit_list for component in kit.getComponentList() if component.getName() == comp_name and ( comp_version is None or component.getVersion() == comp_version) ] if not kits: raise KitNotFound('Kit containing component [%s] not found' % (comp_name)) if len(kits) > 1: raise ComponentNotFound( 'Kit name must be specified, multiple kits contain ' 'component: {}'.format(comp_name)) return kits[0] def enableComponent(self, session: Session, software_profile_name: str, kit_name: str, kit_version: str, kit_iteration: str, comp_name: str, comp_version: Optional[str] = None): """ Enable a component on a software profile. :param software_profile_name: the name of the software profile :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises SoftwareProfileNotFound: :raises ComponentNotFound: """ kit, comp_version = self._get_kit_and_component_version( session, kit_name, kit_version, kit_iteration, comp_name, comp_version) software_profile = self.getSoftwareProfile(session, software_profile_name, {'os': True}) if kit.getIsOs(): best_match_component = self._enable_os_kit_component( session, kit, comp_name, comp_version, software_profile) else: best_match_component = self._enable_kit_component( session, kit, comp_name, comp_version, software_profile) if not best_match_component: self._logger.info('Component not enabled: {}'.format(comp_name)) else: self._logger.info( 'Enabled component on software profile: {} -> {}'.format( best_match_component, software_profile)) def _get_kit_and_component_version(self, session: Session, kit_name, kit_version, kit_iteration, comp_name, comp_version=None): """ Gets a Kit instance and component version. :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the component name :param comp_version: the component version (optional) :return: a tuple, consisting of (Kit, component_version) """ kit = None if kit_name is None: kit = self._get_kit_by_component(session, comp_name, comp_version=comp_version) # # Get component version if required # if comp_version is None: for component in kit.getComponentList(): if component.getName() == comp_name: comp_version = component.getVersion() break elif kit_version is None or kit_iteration is None: kits_found = 0 for k in self._kit_db_api.getKitList(session): if k.getName() == kit_name and \ (kit_version is None or k.getVersion() == kit_version) and \ (kit_iteration is None or k.getIteration() == kit_iteration): kit = k kits_found += 1 if kits_found > 1: if kit_version is not None: raise KitNotFound('Multiple kits found: {}-{}'.format( kit_name, kit_version)) else: raise KitNotFound( 'Multiple kits found {}'.format(kit_name)) else: kit = self._kit_db_api.getKit(session, kit_name, kit_version, kit_iteration) if kit is None: raise KitNotFound('Kit [%s] not found' % (Kit(kit_name, kit_version, kit_iteration))) return kit, comp_version def _enable_kit_component(self, session: Session, kit, comp_name, comp_version, software_profile): """ Enables a regular kit component on a specific software profile. :param kit: the Kit instance, whose component is being enabled :param comp_name: the name of the component to enable :param comp_version: the version of the component to enable :param software_profile: the software profile on which the component will be enabled :return: the Component instance that was enabled """ kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration()) installer = get_kit_installer(kit_spec)() installer.session = session comp_installer = installer.get_component_installer(comp_name) if comp_installer is None: raise ComponentNotFound('Component [%s] not found in kit [%s]' % (comp_name, kit)) if not comp_installer.is_enableable(software_profile): self._logger.warning('Component cannot be enabled: {}'.format( comp_installer.spec)) return None comp_installer.run_action('pre_enable', software_profile.getName()) best_match_component = self._add_component_to_software_profile( session, kit, comp_name, comp_version, software_profile) comp_installer.run_action('enable', software_profile.getName()) comp_installer.run_action('post_enable', software_profile.getName()) return best_match_component def _enable_os_kit_component(self, session: Session, kit, comp_name, comp_version, software_profile): """ Enables an OS kit component on a specific software profile. :param kit: the OS Kit instance, whose component is being enabled :param comp_name: the name of the component to enable :param comp_version: the version of the component to enable :param software_profile: the software profile on which the component will be enabled :return: the Component instance that was enabled """ return self._add_component_to_software_profile(session, kit, comp_name, comp_version, software_profile) def _add_component_to_software_profile(self, session: Session, kit, comp_name, comp_version, software_profile): """ Adds a kit to a software profile. This is a data-only operation, as no pre/post enable actions are called. :param kit: the OS Kit instance, whose component is being added :param comp_name: the name of the component to add :param comp_version: the version of the component to add :param software_profile: the software profile to which the component will be added :return: the Component instance that was added """ best_match_component = \ self._component_db_api.getBestMatchComponent( session, comp_name, comp_version, software_profile.getOsInfo(), kit.getId()) self._component_db_api.addComponentToSoftwareProfile( session, best_match_component.getId(), software_profile.getId()) return best_match_component def disableComponent(self, session: Session, software_profile_name, kit_name, kit_version, kit_iteration, comp_name, comp_version=None): \ # pylint: disable=unused-argument """ Disables a component on a software profile. :param software_profile_name: the name of the software profile :param kit_name: the name of the kit :param kit_version: the version of the kit :param kit_iteration: the iteration of the kit :param comp_name: the name of the component :param comp_version: the version of the component :raises KitNotFound: :raises SoftwareProfileNotFound: :raises ComponentNotFound: """ kit, comp_version = self._get_kit_and_component_version( session, kit_name, kit_version, kit_iteration, comp_name) software_profile = self.getSoftwareProfile(session, software_profile_name, {'os': True}) if kit.getIsOs(): best_match_component = self._disable_os_kit_component( session, kit, comp_name, comp_version, software_profile) else: best_match_component = self._disable_kit_component( session, kit, comp_name, comp_version, software_profile) self._logger.info( 'Disabled component on software profile: {} -> {}'.format( best_match_component, software_profile)) def _disable_kit_component(self, session, kit, comp_name, comp_version, software_profile): """ Disables a regular kit component on a specific software profile. :param kit: the Kit instance, whose component is being disabled :param comp_name: the name of the component to disable :param comp_version: the version of the component to disable :param software_profile: the software profile on which the component will be disable :return: the Component instance that was disabled """ kit_spec = (kit.getName(), kit.getVersion(), kit.getIteration()) installer = get_kit_installer(kit_spec)() installer.session = session comp_installer = installer.get_component_installer(comp_name) if comp_installer is None: raise ComponentNotFound('Component [%s] not found in kit [%s]' % (comp_name, kit)) comp_installer.run_action('pre_disable', software_profile.getName()) comp_installer.run_action('disable', software_profile.getName()) best_match_component = \ self._remove_component_from_software_profile( session, kit, comp_name, comp_version, software_profile) comp_installer.run_action('post_disable', software_profile.getName()) return best_match_component def _disable_os_kit_component(self, session, kit, comp_name, comp_version, software_profile): """ Enables an OS kit component on a specific software profile. :param kit: the OS Kit instance, whose component is being disabled :param comp_name: the name of the component to disable :param comp_version: the version of the component to disable :param software_profile: the software profile on which the component will be disabled :return: the Component instance that was disabled """ return self._remove_component_from_software_profile( session, kit, comp_name, comp_version, software_profile) def _remove_component_from_software_profile(self, session: Session, kit, comp_name, comp_version, software_profile): """ Removes a kit to a software profile. This is a data-only operation, as no pre/post disable actions are called. :param kit: the OS Kit instance, whose component is being removed :param comp_name: the name of the component to remove :param comp_version: the version of the component to remove :param software_profile: the software profile to which the component will be removed :return: the Component instance that was removed """ best_match_component = self._component_db_api.getBestMatchComponent( session, comp_name, comp_version, software_profile.getOsInfo(), kit.getId()) self._component_db_api.deleteComponentFromSoftwareProfile( session, best_match_component.getId(), software_profile.getId()) return best_match_component def deleteSoftwareProfile(self, session: Session, name): """ Delete software profile by name Raises: SoftwareProfileNotFound """ self._sp_db_api.deleteSoftwareProfile(session, name) # Remove all flags for software profile swProfileFlagPath = os.path.join(self._config_manager.getRoot(), 'var/run/actions/%s' % (name)) if os.path.exists(swProfileFlagPath): shutil.rmtree(swProfileFlagPath) self._logger.info('Deleted software profile [%s]' % (name)) def getNodeList(self, session: Session, softwareProfileName): return self._sp_db_api.getNodeList(session, softwareProfileName) def getEnabledComponentList(self, session: Session, name): """ Get the list of enabled components """ return self._sp_db_api.getEnabledComponentList(session, name) def getPartitionList(self, session: Session, softwareProfileName): """ Get list of partitions. """ return self._sp_db_api.getPartitionList(session, softwareProfileName) def addUsableHardwareProfileToSoftwareProfile( self, session: Session, hardwareProfileName: str, softwareProfileName: str) -> None: """ Map software profile to hardware profile """ self._logger.info( 'Mapping hardware profile [%s] to software profile [%s]', hardwareProfileName, softwareProfileName) self._sp_db_api.addUsableHardwareProfileToSoftwareProfile( session, hardwareProfileName, softwareProfileName) def deleteUsableHardwareProfileFromSoftwareProfile(self, session: Session, hardwareProfileName, softwareProfileName): return self._sp_db_api.deleteUsableHardwareProfileFromSoftwareProfile( session, hardwareProfileName, softwareProfileName) def copySoftwareProfile(self, session: Session, srcSoftwareProfileName, dstSoftwareProfileName): validation.validateProfileName(dstSoftwareProfileName) self._logger.info('Copying software profile [%s] to [%s]', srcSoftwareProfileName, dstSoftwareProfileName) self._sp_db_api.copySoftwareProfile(session, srcSoftwareProfileName, dstSoftwareProfileName) # # Fire the tags changed event for all copies that have tags # swp = self.getSoftwareProfile(session, dstSoftwareProfileName) if swp.getTags(): SoftwareProfileTagsChanged.fire(softwareprofile_id=str( swp.getId()), softwareprofile_name=swp.getName(), tags=swp.getTags(), previous_tags={}) def getUsableNodes(self, session: Session, softwareProfileName): return self._sp_db_api.getUsableNodes(session, softwareProfileName) def get_software_profile_metadata(self, session: Session, name: str) -> Dict[str, str]: """ Call action_get_metadata() method for all kits """ self._logger.debug('Retrieving metadata for software profile [%s]', name) metadata: Dict[str, str] = {} for kit in self._kit_db_api.getKitList(session): if kit.getIsOs(): # ignore OS kits continue kit_installer = get_kit_installer( (kit.getName(), kit.getVersion(), kit.getIteration()))() kit_installer.session = session # we are only interested in software profile metadata item = kit_installer.action_get_metadata( software_profile_name=name) if item: metadata.update(item) return metadata
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
def __init__(self, *args, **kwargs): self._hwp_api = HardwareProfileDbApi() self._node_api = NodeDbApi() self._swp_api = SoftwareProfileDbApi() super().__init__(*args, **kwargs)
def test_getNodeByAddHostSession(dbm): with dbm.session() as session: result = NodeDbApi().getNodesByAddHostSession(session, 'xxxx') assert not result
def test_getNode(dbm): with dbm.session() as session: result = NodeDbApi().getNode(session, 'compute-01.private') assert isinstance(result, Node)
class HardwareProfileManager(TortugaObjectManager): def __init__(self): super(HardwareProfileManager, self).__init__() self._hpDbApi = HardwareProfileDbApi() self._spDbApi = SoftwareProfileDbApi() self._networkDbApi = NetworkDbApi() self._globalParameterDbApi = GlobalParameterDbApi() self._nodeDbApi = NodeDbApi() self._logger = logging.getLogger(HARDWARE_PROFILE_NAMESPACE) def getHardwareProfileList(self, session: Session, optionDict: Optional[Union[Dict[str, str], None]] = None, tags: Optional[Tags] = None): """ Return all of the hardwareprofiles with referenced components in this hardwareprofile """ return self._hpDbApi.getHardwareProfileList(session, optionDict=optionDict, tags=tags) def getHardwareProfile(self, session: Session, name: str, optionDict: Optional[Union[dict, None]] = None): return self._hpDbApi.getHardwareProfile(session, name, optionDict) def getHardwareProfileById(self, session: Session, id_, optionDict=None): return self._hpDbApi.getHardwareProfileById(session, id_, optionDict) def addAdmin(self, session: Session, hardwareProfileName: str, adminUsername: str): """ Add an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound HardwareProfileNotFound """ return self._hpDbApi.addAdmin(session, hardwareProfileName, adminUsername) def deleteAdmin(self, session: Session, hardwareProfileName: str, adminUsername: str): """ Remove an admin as an authorized user. Returns: None Throws: TortugaException AdminNotFound HardwareProfileNotFound """ return self._hpDbApi.deleteAdmin(session, hardwareProfileName, adminUsername) def updateHardwareProfile(self, session: Session, hardwareProfileObject: Any): """ Update a hardware profile in the database that matches the passed in hardware profile object. The ID is used as the primary matching criteria. Returns: None Throws: TortugaException HardwareProfileNotFound InvalidArgument """ self._logger.debug('Updating hardware profile [%s]' % (hardwareProfileObject.getName())) existing_hwp = self.getHardwareProfileById( session, hardwareProfileObject.getId()) if hardwareProfileObject.getInstallType() and \ hardwareProfileObject.getInstallType() != \ existing_hwp.getInstallType(): raise InvalidArgument( 'Hardware profile installation type cannot be' ' changed' % (hardwareProfileObject.getName())) self._hpDbApi.updateHardwareProfile(session, hardwareProfileObject) # # Get the new version from the DB # new_hwp = self.getHardwareProfileById(session, hardwareProfileObject.getId()) # # If the tags have changed, fire the tags changed event # if existing_hwp.getTags() != new_hwp.getTags(): HardwareProfileTagsChanged.fire( hardwareprofile_id=str(new_hwp.getId()), hardwareprofile_name=new_hwp.getName(), tags=new_hwp.getTags(), previous_tags=existing_hwp.getTags()) def createHardwareProfile(self, session: Session, hwProfileSpec: HardwareProfile, settingsDict: Optional[Union[dict, None]] = None): bUseDefaults = settingsDict['defaults'] \ if settingsDict and 'defaults' in settingsDict else False osInfo = settingsDict['osInfo'] \ if settingsDict and \ settingsDict and 'osInfo' in settingsDict else None validation.validateProfileName(hwProfileSpec.getName()) if hwProfileSpec.getDescription() is None: hwProfileSpec.setDescription('%s Nodes' % (hwProfileSpec.getName())) installerNode = self._nodeDbApi.getNode(session, ConfigManager().getInstaller(), {'softwareprofile': True}) if bUseDefaults: if not hwProfileSpec.getNetworks(): # No <network>...</network> entries found in the template, # use the default provisioning interface from the primary # installer. # Find first provisioning network and use it for nic in installerNode.getNics(): network = nic.getNetwork() if network.getType() == 'provision': # for now set the default interface to be index 0 # with the same device networkDevice = fixNetworkDeviceName( nic.getNetworkDevice().getName()) network.setNetworkDevice( NetworkDevice(name=networkDevice)) hwProfileSpec.getNetworks().append(network) break else: raise NetworkNotFound( 'Unable to find provisioning network') else: # Ensure network device is defined installerNic = None for network in hwProfileSpec.getNetworks(): for installerNic in installerNode.getNics(): installerNetwork = installerNic.getNetwork() if network.getId() and \ network.getId() == installerNetwork.getId(): break elif network.getAddress() and \ network.getAddress() == \ installerNetwork.getAddress() and \ network.getNetmask() and \ network.getNetmask() == \ installerNetwork.getNetmask(): break else: # Unable to find network matching specification in # template. raise NetworkNotFound( 'Unable to find provisioning network [%s]' % (network)) networkDevice = fixNetworkDeviceName( installerNic.getNetworkDevice().getName()) network.setNetworkDevice(NetworkDevice(name=networkDevice)) if not osInfo: osInfo = installerNode.getSoftwareProfile().getOsInfo() osObjFactory = osUtility.getOsObjectFactory(osInfo.getName()) if not hwProfileSpec.getKernel(): hwProfileSpec.setKernel( osObjFactory.getOsSysManager().getKernel(osInfo)) if not hwProfileSpec.getInitrd(): hwProfileSpec.setInitrd( osObjFactory.getOsSysManager().getInitrd(osInfo)) self._hpDbApi.addHardwareProfile(session, hwProfileSpec) # Iterate over all networks in the newly defined hardware profile # and build assocations to provisioning NICs if bUseDefaults: for network in \ [network for network in hwProfileSpec.getNetworks() if network.getType() == 'provision']: # Get provisioning nic for network try: provisioningNic = self.getProvisioningNicForNetwork( session, network.getAddress(), network.getNetmask()) except NicNotFound: # There is currently no provisioning NIC defined for # this network. This is not a fatal error. continue self.setProvisioningNic(session, hwProfileSpec.getName(), provisioningNic.getId()) # # Fire the tags changed event for all creates that have tags # # Get the latest version from the db in case the create method # added some embellishments # hwp = self.getHardwareProfile(session, hwProfileSpec.getName()) if hwp.getTags(): HardwareProfileTagsChanged.fire(hardwareprofile_id=str( hwp.getId()), hardwareprofile_name=hwp.getName(), tags=hwp.getTags(), previous_tags={}) def deleteHardwareProfile(self, session: Session, name: str) -> None: """ Delete hardwareprofile by name. """ self._hpDbApi.deleteHardwareProfile(session, name) self._logger.info('Deleted hardware profile [%s]' % (name)) def updateSoftwareOverrideAllowed(self, session: Session, hardwareProfileName: str, flag: bool) -> None: self._hpDbApi.updateSoftwareOverrideAllowed(session, hardwareProfileName, flag) def setProvisioningNic(self, session: Session, hardwareProfileName: str, nicId: int): return self._hpDbApi.setProvisioningNic(session, hardwareProfileName, nicId) def getProvisioningNicForNetwork(self, session: Session, network: str, netmask: str): return self._hpDbApi.getProvisioningNicForNetwork( session, network, netmask) def copyHardwareProfile(self, session: Session, srcHardwareProfileName: str, dstHardwareProfileName: str): validation.validateProfileName(dstHardwareProfileName) self._logger.info('Copying hardware profile [%s] to [%s]' % (srcHardwareProfileName, dstHardwareProfileName)) self._hpDbApi.copyHardwareProfile(session, srcHardwareProfileName, dstHardwareProfileName) # # Fire the tags changed event for all copies that have tags # hwp = self.getHardwareProfile(session, dstHardwareProfileName) if hwp.getTags(): HardwareProfileTagsChanged.fire(hardwareprofile_id=str( hwp.getId()), hardwareprofile_name=hwp.getName(), tags=hwp.getTags(), previous_tags={}) def getNodeList(self, session: Session, hardwareProfileName: str): return self._hpDbApi.getNodeList(session, hardwareProfileName)