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')
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}')
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')
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}')
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)
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}')
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}')
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}')
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}')
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}')
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()
def remove_file(path): if os.path.exists(path): os.remove(path) log.info(f'removed {path}')
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