summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2016-08-11 06:34:06 -0600
committerScott Moser <smoser@ubuntu.com>2016-08-12 14:40:13 -0400
commitd9537aaa37f1e17db334c7cf8888ea3c4dcf1436 (patch)
treefd3a557c58f2b9376c6176526dbc2e2c6ab2e4f9
parent80db6eb9d697c21bfab85ab9a0dd5aceee571883 (diff)
downloadvyos-cloud-init-d9537aaa37f1e17db334c7cf8888ea3c4dcf1436.tar.gz
vyos-cloud-init-d9537aaa37f1e17db334c7cf8888ea3c4dcf1436.zip
MAAS: add vendor-data support
Add vendor-data support to maas which will behave like the openstack vendor-data does. Data returned from maas must be yaml loadable. Also update the main in DataSourceMAAS to "just work" on a maas deployed system. LP: #1612313
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py2
-rw-r--r--cloudinit/sources/DataSourceMAAS.py199
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py2
-rw-r--r--cloudinit/sources/__init__.py27
-rw-r--r--cloudinit/sources/helpers/openstack.py25
-rw-r--r--tests/unittests/helpers.py4
-rw-r--r--tests/unittests/test_datasource/test_maas.py127
-rw-r--r--tests/unittests/test_datasource/test_openstack.py3
8 files changed, 208 insertions, 181 deletions
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 91d6ff13..5c9edabe 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -134,7 +134,7 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
vd = results.get('vendordata')
self.vendordata_pure = vd
try:
- self.vendordata_raw = openstack.convert_vendordata_json(vd)
+ self.vendordata_raw = sources.convert_vendordata(vd)
except ValueError as e:
LOG.warn("Invalid content in vendor-data: %s", e)
self.vendordata_raw = None
diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
index d828f078..ab93c0a2 100644
--- a/cloudinit/sources/DataSourceMAAS.py
+++ b/cloudinit/sources/DataSourceMAAS.py
@@ -20,7 +20,6 @@
from __future__ import print_function
-import errno
import os
import time
@@ -32,7 +31,14 @@ from cloudinit import util
LOG = logging.getLogger(__name__)
MD_VERSION = "2012-03-01"
-BINARY_FIELDS = ('user-data',)
+DS_FIELDS = [
+ # remote path, location in dictionary, binary data?, optional?
+ ("meta-data/instance-id", 'meta-data/instance-id', False, False),
+ ("meta-data/local-hostname", 'meta-data/local-hostname', False, False),
+ ("meta-data/public-keys", 'meta-data/public-keys', False, True),
+ ('meta-data/vendor-data', 'vendor-data', True, True),
+ ('user-data', 'user-data', True, True),
+]
class DataSourceMAAS(sources.DataSource):
@@ -43,6 +49,7 @@ class DataSourceMAAS(sources.DataSource):
instance-id
user-data
hostname
+ vendor-data
"""
def __init__(self, sys_cfg, distro, paths):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
@@ -71,10 +78,7 @@ class DataSourceMAAS(sources.DataSource):
mcfg = self.ds_cfg
try:
- (userdata, metadata) = read_maas_seed_dir(self.seed_dir)
- self.userdata_raw = userdata
- self.metadata = metadata
- self.base_url = self.seed_dir
+ self._set_data(self.seed_dir, read_maas_seed_dir(self.seed_dir))
return True
except MAASSeedDirNone:
pass
@@ -95,18 +99,29 @@ class DataSourceMAAS(sources.DataSource):
if not self.wait_for_metadata_service(url):
return False
- self.base_url = url
-
- (userdata, metadata) = read_maas_seed_url(
- self.base_url, read_file_or_url=self.oauth_helper.readurl,
- paths=self.paths, retries=1)
- self.userdata_raw = userdata
- self.metadata = metadata
+ self._set_data(
+ url, read_maas_seed_url(
+ url, read_file_or_url=self.oauth_helper.readurl,
+ paths=self.paths, retries=1))
return True
except Exception:
util.logexc(LOG, "Failed fetching metadata from url %s", url)
return False
+ def _set_data(self, url, data):
+ # takes a url for base_url and a tuple of userdata, metadata, vd.
+ self.base_url = url
+ ud, md, vd = data
+ self.userdata_raw = ud
+ self.metadata = md
+ self.vendordata_pure = vd
+ if vd:
+ try:
+ self.vendordata_raw = sources.convert_vendordata(vd)
+ except ValueError as e:
+ LOG.warn("Invalid content in vendor-data: %s", e)
+ self.vendordata_raw = None
+
def wait_for_metadata_service(self, url):
mcfg = self.ds_cfg
max_wait = 120
@@ -126,6 +141,8 @@ class DataSourceMAAS(sources.DataSource):
LOG.warn("Failed to get timeout, using %s" % timeout)
starttime = time.time()
+ if url.endswith("/"):
+ url = url[:-1]
check_url = "%s/%s/meta-data/instance-id" % (url, MD_VERSION)
urls = [check_url]
url = self.oauth_helper.wait_for_url(
@@ -141,27 +158,13 @@ class DataSourceMAAS(sources.DataSource):
def read_maas_seed_dir(seed_d):
- """
- Return user-data and metadata for a maas seed dir in seed_d.
- Expected format of seed_d are the following files:
- * instance-id
- * local-hostname
- * user-data
- """
- if not os.path.isdir(seed_d):
+ if seed_d.startswith("file://"):
+ seed_d = seed_d[7:]
+ if not os.path.isdir(seed_d) or len(os.listdir(seed_d)) == 0:
raise MAASSeedDirNone("%s: not a directory")
- files = ('local-hostname', 'instance-id', 'user-data', 'public-keys')
- md = {}
- for fname in files:
- try:
- md[fname] = util.load_file(os.path.join(seed_d, fname),
- decode=fname not in BINARY_FIELDS)
- except IOError as e:
- if e.errno != errno.ENOENT:
- raise
-
- return check_seed_contents(md, seed_d)
+ # seed_dir looks in seed_dir, not seed_dir/VERSION
+ return read_maas_seed_url("file://%s" % seed_d, version=None)
def read_maas_seed_url(seed_url, read_file_or_url=None, timeout=None,
@@ -175,73 +178,78 @@ def read_maas_seed_url(seed_url, read_file_or_url=None, timeout=None,
* <seed_url>/<version>/meta-data/instance-id
* <seed_url>/<version>/meta-data/local-hostname
* <seed_url>/<version>/user-data
+ If version is None, then <version>/ will not be used.
"""
- base_url = "%s/%s" % (seed_url, version)
- file_order = [
- 'local-hostname',
- 'instance-id',
- 'public-keys',
- 'user-data',
- ]
- files = {
- 'local-hostname': "%s/%s" % (base_url, 'meta-data/local-hostname'),
- 'instance-id': "%s/%s" % (base_url, 'meta-data/instance-id'),
- 'public-keys': "%s/%s" % (base_url, 'meta-data/public-keys'),
- 'user-data': "%s/%s" % (base_url, 'user-data'),
- }
-
if read_file_or_url is None:
read_file_or_url = util.read_file_or_url
+ if seed_url.endswith("/"):
+ seed_url = seed_url[:-1]
+
md = {}
- for name in file_order:
- url = files.get(name)
- if name == 'user-data':
- item_retries = 0
+ for path, dictname, binary, optional in DS_FIELDS:
+ if version is None:
+ url = "%s/%s" % (seed_url, path)
else:
- item_retries = retries
-
+ url = "%s/%s/%s" % (seed_url, version, path)
try:
ssl_details = util.fetch_ssl_details(paths)
- resp = read_file_or_url(url, retries=item_retries,
- timeout=timeout, ssl_details=ssl_details)
+ resp = read_file_or_url(url, retries=retries, timeout=timeout,
+ ssl_details=ssl_details)
if resp.ok():
- if name in BINARY_FIELDS:
- md[name] = resp.contents
+ if binary:
+ md[path] = resp.contents
else:
- md[name] = util.decode_binary(resp.contents)
+ md[path] = util.decode_binary(resp.contents)
else:
LOG.warn(("Fetching from %s resulted in"
" an invalid http code %s"), url, resp.code)
except url_helper.UrlError as e:
- if e.code != 404:
- raise
+ if e.code == 404 and not optional:
+ raise MAASSeedDirMalformed(
+ "Missing required %s: %s" % (path, e))
+ elif e.code != 404:
+ raise e
+
return check_seed_contents(md, seed_url)
def check_seed_contents(content, seed):
- """Validate if content is Is the content a dict that is valid as a
- return for a datasource.
- Either return a (userdata, metadata) tuple or
+ """Validate if dictionary content valid as a return for a datasource.
+ Either return a (userdata, metadata, vendordata) tuple or
Raise MAASSeedDirMalformed or MAASSeedDirNone
"""
- md_required = ('instance-id', 'local-hostname')
- if len(content) == 0:
+ ret = {}
+ missing = []
+ for spath, dpath, _binary, optional in DS_FIELDS:
+ if spath not in content:
+ if not optional:
+ missing.append(spath)
+ continue
+
+ if "/" in dpath:
+ top, _, p = dpath.partition("/")
+ if top not in ret:
+ ret[top] = {}
+ ret[top][p] = content[spath]
+ else:
+ ret[dpath] = content[spath]
+
+ if len(ret) == 0:
raise MAASSeedDirNone("%s: no data files found" % seed)
- found = list(content.keys())
- missing = [k for k in md_required if k not in found]
- if len(missing):
+ if missing:
raise MAASSeedDirMalformed("%s: missing files %s" % (seed, missing))
- userdata = content.get('user-data', b"")
- md = {}
- for (key, val) in content.items():
- if key == 'user-data':
- continue
- md[key] = val
+ vd_data = None
+ if ret.get('vendor-data'):
+ err = object()
+ vd_data = util.load_yaml(ret.get('vendor-data'), default=err,
+ allowed=(object))
+ if vd_data is err:
+ raise MAASSeedDirMalformed("vendor-data was not loadable as yaml.")
- return (userdata, md)
+ return ret.get('user-data'), ret.get('meta-data'), vd_data
class MAASSeedDirNone(Exception):
@@ -272,6 +280,7 @@ if __name__ == "__main__":
"""
import argparse
import pprint
+ import sys
parser = argparse.ArgumentParser(description='Interact with MAAS DS')
parser.add_argument("--config", metavar="file",
@@ -289,17 +298,25 @@ if __name__ == "__main__":
default=MD_VERSION)
subcmds = parser.add_subparsers(title="subcommands", dest="subcmd")
- subcmds.add_parser('crawl', help="crawl the datasource")
- subcmds.add_parser('get', help="do a single GET of provided url")
- subcmds.add_parser('check-seed', help="read andn verify seed at url")
-
- parser.add_argument("url", help="the data source to query")
+ for (name, help) in (('crawl', 'crawl the datasource'),
+ ('get', 'do a single GET of provided url'),
+ ('check-seed', 'read and verify seed at url')):
+ p = subcmds.add_parser(name, help=help)
+ p.add_argument("url", help="the datasource url", nargs='?',
+ default=None)
args = parser.parse_args()
creds = {'consumer_key': args.ckey, 'token_key': args.tkey,
'token_secret': args.tsec, 'consumer_secret': args.csec}
+ maaspkg_cfg = "/etc/cloud/cloud.cfg.d/90_dpkg_maas.cfg"
+ if (args.config is None and args.url is None and
+ os.path.exists(maaspkg_cfg) and
+ os.access(maaspkg_cfg, os.R_OK)):
+ sys.stderr.write("Used config in %s.\n" % maaspkg_cfg)
+ args.config = maaspkg_cfg
+
if args.config:
cfg = util.read_conf(args.config)
if 'datasource' in cfg:
@@ -307,6 +324,12 @@ if __name__ == "__main__":
for key in creds.keys():
if key in cfg and creds[key] is None:
creds[key] = cfg[key]
+ if args.url is None and 'metadata_url' in cfg:
+ args.url = cfg['metadata_url']
+
+ if args.url is None:
+ sys.stderr.write("Must provide a url or a config with url.\n")
+ sys.exit(1)
oauth_helper = url_helper.OauthUrlHelper(**creds)
@@ -331,16 +354,20 @@ if __name__ == "__main__":
printurl(url)
if args.subcmd == "check-seed":
+ sys.stderr.write("Checking seed at %s\n" % args.url)
readurl = oauth_helper.readurl
if args.url[0] == "/" or args.url.startswith("file://"):
- readurl = None
- (userdata, metadata) = read_maas_seed_url(
- args.url, version=args.apiver, read_file_or_url=readurl,
- retries=2)
- print("=== userdata ===")
- print(userdata.decode())
- print("=== metadata ===")
+ (userdata, metadata, vd) = read_maas_seed_dir(args.url)
+ else:
+ (userdata, metadata, vd) = read_maas_seed_url(
+ args.url, version=args.apiver, read_file_or_url=readurl,
+ retries=2)
+ print("=== user-data ===")
+ print("N/A" if userdata is None else userdata.decode())
+ print("=== meta-data ===")
pprint.pprint(metadata)
+ print("=== vendor-data ===")
+ pprint.pprint("N/A" if vd is None else vd)
elif args.subcmd == "get":
printurl(args.url)
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index c06d17f3..82558214 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -138,7 +138,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
vd = results.get('vendordata')
self.vendordata_pure = vd
try:
- self.vendordata_raw = openstack.convert_vendordata_json(vd)
+ self.vendordata_raw = sources.convert_vendordata(vd)
except ValueError as e:
LOG.warn("Invalid content in vendor-data: %s", e)
self.vendordata_raw = None
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 87b8e524..d1395270 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -21,8 +21,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
+import copy
import os
-
import six
from cloudinit import importer
@@ -355,6 +355,31 @@ def instance_id_matches_system_uuid(instance_id, field='system-uuid'):
return instance_id.lower() == dmi_value.lower()
+def convert_vendordata(data, recurse=True):
+ """data: a loaded 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(data.get('cloud-init'))
+ """
+ if not data:
+ return None
+ if isinstance(data, six.string_types):
+ return data
+ if isinstance(data, list):
+ return copy.deepcopy(data)
+ if isinstance(data, dict):
+ if recurse is True:
+ return convert_vendordata(data.get('cloud-init'),
+ recurse=False)
+ raise ValueError("vendordata['cloud-init'] cannot be dict")
+ raise ValueError("Unknown data type for vendordata: %s" % type(data))
+
+
# 'depends' is a list of dependencies (DEP_FILESYSTEM)
# ds_list is a list of 2 item lists
# ds_list = [
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
index 461fbd0d..84322e0e 100644
--- a/cloudinit/sources/helpers/openstack.py
+++ b/cloudinit/sources/helpers/openstack.py
@@ -621,28 +621,3 @@ def convert_net_json(network_json=None, known_macs=None):
config.append(cfg)
return {'version': 1, 'config': config}
-
-
-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, six.string_types):
- 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))
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index 972245df..de2cf638 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -256,7 +256,9 @@ def populate_dir(path, files):
if not os.path.exists(path):
os.makedirs(path)
for (name, content) in files.items():
- with open(os.path.join(path, name), "wb") as fp:
+ p = os.path.join(path, name)
+ util.ensure_dir(os.path.dirname(p))
+ with open(p, "wb") as fp:
if isinstance(content, six.binary_type):
fp.write(content)
else:
diff --git a/tests/unittests/test_datasource/test_maas.py b/tests/unittests/test_datasource/test_maas.py
index f66f1c6d..0126c883 100644
--- a/tests/unittests/test_datasource/test_maas.py
+++ b/tests/unittests/test_datasource/test_maas.py
@@ -2,6 +2,7 @@ from copy import copy
import os
import shutil
import tempfile
+import yaml
from cloudinit.sources import DataSourceMAAS
from cloudinit import url_helper
@@ -24,41 +25,44 @@ class TestMAASDataSource(TestCase):
def test_seed_dir_valid(self):
"""Verify a valid seeddir is read as such."""
- data = {'instance-id': 'i-valid01',
- 'local-hostname': 'valid01-hostname',
- 'user-data': b'valid01-userdata',
+ userdata = b'valid01-userdata'
+ data = {'meta-data/instance-id': 'i-valid01',
+ 'meta-data/local-hostname': 'valid01-hostname',
+ 'user-data': userdata,
'public-keys': 'ssh-rsa AAAAB3Nz...aC1yc2E= keyname'}
my_d = os.path.join(self.tmp, "valid")
populate_dir(my_d, data)
- (userdata, metadata) = DataSourceMAAS.read_maas_seed_dir(my_d)
+ ud, md, vd = DataSourceMAAS.read_maas_seed_dir(my_d)
- self.assertEqual(userdata, data['user-data'])
+ self.assertEqual(userdata, ud)
for key in ('instance-id', 'local-hostname'):
- self.assertEqual(data[key], metadata[key])
+ self.assertEqual(data["meta-data/" + key], md[key])
# verify that 'userdata' is not returned as part of the metadata
- self.assertFalse(('user-data' in metadata))
+ self.assertFalse(('user-data' in md))
+ self.assertEqual(vd, None)
def test_seed_dir_valid_extra(self):
"""Verify extra files do not affect seed_dir validity."""
- data = {'instance-id': 'i-valid-extra',
- 'local-hostname': 'valid-extra-hostname',
- 'user-data': b'valid-extra-userdata', 'foo': 'bar'}
+ userdata = b'valid-extra-userdata'
+ data = {'meta-data/instance-id': 'i-valid-extra',
+ 'meta-data/local-hostname': 'valid-extra-hostname',
+ 'user-data': userdata, 'foo': 'bar'}
my_d = os.path.join(self.tmp, "valid_extra")
populate_dir(my_d, data)
- (userdata, metadata) = DataSourceMAAS.read_maas_seed_dir(my_d)
+ ud, md, vd = DataSourceMAAS.read_maas_seed_dir(my_d)
- self.assertEqual(userdata, data['user-data'])
+ self.assertEqual(userdata, ud)
for key in ('instance-id', 'local-hostname'):
- self.assertEqual(data[key], metadata[key])
+ self.assertEqual(data['meta-data/' + key], md[key])
# additional files should not just appear as keys in metadata atm
- self.assertFalse(('foo' in metadata))
+ self.assertFalse(('foo' in md))
def test_seed_dir_invalid(self):
"""Verify that invalid seed_dir raises MAASSeedDirMalformed."""
@@ -97,67 +101,60 @@ class TestMAASDataSource(TestCase):
DataSourceMAAS.read_maas_seed_dir,
os.path.join(self.tmp, "nonexistantdirectory"))
+ def mock_read_maas_seed_url(self, data, seed, version="19991231"):
+ """mock up readurl to appear as a web server at seed has provided data.
+ return what read_maas_seed_url returns."""
+ def my_readurl(*args, **kwargs):
+ if len(args):
+ url = args[0]
+ else:
+ url = kwargs['url']
+ prefix = "%s/%s/" % (seed, version)
+ if not url.startswith(prefix):
+ raise ValueError("unexpected call %s" % url)
+
+ short = url[len(prefix):]
+ if short not in data:
+ raise url_helper.UrlError("not found", code=404, url=url)
+ return url_helper.StringResponse(data[short])
+
+ # Now do the actual call of the code under test.
+ with mock.patch("cloudinit.url_helper.readurl") as mock_readurl:
+ mock_readurl.side_effect = my_readurl
+ return DataSourceMAAS.read_maas_seed_url(seed, version=version)
+
def test_seed_url_valid(self):
"""Verify that valid seed_url is read as such."""
valid = {
'meta-data/instance-id': 'i-instanceid',
'meta-data/local-hostname': 'test-hostname',
'meta-data/public-keys': 'test-hostname',
+ 'meta-data/vendor-data': b'my-vendordata',
'user-data': b'foodata',
}
- valid_order = [
- 'meta-data/local-hostname',
- 'meta-data/instance-id',
- 'meta-data/public-keys',
- 'user-data',
- ]
my_seed = "http://example.com/xmeta"
my_ver = "1999-99-99"
- my_headers = {'header1': 'value1', 'header2': 'value2'}
-
- def my_headers_cb(url):
- return my_headers
-
- # Each time url_helper.readurl() is called, something different is
- # returned based on the canned data above. We need to build up a list
- # of side effect return values, which the mock will return. At the
- # same time, we'll build up a list of expected call arguments for
- # asserting after the code under test is run.
- calls = []
-
- def side_effect():
- for key in valid_order:
- resp = valid.get(key)
- url = "%s/%s/%s" % (my_seed, my_ver, key)
- calls.append(
- mock.call(url, headers=None, timeout=mock.ANY,
- data=mock.ANY, sec_between=mock.ANY,
- ssl_details=mock.ANY, retries=mock.ANY,
- headers_cb=my_headers_cb,
- exception_cb=mock.ANY))
- yield url_helper.StringResponse(resp)
-
- # Now do the actual call of the code under test.
- with mock.patch.object(url_helper, 'readurl',
- side_effect=side_effect()) as mockobj:
- userdata, metadata = DataSourceMAAS.read_maas_seed_url(
- my_seed, version=my_ver)
-
- self.assertEqual(b"foodata", userdata)
- self.assertEqual(metadata['instance-id'],
- valid['meta-data/instance-id'])
- self.assertEqual(metadata['local-hostname'],
- valid['meta-data/local-hostname'])
-
- mockobj.has_calls(calls)
-
- def test_seed_url_invalid(self):
- """Verify that invalid seed_url raises MAASSeedDirMalformed."""
- pass
-
- def test_seed_url_missing(self):
- """Verify seed_url with no found entries raises MAASSeedDirNone."""
- pass
+ ud, md, vd = self.mock_read_maas_seed_url(valid, my_seed, my_ver)
+
+ self.assertEqual(valid['meta-data/instance-id'], md['instance-id'])
+ self.assertEqual(
+ valid['meta-data/local-hostname'], md['local-hostname'])
+ self.assertEqual(valid['meta-data/public-keys'], md['public-keys'])
+ self.assertEqual(valid['user-data'], ud)
+ # vendor-data is yaml, which decodes a string
+ self.assertEqual(valid['meta-data/vendor-data'].decode(), vd)
+
+ def test_seed_url_vendor_data_dict(self):
+ expected_vd = {'key1': 'value1'}
+ valid = {
+ 'meta-data/instance-id': 'i-instanceid',
+ 'meta-data/local-hostname': 'test-hostname',
+ 'meta-data/vendor-data': yaml.safe_dump(expected_vd).encode(),
+ }
+ ud, md, vd = self.mock_read_maas_seed_url(
+ valid, "http://example.com/foo")
+ self.assertEqual(valid['meta-data/instance-id'], md['instance-id'])
+ self.assertEqual(expected_vd, vd)
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py
index 5c8592c5..97b99a18 100644
--- a/tests/unittests/test_datasource/test_openstack.py
+++ b/tests/unittests/test_datasource/test_openstack.py
@@ -27,6 +27,7 @@ from six import StringIO
from cloudinit import helpers
from cloudinit import settings
+from cloudinit.sources import convert_vendordata
from cloudinit.sources import DataSourceOpenStack as ds
from cloudinit.sources.helpers import openstack
from cloudinit import util
@@ -318,7 +319,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
class TestVendorDataLoading(test_helpers.TestCase):
def cvj(self, data):
- return openstack.convert_vendordata_json(data)
+ return convert_vendordata(data)
def test_vd_load_none(self):
# non-existant vendor-data should return none