Пример #1
0
 def __init__(self):
     super(SoftwareProfileManager, self).__init__()
     self._sp_db_api = SoftwareProfileDbApi()
     self._node_db_api = NodeDbApi()
     self._component_db_api = ComponentDbApi()
     self._global_param_db_api = GlobalParameterDbApi()
     self._kit_db_api = KitDbApi()
     self._config_manager = ConfigManager()
Пример #2
0
 def __init__(self):
     super(SoftwareProfileManager, self).__init__()
     self._sp_db_api = SoftwareProfileDbApi()
     self._node_db_api = NodeDbApi()
     self._component_db_api = ComponentDbApi()
     self._global_param_db_api = GlobalParameterDbApi()
     self._kit_db_api = KitDbApi()
     self._config_manager = ConfigManager()
     self._logger = logging.getLogger(SOFTWARE_PROFILE_NAMESPACE)
Пример #3
0
    def __init__(self, eula_validator=None):
        super(KitManager, self).__init__()

        self._eula_validator = eula_validator
        if not self._eula_validator:
            self._eula_validator = BaseEulaValidator()

        self._kit_db_api = KitDbApi()
        self._config_manager = ConfigManager()
        self._kits_root = self._config_manager.getKitDir()
        self._component_db_api = componentDbApi.ComponentDbApi()
Пример #4
0
 def _get_kit_id(self):
     for kit in KitDbApi().getKitList():
         if kit.getName() == self.name:
             break
     else:
         raise InvalidActionRequest(
             'Unable to find kit: {}'.format(self.name))
     return kit.getId()
Пример #5
0
    def _get_base_kit(self):
        """
        Gets the base kit from the database.

        :return: a Kit instance

        """
        kit = None
        for k in KitDbApi().getKitList():
            if k.getName() == 'base':
                kit = k
        return kit
Пример #6
0
class KitManager(TortugaObjectManager):
    def __init__(self, eula_validator=None):
        super(KitManager, self).__init__()

        self._eula_validator = eula_validator
        if not self._eula_validator:
            self._eula_validator = BaseEulaValidator()

        self._kit_db_api = KitDbApi()
        self._config_manager = ConfigManager()
        self._kits_root = self._config_manager.getKitDir()
        self._component_db_api = componentDbApi.ComponentDbApi()
        self._logger = logging.getLogger(KIT_NAMESPACE)

    def getKitList(self, session: Session):
        """
        Get all installed kits.

        """
        return self._kit_db_api.getKitList(session)

    def getKit(self, session: Session, name, version=None, iteration=None):
        """
        Get a single kit by name, and optionally version and/or iteration.

        :param name:      the kit name
        :param version:   the kit version
        :param iteration: the kit iteration
        :return:          the kit instance

        """
        return self._kit_db_api.getKit(session, name, version, iteration)

    def getKitById(self,session: Session, id_):
        """
        Get a single kit by id.

        :param id_: the kit id
        :return:    the kit instance

        """
        return self._kit_db_api.getKitById(session, id_)

    def get_kit_url(self, name, version, iteration):
        kit = Kit(name, version, iteration)
        native_repo = repoManager.getRepo()
        return os.path.join(
            native_repo.getRemoteUrl(), kit.getTarBz2FileName())

    def installKit(self, db_manager, name, version, iteration):
        """
        Install kit using kit name/version/iteration.

        The kit package must be located in the remote repository for the
        native OS.

        Kit will be installed for all operating systems that:
            1) have repo configured on the local machine
            2) are specified in the kit.xml file

        Raises:
            KitAlreadyExists
        """

        kit = Kit(name, version, iteration)

        # nativeRepo = repoManager.getRepo()
        kitPkgUrl = self.get_kit_url(name, version, iteration)

        # Check for kit existence.
        with db_manager.session() as session:
            self._check_if_kit_exists(session, kit)

        self._logger.debug(
            '[{0}] Installing kit [{1}]'.format(
                self.__class__.__name__, kit))

        return self.installKitPackage(db_manager, kitPkgUrl)

    def _check_if_kit_exists(self, session: Session, kit):
        """
        Check if a kit exists, if it does then raise an exception.

        :raisesKitAlreadyExists:

        """
        try:
            self._kit_db_api.getKit(
                session, kit.getName(), kit.getVersion(), kit.getIteration())
            raise KitAlreadyExists(
                'Kit already exists: ({}, {}, {})'.format(
                    kit.getName(), kit.getVersion(), kit.getIteration()
                )
            )
        except KitNotFound:
            pass

    def installKitPackage(self, db_manager, kit_pkg_url):
        """
        Install kit from the given kit url (url might be a local file).

        :param db_manager:  a database manager instance
        :param kit_pkg_url: the URL to the kit package archive

        :raises KitAlreadyExists:
        :raises EulaAcceptanceRequired:

        """
        self._logger.debug(
            'Installing kit package: {}'.format(kit_pkg_url))

        with db_manager.session() as session:
            installer = self._prepare_installer(kit_pkg_url)

            installer.session = session

            kit = installer.get_kit()

            try:
                self._run_installer(db_manager, installer)

            except Exception as ex:
                self._delete_kit(session, kit, force=False)
                raise ex

        return kit

    def _prepare_installer(self, kit_pkg_url):
        """
        Extracts a kit archive and prepares the kit installer for the
        installation process.

        :param kit_pkg_url: the URL to the kit package archive

        :return: the KitInstaller instance

        """
        #
        # Download/copy kit archive.
        #
        kit_src_path = os.path.basename(kit_pkg_url)
        kit_pkg_path = utils.retrieve(
            kit_src_path, kit_pkg_url, self._kits_root)

        #
        # Make sure the kit version is compatible
        #
        kit_meta = utils.get_metadata_from_archive(kit_pkg_path)
        kit_spec = (kit_meta['name'], kit_meta['version'],
                    kit_meta['iteration'])
        requires_core = kit_meta.get('requires_core', VERSION)
        if not version_is_compatible(requires_core):
            errmsg = 'The {} kit requires tortuga core >= {}'.format(
                kit_meta['name'], requires_core)

            raise OperationFailed(errmsg)

        #
        # Unpack the archive
        #
        kit_dir = utils.unpack_kit_archive(kit_pkg_path, self._kits_root)

        #
        # Load and initialize kit installer
        #
        load_kits()
        try:
            installer = get_kit_installer(kit_spec)()
            assert installer.is_installable()

        except Exception as ex:
            if os.path.exists(kit_dir):
                self._logger.debug(
                    'Removing kit installation directory: {}'.format(kit_dir))
                osUtility.removeDir(kit_dir)
            self._logger.warning(
                'Kit is not installable: {}'.format(kit_spec))
            return

        return installer

    def _run_installer(self, db_manager: DbManager, installer):
        """
        Runs the installation process for a kit installer.

        :param db_manager: the DbManager instance
        :param installer: the KitInstaller instance to run the install process
                          for

        """
        kit = installer.get_kit()

        #
        # This method will throw KitAlreadyExists, if it does...
        #
        self._check_if_kit_exists(installer.session, kit)

        #
        # Validate eula
        #
        eula = installer.get_eula()
        if not eula:
            self._logger.debug('No EULA acceptance required')
        else:
            if not self._eula_validator.validate_eula(eula):
                raise EulaAcceptanceRequired(
                    'You must accept the EULA to install this kit')

        #
        # Runs the kit pre install method
        #
        installer.run_action('pre_install')

        #
        # Get list of operating systems supported by this installer
        #
        os_info_list = [
            repo.getOsInfo() for repo in repoManager.getRepoList()
        ]

        #
        # Install operating system specific packages
        #
        self._install_os_packages(kit, os_info_list)

        #
        # Initialize any DB tables provided by the kit
        #
        db_manager.init_database()

        #
        # Add the kit to the database
        #
        self._kit_db_api.addKit(installer.session, kit)

        #
        # Clean up the kit archive directory
        #
        self._clean_kit_achiv_dir(kit, installer.install_path)

        #
        # Install puppet modules
        #
        installer.run_action('install_puppet_modules')

        #
        # Run post install
        #
        installer.run_action('post_install')

        if eula:
            ActionManager().logAction(
                'Kit [{}] installed and EULA accepted at [{}]'
                ' local machine time.'.format(
                    installer.spec, time.ctime())
            )
        else:
            ActionManager().logAction(
                'Kit [{}] installed at [{}] local machine time.'.format(
                    installer.spec, time.ctime())
            )

    def _install_os_packages(self, kit, os_info_list):
        """
        Installs OS specific packages from the kit into the repo.

        :param kit:          the kit instance
        :param os_info_list: a list of osInfo instances to install for

        """
        all_component_list = kit.getComponentList()

        for os_info in os_info_list:
            self._logger.debug(
                'Preparing to install ({}, {}, {}) for {}'.format(
                    kit.getName(), kit.getVersion(), kit.getIteration(),
                    os_info
                )
            )

            #
            # Get list of compatible components
            #
            os_object_factory = getOsObjectFactory(
                mapOsName(os_info.getName())
            )
            component_manager = os_object_factory.getComponentManager()
            component_list = component_manager.getCompatibleComponentList(
                os_info, all_component_list)
            if not component_list:
                continue

            #
            # Create the package directory in the repo
            #
            repo = repoManager.getRepo(os_info)
            full_dir = os.path.join(repo.getLocalPath(), kit.getKitRepoDir())
            osUtility.createDir(full_dir)

            #
            # Install the packages into the repo package directory
            #
            for component in component_list:
                self._logger.debug(
                    '[{0}] Found component [{1}]'.format(
                        self.__class__.__name__, component))

                for package in component.getPackageList():
                    package_file = os.path.join(
                        kit.install_path, package.getRelativePath())
                    self._logger.debug(
                        '[{0}] Found package [{1}]'.format(
                            self.__class__.__name__, package_file))
                    repo.addPackage(package_file, kit.getKitRepoDir())
            repo.create(kit.getKitRepoDir())

    def get_kit_eula(self, name, version, iteration=None):
        return self.get_kit_package_eula(
            self.get_kit_url(name, version, iteration)
        )

    def get_kit_package_eula(self, kit_pkg_url):
        #
        # Download/copy and unpack kit archive.
        #
        kit_src_path = os.path.basename(kit_pkg_url)
        kit_pkg_path = utils.retrieve(
            kit_src_path, kit_pkg_url, self._kits_root)
        kit_spec = utils.unpack_archive(kit_pkg_path, self._kits_root)

        #
        # Get the EULA from the installer
        #
        installer = get_kit_installer(kit_spec)()
        eula = installer.get_eula()

        return eula

    def _retrieveOSMedia(self, url: str) -> str:
        """
        Download the OS media.

        :param url: String
        :return: String file path to download
        """
        return urllib.request.urlretrieve(url)[0]

    def _processMediaspec(self, os_media_urls: List[str]) -> List[dict]:
        """
        :param os_media_urls: List String
        :return: List Dictionary

        """
        media_list: List[dict] = []

        for url in os_media_urls:
            m = urllib.parse.urlparse(url)

            media_item_dict = {
                'urlparse': m,
            }

            if m.scheme.lower() in ('http', 'https') and \
                    os.path.splitext(m.path)[1].lower() == '.iso':
                file_name = self._retrieveOSMedia(m.geturl())

                media_item_dict['localFilePath'] = file_name

            media_list.append(media_item_dict)

        return media_list

    @staticmethod
    def _getKitOpsClass(os_family_info) -> Any:
        """
        Import the KitOps class for the specified OS family name

        Raises:
            OsNotSupported
        """
        try:
            _temp = __import__(
                'tortuga.kit.%sOsKitOps' % os_family_info.getName(),
                globals(),
                locals(),
                ['KitOps'],
                0
            )

            return getattr(_temp, 'KitOps')
        except ImportError:
            raise OsNotSupported('Currently unsupported distribution')

    def _checkExistingKit(self, session: Session, kitname: str,
                          kitversion: str, kitarch: str):
        """
        Raises:
            KitAlreadyExists
        """

        try:
            kit = self._kit_db_api.getKit(session, kitname, kitversion)

            longname = format_kit_descriptor(kitname, kitversion, kitarch)

            # Attempt to get matching OS component
            for c in kit.getComponentList(session):
                if c.getName() != longname:
                    continue

                for cOs in c.getOsInfoList():
                    if cOs == OsInfo(kitname, kitversion, kitarch):
                        raise KitAlreadyExists(
                            "OS kit [%s] already installed" % longname)

            # Kit exists, but doesn't have a matching os component
        except KitNotFound:
            pass

    def _create_kit_db_entry(self, session: Session, kit) -> Kit:
        """
        Creates a database entry for a kit.

        :param kit:
        :return: a Kit instance (TortugaObject)

        """
        try:
            return self._kit_db_api.getKit(session, kit['name'], kit['ver'])
        except KitNotFound:
            pass

        # Add the database entries for the kit
        kitObj = Kit(name=kit['name'], version=kit['ver'], iteration='0')
        kitObj.setDescription(kit['sum'])
        kitObj.setIsOs(True)
        kitObj.setIsRemovable(True)

        kit_descr = format_kit_descriptor(kit['name'], kit['ver'], kit['arch'])

        newComp = Component(name=kit_descr, version=kit['ver'])

        newComp.setDescription('%s mock component' % (kit_descr))

        newComp.addOsInfo(
            osHelper.getOsInfo(kit['name'], kit['ver'], kit['arch']))

        kitObj.addComponent(newComp)

        # Kit does not previously exist, perform 'normal' add kit operation
        self._kit_db_api.addKit(session, kitObj)

        return kitObj

    def installOsKit(self, session: Session, os_media_urls: List[str],
                     **kwargs) -> Kit:
        """

        :param os_media_urls:
        :param kwargs:
        :return:
        """
        media_list: List[dict] = self._processMediaspec(os_media_urls)

        os_distro = None
        kit_ops = None
        enable_proxy = False
        mount_manager = None

        is_interactive = kwargs['bInteractive'] \
            if 'bInteractive' in kwargs else False

        use_symlinks = kwargs['bUseSymlinks'] \
            if 'bUseSymlinks' in kwargs else False

        # If 'mirror' is True, treat 'mediaspec' as a mirror, instead of
        # specific OS version. This affects the stored OS version.
        is_mirror = kwargs['mirror'] if 'mirror' in kwargs else False

        media: dict = media_list[0]  # For now, remove support for multiple ISOs / mirrors.
        source_path = None
        mount_manager_source_path = None

        try:
            if use_symlinks:
                source_path = media['urlparse'].path
            elif 'localFilePath' in media:
                # Remote ISO file has been transferred locally and
                # filename is stored in 'localFilePath'

                mount_manager_source_path = media['localFilePath']
            else:
                pr = media['urlparse']

                if pr.scheme.lower() in ('http', 'https'):
                    # This is a proxy URL
                    source_path = pr.geturl()

                    enable_proxy = True
                elif not pr.scheme or pr.scheme.lower() == 'file':
                    if os.path.ismount(pr.path):
                        # Mount point specified
                        source_path = pr.path
                    else:
                        mount_manager_source_path = pr.path
                else:
                    raise UnrecognizedKitMedia(
                        'Unhandled URL scheme [%s]' % pr.scheme)

            # Mount source media, as necessary
            if mount_manager_source_path:
                mount_manager = MountManager(mount_manager_source_path)
                mount_manager.mountMedia()

                source_path = mount_manager.mountpoint

            if os_distro is None:
                # Determine the OS we're working with...
                os_distro = DistributionFactory(source_path)()
                if os_distro is None:
                    raise OsNotSupported('Could not match media')

                os_info = os_distro.get_os_info()

                # Check if OS is already installed before attempting to
                # perform copy operation...
                try:
                    self._checkExistingKit(
                        session,
                        os_info.getName(),
                        os_info.getVersion(),
                        os_info.getArch())
                except KitAlreadyExists:
                    if mount_manager_source_path:
                        mount_manager.unmountMedia()

                        if 'localFilePath' in media:
                            if os.path.exists(
                                    media['localFilePath']):
                                os.unlink(media['localFilePath'])

                    raise

                kit_ops_class = self._getKitOpsClass(
                    os_info.getOsFamilyInfo())

                kit_ops = kit_ops_class(
                    os_distro, bUseSymlinks=use_symlinks, mirror=is_mirror)

                kit = kit_ops.prepareOSKit()

            # Copy files here
            if enable_proxy:
                kit_ops.addProxy(source_path)
            else:
                descr = None

                if is_interactive:
                    descr = "Installing..."

                kit_ops.copyOsMedia(descr=descr)
        finally:
            if mount_manager_source_path:
                # MountManager instance exists.  Unmount any mounted
                # path
                mount_manager.unmountMedia()

                if 'localFilePath' in media:
                    if os.path.exists(media['localFilePath']):
                        os.unlink(media['localFilePath'])

        kit_object = self._create_kit_db_entry(session, kit)

        self._postInstallOsKit(session, kit_object)

        return kit_object

    def _postInstallOsKit(self, session: Session, kit):
        """
        Enable the OS component that may already be associated with an
        existing software profile.  This is possible when OS media is
        not available during installation/creation of software profiles
        """

        osComponents = kit.getComponentList()
        kitOsInfo = osComponents[0].getOsComponentList()[0].getOsInfo()

        # Load the newly added kit component from the database
        c = self._component_db_api.getComponent(
            session,
            osComponents[0].getName(),
            osComponents[0].getVersion(),
            kitOsInfo
        )

        # Iterate over all software profiles looking for matching OS
        for swProfile in \
                SoftwareProfileApi().getSoftwareProfileList(session):
            if swProfile.getOsInfo() != kitOsInfo:
                continue

            # Ensure OS component is enabled on this software profile
            try:
                self._component_db_api.addComponentToSoftwareProfile(
                    session, c.getId(), swProfile.getId())
            except SoftwareProfileComponentAlreadyExists:
                # Not an error...
                pass

    def _clean_kit_achiv_dir(self, kit, kit_dir):
        """
        Remove packages from the kit archive directory

        """
        component_list = kit.getComponentList()
        if not component_list:
            self._logger.debug('No components found')
            return

        for component in component_list:
            self._logger.debug(
                'Found component: {}'.format(component))

            for package in component.getPackageList():
                package_path = os.path.join(kit_dir,
                                            package.getRelativePath())

                if os.path.exists(package_path):
                    self._logger.debug(
                        'Deleting package: {}'.format(package_path))
                    os.remove(package_path)
                else:
                    self._logger.debug(
                        'Skipping non-existent package: {}'.format(
                            package_path))

    def deleteKit(self, session: Session, name, version=None, iteration=None,
                  force=False):
        """
        Delete a kit.

        :param session:   a database session
        :param name:      the kit name
        :param version:   the kit version
        :param iteration: the kit iteration
        :param force:     whether or not to force the deletion

        """
        kit = self.getKit(session, name, version, iteration)

        if kit.getIsOs():
            self._delete_os_kit(session, kit, force)

        else:
            self._delete_kit(session, kit, force)

        self._logger.info('Deleted kit: {}'.format(kit))

    def _delete_os_kit(self, session: Session, kit, force):
        """
        Deletes an OS kit.

        :param kit:   the Kit instance
        :param force: whether or not to force the deletion

        """
        self._cleanup_kit(session, kit, force)

    def _delete_kit(self, session: Session, kit: Kit, force: bool):
        """
        Deletes a regular kit.

        :param session: a database instance
        :param kit:     the Kit instance
        :param force:   whether or not to force the deletion

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

        #
        # If the kit does not exist in the DB, then we want to skip
        # the step of removing it from the DB
        #
        skip_db = False
        try:
            self.getKit(session, *kit_spec)
        except KitNotFound:
            skip_db = True

        kit_install_path = os.path.join(self._kits_root, kit.getDirName())
        if os.path.exists(kit_install_path):

            #
            # Attempt to get the kit installer
            #
            installer = None
            try:
                installer = get_kit_installer(kit_spec)()
                installer.session = session

            except KitNotFound:
                pass

            #
            # Attempt to run pre-uninstall action
            #
            if installer:
                try:
                    installer.run_action('pre_uninstall')
                except Exception as ex:
                    self._logger.warning(
                        'Error running pre_uninstall: {}'.format(
                            str(ex)
                        )
                    )

            #
            # Remove db record and files
            #
            self._cleanup_kit(session, kit, force, skip_db)

            #
            # Attempt to uninstall puppet modules, and perform post-install
            #
            if installer:
                try:
                    installer.run_action('uninstall_puppet_modules')
                except Exception as ex:
                    self._logger.warning(
                        'Error uninstalling puppet modules: {}'.format(
                            str(ex)
                        )
                    )
                try:
                    installer.run_action('post_uninstall')
                except Exception as ex:
                    self._logger.warning(
                        'Error running post-install: {}'.format(
                            str(ex)
                        )
                    )

    def _cleanup_kit(self, session: Session, kit: Kit, force: bool,
                     skip_db: bool = False):
        """
        Uninstalls the kit and it's file repos.

        :param session: a database session
        :param kit:     the Kit instance
        :param force:   whether or not to force the deletion

        """
        repo_dir = kit.getKitRepoDir()

        #
        # Remove the kit from the DB
        #
        if not skip_db:
            self._kit_db_api.deleteKit(session, kit.getName(),
                                       kit.getVersion(), kit.getIteration(),
                                       force=force)

        #
        # Remove the files and repo
        #
        for repo in repoManager.getRepoList():
            #
            # Delete the repo
            #
            repo.delete(repo_dir)

            #
            # Remove repo files
            #
            full_repo_dir = os.path.join(repo.getLocalPath(), repo_dir)
            self._logger.debug(
                'Removing repo dir: {}'.format(full_repo_dir))
            #
            # When LINKOSKITMEDIA is used, the kit directory is a symlink
            # to the real media, delete the link instead of attempting
            # to delete the directory.
            #
            if os.path.islink(full_repo_dir):
                os.unlink(full_repo_dir)
            else:
                osUtility.removeDir(full_repo_dir)

        #
        # Check and clean up proxy
        #
        self.remove_proxy(repo_dir)

        #
        # Remove the kit installation dir
        #
        kit_dir = os.path.join(self._kits_root, kit.getDirName())
        if os.path.exists(kit_dir):
            self._logger.debug(
                'Removing kit installation directory: {}'.format(kit_dir))
            osUtility.removeDir(kit_dir)

    def remove_proxy(self, repoDir):
        # Check for this repo as an actual entry in the tortuga apache.conf
        # file to remove

        config = configparser.ConfigParser()

        cfgfile = os.path.join(
            self._config_manager.getKitConfigBase(),
            'base/apache-component.conf'
        )

        config.read(cfgfile)

        paths = config.get('cache', 'cache_path_list').split()

        newPaths = paths[:]

        for p in paths:
            if p.endswith(repoDir):
                # Remove this dir from the cache
                newPaths.remove(p)

        if config.has_option('proxy', 'proxy_list'):
            proxies = config.get('proxy', 'proxy_list').split()

            if repoDir in ' '.join(proxies):
                # Remove this repository from the proxy
                for entry in proxies[:]:
                    if repoDir in entry:
                        config.remove_option('proxy', entry)
                        proxies.remove(entry)
        else:
            proxies = []

        # Update apache configuration
        config.set('cache', 'cache_path_list', '\n'.join(newPaths))
        config.set('proxy', 'proxy_list', ' '.join(proxies))

        with open(cfgfile, 'w') as fp:
            config.write(fp)

    def configureProxy(self, medialoc, repoDir):
        config = configparser.ConfigParser()

        cfgfile = os.path.join(
            self._config_manager.getKitConfigBase(), 'base/apache-component.conf')

        config.read(cfgfile)

        proxies = []

        paths = []

        if config.has_section('cache') and \
                config.has_option('cache', 'cache_path_list'):
            paths = config.get('cache', 'cache_path_list').split()

        if repoDir not in paths:
            paths.append(repoDir)

        if not config.has_section('cache'):
            config.add_section('cache')

        config.set('cache', 'cache_path_list', '\n'.join(paths))

        if config.has_section('proxy') and \
                config.has_option('proxy', 'proxy_list'):
            proxies = config.get('proxy', 'proxy_list').split()

        if repoDir not in proxies:
            proxies.append(repoDir)

        if not config.has_section('proxy'):
            config.add_section('proxy')

        config.set('proxy', 'proxy_list', ' '.join(proxies))
        config.set('proxy', repoDir, medialoc)

        with open(cfgfile, 'w') as fp:
            config.write(fp)
Пример #7
0
class SoftwareProfileManager(TortugaObjectManager, Singleton):

    BASE_KIT_NAME = 'base'

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

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

        return self._sp_db_api.getSoftwareProfileList(tags=tags)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        baseKit = None

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

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

        baseComp = None

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

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

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

        comp.setKit(baseKit)

        return comp

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

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

            return node.getSoftwareProfile().getOsInfo()

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

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

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

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

        components = kit.getComponentList()

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

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

        return osinfo_list[0]

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

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

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

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

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

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

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

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

                    raise
            else:
                swProfileSpec.setOsInfo(osInfo)

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

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

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

            bFoundOsComponent = False
            bFoundCoreComponent = False
            components = []

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

                k = cobj.getKit()

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

                components.append(cobj)

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

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

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

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

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

                try:
                    comp = self._getCoreComponentForOsInfo(osInfo)

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

                    components.append(comp)
                except ComponentNotFound:
                    pass

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

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

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

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

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

                    continue

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

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

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

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

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

        return comp

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

        :raises KitNotFound:
        :raises ComponentNotFound:

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

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

        return kits[0]

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

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

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

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

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

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

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

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

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

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

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

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

        return kit, comp_version

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

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

        :return:                 the Component instance that was enabled

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

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

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

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

        return best_match_component

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

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

        :return:                 the Component instance that was enabled

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

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

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

        :return:                 the Component instance that was added

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

        return best_match_component

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

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

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

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

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

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

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

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

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

        :return:                 the Component instance that was disabled

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

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

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

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

        return best_match_component

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

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

        :return:                 the Component instance that was disabled

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

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

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

        :return:                 the Component instance that was removed

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

        return best_match_component

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

        Raises:
            SoftwareProfileNotFound
        """

        self._sp_db_api.deleteSoftwareProfile(name)

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

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

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

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

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

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

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

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

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

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

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

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

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

        return softwareProfile

    def getUsableNodes(self, softwareProfileName):
        return self._sp_db_api.getUsableNodes(softwareProfileName)
Пример #8
0
class SoftwareProfileManager(TortugaObjectManager):     \
        # pylint: disable=too-many-public-methods

    BASE_KIT_NAME = 'base'

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

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

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

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

        return results

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

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

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

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

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

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

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

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

        return software_profile_obj

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

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

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

        return software_profile_obj

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

        baseKit = None

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

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

        baseComp = None

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

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

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

        comp.setKit(baseKit)

        return comp

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

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

            return node.getSoftwareProfile().getOsInfo()

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

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

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

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

        components = kit.getComponentList()

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

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

        return osinfo_list[0]

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

        if settingsDict == None:
            settingsDict = {}

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

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

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

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

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

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

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

                    raise
            else:
                swProfileSpec.setOsInfo(osInfo)

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

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

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

            bFoundOsComponent = False
            bFoundCoreComponent = False
            components = []

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

                k = cobj.getKit()

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

                components.append(cobj)

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

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

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

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

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

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

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

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

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

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

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

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

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

                    continue

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

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

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

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

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

        return comp

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

        :raises KitNotFound:
        :raises ComponentNotFound:

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

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

        return kits[0]

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

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

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

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

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

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

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

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

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

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

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

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

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

        return kit, comp_version

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

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

        :return:                 the Component instance that was enabled

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

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

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

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

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

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

        return best_match_component

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

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

        :return:                 the Component instance that was enabled

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

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

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

        :return:                 the Component instance that was added

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

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

        return best_match_component

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

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

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

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

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

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

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

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

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

        :return:                 the Component instance that was disabled

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

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

        comp_installer = installer.get_component_installer(comp_name)

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

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

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

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

        return best_match_component

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

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

        :return:                 the Component instance that was disabled

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

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

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

        :return:                 the Component instance that was removed

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

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

        return best_match_component

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

        Raises:
            SoftwareProfileNotFound
        """

        self._sp_db_api.deleteSoftwareProfile(session, name)

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

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

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

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

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

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

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

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

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

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

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

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

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

        metadata: Dict[str, str] = {}

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

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

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

            if item:
                metadata.update(item)

        return metadata
Пример #9
0
def test_getKitList(dbm):
    with dbm.session() as session:
        result = KitDbApi().getKitList(session)

    assert isinstance(result, TortugaObjectList)