示例#1
0
    def register(self,
                 manifest: Manifest,
                 state: str = 'disabled',
                 owner: str = 'local'):
        """ Register feature in CONFIG DBs.

        Args:
            manifest: Feature's manifest.
            state: Desired feature admin state.
            owner: Owner of this feature (kube/local).
        Returns:
            None.
        """

        name = manifest['service']['name']
        db_connectors = self._sonic_db.get_connectors()
        cfg_entries = self.get_default_feature_entries(state, owner)
        non_cfg_entries = self.get_non_configurable_feature_entries(manifest)

        for conn in db_connectors:
            current_cfg = conn.get_entry(FEATURE, name)

            new_cfg = cfg_entries.copy()
            # Override configurable entries with CONFIG DB data.
            new_cfg = {**new_cfg, **current_cfg}
            # Override CONFIG DB data with non configurable entries.
            new_cfg = {**new_cfg, **non_cfg_entries}

            conn.set_entry(FEATURE, name, new_cfg)

        if self.register_auto_ts(name):
            log.info(f'{name} entry is added to {AUTO_TS_FEATURE} table')
示例#2
0
        def migrate_package(old_package_entry, new_package_entry):
            """ Migrate package routine

            Args:
                old_package_entry: Entry in old package database.
                new_package_entry: Entry in new package database.
            """

            name = new_package_entry.name
            version = new_package_entry.version

            if dockerd_sock:
                # dockerd_sock is defined, so use docked_sock to connect to
                # dockerd and fetch package image from it.
                log.info(f'installing {name} from old docker library')
                docker_api = DockerApi(
                    docker.DockerClient(base_url=f'unix://{dockerd_sock}'))

                image = docker_api.get_image(old_package_entry.image_id)

                with tempfile.NamedTemporaryFile('wb') as file:
                    for chunk in image.save(named=True):
                        file.write(chunk)
                    file.flush()

                    self.install(tarball=file.name)
            else:
                log.info(f'installing {name} version {version}')

                self.install(f'{name}={version}')
示例#3
0
    def update(self, old_manifest: Manifest, new_manifest: Manifest):
        """ Migrate feature configuration. It can be that non-configurable
        feature entries have to be updated. e.g: "has_timer" for example if
        the new feature introduces a service timer or name of the service has
        changed, but user configurable entries are not changed).

        Args:
            old_manifest: Old feature manifest.
            new_manifest: New feature manifest.
        Returns:
            None
        """

        old_name = old_manifest['service']['name']
        new_name = new_manifest['service']['name']
        db_connectors = self._sonic_db.get_connectors()
        non_cfg_entries = self.get_non_configurable_feature_entries(
            new_manifest)

        for conn in db_connectors:
            current_cfg = conn.get_entry(FEATURE, old_name)
            conn.set_entry(FEATURE, old_name, None)

            new_cfg = current_cfg.copy()
            # Override CONFIG DB data with non configurable entries.
            new_cfg = {**new_cfg, **non_cfg_entries}

            conn.set_entry(FEATURE, new_name, new_cfg)

        if self.register_auto_ts(new_name, old_name):
            log.info(f'{new_name} entry is added to {AUTO_TS_FEATURE} table')
示例#4
0
    def generate_dump_script(self, package):
        """ Generates dump plugin script for package.

        Args:
            package: Package object to generate dump plugin script for.
        Returns:
            None.
        """

        name = package.manifest['service']['name']

        if not package.manifest['package']['debug-dump']:
            return

        if not os.path.exists(DEBUG_DUMP_SCRIPT_LOCATION):
            os.mkdir(DEBUG_DUMP_SCRIPT_LOCATION)

        scrip_template = os.path.join(TEMPLATES_PATH,
                                      DEBUG_DUMP_SCRIPT_TEMPLATE)
        script_path = os.path.join(DEBUG_DUMP_SCRIPT_LOCATION, f'{name}')
        render_ctx = {
            'source': get_tmpl_path(SERVICE_MGMT_SCRIPT_TEMPLATE),
            'manifest': package.manifest.unmarshal(),
        }
        render_template(scrip_template,
                        script_path,
                        render_ctx,
                        executable=True)
        log.info(f'generated {script_path}')
示例#5
0
    def _set_feature_state(self,
                           package: Package,
                           state: str,
                           block: bool = True):
        """ Sets the feature state and blocks till operation is finished if
        block argument is set to True.

        Args:
            package: Package object of the feature that will be stopped.
            state: Feature state to set.
            block: Whether to block for operation completion.
        """

        if in_chroot():
            return

        # import from here otherwise this import will fail when executing
        # sonic-package-manager from chroot environment as "config" package
        # tries accessing database at import time.
        from config.feature import set_feature_state

        feature_name = package.manifest['service']['name']
        log.info('{} {}'.format(
            state.replace('ed', 'ing').capitalize(), feature_name))
        cfgdb_clients = {'': self.sonic_db.get_running_db_connector()}
        set_feature_state(cfgdb_clients, feature_name, state, block)
示例#6
0
def remove_if_exists(path):
    """ Remove filepath if it exists """

    if not os.path.exists(path):
        return

    os.remove(path)
    log.info(f'removed {path}')
示例#7
0
 def generate_service_mgmt(self, package: Package):
     name = package.manifest['service']['name']
     multi_instance_services = self.feature_registry.get_multi_instance_features(
     )
     script_path = os.path.join(SERVICE_MGMT_SCRIPT_LOCATION, f'{name}.sh')
     scrip_template = get_tmpl_path(SERVICE_MGMT_SCRIPT_TEMPLATE)
     render_ctx = {
         'source': get_tmpl_path(SERVICE_MGMT_SCRIPT_TEMPLATE),
         'manifest': package.manifest.unmarshal(),
         'multi_instance_services': multi_instance_services,
     }
     render_template(scrip_template,
                     script_path,
                     render_ctx,
                     executable=True)
     log.info(f'generated {script_path}')
示例#8
0
    def generate_container_mgmt(self, package: Package):
        """ Generates container management script under /usr/bin/<service>.sh for package.

        Args:
            package: Package object to generate script for.
        Returns:
            None
        """

        image_id = package.image_id
        name = package.manifest['service']['name']
        container_spec = package.manifest['container']
        script_path = os.path.join(DOCKER_CTL_SCRIPT_LOCATION, f'{name}.sh')
        script_template = get_tmpl_path(DOCKER_CTL_SCRIPT_TEMPLATE)
        run_opt = []

        if container_spec['privileged']:
            run_opt.append('--privileged')

        run_opt.append('-t')

        for volume in container_spec['volumes']:
            run_opt.append(f'-v {volume}')

        for mount in container_spec['mounts']:
            mount_type, source, target = mount['type'], mount['source'], mount[
                'target']
            run_opt.append(
                f'--mount type={mount_type},source={source},target={target}')

        for tmpfs_mount in container_spec['tmpfs']:
            run_opt.append(f'--tmpfs {tmpfs_mount}')

        for env_name, value in container_spec['environment'].items():
            run_opt.append(f'-e {env_name}={value}')

        run_opt = ' '.join(run_opt)
        render_ctx = {
            'docker_container_name': name,
            'docker_image_id': image_id,
            'docker_image_run_opt': run_opt,
        }
        render_template(script_template,
                        script_path,
                        render_ctx,
                        executable=True)
        log.info(f'generated {script_path}')
示例#9
0
    def generate_systemd_service(self, package: Package):
        """ Generates systemd service(s) file and timer(s) (if needed) for package.

        Args:
            package: Package object to generate service for.
        Returns:
            None
        """

        name = package.manifest['service']['name']
        multi_instance_services = self.feature_registry.get_multi_instance_features(
        )

        template = get_tmpl_path(SERVICE_FILE_TEMPLATE)
        template_vars = {
            'source': get_tmpl_path(SERVICE_FILE_TEMPLATE),
            'manifest': package.manifest.unmarshal(),
            'multi_instance': False,
            'multi_instance_services': multi_instance_services,
        }
        output_file = os.path.join(SYSTEMD_LOCATION, f'{name}.service')
        render_template(template, output_file, template_vars)
        log.info(f'generated {output_file}')

        if package.manifest['service']['asic-service']:
            output_file = os.path.join(SYSTEMD_LOCATION, f'{name}@.service')
            template_vars['multi_instance'] = True
            render_template(template, output_file, template_vars)
            log.info(f'generated {output_file}')

        if package.manifest['service']['delayed']:
            template_vars = {
                'source': get_tmpl_path(TIMER_UNIT_TEMPLATE),
                'manifest': package.manifest.unmarshal(),
                'multi_instance': False,
            }
            output_file = os.path.join(SYSTEMD_LOCATION, f'{name}.timer')
            template = os.path.join(TEMPLATES_PATH, TIMER_UNIT_TEMPLATE)
            render_template(template, output_file, template_vars)
            log.info(f'generated {output_file}')

            if package.manifest['service']['asic-service']:
                output_file = os.path.join(SYSTEMD_LOCATION, f'{name}@.timer')
                template_vars['multi_instance'] = True
                render_template(template, output_file, template_vars)
                log.info(f'generated {output_file}')
示例#10
0
    def generate_service_mgmt(self, package: Package):
        """ Generates service management script under /usr/local/bin/<service>.sh for package.

        Args:
            package: Package object to generate script for.
        Returns:
            None
        """

        name = package.manifest['service']['name']
        multi_instance_services = self.feature_registry.get_multi_instance_features(
        )
        script_path = os.path.join(SERVICE_MGMT_SCRIPT_LOCATION, f'{name}.sh')
        scrip_template = get_tmpl_path(SERVICE_MGMT_SCRIPT_TEMPLATE)
        render_ctx = {
            'source': get_tmpl_path(SERVICE_MGMT_SCRIPT_TEMPLATE),
            'manifest': package.manifest.unmarshal(),
            'multi_instance_services': multi_instance_services,
        }
        render_template(scrip_template,
                        script_path,
                        render_ctx,
                        executable=True)
        log.info(f'generated {script_path}')
示例#11
0
    def migrate_packages(self,
                         old_package_database: PackageDatabase,
                         dockerd_sock: Optional[str] = None):
        """
        Migrate packages from old database. This function can do a comparison between
        current database and the database passed in as argument. If the package is
        missing in the current database it will be added. If the package is installed
        in the passed database and in the current it is not installed it will be
        installed with a passed database package version. If the package is installed
        in the passed database and it is installed in the current database but with
        older version the package will be upgraded to the never version. If the package
        is installed in the passed database and in the current it is installed but with
        never version - no actions are taken. If dockerd_sock parameter is passed, the
        migration process will use loaded images from docker library of the currently
        installed image.

        Args:
            old_package_database: SONiC Package Database to migrate packages from.
            dockerd_sock: Path to dockerd socket.
        Raises:
            PackageManagerError
        """

        self._migrate_package_database(old_package_database)

        def migrate_package(old_package_entry, new_package_entry):
            """ Migrate package routine

            Args:
                old_package_entry: Entry in old package database.
                new_package_entry: Entry in new package database.
            """

            name = new_package_entry.name
            version = new_package_entry.version

            if dockerd_sock:
                # dockerd_sock is defined, so use docked_sock to connect to
                # dockerd and fetch package image from it.
                log.info(f'installing {name} from old docker library')
                docker_api = DockerApi(
                    docker.DockerClient(base_url=f'unix://{dockerd_sock}'))

                image = docker_api.get_image(old_package_entry.image_id)

                with tempfile.NamedTemporaryFile('wb') as file:
                    for chunk in image.save(named=True):
                        file.write(chunk)
                    file.flush()

                    self.install(tarball=file.name)
            else:
                log.info(f'installing {name} version {version}')

                self.install(f'{name}={version}')

        # TODO: Topological sort packages by their dependencies first.
        for old_package in old_package_database:
            if not old_package.installed or old_package.built_in:
                continue

            log.info(f'migrating package {old_package.name}')

            new_package = self.database.get_package(old_package.name)

            if new_package.installed:
                if old_package.version > new_package.version:
                    log.info(f'{old_package.name} package version is greater '
                             f'then installed in new image: '
                             f'{old_package.version} > {new_package.version}')
                    log.info(
                        f'upgrading {new_package.name} to {old_package.version}'
                    )
                    new_package.version = old_package.version
                    migrate_package(old_package, new_package)
                else:
                    log.info(
                        f'skipping {new_package.name} as installed version is newer'
                    )
            elif new_package.default_reference is not None:
                new_package_ref = PackageReference(
                    new_package.name, new_package.default_reference)
                package_source = self.get_package_source(
                    package_ref=new_package_ref)
                package = package_source.get_package()
                new_package_default_version = package.manifest['package'][
                    'version']
                if old_package.version > new_package_default_version:
                    log.info(
                        f'{old_package.name} package version is lower '
                        f'then the default in new image: '
                        f'{old_package.version} > {new_package_default_version}'
                    )
                    new_package.version = old_package.version
                    migrate_package(old_package, new_package)
                else:
                    self.install(
                        f'{new_package.name}={new_package_default_version}')
            else:
                # No default version and package is not installed.
                # Migrate old package same version.
                new_package.version = old_package.version
                migrate_package(old_package, new_package)

            self.database.commit()
示例#12
0
 def remove_file(path):
     if os.path.exists(path):
         os.remove(path)
         log.info(f'removed {path}')
示例#13
0
 def service_exists(service):
     for package in packages.values():
         if package.manifest['service']['name'] == service:
             return True
     log.info(f'Service {service} is not installed, it is skipped...')
     return False