def test_updateSoftwareProfileTags(dbm): api = SoftwareProfileDbApi() tags = {'tag1': 'tag1 value', 'tag2': 'tag2 value'} with dbm.session() as session: swprofile = api.getSoftwareProfile(session, 'notags') # # Set tags # swprofile.setTags({'tag1': 'tag1 value', 'tag2': 'tag2 value'}) api.updateSoftwareProfile(session, swprofile) session.commit() swprofile = api.getSoftwareProfile(session, 'notags') assert swprofile.getTags() == tags # # Remove tags # swprofile.setTags({}) api.updateSoftwareProfile(session, swprofile) session.commit() swprofile = api.getSoftwareProfile(session, 'notags') assert swprofile.getTags() == {}
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 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)
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