diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | cloudinit/config/cc_lxd.py | 121 | ||||
-rw-r--r-- | cloudinit/distros/rhel_util.py | 2 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceCloudSigma.py | 2 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceNoCloud.py | 7 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceSmartOS.py | 2 | ||||
-rw-r--r-- | doc/examples/cloud-config-lxd.txt | 27 | ||||
-rw-r--r-- | tests/unittests/test_distros/test_resolv.py | 6 | ||||
-rw-r--r-- | tests/unittests/test_handler/test_handler_lxd.py | 59 |
9 files changed, 208 insertions, 19 deletions
@@ -102,6 +102,7 @@ - disk_setup: correctly send --force to mkfs on block devices (LP: #1548772) - chef: fix chef install from gems (LP: #1553345) - systemd: do not specify After of obsolete syslog.target (LP: #1536964) + - centos: Ensure that resolve conf object is written as a str (LP: #1479988) 0.7.6: - open 0.7.6 diff --git a/cloudinit/config/cc_lxd.py b/cloudinit/config/cc_lxd.py index 63b8fb63..bf735648 100644 --- a/cloudinit/config/cc_lxd.py +++ b/cloudinit/config/cc_lxd.py @@ -30,6 +30,19 @@ Example config: storage_create_loop: <size> storage_pool: <name> trust_password: <password> + bridge: + mode: <new, existing or none> + name: <name> + ipv4_address: <ip addr> + ipv4_netmask: <cidr> + ipv4_dhcp_first: <ip addr> + ipv4_dhcp_last: <ip addr> + ipv4_dhcp_leases: <size> + ipv4_nat: <bool> + ipv6_address: <ip addr> + ipv6_netmask: <cidr> + ipv6_nat: <bool> + domain: <domain> """ from cloudinit import util @@ -46,22 +59,24 @@ def handle(name, cfg, cloud, log, args): type(lxd_cfg)) return + # Grab the configuration init_cfg = lxd_cfg.get('init') if not isinstance(init_cfg, dict): log.warn("lxd/init config must be a dictionary. found a '%s'", type(init_cfg)) init_cfg = {} - if not init_cfg: - log.debug("no lxd/init config. disabled.") - return + bridge_cfg = lxd_cfg.get('bridge') + if not isinstance(bridge_cfg, dict): + log.warn("lxd/bridge config must be a dictionary. found a '%s'", + type(bridge_cfg)) + bridge_cfg = {} + # Install the needed packages packages = [] - # Ensure lxd is installed if not util.which("lxd"): packages.append('lxd') - # if using zfs, get the utils if init_cfg.get("storage_backend") == "zfs" and not util.which('zfs'): packages.append('zfs') @@ -73,13 +88,89 @@ def handle(name, cfg, cloud, log, args): return # Set up lxd if init config is given - init_keys = ( - 'network_address', 'network_port', 'storage_backend', - 'storage_create_device', 'storage_create_loop', - 'storage_pool', 'trust_password') - cmd = ['lxd', 'init', '--auto'] - for k in init_keys: - if init_cfg.get(k): - cmd.extend(["--%s=%s" % - (k.replace('_', '-'), str(init_cfg[k]))]) - util.subp(cmd) + if init_cfg: + init_keys = ( + 'network_address', 'network_port', 'storage_backend', + 'storage_create_device', 'storage_create_loop', + 'storage_pool', 'trust_password') + cmd = ['lxd', 'init', '--auto'] + for k in init_keys: + if init_cfg.get(k): + cmd.extend(["--%s=%s" % + (k.replace('_', '-'), str(init_cfg[k]))]) + util.subp(cmd) + + # Set up lxd-bridge if bridge config is given + dconf_comm = "debconf-communicate" + if bridge_cfg and util.which(dconf_comm): + debconf = bridge_to_debconf(bridge_cfg) + + # Update debconf database + try: + log.debug("Setting lxd debconf via " + dconf_comm) + data = "\n".join(["set %s %s" % (k, v) + for k, v in debconf.items()]) + "\n" + util.subp(['debconf-communicate'], data) + except: + util.logexc(log, "Failed to run '%s' for lxd with" % dconf_comm) + + # Remove the existing configuration file (forces re-generation) + util.del_file("/etc/default/lxd-bridge") + + # Run reconfigure + log.debug("Running dpkg-reconfigure for lxd") + util.subp(['dpkg-reconfigure', 'lxd', + '--frontend=noninteractive']) + elif bridge_cfg: + raise RuntimeError( + "Unable to configure lxd bridge without %s." + dconf_comm) + + +def bridge_to_debconf(bridge_cfg): + debconf = {} + + if bridge_cfg.get("mode") == "none": + debconf["lxd/setup-bridge"] = "false" + debconf["lxd/bridge-name"] = "" + + elif bridge_cfg.get("mode") == "existing": + debconf["lxd/setup-bridge"] = "false" + debconf["lxd/use-existing-bridge"] = "true" + debconf["lxd/bridge-name"] = bridge_cfg.get("name") + + elif bridge_cfg.get("mode") == "new": + debconf["lxd/setup-bridge"] = "true" + if bridge_cfg.get("name"): + debconf["lxd/bridge-name"] = bridge_cfg.get("name") + + if bridge_cfg.get("ipv4_address"): + debconf["lxd/bridge-ipv4"] = "true" + debconf["lxd/bridge-ipv4-address"] = \ + bridge_cfg.get("ipv4_address") + debconf["lxd/bridge-ipv4-netmask"] = \ + bridge_cfg.get("ipv4_netmask") + debconf["lxd/bridge-ipv4-dhcp-first"] = \ + bridge_cfg.get("ipv4_dhcp_first") + debconf["lxd/bridge-ipv4-dhcp-last"] = \ + bridge_cfg.get("ipv4_dhcp_last") + debconf["lxd/bridge-ipv4-dhcp-leases"] = \ + bridge_cfg.get("ipv4_dhcp_leases") + debconf["lxd/bridge-ipv4-nat"] = \ + bridge_cfg.get("ipv4_nat", "true") + + if bridge_cfg.get("ipv6_address"): + debconf["lxd/bridge-ipv6"] = "true" + debconf["lxd/bridge-ipv6-address"] = \ + bridge_cfg.get("ipv6_address") + debconf["lxd/bridge-ipv6-netmask"] = \ + bridge_cfg.get("ipv6_netmask") + debconf["lxd/bridge-ipv6-nat"] = \ + bridge_cfg.get("ipv6_nat", "false") + + if bridge_cfg.get("domain"): + debconf["lxd/bridge-domain"] = bridge_cfg.get("domain") + + else: + raise Exception("invalid bridge mode \"%s\"" % bridge_cfg.get("mode")) + + return debconf diff --git a/cloudinit/distros/rhel_util.py b/cloudinit/distros/rhel_util.py index 84aad623..903d7793 100644 --- a/cloudinit/distros/rhel_util.py +++ b/cloudinit/distros/rhel_util.py @@ -86,4 +86,4 @@ def update_resolve_conf_file(fn, dns_servers, search_servers): r_conf.add_search_domain(s) except ValueError: util.logexc(LOG, "Failed at adding search domain %s", s) - util.write_file(fn, r_conf, 0o644) + util.write_file(fn, str(r_conf), 0o644) diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py index f8f94759..d7d4e844 100644 --- a/cloudinit/sources/DataSourceCloudSigma.py +++ b/cloudinit/sources/DataSourceCloudSigma.py @@ -56,7 +56,7 @@ class DataSourceCloudSigma(sources.DataSource): LOG.debug("determining hypervisor product name via dmi data") sys_product_name = util.read_dmi_data("system-product-name") if not sys_product_name: - LOG.warn("failed to get hypervisor product name via dmi data") + LOG.debug("system-product-name not available in dmi data") return False else: LOG.debug("detected hypervisor as %s", sys_product_name) diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py index c2fba4d2..74d0e5ec 100644 --- a/cloudinit/sources/DataSourceNoCloud.py +++ b/cloudinit/sources/DataSourceNoCloud.py @@ -216,8 +216,11 @@ class DataSourceNoCloud(sources.DataSource): if not current: return None + # LP: #1568150 need getattr in the case that an old class object + # has been loaded from a pickled file and now executing new source. + dirs = getattr(self, 'seed_dirs', [self.seed_dir]) quick_id = _quick_read_instance_id(cmdline_id=self.cmdline_id, - dirs=self.seed_dirs) + dirs=dirs) if not quick_id: return None return quick_id == current @@ -238,6 +241,8 @@ def _quick_read_instance_id(cmdline_id, dirs=None): return fill[iid_key] for d in dirs: + if d is None: + continue try: data = util.pathprefix2dict(d, required=['meta-data']) md = util.load_yaml(data['meta-data']) diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py index 5edab152..6cbd8dfa 100644 --- a/cloudinit/sources/DataSourceSmartOS.py +++ b/cloudinit/sources/DataSourceSmartOS.py @@ -242,7 +242,7 @@ class DataSourceSmartOS(sources.DataSource): # SDC KVM instances will provide dmi data, LX-brand does not if self.smartos_type == 'kvm': dmi_info = dmi_data() - if dmi_info is False: + if dmi_info is None: LOG.debug("No dmidata utility found") return False diff --git a/doc/examples/cloud-config-lxd.txt b/doc/examples/cloud-config-lxd.txt index b9bb4aa5..e96f314b 100644 --- a/doc/examples/cloud-config-lxd.txt +++ b/doc/examples/cloud-config-lxd.txt @@ -12,6 +12,20 @@ # storage_create_loop: set up loop based storage with size in GB # storage_pool: name of storage pool to use or create # trust_password: password required to add new clients +# bridge: dict of options for the lxd bridge +# mode: one of "new", "existing" or "none". Defaults to "new" +# name: the name of the bridge. Defaults to "lxdbr0" +# ipv4_address: an IPv4 address (e.g. 10.0.8.1) +# ipv4_netmask: a CIDR mask value (e.g. 24) +# ipv4_dhcp_first: the first IP of the DHCP range (e.g. 10.0.8.2) +# ipv4_dhcp_last: the last IP of the DHCP range (e.g. 10.0.8.254) +# ipv4_dhcp_leases: the size of the DHCP pool (e.g. 250) +# ipv4_nat: either "true" or "false" +# ipv6_address: an IPv6 address (e.g. fd98:9e0:3744::1) +# ipv6_netmask: a CIDR mask value (e.g. 64) +# ipv6_nat: either "true" or "false" +# domain: domain name to use for the bridge + lxd: init: @@ -20,6 +34,19 @@ lxd: storage_backend: zfs storage_pool: datapool storage_create_loop: 10 + bridge: + mode: new + name: lxdbr0 + ipv4_address: 10.0.8.1 + ipv4_netmask: 24 + ipv4_dhcp_first: 10.0.8.2 + ipv4_dhcp_last: 10.0.8.3 + ipv4_dhcp_leases: 250 + ipv4_nat: true + ipv6_address: fd98:9e0:3744::1 + ipv6_netmask: 64 + ipv6_nat: true + domain: lxd # The simplist working configuration is diff --git a/tests/unittests/test_distros/test_resolv.py b/tests/unittests/test_distros/test_resolv.py index faaf5b7f..9edeb6e7 100644 --- a/tests/unittests/test_distros/test_resolv.py +++ b/tests/unittests/test_distros/test_resolv.py @@ -1,6 +1,8 @@ from cloudinit.distros.parsers import resolv_conf +from cloudinit.distros import rhel_util import re +import tempfile from ..helpers import TestCase @@ -19,6 +21,10 @@ class TestResolvHelper(TestCase): rp_r = str(rp).strip() self.assertEquals(BASE_RESOLVE, rp_r) + def test_write_works(self): + with tempfile.NamedTemporaryFile() as fh: + rhel_util.update_resolve_conf_file(fh.name, [], []) + def test_local_domain(self): rp = resolv_conf.ResolvConf(BASE_RESOLVE) self.assertEquals(None, rp.local_domain) diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py index 7ffa2a53..5f61ba6a 100644 --- a/tests/unittests/test_handler/test_handler_lxd.py +++ b/tests/unittests/test_handler/test_handler_lxd.py @@ -73,3 +73,62 @@ class TestLxd(t_help.TestCase): cc_lxd.handle('cc_lxd', {'package_update': True}, cc, LOG, []) self.assertFalse(cc.distro.install_packages.called) self.assertFalse(mock_util.subp.called) + + def test_lxd_debconf_new_full(self): + data = {"mode": "new", + "name": "testbr0", + "ipv4_address": "10.0.8.1", + "ipv4_netmask": "24", + "ipv4_dhcp_first": "10.0.8.2", + "ipv4_dhcp_last": "10.0.8.254", + "ipv4_dhcp_leases": "250", + "ipv4_nat": "true", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true", + "domain": "lxd"} + self.assertEquals( + cc_lxd.bridge_to_debconf(data), + {"lxd/setup-bridge": "true", + "lxd/bridge-name": "testbr0", + "lxd/bridge-ipv4": "true", + "lxd/bridge-ipv4-address": "10.0.8.1", + "lxd/bridge-ipv4-netmask": "24", + "lxd/bridge-ipv4-dhcp-first": "10.0.8.2", + "lxd/bridge-ipv4-dhcp-last": "10.0.8.254", + "lxd/bridge-ipv4-dhcp-leases": "250", + "lxd/bridge-ipv4-nat": "true", + "lxd/bridge-ipv6": "true", + "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", + "lxd/bridge-ipv6-netmask": "64", + "lxd/bridge-ipv6-nat": "true", + "lxd/bridge-domain": "lxd"}) + + def test_lxd_debconf_new_partial(self): + data = {"mode": "new", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true"} + self.assertEquals( + cc_lxd.bridge_to_debconf(data), + {"lxd/setup-bridge": "true", + "lxd/bridge-ipv6": "true", + "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", + "lxd/bridge-ipv6-netmask": "64", + "lxd/bridge-ipv6-nat": "true"}) + + def test_lxd_debconf_existing(self): + data = {"mode": "existing", + "name": "testbr0"} + self.assertEquals( + cc_lxd.bridge_to_debconf(data), + {"lxd/setup-bridge": "false", + "lxd/use-existing-bridge": "true", + "lxd/bridge-name": "testbr0"}) + + def test_lxd_debconf_none(self): + data = {"mode": "none"} + self.assertEquals( + cc_lxd.bridge_to_debconf(data), + {"lxd/setup-bridge": "false", + "lxd/bridge-name": ""}) |