def test_database_update_package(fake_db): test_package = fake_db.get_package('test-package-2') test_package.installed = True test_package.version = Version.parse('1.2.3') fake_db.update_package(test_package) test_package = fake_db.get_package('test-package-2') assert test_package.installed assert test_package.version == Version.parse('1.2.3')
def test_constraint_multiple(): package_constraint = PackageConstraint.parse('swss>1.2.0,<3.0.0,!=2.2.2') assert package_constraint.name == 'swss' assert not package_constraint.constraint.allows(Version.parse('2.2.2')) assert not package_constraint.constraint.allows(Version.parse('3.2.0')) assert not package_constraint.constraint.allows(Version.parse('0.2.0')) assert package_constraint.constraint.allows(Version.parse('2.2.3')) assert package_constraint.constraint.allows(Version.parse('1.2.3'))
def __init__(self): self.metadata_store = {} self.add('docker-database', 'latest', 'database', '1.0.0') self.add( 'docker-orchagent', 'latest', 'swss', '1.0.0', components={ 'libswsscommon': Version.parse('1.0.0'), 'libsairedis': Version.parse('1.0.0') }, warm_shutdown={ 'before': ['syncd'], }, fast_shutdown={ 'before': ['syncd'], }, processes=[{ 'name': 'orchagent', 'reconciles': True, }, { 'name': 'neighsyncd', 'reconciles': True, }], ) self.add('docker-syncd', 'latest', 'syncd', '1.0.0') self.add('docker-teamd', 'latest', 'teamd', '1.0.0', components={ 'libswsscommon': Version.parse('1.0.0'), 'libsairedis': Version.parse('1.0.0') }, warm_shutdown={ 'before': ['syncd'], 'after': ['swss'], }, fast_shutdown={ 'before': ['swss'], }) self.add('Azure/docker-test', '1.6.0', 'test-package', '1.6.0') self.add('Azure/docker-test-2', '1.5.0', 'test-package-2', '1.5.0') self.add('Azure/docker-test-2', '2.0.0', 'test-package-2', '2.0.0') self.add('Azure/docker-test-3', 'latest', 'test-package-3', '1.6.0') self.add('Azure/docker-test-3', '1.5.0', 'test-package-3', '1.5.0') self.add('Azure/docker-test-3', '1.6.0', 'test-package-3', '1.6.0') self.add('Azure/docker-test-4', '1.5.0', 'test-package-4', '1.5.0') self.add('Azure/docker-test-5', '1.5.0', 'test-package-5', '1.5.0') self.add('Azure/docker-test-5', '1.9.0', 'test-package-5', '1.9.0') self.add('Azure/docker-test-6', '1.5.0', 'test-package-6', '1.5.0') self.add('Azure/docker-test-6', '1.9.0', 'test-package-6', '1.9.0') self.add('Azure/docker-test-6', '2.0.0', 'test-package-6', '2.0.0') self.add('Azure/docker-test-6', 'latest', 'test-package-6', '1.5.0')
def test_manager_package_reset(package_manager, sonic_fs): package_manager.install('test-package-6=1.5.0') package_manager.install('test-package-6=2.0.0') package_manager.reset('test-package-6') upgraded_package = package_manager.get_installed_package('test-package-6') assert upgraded_package.entry.version == Version.parse('1.5.0')
def test_database_get_package(fake_db): swss_package = fake_db.get_package('swss') assert swss_package.installed assert swss_package.built_in assert swss_package.repository == 'docker-orchagent' assert swss_package.default_reference == '1.0.0' assert swss_package.version == Version.parse('1.0.0')
def test_constraint_from_dict(): package_constraint = PackageConstraint.parse({ 'name': 'swss', 'version': '^1.0.0', 'components': { 'libswsscommon': '^1.1.0', }, }) assert package_constraint.name == 'swss' assert package_constraint.constraint.allows(Version.parse('1.0.0')) assert not package_constraint.constraint.allows(Version.parse('2.0.0')) assert package_constraint.components['libswsscommon'].allows( Version.parse('1.2.0')) assert not package_constraint.components['libswsscommon'].allows( Version.parse('1.0.0')) assert not package_constraint.components['libswsscommon'].allows( Version.parse('2.0.0'))
def test_manager_upgrade(package_manager, sonic_fs): package_manager.install('test-package-6=1.5.0') package = package_manager.get_installed_package('test-package-6') package_manager.install('test-package-6=2.0.0') upgraded_package = package_manager.get_installed_package('test-package-6') assert upgraded_package.entry.version == Version.parse('2.0.0') assert upgraded_package.entry.default_reference == package.entry.default_reference
def __init__(self): self.metadata_store = {} self.add('docker-database', 'latest', 'database', '1.0.0') self.add('docker-orchagent', 'latest', 'swss', '1.0.0', components={ 'libswsscommon': Version.parse('1.0.0'), 'libsairedis': Version.parse('1.0.0') } ) self.add('Azure/docker-test', '1.6.0', 'test-package', '1.6.0') self.add('Azure/docker-test-2', '1.5.0', 'test-package-2', '1.5.0') self.add('Azure/docker-test-2', '2.0.0', 'test-package-2', '2.0.0') self.add('Azure/docker-test-3', 'latest', 'test-package-3', '1.6.0') self.add('Azure/docker-test-3', '1.5.0', 'test-package-3', '1.5.0') self.add('Azure/docker-test-3', '1.6.0', 'test-package-3', '1.6.0') self.add('Azure/docker-test-4', '1.5.0', 'test-package-4', '1.5.0') self.add('Azure/docker-test-5', '1.5.0', 'test-package-5', '1.5.0') self.add('Azure/docker-test-5', '1.9.0', 'test-package-5', '1.9.0') self.add('Azure/docker-test-6', '1.5.0', 'test-package-6', '1.5.0') self.add('Azure/docker-test-6', '1.9.0', 'test-package-6', '1.9.0') self.add('Azure/docker-test-6', '2.0.0', 'test-package-6', '2.0.0') self.add('Azure/docker-test-6', 'latest', 'test-package-6', '1.5.0')
def package_from_dict(name: str, package_info: Dict) -> PackageEntry: """ Parse dictionary into PackageEntry object.""" repository = package_info.get('repository') description = package_info.get('description') default_reference = package_info.get('default-reference') version = package_info.get('installed-version') if version: version = Version.parse(version) installed = package_info.get('installed', False) built_in = package_info.get('built-in', False) image_id = package_info.get('image-id') return PackageEntry(name, repository, description, default_reference, version, installed, built_in, image_id)
def test_installation_components_dependencies_satisfied( package_manager, fake_metadata_resolver): metadata = fake_metadata_resolver.metadata_store['Azure/docker-test'][ '1.6.0'] manifest = metadata['manifest'] metadata['components'] = {'libswsscommon': Version.parse('1.1.0')} manifest['package']['depends'] = [ { 'name': 'swss', 'version': '>=1.0.0', 'components': { 'libswsscommon': '^1.0.0', }, }, ] package_manager.install('test-package')
def test_installation_components_dependencies_implicit(package_manager, fake_metadata_resolver): metadata = fake_metadata_resolver.metadata_store['Azure/docker-test'][ '1.6.0'] manifest = metadata['manifest'] metadata['components'] = {'libswsscommon': Version.parse('2.1.0')} manifest['package']['depends'] = [ { 'name': 'swss', 'version': '>=1.0.0', }, ] with pytest.raises( PackageInstallationError, match='Package test-package requires libswsscommon >=2.1.0,<3.0.0 ' 'in package swss>=1.0.0 but version 1.0.0 is installed'): package_manager.install('test-package')
def validate_package_base_os_constraints(package: Package, sonic_version_info: Dict[str, str]): """ Verify that all dependencies on base OS components are met. Args: package: Package to check constraints for. sonic_version_info: SONiC components version information. Raises: PackageSonicRequirementError: in case dependency is not satisfied. """ base_os_constraints = package.manifest['package']['base-os'].components for component, constraint in base_os_constraints.items(): if component not in sonic_version_info: raise PackageSonicRequirementError(package.name, component, constraint) version = Version.parse(sonic_version_info[component]) if not constraint.allows_all(version): raise PackageSonicRequirementError(package.name, component, constraint, version)
def from_labels(cls, labels: Dict[str, str]) -> Metadata: """ Get manifest from image labels. Args: labels: key, value string pairs Returns: Metadata Raises: MetadataError """ metadata_dict = translate_plain_to_tree(labels) try: sonic_metadata = metadata_dict['com']['azure']['sonic'] except KeyError: raise MetadataError('No metadata found in image labels') try: manifest_string = sonic_metadata['manifest'] except KeyError: raise MetadataError('No manifest found in image labels') try: manifest_dict = json.loads(manifest_string) except (ValueError, TypeError) as err: raise MetadataError(f'Failed to parse manifest JSON: {err}') components = {} if 'versions' in sonic_metadata: for component, version in sonic_metadata['versions'].items(): try: components[component] = Version.parse(version) except ValueError as err: raise MetadataError( f'Failed to parse component version: {err}') yang_module_str = sonic_metadata.get('yang-module') return Metadata(Manifest.marshal(manifest_dict), components, yang_module_str)
class ManifestSchema: """ ManifestSchema class describes and provides marshalling and unmarshalling methods. """ class Marshaller: """ Base class for marshaling and un-marshaling. """ def marshal(self, value): """ Validates and returns a valid manifest dictionary. Args: value: input value to validate. Returns: valid manifest node. """ raise NotImplementedError def unmarshal(self, value): """ Un-marshals the manifest to a dictionary. Args: value: input value to validate. Returns: valid manifest node. """ raise NotImplementedError @dataclass class ParsedMarshaller(Marshaller): """ Marshaller used on types which support class method "parse" """ type: Any def marshal(self, value): try: return self.type.parse(value) except ValueError as err: raise ManifestError(f'Failed to marshal {value}: {err}') def unmarshal(self, value): try: if hasattr(value, 'deparse'): return value.deparse() return str(value) except Exception as err: raise ManifestError(f'Failed to unmarshal {value}: {err}') @dataclass class DefaultMarshaller(Marshaller): """ Default marshaller that validates if the given value is instance of given type. """ type: type def marshal(self, value): if not isinstance(value, self.type): raise ManifestError(f'{value} is not of type {self.type.__name__}') return value def unmarshal(self, value): return value @dataclass class ManifestNode(Marshaller, ABC): """ Base class for any manifest object. Attrs: key: String representing the key for this object. """ key: str @dataclass class ManifestRoot(ManifestNode): items: List def marshal(self, value: Optional[dict]): result = {} value = value or {} if not isinstance(value, dict): raise ManifestError(f'"{self.key}" field has to be a dictionary') for item in self.items: next_value = value.get(item.key) result[item.key] = item.marshal(next_value) return result def unmarshal(self, value): return_value = {} for item in self.items: return_value[item.key] = item.unmarshal(value[item.key]) return return_value @dataclass class ManifestField(ManifestNode): type: Any default: Optional[Any] = None def marshal(self, value): if value is None: if self.default is not None: return self.default raise ManifestError(f'"{self.key}" is a required field but it is missing') try: return_value = self.type.marshal(value) except Exception as err: raise ManifestError(f'Failed to marshal {self.key}: {err}') return return_value def unmarshal(self, value): return self.type.unmarshal(value) @dataclass class ManifestArray(ManifestNode): type: Any def marshal(self, value): return_value = [] value = value or [] if not isinstance(value, list): raise ManifestError(f'"{self.key}" has to be of type list') try: for item in value: return_value.append(self.type.marshal(item)) except Exception as err: raise ManifestError(f'Failed to convert {self.key}={value} to array: {err}') return return_value def unmarshal(self, value): return [self.type.unmarshal(item) for item in value] # TODO: add description for each field SCHEMA = ManifestRoot('root', [ ManifestField('version', ParsedMarshaller(Version), Version.parse('1.0.0')), ManifestRoot('package', [ ManifestField('version', ParsedMarshaller(Version)), ManifestField('name', DefaultMarshaller(str)), ManifestField('description', DefaultMarshaller(str), ''), ManifestField('base-os', ParsedMarshaller(ComponentConstraints), ComponentConstraints()), ManifestArray('depends', ParsedMarshaller(PackageConstraint)), ManifestArray('breaks', ParsedMarshaller(PackageConstraint)), ManifestField('init-cfg', DefaultMarshaller(dict), dict()), ManifestField('changelog', DefaultMarshaller(dict), dict()), ManifestField('debug-dump', DefaultMarshaller(str), ''), ]), ManifestRoot('service', [ ManifestField('name', DefaultMarshaller(str)), ManifestArray('requires', DefaultMarshaller(str)), ManifestArray('requisite', DefaultMarshaller(str)), ManifestArray('wanted-by', DefaultMarshaller(str)), ManifestArray('after', DefaultMarshaller(str)), ManifestArray('before', DefaultMarshaller(str)), ManifestArray('dependent', DefaultMarshaller(str)), ManifestArray('dependent-of', DefaultMarshaller(str)), ManifestField('post-start-action', DefaultMarshaller(str), ''), ManifestField('pre-shutdown-action', DefaultMarshaller(str), ''), ManifestField('asic-service', DefaultMarshaller(bool), False), ManifestField('host-service', DefaultMarshaller(bool), True), ManifestField('delayed', DefaultMarshaller(bool), False), ManifestRoot('warm-shutdown', [ ManifestArray('after', DefaultMarshaller(str)), ManifestArray('before', DefaultMarshaller(str)), ]), ManifestRoot('fast-shutdown', [ ManifestArray('after', DefaultMarshaller(str)), ManifestArray('before', DefaultMarshaller(str)), ]), ]), ManifestRoot('container', [ ManifestField('privileged', DefaultMarshaller(bool), False), ManifestArray('volumes', DefaultMarshaller(str)), ManifestArray('mounts', ManifestRoot('mounts', [ ManifestField('source', DefaultMarshaller(str)), ManifestField('target', DefaultMarshaller(str)), ManifestField('type', DefaultMarshaller(str)), ])), ManifestField('environment', DefaultMarshaller(dict), dict()), ManifestArray('tmpfs', DefaultMarshaller(str)), ]), ManifestArray('processes', ManifestRoot('processes', [ ManifestField('name', DefaultMarshaller(str)), ManifestField('reconciles', DefaultMarshaller(bool), False), ])), ManifestRoot('cli', [ ManifestField('mandatory', DefaultMarshaller(bool), False), ManifestField('show', DefaultMarshaller(str), ''), ManifestField('config', DefaultMarshaller(str), ''), ManifestField('clear', DefaultMarshaller(str), '') ]) ])
def test_invalid_version(invalid_version): with pytest.raises(Exception): Version.parse(invalid_version)
def test_version_comparison(newer, older): assert Version.parse(newer) > Version.parse(older)
def test_version_to_tag(): assert version.version_to_tag(Version.parse('1.0.0-rc0')) == '1.0.0-rc0' assert version.version_to_tag( Version.parse('1.0.0-rc0+152')) == '1.0.0-rc0_152'
def test_constraint(): package_constraint = PackageConstraint.parse('swss>1.0.0') assert package_constraint.name == 'swss' assert not package_constraint.constraint.allows(Version.parse('0.9.1')) assert package_constraint.constraint.allows(Version.parse('1.1.1'))
def test_constraint_range(): package_constraint = PackageConstraint.parse('swss^1.2.0') assert package_constraint.name == 'swss' assert not package_constraint.constraint.allows(Version.parse('1.1.1')) assert package_constraint.constraint.allows(Version.parse('1.2.5')) assert not package_constraint.constraint.allows(Version.parse('2.0.1'))
def test_constraint_match(): package_constraint = PackageConstraint.parse('swss==1.2.*') assert package_constraint.name == 'swss' assert not package_constraint.constraint.allows(Version.parse('1.1.1')) assert package_constraint.constraint.allows(Version.parse('1.2.0'))