cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #01272
[Merge] ~kaihuan-pkh/cloud-init:aliyun-datasource into cloud-init:master
lawrence peng has proposed merging ~kaihuan-pkh/cloud-init:aliyun-datasource into cloud-init:master.
Requested reviews:
cloud init development team (cloud-init-dev)
For more details, see:
https://code.launchpad.net/~kaihuan-pkh/cloud-init/+git/cloud-init/+merge/308483
add the datasource support for Ali-Cloud ECS, the elastic computing service of Alibaba Group Cloud Platform.
--
Your team cloud init development team is requested to review the proposed merge of ~kaihuan-pkh/cloud-init:aliyun-datasource into cloud-init:master.
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
new file mode 100644
index 0000000..bba0eec
--- /dev/null
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -0,0 +1,121 @@
+# vi: ts=4 expandtab
+
+import os
+import time
+
+from cloudinit import log as logging
+from cloudinit import sources
+from cloudinit import url_helper as uhelp
+from cloudinit import util
+from cloudinit.sources import DataSourceEc2 as EC2
+
+LOG = logging.getLogger(__name__)
+
+DEF_MD_URL = "http://100.100.100.200";
+
+DEF_MD_VERSION = "latest"
+
+# Default metadata urls that will be used if none are provided
+# They will be checked for 'resolveability' and some of the
+# following may be discarded if they do not resolve
+DEF_MD_URLS = [DEF_MD_URL, ]
+
+
+class DataSourceAliYun(EC2.DataSourceEc2):
+ def __init__(self, sys_cfg, distro, paths):
+ super(DataSourceAliYun, self).__init__(sys_cfg, distro, paths)
+ self.metadata_address = DEF_MD_URL
+ self.seed_dir = os.path.join(paths.seed_dir, "AliYun")
+ self.api_ver = DEF_MD_VERSION
+
+ def wait_for_metadata_service(self):
+ mcfg = self.ds_cfg
+ if not mcfg:
+ mcfg = {}
+
+ (max_wait, timeout) = self._get_url_settings()
+ if max_wait <= 0:
+ return False
+
+ # Remove addresses from the list that wont resolve.
+ mdurls = mcfg.get("metadata_urls", DEF_MD_URLS)
+ filtered = [x for x in mdurls if util.is_resolvable_url(x)]
+
+ if set(filtered) != set(mdurls):
+ LOG.debug("Removed the following from metadata urls: %s",
+ list((set(mdurls) - set(filtered))))
+
+ if len(filtered):
+ mdurls = filtered
+ else:
+ LOG.warn("Empty metadata url list! using default list")
+ mdurls = DEF_MD_URLS
+
+ urls = []
+ url2base = {}
+ for url in mdurls:
+ cur = "%s/%s/meta-data/instance-id" % (url, self.api_ver)
+ urls.append(cur)
+ url2base[cur] = url
+
+ start_time = time.time()
+ url = uhelp.wait_for_url(urls=urls, max_wait=max_wait,
+ timeout=timeout, status_cb=LOG.warn)
+
+ if url:
+ LOG.debug("Using metadata source: '%s'", url2base[url])
+ else:
+ LOG.critical("Giving up on md from %s after %s seconds",
+ urls, int(time.time() - start_time))
+
+ self.metadata_address = url2base.get(url)
+ return bool(url)
+
+ def get_hostname(self, fqdn=False, _resolve_ip=False):
+ if not self.metadata:
+ return 'localhost.localdomain'
+ return self.metadata.get('hostname', 'localhost.localdomain')
+
+ def get_public_ssh_keys(self):
+ if not self.metadata:
+ return []
+ return self.parse_public_keys(self.metadata.get('public-keys', {}))
+
+ def parse_public_keys(self, public_keys):
+ keys = []
+ for key_id, key_body in public_keys.items():
+ if isinstance(key_body, str):
+ keys.append(key_body.strip())
+ elif isinstance(key_body, list):
+ keys.extend(key_body)
+ elif isinstance(key_body, dict):
+ key = key_body.get('openssh-key', [])
+ if isinstance(key, str):
+ keys.append(key.strip())
+ elif isinstance(key, list):
+ keys.extend(key)
+ return keys
+
+ def get_ntp_conf(self):
+ if not self.metadata:
+ return {}
+ return self.metadata.get('ntp-conf')
+
+ def get_source_address(self):
+ if not self.metadata or not self.metadata.get('source-address'):
+ return []
+ source_address = self.metadata.get('source-address')
+ if isinstance(source_address, str):
+ source_address = [source_address]
+ return source_address
+
+
+# Used to match classes to dependencies
+datasources = [
+ (DataSourceAliYun, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
+]
+
+
+# Return a list of data sources that match this set of dependencies
+def get_datasource_list(depends):
+ return sources.list_from_depends(depends, datasources)
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
new file mode 100644
index 0000000..e1d7db8
--- /dev/null
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -0,0 +1,143 @@
+import os
+import json
+import requests
+import httpretty
+import functools
+import hashlib
+
+from .. import helpers as test_helpers
+from cloudinit import helpers
+from cloudinit.sources import DataSourceAliYun as ay
+
+DEFAULT_METADATA = {
+ 'instance-id': 'aliyun-test-vm-00',
+ 'eipv4': '10.0.0.1',
+ 'hostname': 'test-hostname',
+ 'image-id': 'm-test',
+ 'launch-index': '0',
+ 'mac': '00:16:3e:00:00:00',
+ 'network-type': 'vpc',
+ 'private-ipv4': '192.168.0.1',
+ 'serial-number': 'test-string',
+ 'vpc-cidr-block': '192.168.0.0/16',
+ 'vpc-id': 'test-vpc',
+ 'vswitch-id': 'test-vpc',
+ 'vswitch-cidr-block': '192.168.0.0/16',
+ 'zone-id': 'test-zone-1',
+ 'ntp-conf': {'ntp_servers': [
+ 'ntp1.aliyun.com',
+ 'ntp2.aliyun.com',
+ 'ntp3.aliyun.com']},
+ 'source-address': ['http://mirrors.aliyun.com',
+ 'http://mirrors.aliyuncs.com'],
+ 'public-keys': {'key-pair-1': {'openssh-key': 'ssh-rsa AAAAB3...'},
+ 'key-pair-2': {'openssh-key': 'ssh-rsa AAAAB3...'}}
+}
+
+DEFAULT_USERDATA = """\
+#cloud-config
+
+hostname: localhost"""
+
+
+def register_mock_metaserver(base_url, data):
+ def register_helper(register, base_url, body):
+ if isinstance(body, str):
+ register(base_url, body)
+ elif isinstance(body, list):
+ register(base_url.rstrip('/'), '\n'.join(body)+'\n')
+ elif isinstance(body, dict):
+ vals = []
+ for k, v in body.items():
+ if isinstance(v, (str, list)):
+ suffix = k.rstrip('/')
+ else:
+ suffix = k.rstrip('/')+'/'
+ vals.append(suffix)
+ url = base_url.rstrip('/') + '/' + suffix
+ register_helper(register, url, v)
+ register(base_url, '\n'.join(vals)+'\n')
+
+ register = functools.partial(httpretty.register_uri, httpretty.GET)
+ register_helper(register, base_url, data)
+
+
+class TestAliYunDatasource(test_helpers.HttprettyTestCase):
+ def setUp(self):
+ super(TestAliYunDatasource, self).setUp()
+ cfg = {'datasource': {'AliYun': {'timeout': '1', 'max_wait': '1'}}}
+ distro = {}
+ paths = helpers.Paths({})
+ self.ds = ay.DataSourceAliYun(cfg, distro, paths)
+ self.metadata_address = self.ds.metadata_address
+ self.api_ver = self.ds.api_ver
+
+ @property
+ def default_metadata(self):
+ return DEFAULT_METADATA
+
+ @property
+ def default_userdata(self):
+ return DEFAULT_USERDATA
+
+ @property
+ def metadata_url(self):
+ return os.path.join(self.metadata_address,
+ self.api_ver, 'meta-data') + '/'
+
+ @property
+ def userdata_url(self):
+ return os.path.join(self.metadata_address,
+ self.api_ver, 'user-data')
+
+ def regist_default_server(self):
+ register_mock_metaserver(self.metadata_url, self.default_metadata)
+ register_mock_metaserver(self.userdata_url, self.default_userdata)
+
+ def _test_get_data(self):
+ self.assertEqual(self.ds.metadata, self.default_metadata)
+ self.assertEqual(self.ds.userdata_raw,
+ self.default_userdata.encode('utf8'))
+
+ def _test_get_sshkey(self):
+ pub_keys = [v['openssh-key'] for (_, v) in
+ self.default_metadata['public-keys'].items()]
+ self.assertEqual(self.ds.get_public_ssh_keys(), pub_keys)
+
+ def _test_get_iid(self):
+ self.assertEqual(self.default_metadata['instance-id'],
+ self.ds.get_instance_id())
+
+ def _test_host_name(self):
+ self.assertEqual(self.default_metadata['hostname'],
+ self.ds.get_hostname())
+
+ def _test_ntp_conf(self):
+ self.assertEqual(self.default_metadata['ntp-conf'],
+ self.ds.get_ntp_conf())
+
+ def _test_source_address(self):
+ self.assertEqual(self.default_metadata['source-address'],
+ self.ds.get_source_address())
+
+ @httpretty.activate
+ def test_with_mock_server(self):
+ self.regist_default_server()
+ self.ds.get_data()
+ self._test_get_data()
+ self._test_get_sshkey()
+ self._test_get_iid()
+ self._test_host_name()
+ self._test_ntp_conf()
+ self._test_source_address()
+
+ def test_get_hostname_default(self):
+ self.assertEqual(self.ds.get_hostname(), 'localhost.localdomain')
+
+ def test_parse_public_keys(self):
+ public_keys = {'key-pair-0': 'ssh-key-0'}
+ self.assertEqual([public_keys['key-pair-0']],
+ self.ds.parse_public_keys(public_keys))
+ public_keys = {'key-pair-0': ['ssh-key-0', 'ssh-key-1']}
+ self.assertEqual(public_keys['key-pair-0'],
+ self.ds.parse_public_keys(public_keys))
Follow ups