Skip to content

Commit

Permalink
VLAN trunks for portgroups
Browse files Browse the repository at this point in the history
Check reserved ports for one host only
Sync all VNC VNs on startup

Change-Id: I8750ed3430fe324c81e291fd2deda82705864093
Closes-Bug: #1783768
  • Loading branch information
aszc-dev committed Jul 27, 2018
1 parent 461394a commit 03f1c02
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 18 deletions.
65 changes: 59 additions & 6 deletions cvm/clients.py
Expand Up @@ -10,7 +10,8 @@
from vnc_api import vnc_api
from vnc_api.exceptions import NoIdError

from cvm.constants import (VM_PROPERTY_FILTERS, VNC_ROOT_DOMAIN,
from cvm.constants import (VLAN_TRUNK_END, VLAN_TRUNK_START,
VM_PROPERTY_FILTERS, VNC_ROOT_DOMAIN,
VNC_VCENTER_DEFAULT_SG, VNC_VCENTER_DEFAULT_SG_FQN,
VNC_VCENTER_IPAM, VNC_VCENTER_IPAM_FQN,
VNC_VCENTER_PROJECT)
Expand Down Expand Up @@ -65,6 +66,25 @@ def make_pg_config_vlan_override(portgroup):
return pg_config_spec


def make_pg_config_vlan_trunk(portgroup):
pg_config_spec = vim.dvs.DistributedVirtualPortgroup.ConfigSpec()
pg_config_spec.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy()
vlan_trunk_spec = vim.dvs.VmwareDistributedVirtualSwitch.TrunkVlanSpec()
vlan_trunk_spec.vlanId = [vim.NumericRange(start=VLAN_TRUNK_START, end=VLAN_TRUNK_END)]
pg_config_spec.defaultPortConfig.vlan = vlan_trunk_spec
pg_config_spec.configVersion = portgroup.config.configVersion
return pg_config_spec


def wait_for_task(task, success_message, fault_message):
while task.info.state == 'running':
continue
if task.info.state == 'success':
logger.info(success_message)
elif task.info.state == 'error':
logger.error(fault_message, task.info.error.msg)


class VSphereAPIClient(object):
def __init__(self):
self._si = None
Expand Down Expand Up @@ -169,6 +189,12 @@ def get_dpg_by_key(self, key):
return dpg
return None

def get_dpg_by_name(self, name):
for dpg in self._datacenter.network:
if isinstance(dpg, vim.dvs.DistributedVirtualPortgroup) and dpg.name == name:
return dpg
return None

def set_vlan_id(self, vcenter_port):
dv_port = self._fetch_port_from_dvs(vcenter_port.port_key)
if not dv_port:
Expand All @@ -192,10 +218,19 @@ def restore_vlan_id(self, vcenter_port):
dv_port_config_spec = make_dv_port_spec(dv_port)
self._dvs.ReconfigureDVPort_Task(port=[dv_port_config_spec])

def get_reserved_vlan_ids(self):
def get_reserved_vlan_ids(self, vrouter_uuid):
"""In this method treats vrouter_uuid as esxi host id"""
criteria = vim.dvs.PortCriteria()
criteria.connected = True
logger.info('Retrieving reserved VLAN IDs')
return [port.config.setting.vlan.vlanId for port in self._dvs.FetchDVPorts()
if isinstance(port.config.setting.vlan, vim.dvs.VmwareDistributedVirtualSwitch.VlanIdSpec)]
reserved_vland_ids = []
for port in self._dvs.FetchDVPorts(criteria=criteria):
if not isinstance(port.config.setting.vlan, vim.dvs.VmwareDistributedVirtualSwitch.VlanIdSpec):
continue
if find_vrouter_uuid(port.proxyHost) != vrouter_uuid:
continue
reserved_vland_ids.append(port.config.setting.vlan.vlanId)
return reserved_vland_ids

def _get_datacenter(self, name):
return self._get_object([vim.Datacenter], name)
Expand All @@ -214,8 +249,22 @@ def _fetch_port_from_dvs(self, port_key):
@staticmethod
def enable_vlan_override(portgroup):
pg_config_spec = make_pg_config_vlan_override(portgroup)
portgroup.ReconfigureDVPortgroup_Task(pg_config_spec)
logger.info('Enabled vCenter portgroup %s vlan override', portgroup.name)
task = portgroup.ReconfigureDVPortgroup_Task(pg_config_spec)
success_message = 'Enabled vCenter portgroup %s vlan override' % portgroup.name
fault_message = 'Enabling VLAN override on portgroup {} failed: %s'.format(portgroup.name)
wait_for_task(task, success_message, fault_message)

@staticmethod
def set_vlan_trunk(portgroup):
pg_config_spec = make_pg_config_vlan_trunk(portgroup)
task = portgroup.ReconfigureDVPortgroup_Task(pg_config_spec)
success_message = 'Set vCenter portgroup %s to VLAN Trunk (start=%d, end=%d)' % (
portgroup.name,
pg_config_spec.defaultPortConfig.vlan.vlanId[0].start,
pg_config_spec.defaultPortConfig.vlan.vlanId[0].end,
)
fault_message = 'Setting vCenter portgroup {} to VLAN Trunk failed: %s'.format(portgroup.name)
wait_for_task(task, success_message, fault_message)


class VNCAPIClient(object):
Expand Down Expand Up @@ -303,6 +352,10 @@ def get_vmis_for_vm(self, vm_model):
def read_vmi(self, uuid):
return self.vnc_lib.virtual_machine_interface_read(id=uuid)

def get_vns_by_project(self, project):
vns = self.vnc_lib.virtual_networks_list(parent_id=project.uuid).get('virtual-networks')
return [self.vnc_lib.virtual_network_read(vn['fq_name']) for vn in vns]

def read_vn(self, fq_name):
try:
return self.vnc_lib.virtual_network_read(fq_name)
Expand Down
3 changes: 3 additions & 0 deletions cvm/constants.py
Expand Up @@ -41,4 +41,7 @@
VLAN_ID_RANGE_START = 0
VLAN_ID_RANGE_END = 4095

VLAN_TRUNK_START = 0
VLAN_TRUNK_END = 4094

ID_PERMS = IdPermsType(creator='vcenter-manager', enable=True)
2 changes: 1 addition & 1 deletion cvm/controllers.py
Expand Up @@ -20,7 +20,7 @@ def initialize_database(self):
with self._lock:
self._vmi_service.sync_vlan_ids()
self._vm_service.get_vms_from_vmware()
self._vn_service.update_vns()
self._vn_service.sync_vns()
self._vmi_service.sync_vmis()
self._vm_service.delete_unused_vms_in_vnc()
self._vrouter_port_service.sync_ports()
Expand Down
26 changes: 20 additions & 6 deletions cvm/services.py
Expand Up @@ -161,6 +161,15 @@ def __init__(self, vcenter_api_client, vnc_api_client, database):
super(VirtualNetworkService, self).__init__(vnc_api_client, database)
self._vcenter_api_client = vcenter_api_client

def sync_vns(self):
with self._vcenter_api_client:
for vnc_vn in self._vnc_api_client.get_vns_by_project(self._project):
dpg = self._vcenter_api_client.get_dpg_by_name(vnc_vn.name)
if vnc_vn and dpg:
self._create_vn_model(dpg, vnc_vn)
else:
logger.error('Unable to fetch new portgroup for name: %s', vnc_vn.name)

def update_vns(self):
for vmi_model in self._database.vmis_to_update:
portgroup_key = vmi_model.vcenter_port.portgroup_key
Expand All @@ -172,14 +181,18 @@ def update_vns(self):
fq_name = [VNC_ROOT_DOMAIN, VNC_VCENTER_PROJECT, dpg.name]
vnc_vn = self._vnc_api_client.read_vn(fq_name)
if dpg and vnc_vn:
logger.info('Fetched new portgroup key: %s name: %s', dpg.key, vnc_vn.name)
vn_model = VirtualNetworkModel(dpg, vnc_vn)
self._vcenter_api_client.enable_vlan_override(vn_model.vmware_vn)
self._database.save(vn_model)
logger.info('Created %s', vn_model)
self._create_vn_model(dpg, vnc_vn)
else:
logger.error('Unable to fetch new portgroup for key: %s', portgroup_key)

def _create_vn_model(self, dpg, vnc_vn):
logger.info('Fetched new portgroup key: %s name: %s', dpg.key, vnc_vn.name)
vn_model = VirtualNetworkModel(dpg, vnc_vn)
self._vcenter_api_client.enable_vlan_override(vn_model.vmware_vn)
self._vcenter_api_client.set_vlan_trunk(vn_model.vmware_vn)
self._database.save(vn_model)
logger.info('Created %s', vn_model)


class VirtualMachineInterfaceService(Service):
def __init__(self, vcenter_api_client, vnc_api_client, database,
Expand All @@ -194,8 +207,9 @@ def sync_vmis(self):
self._delete_unused_vmis()

def sync_vlan_ids(self):
vrouter_uuid = self._esxi_api_client.read_vrouter_uuid()
with self._vcenter_api_client:
reserved_vlan_ids = self._vcenter_api_client.get_reserved_vlan_ids()
reserved_vlan_ids = self._vcenter_api_client.get_reserved_vlan_ids(vrouter_uuid)
for vlan_id in reserved_vlan_ids:
self._vlan_id_pool.reserve(vlan_id)

Expand Down
12 changes: 11 additions & 1 deletion tests/conftest.py
Expand Up @@ -4,7 +4,6 @@
from pyVmomi import vim, vmodl # pylint: disable=no-name-in-module
from vnc_api import vnc_api

from tests.utils import assign_ip_to_instance_ip, wrap_into_update_set
from cvm.controllers import (GuestNetHandler, PowerStateHandler, UpdateHandler,
VmReconfiguredHandler, VmRemovedHandler,
VmRenamedHandler, VmUpdatedHandler,
Expand All @@ -15,6 +14,7 @@
from cvm.services import (VirtualMachineInterfaceService,
VirtualMachineService, VirtualNetworkService,
VRouterPortService)
from tests.utils import assign_ip_to_instance_ip, wrap_into_update_set


@pytest.fixture()
Expand Down Expand Up @@ -74,6 +74,16 @@ def portgroup():
return pg


@pytest.fixture()
def portgroup():
pg = Mock(key='dvportgroup-1')
pg.configure_mock(name='DPG1')
pg.config.policy = Mock(spec=vim.dvs.DistributedVirtualPortgroup.PortgroupPolicy())
pg.config.defaultPortConfig = Mock(spec=vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy())
pg.config.configVersion = '1'
return pg


@pytest.fixture()
def vmware_vm_1():
vmware_vm = Mock(spec=vim.VirtualMachine)
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/clients/conftest.py
Expand Up @@ -5,6 +5,7 @@

from cvm.clients import VCenterAPIClient, VNCAPIClient
from cvm.models import VCenterPort
from tests.utils import create_dv_port


@pytest.fixture()
Expand All @@ -14,6 +15,21 @@ def dv_port():
return port


@pytest.fixture()
def dv_port_1():
return create_dv_port(10, 'vrouter_uuid_1')


@pytest.fixture()
def dv_port_2():
return create_dv_port(7, 'vrouter_uuid_1')


@pytest.fixture()
def dv_port_3():
return create_dv_port(5, 'vrouter_uuid_2')


@pytest.fixture()
def vcenter_port():
device = Mock(macAddress='mac-address')
Expand All @@ -29,6 +45,13 @@ def dvs(dv_port):
return dvswitch


@pytest.fixture()
def dvs_1(dv_port_1, dv_port_2, dv_port_3):
dvswitch = Mock()
dvswitch.FetchDVPorts.return_value = [dv_port_1, dv_port_2, dv_port_3]
return dvswitch


@pytest.fixture()
def vcenter_api_client():
return VCenterAPIClient({})
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/clients/test_vcenter_api_client.py
@@ -1,4 +1,5 @@
from mock import patch
from pyVmomi import vim

from cvm.clients import VCenterAPIClient

Expand Down Expand Up @@ -30,6 +31,19 @@ def test_enable_vlan_override(vcenter_api_client, portgroup):
assert config.configVersion == '1'


def test_set_vlan_trunk(vcenter_api_client, portgroup):
with patch('cvm.clients.SmartConnectNoSSL'):
with vcenter_api_client:
vcenter_api_client.set_vlan_trunk(portgroup=portgroup)

portgroup.ReconfigureDVPortgroup_Task.assert_called_once()
vlan_config = portgroup.ReconfigureDVPortgroup_Task.call_args[0][0].defaultPortConfig.vlan
assert isinstance(vlan_config, vim.dvs.VmwareDistributedVirtualSwitch.TrunkVlanSpec)
assert isinstance(vlan_config.vlanId[0], vim.NumericRange)
assert vlan_config.vlanId[0].start == 0
assert vlan_config.vlanId[0].end == 4094


def test_get_vlan_id(vcenter_api_client, dvs, vcenter_port, dv_port):
dv_port.config.setting.vlan.vlanId = 10
dv_port.config.setting.vlan.inherited = False
Expand All @@ -54,3 +68,14 @@ def test_restore_vlan_id(vcenter_api_client, dvs, vcenter_port):
assert spec.key == '8'
assert spec.configVersion == '1'
assert spec.setting.vlan.inherited is True


def test_get_reserved_vlan_ids(vcenter_api_client, dvs_1):
with patch('cvm.clients.SmartConnectNoSSL'):
with patch.object(VCenterAPIClient, '_get_dvswitch', return_value=dvs_1):
with vcenter_api_client:
vlans_1 = vcenter_api_client.get_reserved_vlan_ids('vrouter_uuid_1')
vlans_2 = vcenter_api_client.get_reserved_vlan_ids('vrouter_uuid_2')

assert vlans_1 == [10, 7]
assert vlans_2 == [5]
26 changes: 23 additions & 3 deletions tests/unit/services/test_vn_service.py
Expand Up @@ -28,7 +28,27 @@ def test_update_vns(vn_service, database, vcenter_api_client, vnc_api_client, vm
vnc_vn=vnc_vn_1,
vmware_vn=portgroup,
)
vcenter_api_client.get_dpg_by_key.called_once_with('dvportgroup-1')
vcenter_api_client.enable_vlan_override.called_once_with(portgroup)
vcenter_api_client.get_dpg_by_key.assert_called_once_with('dvportgroup-1')
vcenter_api_client.enable_vlan_override.assert_called_once_with(portgroup)
vcenter_api_client.set_vlan_trunk.assert_called_once_with(portgroup)
fq_name = [VNC_ROOT_DOMAIN, VNC_VCENTER_PROJECT, 'DPG1']
vnc_api_client.read_vn.called_once_with(fq_name)
vnc_api_client.read_vn.assert_called_once_with(fq_name)


def test_sync_vns(vn_service, database, vcenter_api_client, vnc_api_client, vnc_vn_1, portgroup):
vnc_api_client.get_vns_by_project.return_value = [vnc_vn_1]
vcenter_api_client.get_dpg_by_name.return_value = portgroup

vn_service.sync_vns()

vn_model = database.get_vn_model_by_key('dvportgroup-1')
assert vn_model is not None
assert_vn_model_state(
vn_model,
key='dvportgroup-1',
vnc_vn=vnc_vn_1,
vmware_vn=portgroup,
)

vcenter_api_client.enable_vlan_override.assert_called_once_with(portgroup)
vcenter_api_client.set_vlan_trunk.assert_called_once_with(portgroup)
14 changes: 13 additions & 1 deletion tests/utils.py
@@ -1,8 +1,20 @@
from pyVmomi import vmodl # pylint: disable=no-name-in-module
from mock import Mock
from pyVmomi import vim, vmodl # pylint: disable=no-name-in-module

from cvm.clients import make_filter_spec


def create_dv_port(vlan_id, vrouter_uuid):
port = Mock()
port.config.setting.vlan = Mock(spec=vim.dvs.VmwareDistributedVirtualSwitch.VlanIdSpec)
port.config.setting.vlan.vlanId = vlan_id
vm_mock = Mock()
vm_mock.name = 'ContrailVM'
vm_mock.config.instanceUuid = vrouter_uuid
port.proxyHost.vm = [vm_mock]
return port


def create_property_filter(obj, filters):
filter_spec = make_filter_spec(obj, filters)
return vmodl.query.PropertyCollector.Filter(filter_spec)
Expand Down

0 comments on commit 03f1c02

Please sign in to comment.