cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #05336
Re: [Merge] ~smoser/cloud-init:feature/oracle-datasource into cloud-init:master
Diff comments:
> diff --git a/.pylintrc b/.pylintrc
> index 3bfa0c8..df0f94a 100644
> --- a/.pylintrc
> +++ b/.pylintrc
> @@ -61,7 +61,7 @@ ignored-modules=
> # List of class names for which member attributes should not be checked (useful
> # for classes with dynamically set attributes). This supports the use of
> # qualified names.
> -ignored-classes=optparse.Values,thread._local
> +ignored-classes=argparse.Namespace,optparse.Values,thread._local
https://github.com/PyCQA/pylint/issues/2413
>
> # List of members which are set dynamically and missed by pylint inference
> # system, and so shouldn't trigger E1101 when accessed. Python regular
> diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
> new file mode 100644
> index 0000000..ab56d0e
> --- /dev/null
> +++ b/cloudinit/sources/tests/test_oracle.py
> @@ -0,0 +1,280 @@
> +# This file is part of cloud-init. See LICENSE file for license information.
> +
> +from cloudinit.sources import DataSourceOracle as oracle
> +from cloudinit.sources import BrokenMetadata
> +from cloudinit import helpers
> +
> +from cloudinit.tests import helpers as test_helpers
> +
> +from textwrap import dedent
> +import argparse
> +import httpretty
> +import json
> +import mock
> +import os
> +import six
> +import uuid
> +
> +DS_PATH = "cloudinit.sources.DataSourceOracle"
> +
> +
> +class TestDataSourceOracle(test_helpers.CiTestCase):
> + """Test the datasource."""
> +
> + ds_class = oracle.DataSourceOracle
> +
> + my_uuid = str(uuid.uuid4())
> + my_md = {"uuid": "ocid1.instance.oc1.phx.abyhqlj",
> + "name": "ci-vm1", "availability_zone": "phx-ad-3",
> + "hostname": "ci-vm1hostname",
> + "launch_index": 0, "files": [],
> + "public_keys": {"0": "ssh-rsa AAAAB3N...== user@host"},
> + "meta": {}}
> +
> + def _patch_instance(self, inst, patches):
> + """Patch an instance of a class 'inst'.
> + for each name, kwargs in patches:
> + inst.name = mock.Mock(**kwargs)
> + returns a namespace object that has
> + namespace.name = mock.Mock(**kwargs)
> + Do not bother with cleanup as instance is assumed transient."""
> + mocks = argparse.Namespace()
> + for name, kwargs in patches.items():
> + imock = mock.Mock(name=name, spec=getattr(inst, name), **kwargs)
> + setattr(mocks, name, imock)
> + setattr(inst, name, imock)
> + return mocks
> +
> + def _get_ds(self, sys_cfg=None, distro=None, paths=None, ud_proc=None,
> + patches=None):
> + if sys_cfg is None:
> + sys_cfg = {}
> + if patches is None:
> + patches = {}
> + if paths is None:
> + tmpd = self.tmp_dir()
> + dirs = {'cloud_dir': self.tmp_path('cloud_dir', tmpd),
> + 'run_dir': self.tmp_path('run_dir')}
> + for d in dirs.values():
> + os.mkdir(d)
> + paths = helpers.Paths(dirs)
> +
> + ds = self.ds_class(sys_cfg=sys_cfg, distro=distro,
> + paths=paths, ud_proc=ud_proc)
> +
> + return ds, self._patch_instance(ds, patches)
> +
> + def test_platform_not_viable_returns_false(self):
> + ds, mocks = self._get_ds(
> + patches={'_is_platform_viable': {'return_value': False}})
> + self.assertFalse(ds._get_data())
> + mocks._is_platform_viable.assert_called_once_with()
having _get_ds() return the created instance and a 'mocks' object
allows us to then use mocks.name and get the attribute-error
safety that comes with it.
the alternative is the little-bit more verbose
mocks['_is_platform_viable'].assert_called_once_with()
unfortunately, just skipping the 'mocks' liek this and
trying the 'ds' object directly (remember that ds._is_platform_viable
is actually the same as mocks._is_platform_viable) also
results in a pylint error E1101.
> +
> + def test_without_userdata(self):
> + """If no user-data is provided, it should not be in return dict."""
> + ds, mocks = self._get_ds(patches={
> + '_is_platform_viable': {'return_value': True},
> + 'crawl_metadata': {
> + 'return_value': {'system_uuid': self.my_uuid,
> + 'meta_data': self.my_md}}})
> + self.assertTrue(ds._get_data())
> + mocks._is_platform_viable.assert_called_once_with()
> + mocks.crawl_metadata.assert_called_once_with()
> + self.assertEqual(self.my_uuid, ds.system_uuid)
> + self.assertEqual(self.my_md['availability_zone'], ds.availability_zone)
> + self.assertIn(self.my_md["public_keys"]["0"], ds.get_public_ssh_keys())
> + self.assertEqual(self.my_md['uuid'], ds.get_instance_id())
> + self.assertIsNone(ds.userdata_raw)
> +
> + def test_with_vendordata(self):
> + """Test with vendor data."""
> + vd = {'cloud-init': '#cloud-config\nkey: value'}
> + ds, mocks = self._get_ds(patches={
> + '_is_platform_viable': {'return_value': True},
> + 'crawl_metadata': {
> + 'return_value': {'system_uuid': self.my_uuid,
> + 'meta_data': self.my_md,
> + 'vendor_data': vd}}})
> + self.assertTrue(ds._get_data())
> + mocks._is_platform_viable.assert_called_once_with()
> + mocks.crawl_metadata.assert_called_once_with()
> + self.assertEqual(vd, ds.vendordata_pure)
> + self.assertEqual(vd['cloud-init'], ds.vendordata_raw)
> +
> + def test_with_userdata(self):
> + """Ensure user-data is populated if present and is binary."""
> + my_userdata = b'abcdefg'
> + ds, mocks = self._get_ds(patches={
> + '_is_platform_viable': {'return_value': True},
> + 'crawl_metadata': {
> + 'return_value': {'system_uuid': self.my_uuid,
> + 'meta_data': self.my_md,
> + 'user_data': my_userdata}}})
> + self.assertTrue(ds._get_data())
> + mocks._is_platform_viable.assert_called_once_with()
> + mocks.crawl_metadata.assert_called_once_with()
> + self.assertEqual(self.my_uuid, ds.system_uuid)
> + self.assertIn(self.my_md["public_keys"]["0"], ds.get_public_ssh_keys())
> + self.assertEqual(self.my_md['uuid'], ds.get_instance_id())
> + self.assertEqual(my_userdata, ds.userdata_raw)
> +
> +
> +@mock.patch(DS_PATH + "._read_system_uuid", return_value=str(uuid.uuid4()))
> +class TestReadMetaData(test_helpers.HttprettyTestCase):
> + """Test the read_metadata which interacts with http metadata service."""
> +
> + mdurl = oracle.METADATA_ENDPOINT
> + my_md = {"uuid": "ocid1.instance.oc1.phx.abyhqlj",
> + "name": "ci-vm1", "availability_zone": "phx-ad-3",
> + "hostname": "ci-vm1hostname",
> + "launch_index": 0, "files": [],
> + "public_keys": {"0": "ssh-rsa AAAAB3N...== user@host"},
> + "meta": {}}
> +
> + def populate_md(self, data):
> + """call httppretty.register_url for each item dict 'data',
> + including valid indexes. Text values converted to bytes."""
> + httpretty.register_uri(
> + httpretty.GET, self.mdurl, '\n'.join(data.keys()).encode('utf-8'))
> + for k, v in data.items():
> + httpretty.register_uri(
> + httpretty.GET, self.mdurl + k,
> + v if not isinstance(v, six.text_type) else v.encode('utf-8'))
> +
> + def test_broken_no_sys_uuid(self, m_read_system_uuid):
> + """Datasource requires ability to read system_uuid and true return."""
> + m_read_system_uuid.return_value = None
> + self.assertRaises(BrokenMetadata, oracle.read_metadata)
> +
> + def test_broken_no_metadata_json(self, m_read_system_uuid):
> + """Datasource requires meta_data.json."""
> + httpretty.register_uri(
> + httpretty.GET, self.mdurl,
> + '\n'.join(['user_data']).encode('utf-8'))
> + with self.assertRaises(BrokenMetadata) as cm:
> + oracle.read_metadata()
> + self.assertIn("Required field 'meta_data.json' missing",
> + str(cm.exception))
> +
> + def test_with_userdata(self, m_read_system_uuid):
> + data = {'user_data': b'#!/bin/sh\necho hi world\n',
> + 'meta_data.json': json.dumps(self.my_md)}
> + self.populate_md(data)
> + result = oracle.read_metadata()
> + self.assertEqual(data['user_data'], result['user_data'])
> + self.assertEqual(self.my_md, result['meta_data'])
> +
> + def test_without_userdata(self, m_read_system_uuid):
> + data = {'meta_data.json': json.dumps(self.my_md)}
> + self.populate_md(data)
> + result = oracle.read_metadata()
> + self.assertNotIn('user_data', result)
> + self.assertEqual(self.my_md, result['meta_data'])
> +
> + def test_unknown_fields_included(self, m_read_system_uuid):
> + """Unknown fields listed in index should be included.
> + And those ending in .json should be decoded."""
> + some_data = {'key1': 'data1', 'subk1': {'subd1': 'subv'}}
> + some_vendor_data = {'cloud-init': 'foo'}
> + data = {'meta_data.json': json.dumps(self.my_md),
> + 'some_data.json': json.dumps(some_data),
> + 'vendor_data.json': json.dumps(some_vendor_data),
> + 'other_blob': b'this is blob'}
> + self.populate_md(data)
> + result = oracle.read_metadata()
> + self.assertNotIn('user_data', result)
> + self.assertEqual(self.my_md, result['meta_data'])
> + self.assertEqual(some_data, result['some_data'])
> + self.assertEqual(some_vendor_data, result['vendor_data'])
> + self.assertEqual(data['other_blob'], result['other_blob'])
> +
> +
> +class TestIsPlatformViable(test_helpers.CiTestCase):
> + @mock.patch(DS_PATH + ".util.read_dmi_data",
> + return_value=oracle.CHASSIS_ASSET_TAG)
> + def test_expected_viable(self, m_read_dmi_data):
> + """System with known chassis tag is viable."""
> + self.assertTrue(oracle._is_platform_viable())
> + m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
> +
> + @mock.patch(DS_PATH + ".util.read_dmi_data", return_value=None)
> + def test_expected_not_viable_dmi_data_none(self, m_read_dmi_data):
> + """System without known chassis tag is not viable."""
> + self.assertFalse(oracle._is_platform_viable())
> + m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
> +
> + @mock.patch(DS_PATH + ".util.read_dmi_data", return_value="LetsGoCubs")
> + def test_expected_not_viable_other(self, m_read_dmi_data):
> + """System with unnown chassis tag is not viable."""
> + self.assertFalse(oracle._is_platform_viable())
> + m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
> +
> +
> +class TestLoadIndex(test_helpers.CiTestCase):
> + """_load_index handles parsing of an index into a proper list.
> + The tests here guarantee correct parsing of html version or
> + a fixed version. See the function docstring for more doc."""
> +
> + _known_html_api_versions = dedent("""\
> + <html>
> + <head><title>Index of /openstack/</title></head>
> + <body bgcolor="white">
> + <h1>Index of /openstack/</h1><hr><pre><a href="../">../</a>
> + <a href="2013-10-17/">2013-10-17/</a> 27-Jun-2018 12:22 -
> + <a href="latest/">latest/</a> 27-Jun-2018 12:22 -
> + </pre><hr></body>
> + </html>""")
> +
> + _known_html_contents = dedent("""\
> + <html>
> + <head><title>Index of /openstack/2013-10-17/</title></head>
> + <body bgcolor="white">
> + <h1>Index of /openstack/2013-10-17/</h1><hr><pre><a href="../">../</a>
> + <a href="meta_data.json">meta_data.json</a> 27-Jun-2018 12:22 679
> + <a href="user_data">user_data</a> 27-Jun-2018 12:22 146
> + </pre><hr></body>
> + </html>""")
> +
> + def test_parse_html(self):
> + """Test parsing of lower case html."""
> + self.assertEqual(
> + ['2013-10-17/', 'latest/'],
> + oracle._load_index(self._known_html_api_versions))
> + self.assertEqual(
> + ['meta_data.json', 'user_data'],
> + oracle._load_index(self._known_html_contents))
> +
> + def test_parse_html_upper(self):
> + """Test parsing of upper case html, although known content is lower."""
> + def _toupper(data):
> + return data.replace("<a", "<A").replace("html>", "HTML>")
> +
> + self.assertEqual(
> + ['2013-10-17/', 'latest/'],
> + oracle._load_index(_toupper(self._known_html_api_versions)))
> + self.assertEqual(
> + ['meta_data.json', 'user_data'],
> + oracle._load_index(_toupper(self._known_html_contents)))
> +
> + def test_parse_newline_list_with_endl(self):
> + """Test parsing of newline separated list with ending newline."""
> + self.assertEqual(
> + ['2013-10-17/', 'latest/'],
> + oracle._load_index("\n".join(["2013-10-17/", "latest/", ""])))
> + self.assertEqual(
> + ['meta_data.json', 'user_data'],
> + oracle._load_index("\n".join(["meta_data.json", "user_data", ""])))
> +
> + def test_parse_newline_list_without_endl(self):
> + """Test parsing of newline separated list with no ending newline.
> +
> + Actual openstack implementation does not include trailing newline."""
> + self.assertEqual(
> + ['2013-10-17/', 'latest/'],
> + oracle._load_index("\n".join(["2013-10-17/", "latest/"])))
> + self.assertEqual(
> + ['meta_data.json', 'user_data'],
> + oracle._load_index("\n".join(["meta_data.json", "user_data"])))
> +
> +
> +# vi: ts=4 expandtab
--
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/352921
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:feature/oracle-datasource into cloud-init:master.
References