From 04f82ae934fda23612f78b2be9dfb24b06268c87 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sun, 2 Sep 2018 02:06:26 -0700 Subject: [PATCH] Add DVS utility methods Provide methods that wil enable nova/neutron to manage port groups. Change-Id: I81fc039e86b56388881427a6fbc4efd41207fafa --- oslo_vmware/dvs_util.py | 159 +++++++++++++++++++++++++++++ oslo_vmware/tests/test_dvs_util.py | 139 +++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 oslo_vmware/dvs_util.py create mode 100644 oslo_vmware/tests/test_dvs_util.py diff --git a/oslo_vmware/dvs_util.py b/oslo_vmware/dvs_util.py new file mode 100644 index 00000000..9ee54dd2 --- /dev/null +++ b/oslo_vmware/dvs_util.py @@ -0,0 +1,159 @@ +# Copyright (c) 2018 VMware, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from oslo_vmware import vim_util + + +LOG = logging.getLogger(__name__) + + +def get_dvs_moref(value): + """Get managed DVS object reference. + + :param value: value of the DVS managed object + :returns: managed object reference with given value and type + 'VmwareDistributedVirtualSwitch' + """ + + return vim_util.get_moref(value, 'VmwareDistributedVirtualSwitch') + + +def get_vlan_spec(session, vlan_id): + """Gets portgroup vlan spec. + + :param session: vCenter soap session + :param vlan_id: the vlan_id for the port + :returns: The configuration when a single vlan_id is used for a port + """ + # Create the spec for the vlan tag + client_factory = session.vim.client.factory + spec_ns = 'ns0:VmwareDistributedVirtualSwitchVlanIdSpec' + vl_spec = client_factory.create(spec_ns) + vl_spec.vlanId = vlan_id + vl_spec.inherited = '0' + return vl_spec + + +def get_trunk_vlan_spec(session, start=0, end=4094): + """Gets portgroup trunk vlan spec. + + :param session: vCenter soap session + :param start: the starting id + :param end: then end id + :returns: The configuration when a port uses trunk mode. This allows + a guest to manage the vlan id. + """ + client_factory = session.vim.client.factory + spec_ns = 'ns0:VmwareDistributedVirtualSwitchTrunkVlanSpec' + vlan_id = client_factory.create('ns0:NumericRange') + vlan_id.start = start + vlan_id.end = end + vl_spec = client_factory.create(spec_ns) + vl_spec.vlanId = vlan_id + vl_spec.inherited = '0' + return vl_spec + + +def get_port_group_spec(session, name, vlan_id, trunk_mode=False): + """Gets the port group spec for a distributed port group + + :param session: vCenter soap session + :param name: the name of the port group + :param vlan_id: vlan_id for the port + :param trunk_mode: indicates if the port will have trunk mode or use + specific tag above + :returns: The configuration for a port group. + """ + client_factory = session.vim.client.factory + pg_spec = client_factory.create('ns0:DVPortgroupConfigSpec') + pg_spec.name = name + pg_spec.type = 'ephemeral' + config = client_factory.create('ns0:VMwareDVSPortSetting') + if trunk_mode: + config.vlan = get_trunk_vlan_spec(session) + elif vlan_id: + config.vlan = get_vlan_spec(session, vlan_id) + pg_spec.defaultPortConfig = config + return pg_spec + + +def add_port_group(session, dvs_moref, name, vlan_id=None, + trunk_mode=False): + """Add a new port group to the dvs_moref + + :param session: vCenter soap session + :param dvs_moref: managed DVS object reference + :param name: the name of the port group + :param vlan_id: vlan_id for the port + :param trunk_mode: indicates if the port will have trunk mode or use + specific tag above + :returns: The new portgroup moref + """ + pg_spec = get_port_group_spec(session, name, vlan_id, + trunk_mode=trunk_mode) + task = session.invoke_api(session.vim, + 'CreateDVPortgroup_Task', + dvs_moref, + spec=pg_spec) + task_info = session.wait_for_task(task) + LOG.info("%(name)s create on %(dvs)s with %(value)s.", + {'name': name, + 'dvs': dvs_moref.value, + 'value': task_info.result.value}) + return task_info.result + + +def get_portgroups(session, dvs_moref): + """Gets all configured portgroups on the dvs_moref + + :param session: vCenter soap session + :param dvs_moref: managed DVS object reference + :returns: List of tuples that have the following format: + (portgroup name, port group moref) + """ + pgs = [] + port_groups = session.invoke_api(vim_util, + 'get_object_properties', + session.vim, + dvs_moref, + ['portgroup']) + while port_groups: + if len(port_groups) and hasattr(port_groups[0], 'propSet'): + for prop in port_groups[0].propSet: + for val in prop.val[0]: + props = session.invoke_api(vim_util, + 'get_object_properties', + session.vim, + val, ['name']) + if len(props) and hasattr(props[0], 'propSet'): + for prop in props[0].propSet: + pgs.append((prop.val, val)) + port_groups = session._call_method(vim_util, 'continue_retrieval', + port_groups) + return pgs + + +def delete_port_group(session, portgroup_moref): + """Delete a specific port group + + :param session: vCenter soap session + :param portgroup_moref: managed portgroup object reference + """ + task = session.invoke_api(session.vim, + 'Destroy_Task', + portgroup_moref) + session.wait_for_task(task) diff --git a/oslo_vmware/tests/test_dvs_util.py b/oslo_vmware/tests/test_dvs_util.py new file mode 100644 index 00000000..1179d46f --- /dev/null +++ b/oslo_vmware/tests/test_dvs_util.py @@ -0,0 +1,139 @@ +# Copyright (c) 2018 VMware, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for VMware DVS utility module. +""" + +import collections + +import mock + +from oslo_vmware import dvs_util +from oslo_vmware.tests import base +from oslo_vmware import vim_util + +ObjectContent = collections.namedtuple('ObjectContent', ['obj', 'propSet']) +DynamicProperty = collections.namedtuple('Property', ['name', 'val']) +Moref = collections.namedtuple('Moref', ['name', 'type']) + + +class DvsUtilTest(base.TestCase): + """Test class for utility methods in dvs_util.""" + + def test_get_dvs_moref(self): + moref = dvs_util.get_dvs_moref('dvs-123') + self.assertEqual('dvs-123', moref.value) + self.assertEqual('VmwareDistributedVirtualSwitch', moref._type) + + def test_get_vlan_spec(self): + session = mock.Mock() + spec = dvs_util.get_vlan_spec(session, 7) + self.assertEqual(7, spec.vlanId) + + def test_get_trunk_vlan_spec(self): + session = mock.Mock() + spec = dvs_util.get_trunk_vlan_spec(session, start=1, end=2) + self.assertEqual(1, spec.vlanId.start) + self.assertEqual(2, spec.vlanId.end) + + def test_get_port_group_spec(self): + session = mock.Mock() + spec = dvs_util.get_port_group_spec(session, 'pg', 7) + self.assertEqual('pg', spec.name) + self.assertEqual('ephemeral', spec.type) + self.assertEqual(7, spec.defaultPortConfig.vlan.vlanId) + + def test_get_port_group_spec_trunk(self): + session = mock.Mock() + spec = dvs_util.get_port_group_spec(session, 'pg', None, + trunk_mode=True) + self.assertEqual('pg', spec.name) + self.assertEqual('ephemeral', spec.type) + self.assertEqual(0, spec.defaultPortConfig.vlan.start) + self.assertEqual(4094, spec.defaultPortConfig.vlan.end) + + @mock.patch.object(dvs_util, 'get_port_group_spec') + def test_add_port_group(self, mock_spec): + session = mock.Mock() + dvs_moref = dvs_util.get_dvs_moref('dvs-123') + spec = dvs_util.get_port_group_spec(session, 'pg', 7) + mock_spec.return_value = spec + pg_moref = vim_util.get_moref('dvportgroup-7', + 'DistributedVirtualPortgroup') + + def wait_for_task_side_effect(task): + task_info = mock.Mock() + task_info.result = pg_moref + return task_info + + session.wait_for_task.side_effect = wait_for_task_side_effect + pg = dvs_util.add_port_group(session, dvs_moref, 'pg', + vlan_id=7) + self.assertEqual(pg, pg_moref) + session.invoke_api.assert_called_once_with( + session.vim, 'CreateDVPortgroup_Task', dvs_moref, + spec=spec) + + @mock.patch.object(dvs_util, 'get_port_group_spec') + def test_add_port_group_trunk(self, mock_spec): + session = mock.Mock() + dvs_moref = dvs_util.get_dvs_moref('dvs-123') + spec = dvs_util.get_port_group_spec(session, 'pg', None, + trunk_mode=True) + mock_spec.return_value = spec + dvs_util.add_port_group(session, dvs_moref, 'pg', + trunk_mode=True) + session.invoke_api.assert_called_once_with( + session.vim, 'CreateDVPortgroup_Task', dvs_moref, + spec=spec) + + def test_get_portgroups_empty(self): + session = mock.Mock() + dvs_moref = dvs_util.get_dvs_moref('dvs-123') + session.invoke_api.return_value = [] + pgs = dvs_util.get_portgroups(session, dvs_moref) + self.assertEqual([], pgs) + + def test_get_portgroups(self): + session = mock.Mock() + dvs_moref = dvs_util.get_dvs_moref('dvs-123') + pg_moref = vim_util.get_moref('dvportgroup-7', + 'DistributedVirtualPortgroup') + + def session_invoke_api_side_effect(module, method, *args, **kwargs): + if module == vim_util and method == 'get_object_properties': + if ['portgroup'] in args: + propSet = [DynamicProperty(name='portgroup', + val=[[pg_moref]])] + return [ObjectContent(obj=dvs_moref, + propSet=propSet)] + if ['name'] in args: + propSet = [DynamicProperty(name='name', + val='pg-name')] + return [ObjectContent(obj=pg_moref, + propSet=propSet)] + + session.invoke_api.side_effect = session_invoke_api_side_effect + session._call_method.return_value = [] + pgs = dvs_util.get_portgroups(session, dvs_moref) + result = [('pg-name', pg_moref)] + self.assertEqual(result, pgs) + + def test_delete_port_group(self): + session = mock.Mock() + dvs_util.delete_port_group(session, 'pg-moref') + session.invoke_api.assert_called_once_with( + session.vim, 'Destroy_Task', 'pg-moref')