diff --git a/os_xenapi/tests/utils/test_common_function.py b/os_xenapi/tests/utils/test_common_function.py index 68b85e6..9ffb033 100644 --- a/os_xenapi/tests/utils/test_common_function.py +++ b/os_xenapi/tests/utils/test_common_function.py @@ -56,3 +56,65 @@ class CommonUtilFuncTestCase(base.TestCase): self.assertEqual(ipv4s, expect) mock_client.ssh.assert_called() + + def test_get_vm_vifs(self): + mock_client = mock.Mock() + vm_uuid = '9eeeea9f-de18-f101-fcc2-ae7366b540f2' + vif_list_data = u'0\r\n\n1\r\n' + + vif_0_data = u'vif-id = "0"\r\n\n' + vif_0_data += u'mac = "9a:77:18:20:cf:14"\r\n\n' + vif_0_data += u'bridge = "xapi1"\r\n' + + vif_1_data = u'vif-id = "1"\r\n\n' + vif_1_data += u'mac = "02:e3:69:a6:7b:b8"\r\n\n' + vif_1_data += u'bridge = "xapi0"\r\n' + + mock_client.ssh.side_effect = [ + (0, vif_list_data, ''), # xenstore-list + (0, vif_0_data, ''), # xenstore-ls - vif 0 + (0, vif_1_data, ''), # xenstore-ls - vif 1 + ] + + expect = [{u'bridge': u'xapi1', + u'mac': u'9a:77:18:20:cf:14', + u'vif-id': u'0'}, + {u'bridge': u'xapi0', + u'mac': u'02:e3:69:a6:7b:b8', + u'vif-id': u'1'}] + vifs = common_function.get_vm_vifs(mock_client, vm_uuid) + self.assertEqual(vifs, expect) + + @mock.patch.object(common_function.netifaces, 'ifaddresses') + @mock.patch.object(common_function.netifaces, 'interfaces') + @mock.patch.object(common_function, 'execute') + @mock.patch.object(common_function, 'get_vm_vifs') + def test_get_domu_vifs_by_eth(self, mock_get, mock_exec, + mock_if, mock_ifaddr): + mock_client = mock.Mock() + vm_uuid = '9eeeea9f-de18-f101-fcc2-ae7366b540f2' + mock_exec.return_value = '/vm/%s' % vm_uuid + vif_0 = {u'vif-id': u'0', + u'bridge': u'xapi1', + u'mac': u'9a:77:18:20:cf:14'} + vif_1 = {u'vif-id': u'1', + u'bridge': u'xapi0', + u'mac': u'02:e3:69:a6:7b:b8'} + + mock_get.return_value = [vif_0, vif_1] + mock_if.return_value = ['eth0', 'eth1'] + AF_LINK = common_function.netifaces.AF_LINK + mock_ifaddr.side_effect = [ + {AF_LINK: [{u'addr': u'9a:77:18:20:cf:14'}]}, + {AF_LINK: [{u'addr': u'02:e3:69:a6:7b:b8'}]}] + + vifs_by_eth = common_function.get_domu_vifs_by_eth(mock_client) + + expect = {'eth0': vif_0, + 'eth1': vif_1} + self.assertEqual(vifs_by_eth, expect) + mock_exec.assert_called_with('xenstore-read', 'vm') + mock_get.assert_called_with(mock_client, vm_uuid) + mock_if.assert_called_with() + self.assertEqual(mock_ifaddr.call_args_list, + [mock.call('eth0'), mock.call('eth1')]) diff --git a/os_xenapi/tests/utils/test_xenapi_facts.py b/os_xenapi/tests/utils/test_xenapi_facts.py index 8a7b095..9198bfe 100644 --- a/os_xenapi/tests/utils/test_xenapi_facts.py +++ b/os_xenapi/tests/utils/test_xenapi_facts.py @@ -20,11 +20,13 @@ from os_xenapi.utils import xenapi_facts class XenapiFactsTestCase(base.TestCase): + @mock.patch.object(common_function, 'get_domu_vifs_by_eth') @mock.patch.object(common_function, 'get_host_ipv4s') @mock.patch.object(common_function, 'get_remote_hostname') @mock.patch.object(himn, 'get_local_himn_eth') @mock.patch.object(netifaces, 'ifaddresses') - def test_get_facts(self, mock_ifaddr, mock_eth, mock_hostname, mock_ip): + def test_get_facts(self, mock_ifaddr, mock_eth, mock_hostname, mock_ip, + mock_vifs): mock_client = mock.Mock() mock_client.ip = mock.sentinel.dom0_himn_ip mock_eth.return_value = 'eth3' @@ -48,12 +50,46 @@ class XenapiFactsTestCase(base.TestCase): } ] mock_ip.return_value = fake_ipv4s + fake_vifs_by_eth = { + "eth0": { + "MTU": "1500", + "backend-id": "0", + "backend-kind": "vif", + "bridge": "xapi1", + "bridge-MAC": "fe:ff:ff:ff:ff:ff", + "locking-mode": "unlocked", + "mac": "9a:77:18:20:cf:14", + "network-uuid": "f06300db-6b6c-006c-0ea1-9d1eb1e97350", + "setup-pvs-proxy-rules": "/usr/libexec/xenopsd/rules", + "setup-vif-rules": "/usr/libexec/xenopsd/setup-vif-rules", + "vif-id": "0", + "vif-uuid": "9e1f78d1-956c-cb2d-0327-652d96302f9d", + "xenopsd-backend": "classic" + }, + "eth1": { + "MTU": "1500", + "backend-id": "0", + "backend-kind": "vif", + "bridge": "xapi0", + "bridge-MAC": "fe:ff:ff:ff:ff:ff", + "locking-mode": "unlocked", + "mac": "02:e3:69:a6:7b:b8", + "network-uuid": "41968989-1d86-383b-114e-3d1ccae02157", + "setup-pvs-proxy-rules": "/usr/libexec/xenopsd/rules", + "setup-vif-rules": "/usr/libexec/xenopsd/setup-vif-rules", + "vif-id": "1", + "vif-uuid": "80eea22b-a778-afec-0eac-3891cfd5faf9", + "xenopsd-backend": "classic" + } + } + mock_vifs.return_value = fake_vifs_by_eth ret_facts = xenapi_facts.get_xenapi_facts(mock_client) expect_facts = {"domu_himn_ip": "169.254.0.2", "domu_himn_eth": "eth3", "dom0_hostname": "traya", - "dom0_ipv4s": fake_ipv4s} + "dom0_ipv4s": fake_ipv4s, + "domu_vifs": fake_vifs_by_eth} self.assertEqual(ret_facts, expect_facts) mock_eth.assert_called_with(mock.sentinel.dom0_himn_ip) diff --git a/os_xenapi/utils/common_function.py b/os_xenapi/utils/common_function.py index e8171f1..6420c55 100644 --- a/os_xenapi/utils/common_function.py +++ b/os_xenapi/utils/common_function.py @@ -27,6 +27,9 @@ import sys from os_xenapi.client import exception +PATTERN_XENSTORE_VIFS_IN_VM = '/xapi/%s/private/vif' + + LOG = logging.getLogger('XenAPI_utils') @@ -119,6 +122,61 @@ def get_host_ipv4s(host_client): return ipv4s +def get_vm_vifs(xenserver_client, vm_uuid): + """Get a specific VM's vifs + + This function can be used to get vif list for a specific VM. + :param xenserver_client: the ssh client connected to XenServer where + the domU belongs to. + :param vm_uuid: the VM's uuid + :returns: list -- list the VM's vif data. + """ + + vm_vifs = PATTERN_XENSTORE_VIFS_IN_VM % vm_uuid + _, out, _ = xenserver_client.ssh('xenstore-list %s' % vm_vifs) + vif_ids = [x.strip() for x in out.split('\n') if x.strip()] + + vifs = [] + for id in vif_ids: + vif_ent = '/'.join([vm_vifs, id]) + _, out, _ = xenserver_client.ssh('xenstore-ls %s' % vif_ent) + key_values = [x.strip().split(' = ') for x in out.split('\n') + if ' = ' in x] + vif_dict = {x[0]: x[1].replace('\"', '') for x in key_values} + vifs.append(vif_dict) + + return vifs + + +def get_domu_vifs_by_eth(xenserver_client): + """Get domU's vifs + + This function can be used to get a domU's vifs. + :param xenserver_client: the ssh client connected to XenServer where + the domU belongs to. + :returns: dict -- The domU's vifs with ethernet interfaces as the keys. + """ + + # Get domU VM's uuid + out = execute('xenstore-read', 'vm') + vm_uuid = out.split('/')[-1] + + vifs = get_vm_vifs(xenserver_client, vm_uuid) + vifs_by_mac = {vif['mac']: vif for vif in vifs} + + # Get all ethernet interfaces and mapping them into vifs basing on + # mac address + vifs_by_eth = {} + for eth in netifaces.interfaces(): + mac_addrs = [x['addr'] for x in + netifaces.ifaddresses(eth)[netifaces.AF_LINK]] + for mac in vifs_by_mac: + if mac in mac_addrs: + vifs_by_eth[eth] = vifs_by_mac[mac] + break + return vifs_by_eth + + def scp_and_execute(dom0_client, script_name): # copy script to remote host and execute it TMP_SH_DIR = dom0_client.ssh("mktemp -d /tmp/domu_sh.XXXXXX", output=True) diff --git a/os_xenapi/utils/xenapi_facts.py b/os_xenapi/utils/xenapi_facts.py index 04159c3..cb89548 100644 --- a/os_xenapi/utils/xenapi_facts.py +++ b/os_xenapi/utils/xenapi_facts.py @@ -50,6 +50,9 @@ def get_xenapi_facts(dom0_client): facts['domu_himn_eth'] = eth facts['domu_himn_ip'] = ip_addr + # get domU eths' vif data + facts['domu_vifs'] = common_function.get_domu_vifs_by_eth(dom0_client) + return facts