diff options
Diffstat (limited to 'cloudinit/sources')
-rw-r--r-- | cloudinit/sources/DataSourceConfigDrive.py | 82 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceEc2.py | 16 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceMAAS.py | 43 | ||||
-rw-r--r-- | cloudinit/sources/__init__.py | 19 |
4 files changed, 138 insertions, 22 deletions
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index b477560c..9729cfb9 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -48,6 +48,7 @@ class DataSourceConfigDrive(sources.DataSource): self.dsmode = 'local' self.seed_dir = os.path.join(paths.seed_dir, 'config_drive') self.version = None + self.ec2_metadata = None def __str__(self): mstr = "%s [%s,ver=%s]" % (util.obj_name(self), self.dsmode, @@ -55,6 +56,74 @@ class DataSourceConfigDrive(sources.DataSource): mstr += "[source=%s]" % (self.source) return mstr + def _ec2_name_to_device(self, name): + if not self.ec2_metadata: + return None + bdm = self.ec2_metadata.get('block-device-mapping', {}) + for (ent_name, device) in bdm.items(): + if name == ent_name: + return device + return None + + def _os_name_to_device(self, name): + device = None + try: + criteria = 'LABEL=%s' % (name) + if name in ['swap']: + criteria = 'TYPE=%s' % (name) + dev_entries = util.find_devs_with(criteria) + if dev_entries: + device = dev_entries[0] + except util.ProcessExecutionError: + pass + return device + + def _validate_device_name(self, device): + if not device: + return None + if not device.startswith("/"): + device = "/dev/%s" % device + if os.path.exists(device): + return device + # Durn, try adjusting the mapping + remapped = self._remap_device(os.path.basename(device)) + if remapped: + LOG.debug("Remapped device name %s => %s", device, remapped) + return remapped + return None + + def device_name_to_device(self, name): + # Translate a 'name' to a 'physical' device + if not name: + return None + # Try the ec2 mapping first + names = [name] + if name == 'root': + names.insert(0, 'ami') + if name == 'ami': + names.append('root') + device = None + LOG.debug("Using ec2 metadata lookup to find device %s", names) + for n in names: + device = self._ec2_name_to_device(n) + device = self._validate_device_name(device) + if device: + break + # Try the openstack way second + if not device: + LOG.debug("Using os lookup to find device %s", names) + for n in names: + device = self._os_name_to_device(n) + device = self._validate_device_name(device) + if device: + break + # Ok give up... + if not device: + return None + else: + LOG.debug("Using cfg drive lookup mapped to device %s", device) + return device + def get_data(self): found = None md = {} @@ -85,6 +154,16 @@ class DataSourceConfigDrive(sources.DataSource): md = results['metadata'] md = util.mergedict(md, DEFAULT_METADATA) + # Perform some metadata 'fixups' + # + # OpenStack uses the 'hostname' key + # while most of cloud-init uses the metadata + # 'local-hostname' key instead so if it doesn't + # exist we need to make sure its copied over. + for (tgt, src) in [('local-hostname', 'hostname')]: + if tgt not in md and src in md: + md[tgt] = md[src] + user_dsmode = results.get('dsmode', None) if user_dsmode not in VALID_DSMODES + (None,): LOG.warn("user specified invalid mode: %s" % user_dsmode) @@ -133,6 +212,7 @@ class DataSourceConfigDrive(sources.DataSource): self.source = found self.metadata = md + self.ec2_metadata = results.get('ec2-metadata') self.userdata_raw = results.get('userdata') self.version = results['cfgdrive_ver'] @@ -217,7 +297,7 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"): ('metadata', "openstack/%s/meta_data.json" % version, True, json.loads), ('userdata', "openstack/%s/user_data" % version, False, None), - ('ec2-metadata', "ec2/latest/metadata.json", False, json.loads), + ('ec2-metadata', "ec2/latest/meta-data.json", False, json.loads), ) results = {'userdata': None} diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index c7ad6d54..3686fa10 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -151,22 +151,6 @@ class DataSourceEc2(sources.DataSource): self.metadata_address = url2base.get(url) return bool(url) - def _remap_device(self, short_name): - # LP: #611137 - # the metadata service may believe that devices are named 'sda' - # when the kernel named them 'vda' or 'xvda' - # we want to return the correct value for what will actually - # exist in this instance - mappings = {"sd": ("vd", "xvd")} - for (nfrom, tlist) in mappings.iteritems(): - if not short_name.startswith(nfrom): - continue - for nto in tlist: - cand = "/dev/%s%s" % (nto, short_name[len(nfrom):]) - if os.path.exists(cand): - return cand - return None - def device_name_to_device(self, name): # Consult metadata service, that has # ephemeral0: sdb diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py index d166e9e3..b55d8a21 100644 --- a/cloudinit/sources/DataSourceMAAS.py +++ b/cloudinit/sources/DataSourceMAAS.py @@ -18,6 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from email.utils import parsedate import errno import oauth.oauth as oauth import os @@ -46,6 +47,7 @@ class DataSourceMAAS(sources.DataSource): sources.DataSource.__init__(self, sys_cfg, distro, paths) self.base_url = None self.seed_dir = os.path.join(paths.seed_dir, 'maas') + self.oauth_clockskew = None def __str__(self): return "%s [%s]" % (util.obj_name(self), self.base_url) @@ -95,11 +97,17 @@ class DataSourceMAAS(sources.DataSource): return {} consumer_secret = mcfg.get('consumer_secret', "") + + timestamp = None + if self.oauth_clockskew: + timestamp = int(time.time()) + self.oauth_clockskew + return oauth_headers(url=url, consumer_key=mcfg['consumer_key'], token_key=mcfg['token_key'], token_secret=mcfg['token_secret'], - consumer_secret=consumer_secret) + consumer_secret=consumer_secret, + timestamp=timestamp) def wait_for_metadata_service(self, url): mcfg = self.ds_cfg @@ -124,7 +132,7 @@ class DataSourceMAAS(sources.DataSource): check_url = "%s/%s/meta-data/instance-id" % (url, MD_VERSION) urls = [check_url] url = uhelp.wait_for_url(urls=urls, max_wait=max_wait, - timeout=timeout, status_cb=LOG.warn, + timeout=timeout, exception_cb=self._except_cb, headers_cb=self.md_headers) if url: @@ -135,6 +143,26 @@ class DataSourceMAAS(sources.DataSource): return bool(url) + def _except_cb(self, msg, exception): + if not (isinstance(exception, urllib2.HTTPError) and + (exception.code == 403 or exception.code == 401)): + return + if 'date' not in exception.headers: + LOG.warn("date field not in %d headers" % exception.code) + return + + date = exception.headers['date'] + + try: + ret_time = time.mktime(parsedate(date)) + except: + LOG.warn("failed to convert datetime '%s'") + return + + self.oauth_clockskew = int(ret_time - time.time()) + LOG.warn("set oauth clockskew to %d" % self.oauth_clockskew) + return + def read_maas_seed_dir(seed_d): """ @@ -229,13 +257,20 @@ def check_seed_contents(content, seed): return (userdata, md) -def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret): +def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret, + timestamp=None): consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) token = oauth.OAuthToken(token_key, token_secret) + + if timestamp is None: + ts = int(time.time()) + else: + ts = timestamp + params = { 'oauth_version': "1.0", 'oauth_nonce': oauth.generate_nonce(), - 'oauth_timestamp': int(time.time()), + 'oauth_timestamp': ts, 'oauth_token': token.key, 'oauth_consumer_key': consumer.key, } diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 6f126091..b22369a8 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -23,6 +23,7 @@ from email.mime.multipart import MIMEMultipart import abc +import os from cloudinit import importer from cloudinit import log as logging @@ -128,6 +129,22 @@ class DataSource(object): return keys + def _remap_device(self, short_name): + # LP: #611137 + # the metadata service may believe that devices are named 'sda' + # when the kernel named them 'vda' or 'xvda' + # we want to return the correct value for what will actually + # exist in this instance + mappings = {"sd": ("vd", "xvd")} + for (nfrom, tlist) in mappings.iteritems(): + if not short_name.startswith(nfrom): + continue + for nto in tlist: + cand = "/dev/%s%s" % (nto, short_name[len(nfrom):]) + if os.path.exists(cand): + return cand + return None + def device_name_to_device(self, _name): # translate a 'name' to a device # the primary function at this point is on ec2 @@ -173,7 +190,7 @@ class DataSource(object): # make up a hostname (LP: #475354) in format ip-xx.xx.xx.xx lhost = self.metadata['local-hostname'] if util.is_ipv4(lhost): - toks = [ "ip-%s" % lhost.replace(".", "-") ] + toks = ["ip-%s" % lhost.replace(".", "-")] else: toks = lhost.split(".") |