diff options
Diffstat (limited to 'cloudinit/sources/helpers')
| -rw-r--r-- | cloudinit/sources/helpers/openstack.py | 157 | 
1 files changed, 104 insertions, 53 deletions
| diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index 0fac0335..b7e19314 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -21,6 +21,7 @@  import abc  import base64  import copy +import functools  import os  from cloudinit import ec2_utils @@ -48,6 +49,7 @@ OS_LATEST = 'latest'  OS_FOLSOM = '2012-08-10'  OS_GRIZZLY = '2013-04-04'  OS_HAVANA = '2013-10-17' +# keep this in chronological order. new supported versions go at the end.  OS_VERSIONS = (      OS_FOLSOM,      OS_GRIZZLY, @@ -150,17 +152,40 @@ class BaseReader(object):          pass      @abc.abstractmethod -    def _path_exists(self, path): +    def _path_read(self, path):          pass      @abc.abstractmethod -    def _path_read(self, path): +    def _fetch_available_versions(self):          pass      @abc.abstractmethod      def _read_ec2_metadata(self):          pass +    def _find_working_version(self): +        try: +            versions_available = self._fetch_available_versions() +        except Exception as e: +            LOG.debug("Unable to read openstack versions from %s due to: %s", +                      self.base_path, e) +            versions_available = [] + +        # openstack.OS_VERSIONS is stored in chronological order, so +        # reverse it to check newest first. +        supported = [v for v in reversed(list(OS_VERSIONS))] +        selected_version = OS_LATEST + +        for potential_version in supported: +            if potential_version not in versions_available: +                continue +            selected_version = potential_version +            break + +        LOG.debug("Selected version '%s' from %s", selected_version, +                  versions_available) +        return selected_version +      def _read_content_path(self, item):          path = item.get('content_path', '').lstrip("/")          path_pieces = path.split("/") @@ -170,24 +195,7 @@ class BaseReader(object):          path = self._path_join(self.base_path, "openstack", *path_pieces)          return self._path_read(path) -    def _find_working_version(self, version): -        search_versions = [version] + list(OS_VERSIONS) -        for potential_version in search_versions: -            if not potential_version: -                continue -            path = self._path_join(self.base_path, "openstack", -                                   potential_version) -            if self._path_exists(path): -                if potential_version != version: -                    LOG.debug("Version '%s' not available, attempting to use" -                              " version '%s' instead", version, -                              potential_version) -                return potential_version -        LOG.debug("Version '%s' not available, attempting to use '%s'" -                  " instead", version, OS_LATEST) -        return OS_LATEST - -    def read_v2(self, version=None): +    def read_v2(self):          """Reads a version 2 formatted location.          Return a dict with metadata, userdata, ec2-metadata, dsmode, @@ -196,6 +204,9 @@ class BaseReader(object):          If not a valid location, raise a NonReadable exception.          """ +        load_json_anytype = functools.partial( +            util.load_json, root_types=(dict, basestring, list)) +          def datafiles(version):              files = {}              files['metadata'] = ( @@ -214,29 +225,32 @@ class BaseReader(object):              files['vendordata'] = (                  self._path_join("openstack", version, 'vendor_data.json'),                  False, -                util.load_json, +                load_json_anytype,              )              return files -        version = self._find_working_version(version)          results = {              'userdata': '',              'version': 2,          } -        data = datafiles(version) +        data = datafiles(self._find_working_version())          for (name, (path, required, translator)) in data.iteritems():              path = self._path_join(self.base_path, path)              data = None              found = False -            if self._path_exists(path): -                try: -                    data = self._path_read(path) -                except IOError: -                    raise NonReadable("Failed to read: %s" % path) -                found = True +            try: +                data = self._path_read(path) +            except IOError as e: +                if not required: +                    LOG.debug("Failed reading optional path %s due" +                              " to: %s", path, e) +                else: +                    LOG.debug("Failed reading mandatory path %s due" +                              " to: %s", path, e)              else: -                if required: -                    raise NonReadable("Missing mandatory path: %s" % path) +                found = True +            if required and not found: +                raise NonReadable("Missing mandatory path: %s" % path)              if found and translator:                  try:                      data = translator(data) @@ -304,21 +318,27 @@ class BaseReader(object):  class ConfigDriveReader(BaseReader):      def __init__(self, base_path):          super(ConfigDriveReader, self).__init__(base_path) +        self._versions = None      def _path_join(self, base, *add_ons):          components = [base] + list(add_ons)          return os.path.join(*components) -    def _path_exists(self, path): -        return os.path.exists(path) -      def _path_read(self, path):          return util.load_file(path) +    def _fetch_available_versions(self): +        if self._versions is None: +            path = self._path_join(self.base_path, 'openstack') +            found = [d for d in os.listdir(path) +                     if os.path.isdir(os.path.join(path))] +            self._versions = found +        return self._versions +      def _read_ec2_metadata(self):          path = self._path_join(self.base_path,                                 'ec2', 'latest', 'meta-data.json') -        if not self._path_exists(path): +        if not os.path.exists(path):              return {}          else:              try: @@ -338,7 +358,7 @@ class ConfigDriveReader(BaseReader):          found = {}          for name in FILES_V1.keys():              path = self._path_join(self.base_path, name) -            if self._path_exists(path): +            if os.path.exists(path):                  found[name] = path          if len(found) == 0:              raise NonReadable("%s: no files found" % (self.base_path)) @@ -400,17 +420,26 @@ class MetadataReader(BaseReader):          self.ssl_details = ssl_details          self.timeout = float(timeout)          self.retries = int(retries) +        self._versions = None + +    def _fetch_available_versions(self): +        # <baseurl>/openstack/ returns a newline separated list of versions +        if self._versions is not None: +            return self._versions +        found = [] +        version_path = self._path_join(self.base_path, "openstack") +        content = self._path_read(version_path) +        for line in content.splitlines(): +            line = line.strip() +            if not line: +                continue +            found.append(line) +        self._versions = found +        return self._versions      def _path_read(self, path): -        response = url_helper.readurl(path, -                                      retries=self.retries, -                                      ssl_details=self.ssl_details, -                                      timeout=self.timeout) -        return response.contents -    def _path_exists(self, path): - -        def should_retry_cb(request, cause): +        def should_retry_cb(_request_args, cause):              try:                  code = int(cause.code)                  if code >= 400: @@ -420,15 +449,12 @@ class MetadataReader(BaseReader):                  pass              return True -        try: -            response = url_helper.readurl(path, -                                          retries=self.retries, -                                          ssl_details=self.ssl_details, -                                          timeout=self.timeout, -                                          exception_cb=should_retry_cb) -            return response.ok() -        except IOError: -            return False +        response = url_helper.readurl(path, +                                      retries=self.retries, +                                      ssl_details=self.ssl_details, +                                      timeout=self.timeout, +                                      exception_cb=should_retry_cb) +        return response.contents      def _path_join(self, base, *add_ons):          return url_helper.combine_url(base, *add_ons) @@ -437,3 +463,28 @@ class MetadataReader(BaseReader):          return ec2_utils.get_instance_metadata(ssl_details=self.ssl_details,                                                 timeout=self.timeout,                                                 retries=self.retries) + + +def convert_vendordata_json(data, recurse=True): +    """ data: a loaded json *object* (strings, arrays, dicts). +    return something suitable for cloudinit vendordata_raw. + +    if data is: +       None: return None +       string: return string +       list: return data +             the list is then processed in UserDataProcessor +       dict: return convert_vendordata_json(data.get('cloud-init')) +    """ +    if not data: +        return None +    if isinstance(data, (str, unicode, basestring)): +        return data +    if isinstance(data, list): +        return copy.deepcopy(data) +    if isinstance(data, dict): +        if recurse is True: +            return convert_vendordata_json(data.get('cloud-init'), +                                           recurse=False) +        raise ValueError("vendordata['cloud-init'] cannot be dict") +    raise ValueError("Unknown data type for vendordata: %s" % type(data)) | 
