# This file is part of cloud-init. See LICENSE file for license information. import os import pwd import unittest import pytest from cloudinit import helpers, util from cloudinit.sources import DataSourceOpenNebula as ds from tests.unittests.helpers import CiTestCase, mock, populate_dir TEST_VARS = { "VAR1": "single", "VAR2": "double word", "VAR3": "multi\nline\n", "VAR4": "'single'", "VAR5": "'double word'", "VAR6": "'multi\nline\n'", "VAR7": "single\\t", "VAR8": "double\\tword", "VAR9": "multi\\t\nline\n", "VAR10": "\\", # expect '\' "VAR11": "'", # expect ' "VAR12": "$", # expect $ } INVALID_CONTEXT = ";" USER_DATA = "#cloud-config\napt_upgrade: true" SSH_KEY = "ssh-rsa AAAAB3NzaC1....sIkJhq8wdX+4I3A4cYbYP ubuntu@server-460-%i" HOSTNAME = "foo.example.com" PUBLIC_IP = "10.0.0.3" MACADDR = "02:00:0a:12:01:01" IP_BY_MACADDR = "10.18.1.1" IP4_PREFIX = "24" IP6_GLOBAL = "2001:db8:1:0:400:c0ff:fea8:1ba" IP6_ULA = "fd01:dead:beaf:0:400:c0ff:fea8:1ba" IP6_GW = "2001:db8:1::ffff" IP6_PREFIX = "48" DS_PATH = "cloudinit.sources.DataSourceOpenNebula" class TestOpenNebulaDataSource(CiTestCase): parsed_user = None allowed_subp = ["bash"] def setUp(self): super(TestOpenNebulaDataSource, self).setUp() self.tmp = self.tmp_dir() self.paths = helpers.Paths( {"cloud_dir": self.tmp, "run_dir": self.tmp} ) # defaults for few tests self.ds = ds.DataSourceOpenNebula self.seed_dir = os.path.join(self.paths.seed_dir, "opennebula") self.sys_cfg = {"datasource": {"OpenNebula": {"dsmode": "local"}}} # we don't want 'sudo' called in tests. so we patch switch_user_cmd def my_switch_user_cmd(user): self.parsed_user = user return [] self.switch_user_cmd_real = ds.switch_user_cmd ds.switch_user_cmd = my_switch_user_cmd def tearDown(self): ds.switch_user_cmd = self.switch_user_cmd_real super(TestOpenNebulaDataSource, self).tearDown() def test_get_data_non_contextdisk(self): orig_find_devs_with = util.find_devs_with try: # dont' try to lookup for CDs util.find_devs_with = lambda n: [] # type: ignore dsrc = self.ds(sys_cfg=self.sys_cfg, distro=None, paths=self.paths) ret = dsrc.get_data() self.assertFalse(ret) finally: util.find_devs_with = orig_find_devs_with def test_get_data_broken_contextdisk(self): orig_find_devs_with = util.find_devs_with try: # dont' try to lookup for CDs util.find_devs_with = lambda n: [] # type: ignore populate_dir(self.seed_dir, {"context.sh": INVALID_CONTEXT}) dsrc = self.ds(sys_cfg=self.sys_cfg, distro=None, paths=self.paths) self.assertRaises(ds.BrokenContextDiskDir, dsrc.get_data) finally: util.find_devs_with = orig_find_devs_with def test_get_data_invalid_identity(self): orig_find_devs_with = util.find_devs_with try: # generate non-existing system user name sys_cfg = self.sys_cfg invalid_user = "invalid" while not sys_cfg["datasource"]["OpenNebula"].get("parseuser"): try: pwd.getpwnam(invalid_user) invalid_user += "X" except KeyError: sys_cfg["datasource"]["OpenNebula"][ "parseuser" ] = invalid_user # dont' try to lookup for CDs util.find_devs_with = lambda n: [] # type: ignore populate_context_dir(self.seed_dir, {"KEY1": "val1"}) dsrc = self.ds(sys_cfg=sys_cfg, distro=None, paths=self.paths) self.assertRaises(ds.BrokenContextDiskDir, dsrc.get_data) finally: util.find_devs_with = orig_find_devs_with def test_get_data(self): orig_find_devs_with = util.find_devs_with try: # dont' try to lookup for CDs util.find_devs_with = lambda n: [] # type: ignore populate_context_dir(self.seed_dir, {"KEY1": "val1"}) dsrc = self.ds(sys_cfg=self.sys_cfg, distro=None, paths=self.paths) with mock.patch(DS_PATH + ".pwd.getpwnam") as getpwnam: ret = dsrc.get_data() self.assertEqual([mock.call("nobody")], getpwnam.call_args_list) self.assertTrue(ret) finally: util.find_devs_with = orig_find_devs_with self.assertEqual("opennebula", dsrc.cloud_name) self.assertEqual("opennebula", dsrc.platform_type) self.assertEqual( "seed-dir (%s/seed/opennebula)" % self.tmp, dsrc.subplatform ) def test_seed_dir_non_contextdisk(self): self.assertRaises( ds.NonContextDiskDir, ds.read_context_disk_dir, self.seed_dir, mock.Mock(), ) def test_seed_dir_empty1_context(self): populate_dir(self.seed_dir, {"context.sh": ""}) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertIsNone(results["userdata"]) self.assertEqual(results["metadata"], {}) def test_seed_dir_empty2_context(self): populate_context_dir(self.seed_dir, {}) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertIsNone(results["userdata"]) self.assertEqual(results["metadata"], {}) def test_seed_dir_broken_context(self): populate_dir(self.seed_dir, {"context.sh": INVALID_CONTEXT}) self.assertRaises( ds.BrokenContextDiskDir, ds.read_context_disk_dir, self.seed_dir, mock.Mock(), ) def test_context_parser(self): populate_context_dir(self.seed_dir, TEST_VARS) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("metadata" in results) self.assertEqual(TEST_VARS, results["metadata"]) def test_ssh_key(self): public_keys = ["first key", "second key"] for c in range(4): for k in ("SSH_KEY", "SSH_PUBLIC_KEY"): my_d = os.path.join(self.tmp, "%s-%i" % (k, c)) populate_context_dir(my_d, {k: "\n".join(public_keys)}) results = ds.read_context_disk_dir(my_d, mock.Mock()) self.assertTrue("metadata" in results) self.assertTrue("public-keys" in results["metadata"]) self.assertEqual( public_keys, results["metadata"]["public-keys"] ) public_keys.append(SSH_KEY % (c + 1,)) def test_user_data_plain(self): for k in ("USER_DATA", "USERDATA"): my_d = os.path.join(self.tmp, k) populate_context_dir(my_d, {k: USER_DATA, "USERDATA_ENCODING": ""}) results = ds.read_context_disk_dir(my_d, mock.Mock()) self.assertTrue("userdata" in results) self.assertEqual(USER_DATA, results["userdata"]) def test_user_data_encoding_required_for_decode(self): b64userdata = util.b64e(USER_DATA) for k in ("USER_DATA", "USERDATA"): my_d = os.path.join(self.tmp, k) populate_context_dir(my_d, {k: b64userdata}) results = ds.read_context_disk_dir(my_d, mock.Mock()) self.assertTrue("userdata" in results) self.assertEqual(b64userdata, results["userdata"]) def test_user_data_base64_encoding(self): for k in ("USER_DATA", "USERDATA"): my_d = os.path.join(self.tmp, k) populate_context_dir( my_d, {k: util.b64e(USER_DATA), "USERDATA_ENCODING": "base64"} ) results = ds.read_context_disk_dir(my_d, mock.Mock()) self.assertTrue("userdata" in results) self.assertEqual(USER_DATA, results["userdata"]) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_hostname(self, m_get_phys_by_mac): for dev in ("eth0", "ens3"): m_get_phys_by_mac.return_value = {MACADDR: dev} for k in ( "SET_HOSTNAME", "HOSTNAME", "PUBLIC_IP", "IP_PUBLIC", "ETH0_IP", ): my_d = os.path.join(self.tmp, k) populate_context_dir(my_d, {k: PUBLIC_IP}) results = ds.read_context_disk_dir(my_d, mock.Mock()) self.assertTrue("metadata" in results) self.assertTrue("local-hostname" in results["metadata"]) self.assertEqual( PUBLIC_IP, results["metadata"]["local-hostname"] ) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_network_interfaces(self, m_get_phys_by_mac): for dev in ("eth0", "ens3"): m_get_phys_by_mac.return_value = {MACADDR: dev} # without ETH0_MAC # for Older OpenNebula? populate_context_dir(self.seed_dir, {"ETH0_IP": IP_BY_MACADDR}) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP_BY_MACADDR + "/" + IP4_PREFIX in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP and ETH0_MAC populate_context_dir( self.seed_dir, {"ETH0_IP": IP_BY_MACADDR, "ETH0_MAC": MACADDR} ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP_BY_MACADDR + "/" + IP4_PREFIX in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP with empty string and ETH0_MAC # in the case of using Virtual Network contains # "AR = [ TYPE = ETHER ]" populate_context_dir( self.seed_dir, {"ETH0_IP": "", "ETH0_MAC": MACADDR} ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP_BY_MACADDR + "/" + IP4_PREFIX in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_MASK populate_context_dir( self.seed_dir, { "ETH0_IP": IP_BY_MACADDR, "ETH0_MAC": MACADDR, "ETH0_MASK": "255.255.0.0", }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP_BY_MACADDR + "/16" in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_MASK with empty string populate_context_dir( self.seed_dir, { "ETH0_IP": IP_BY_MACADDR, "ETH0_MAC": MACADDR, "ETH0_MASK": "", }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP_BY_MACADDR + "/" + IP4_PREFIX in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP6 populate_context_dir( self.seed_dir, { "ETH0_IP6": IP6_GLOBAL, "ETH0_MAC": MACADDR, }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP6_GLOBAL + "/64" in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP6_ULA populate_context_dir( self.seed_dir, { "ETH0_IP6_ULA": IP6_ULA, "ETH0_MAC": MACADDR, }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP6_ULA + "/64" in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP6 and ETH0_IP6_PREFIX_LENGTH populate_context_dir( self.seed_dir, { "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_PREFIX_LENGTH": IP6_PREFIX, "ETH0_MAC": MACADDR, }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP6_GLOBAL + "/" + IP6_PREFIX in results["network-interfaces"]["ethernets"][dev]["addresses"] ) # ETH0_IP6 and ETH0_IP6_PREFIX_LENGTH with empty string populate_context_dir( self.seed_dir, { "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_PREFIX_LENGTH": "", "ETH0_MAC": MACADDR, }, ) results = ds.read_context_disk_dir(self.seed_dir, mock.Mock()) self.assertTrue("network-interfaces" in results) self.assertTrue( IP6_GLOBAL + "/64" in results["network-interfaces"]["ethernets"][dev]["addresses"] ) def test_find_candidates(self): def my_devs_with(criteria): return { "LABEL=CONTEXT": ["/dev/sdb"], "LABEL=CDROM": ["/dev/sr0"], "TYPE=iso9660": ["/dev/vdb"], }.get(criteria, []) orig_find_devs_with = util.find_devs_with try: util.find_devs_with = my_devs_with self.assertEqual( ["/dev/sdb", "/dev/sr0", "/dev/vdb"], ds.find_candidate_devs() ) finally: util.find_devs_with = orig_find_devs_with @mock.patch(DS_PATH + ".net.get_interfaces_by_mac", mock.Mock(return_value={})) class TestOpenNebulaNetwork(unittest.TestCase): system_nics = ("eth0", "ens3") def test_context_devname(self): """Verify context_devname correctly returns mac and name.""" context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH1_MAC": "02:00:0a:12:0f:0f", } expected = { "02:00:0a:12:01:01": "ETH0", "02:00:0a:12:0f:0f": "ETH1", } net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(expected, net.context_devname) def test_get_nameservers(self): """ Verify get_nameservers('device') correctly returns DNS server addresses and search domains. """ context = { "DNS": "1.2.3.8", "ETH0_DNS": "1.2.3.6 1.2.3.7", "ETH0_SEARCH_DOMAIN": "example.com example.org", } expected = { "addresses": ["1.2.3.6", "1.2.3.7", "1.2.3.8"], "search": ["example.com", "example.org"], } net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_nameservers("eth0") self.assertEqual(expected, val) def test_get_mtu(self): """Verify get_mtu('device') correctly returns MTU size.""" context = {"ETH0_MTU": "1280"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_mtu("eth0") self.assertEqual("1280", val) def test_get_ip(self): """Verify get_ip('device') correctly returns IPv4 address.""" context = {"ETH0_IP": PUBLIC_IP} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip("eth0", MACADDR) self.assertEqual(PUBLIC_IP, val) def test_get_ip_emptystring(self): """ Verify get_ip('device') correctly returns IPv4 address. It returns IP address created by MAC address if ETH0_IP has empty string. """ context = {"ETH0_IP": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip("eth0", MACADDR) self.assertEqual(IP_BY_MACADDR, val) def test_get_ip6(self): """ Verify get_ip6('device') correctly returns IPv6 address. In this case, IPv6 address is Given by ETH0_IP6. """ context = { "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_ULA": "", } expected = [IP6_GLOBAL] net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip6("eth0") self.assertEqual(expected, val) def test_get_ip6_ula(self): """ Verify get_ip6('device') correctly returns IPv6 address. In this case, IPv6 address is Given by ETH0_IP6_ULA. """ context = { "ETH0_IP6": "", "ETH0_IP6_ULA": IP6_ULA, } expected = [IP6_ULA] net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip6("eth0") self.assertEqual(expected, val) def test_get_ip6_dual(self): """ Verify get_ip6('device') correctly returns IPv6 address. In this case, IPv6 addresses are Given by ETH0_IP6 and ETH0_IP6_ULA. """ context = { "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_ULA": IP6_ULA, } expected = [IP6_GLOBAL, IP6_ULA] net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip6("eth0") self.assertEqual(expected, val) def test_get_ip6_prefix(self): """ Verify get_ip6_prefix('device') correctly returns IPv6 prefix. """ context = {"ETH0_IP6_PREFIX_LENGTH": IP6_PREFIX} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip6_prefix("eth0") self.assertEqual(IP6_PREFIX, val) def test_get_ip6_prefix_emptystring(self): """ Verify get_ip6_prefix('device') correctly returns IPv6 prefix. It returns default value '64' if ETH0_IP6_PREFIX_LENGTH has empty string. """ context = {"ETH0_IP6_PREFIX_LENGTH": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_ip6_prefix("eth0") self.assertEqual("64", val) def test_get_gateway(self): """ Verify get_gateway('device') correctly returns IPv4 default gateway address. """ context = {"ETH0_GATEWAY": "1.2.3.5"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_gateway("eth0") self.assertEqual("1.2.3.5", val) def test_get_gateway6(self): """ Verify get_gateway6('device') correctly returns IPv6 default gateway address. """ for k in ("GATEWAY6", "IP6_GATEWAY"): context = {"ETH0_" + k: IP6_GW} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_gateway6("eth0") self.assertEqual(IP6_GW, val) def test_get_mask(self): """ Verify get_mask('device') correctly returns IPv4 subnet mask. """ context = {"ETH0_MASK": "255.255.0.0"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_mask("eth0") self.assertEqual("255.255.0.0", val) def test_get_mask_emptystring(self): """ Verify get_mask('device') correctly returns IPv4 subnet mask. It returns default value '255.255.255.0' if ETH0_MASK has empty string. """ context = {"ETH0_MASK": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_mask("eth0") self.assertEqual("255.255.255.0", val) def test_get_network(self): """ Verify get_network('device') correctly returns IPv4 network address. """ context = {"ETH0_NETWORK": "1.2.3.0"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_network("eth0", MACADDR) self.assertEqual("1.2.3.0", val) def test_get_network_emptystring(self): """ Verify get_network('device') correctly returns IPv4 network address. It returns network address created by MAC address if ETH0_NETWORK has empty string. """ context = {"ETH0_NETWORK": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_network("eth0", MACADDR) self.assertEqual("10.18.1.0", val) def test_get_field(self): """ Verify get_field('device', 'name') returns *context* value. """ context = {"ETH9_DUMMY": "DUMMY_VALUE"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_field("eth9", "dummy") self.assertEqual("DUMMY_VALUE", val) def test_get_field_withdefaultvalue(self): """ Verify get_field('device', 'name', 'default value') returns *context* value. """ context = {"ETH9_DUMMY": "DUMMY_VALUE"} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_field("eth9", "dummy", "DEFAULT_VALUE") self.assertEqual("DUMMY_VALUE", val) def test_get_field_withdefaultvalue_emptycontext(self): """ Verify get_field('device', 'name', 'default value') returns *default* value if context value is empty string. """ context = {"ETH9_DUMMY": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_field("eth9", "dummy", "DEFAULT_VALUE") self.assertEqual("DEFAULT_VALUE", val) def test_get_field_emptycontext(self): """ Verify get_field('device', 'name') returns None if context value is empty string. """ context = {"ETH9_DUMMY": ""} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_field("eth9", "dummy") self.assertEqual(None, val) def test_get_field_nonecontext(self): """ Verify get_field('device', 'name') returns None if context value is None. """ context = {"ETH9_DUMMY": None} net = ds.OpenNebulaNetwork(context, mock.Mock()) val = net.get_field("eth9", "dummy") self.assertEqual(None, val) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_gen_conf_gateway(self, m_get_phys_by_mac): """Test rendering with/without IPv4 gateway""" self.maxDiff = None # empty ETH0_GATEWAY context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_GATEWAY": "", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) # set ETH0_GATEWAY context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_GATEWAY": "1.2.3.5", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "gateway4": "1.2.3.5", "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_gen_conf_gateway6(self, m_get_phys_by_mac): """Test rendering with/without IPv6 gateway""" self.maxDiff = None # empty ETH0_GATEWAY6 context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_GATEWAY6": "", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) # set ETH0_GATEWAY6 context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_GATEWAY6": IP6_GW, } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "gateway6": IP6_GW, "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_gen_conf_ipv6address(self, m_get_phys_by_mac): """Test rendering with/without IPv6 address""" self.maxDiff = None # empty ETH0_IP6, ETH0_IP6_ULA, ETH0_IP6_PREFIX_LENGTH context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_IP6": "", "ETH0_IP6_ULA": "", "ETH0_IP6_PREFIX_LENGTH": "", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) # set ETH0_IP6, ETH0_IP6_ULA, ETH0_IP6_PREFIX_LENGTH context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_PREFIX_LENGTH": IP6_PREFIX, "ETH0_IP6_ULA": IP6_ULA, } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [ IP_BY_MACADDR + "/" + IP4_PREFIX, IP6_GLOBAL + "/" + IP6_PREFIX, IP6_ULA + "/" + IP6_PREFIX, ], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_gen_conf_dns(self, m_get_phys_by_mac): """Test rendering with/without DNS server, search domain""" self.maxDiff = None # empty DNS, ETH0_DNS, ETH0_SEARCH_DOMAIN context = { "ETH0_MAC": "02:00:0a:12:01:01", "DNS": "", "ETH0_DNS": "", "ETH0_SEARCH_DOMAIN": "", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) # set DNS, ETH0_DNS, ETH0_SEARCH_DOMAIN context = { "ETH0_MAC": "02:00:0a:12:01:01", "DNS": "1.2.3.8", "ETH0_DNS": "1.2.3.6 1.2.3.7", "ETH0_SEARCH_DOMAIN": "example.com example.org", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "nameservers": { "addresses": ["1.2.3.6", "1.2.3.7", "1.2.3.8"], "search": ["example.com", "example.org"], }, "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_gen_conf_mtu(self, m_get_phys_by_mac): """Test rendering with/without MTU""" self.maxDiff = None # empty ETH0_MTU context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_MTU": "", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) # set ETH0_MTU context = { "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_MTU": "1280", } for nic in self.system_nics: expected = { "version": 2, "ethernets": { nic: { "mtu": "1280", "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork(context, mock.Mock()) self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_eth0(self, m_get_phys_by_mac): for nic in self.system_nics: m_get_phys_by_mac.return_value = {MACADDR: nic} net = ds.OpenNebulaNetwork({}, mock.Mock()) expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/" + IP4_PREFIX], } }, } self.assertEqual(net.gen_conf(), expected) @mock.patch(DS_PATH + ".get_physical_nics_by_mac") def test_distro_passed_through(self, m_get_physical_nics_by_mac): ds.OpenNebulaNetwork({}, mock.sentinel.distro) self.assertEqual( [mock.call(mock.sentinel.distro)], m_get_physical_nics_by_mac.call_args_list, ) def test_eth0_override(self): self.maxDiff = None context = { "DNS": "1.2.3.8", "ETH0_DNS": "1.2.3.6 1.2.3.7", "ETH0_GATEWAY": "1.2.3.5", "ETH0_GATEWAY6": "", "ETH0_IP": IP_BY_MACADDR, "ETH0_IP6": "", "ETH0_IP6_PREFIX_LENGTH": "", "ETH0_IP6_ULA": "", "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_MASK": "255.255.0.0", "ETH0_MTU": "", "ETH0_NETWORK": "10.18.0.0", "ETH0_SEARCH_DOMAIN": "", } for nic in self.system_nics: net = ds.OpenNebulaNetwork( context, mock.Mock(), system_nics_by_mac={MACADDR: nic} ) expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [IP_BY_MACADDR + "/16"], "gateway4": "1.2.3.5", "nameservers": { "addresses": ["1.2.3.6", "1.2.3.7", "1.2.3.8"] }, } }, } self.assertEqual(expected, net.gen_conf()) def test_eth0_v4v6_override(self): self.maxDiff = None context = { "DNS": "1.2.3.8", "ETH0_DNS": "1.2.3.6 1.2.3.7", "ETH0_GATEWAY": "1.2.3.5", "ETH0_GATEWAY6": IP6_GW, "ETH0_IP": IP_BY_MACADDR, "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_PREFIX_LENGTH": IP6_PREFIX, "ETH0_IP6_ULA": IP6_ULA, "ETH0_MAC": "02:00:0a:12:01:01", "ETH0_MASK": "255.255.0.0", "ETH0_MTU": "1280", "ETH0_NETWORK": "10.18.0.0", "ETH0_SEARCH_DOMAIN": "example.com example.org", } for nic in self.system_nics: net = ds.OpenNebulaNetwork( context, mock.Mock(), system_nics_by_mac={MACADDR: nic} ) expected = { "version": 2, "ethernets": { nic: { "match": {"macaddress": MACADDR}, "addresses": [ IP_BY_MACADDR + "/16", IP6_GLOBAL + "/" + IP6_PREFIX, IP6_ULA + "/" + IP6_PREFIX, ], "gateway4": "1.2.3.5", "gateway6": IP6_GW, "nameservers": { "addresses": ["1.2.3.6", "1.2.3.7", "1.2.3.8"], "search": ["example.com", "example.org"], }, "mtu": "1280", } }, } self.assertEqual(expected, net.gen_conf()) def test_multiple_nics(self): """Test rendering multiple nics with names that differ from context.""" self.maxDiff = None MAC_1 = "02:00:0a:12:01:01" MAC_2 = "02:00:0a:12:01:02" context = { "DNS": "1.2.3.8", "ETH0_DNS": "1.2.3.6 1.2.3.7", "ETH0_GATEWAY": "1.2.3.5", "ETH0_GATEWAY6": IP6_GW, "ETH0_IP": "10.18.1.1", "ETH0_IP6": IP6_GLOBAL, "ETH0_IP6_PREFIX_LENGTH": "", "ETH0_IP6_ULA": IP6_ULA, "ETH0_MAC": MAC_2, "ETH0_MASK": "255.255.0.0", "ETH0_MTU": "1280", "ETH0_NETWORK": "10.18.0.0", "ETH0_SEARCH_DOMAIN": "example.com", "ETH3_DNS": "10.3.1.2", "ETH3_GATEWAY": "10.3.0.1", "ETH3_GATEWAY6": "", "ETH3_IP": "10.3.1.3", "ETH3_IP6": "", "ETH3_IP6_PREFIX_LENGTH": "", "ETH3_IP6_ULA": "", "ETH3_MAC": MAC_1, "ETH3_MASK": "255.255.0.0", "ETH3_MTU": "", "ETH3_NETWORK": "10.3.0.0", "ETH3_SEARCH_DOMAIN": "third.example.com third.example.org", } net = ds.OpenNebulaNetwork( context, mock.Mock(), system_nics_by_mac={MAC_1: "enp0s25", MAC_2: "enp1s2"}, ) expected = { "version": 2, "ethernets": { "enp1s2": { "match": {"macaddress": MAC_2}, "addresses": [ "10.18.1.1/16", IP6_GLOBAL + "/64", IP6_ULA + "/64", ], "gateway4": "1.2.3.5", "gateway6": IP6_GW, "nameservers": { "addresses": ["1.2.3.6", "1.2.3.7", "1.2.3.8"], "search": ["example.com"], }, "mtu": "1280", }, "enp0s25": { "match": {"macaddress": MAC_1}, "addresses": ["10.3.1.3/16"], "gateway4": "10.3.0.1", "nameservers": { "addresses": ["10.3.1.2", "1.2.3.8"], "search": ["third.example.com", "third.example.org"], }, }, }, } self.assertEqual(expected, net.gen_conf()) class TestParseShellConfig: @pytest.mark.allow_subp_for("bash") def test_no_seconds(self): cfg = "\n".join(["foo=bar", "SECONDS=2", "xx=foo"]) # we could test 'sleep 2', but that would make the test run slower. ret = ds.parse_shell_config(cfg) assert ret == {"foo": "bar", "xx": "foo"} class TestGetPhysicalNicsByMac: @pytest.mark.parametrize( "interfaces_by_mac,physical_devs,expected_return", [ # No interfaces => empty return ({}, [], {}), # Only virtual interface => empty return ({"mac1": "virtual0"}, [], {}), # Only physical interface => it is returned ({"mac2": "physical0"}, ["physical0"], {"mac2": "physical0"}), # Combination of physical and virtual => only physical returned ( {"mac3": "physical1", "mac4": "virtual1"}, ["physical1"], {"mac3": "physical1"}, ), ], ) def test(self, interfaces_by_mac, physical_devs, expected_return): distro = mock.Mock() distro.networking.is_physical.side_effect = ( lambda devname: devname in physical_devs ) with mock.patch( DS_PATH + ".net.get_interfaces_by_mac", return_value=interfaces_by_mac, ): assert expected_return == ds.get_physical_nics_by_mac(distro) def populate_context_dir(path, variables): data = "# Context variables generated by OpenNebula\n" for k, v in variables.items(): data += "%s='%s'\n" % (k.upper(), v.replace(r"'", r"'\''")) populate_dir(path, {"context.sh": data}) # vi: ts=4 expandtab