diff --git a/devstack/distro.py b/devstack/distro.py new file mode 100644 index 00000000..1f8000fc --- /dev/null +++ b/devstack/distro.py @@ -0,0 +1,106 @@ +import glob +import os +import platform +import re + +import yaml + +from devstack import importer +from devstack import log as logging +from devstack import settings +from devstack import utils + + +LOG = logging.getLogger('devstack.distro') + +DISTRO_CONF_DIR = os.path.join(settings.STACK_CONFIG_DIR, 'distros') + + +class Distro(object): + + @classmethod + def load_all(cls, path=DISTRO_CONF_DIR): + """Returns a list of the known distros.""" + results = [] + input_files = glob.glob(os.path.join(DISTRO_CONF_DIR, '*.yaml')) + if not input_files: + raise RuntimeError( + 'Did not find any distro definition files in %s' % + DISTRO_CONF_DIR) + for filename in input_files: + try: + with open(filename, 'r') as f: + data = yaml.load(f) + results.append(cls(**data)) + except Exception as err: + LOG.warning('Could not load distro definition from %s: %s', + filename, err) + return results + + @classmethod + def get_current(cls): + """Returns a Distro instance configured for the current system.""" + plt = platform.platform() + distname = platform.linux_distribution()[0] + if not distname: + raise RuntimeError('Unsupported platform %s' % plt) + LOG.debug('Looking for distro data for %s (%s)', plt, distname) + for p in cls.load_all(): + if p.supports_distro(plt): + LOG.info('Using distro "%s" for "%s"', p.name, plt) + return p + else: + raise RuntimeError( + 'No platform configuration data for %s (%s)' % + (plt, distname)) + + def __init__(self, name, distro_pattern, packager_name, commands, components): + self.name = name + self.distro_pattern = re.compile(distro_pattern, re.IGNORECASE) + self.packager_name = packager_name + self.commands = commands + self.components = components + + def supports_distro(self, distro_name): + """Does this distro support the named Linux distro? + + :param distro_name: Return value from platform.linux_distribution(). + """ + return bool(self.distro_pattern.search(distro_name)) + + def get_packager_factory(self): + """Return a factory for a package manager.""" + return importer.import_entry_point(self.packager_name) + + def get_component_action_class(self, name, action): + """Return the class to use for doing the action w/the component.""" + try: + entry_point = self.components[name][action] + except KeyError: + raise RuntimeError('No class configured to %s %s on %s' % + (action, name, self.name)) + return importer.import_entry_point(entry_point) + + def resolve_component_dependencies(self, components): + """Returns list of all components needed for the named components.""" + all_components = {} + active_names = [(c, None) for c in components] + while active_names: + component, parent = active_names.pop() + try: + component_details = self.components[component] + except KeyError: + if parent: + raise RuntimeError( + 'Could not find details about component %r, a dependency of %s, for %s' % + (component, parent, self.name)) + else: + raise RuntimeError( + 'Could not find details about component %r for %s' % + (component, self.name)) + deps = set(component_details.get('dependencies', [])) + all_components[component] = deps + for d in deps: + if d not in all_components and d not in active_names: + active_names.append((d, component)) + return all_components diff --git a/tests/test_distro.py b/tests/test_distro.py new file mode 100644 index 00000000..b3a8468a --- /dev/null +++ b/tests/test_distro.py @@ -0,0 +1,14 @@ + +from devstack import distro + + +def test_component_dependencies(): + d = distro.Distro('fake', 'ignore', 'apt', {}, + {'a': {'dependencies': ['b'], + }, + 'b': {}, + }) + actual = d.resolve_component_dependencies(['a']) + assert actual == {'a': set(['b']), + 'b': set(), + }