Skip to content

Commit

Permalink
[VNC OpenStack] Use first VMI of the first SI's VM as router gateway …
Browse files Browse the repository at this point in the history
…interface

Contrail model diverges strongly from the Neutron model regarding the
way router gateway is implemented. Contrail uses a service instance to
insert a SNAT service between private and public networks. And
instantiates two VM with VMIs on that networks.
That patch proposes to use the first right VMI of the first SI's VM as
Neutron port for a router's gateway interface. It also limits the
visibility of that router gateway interface to admin users as it's done
in Neutron [1].

[1] https://github.com/openstack/neutron/blob/master/neutron/db/l3_db.py#L350

Change-Id: Ic6141c6dce8e56f95c81c492bc2e59fadea67ced
Closes-Bug: #1509101
(cherry picked from commit b5d159a)
  • Loading branch information
Édouard Thuleau committed Aug 29, 2017
1 parent 2166fe5 commit 66058bf
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/config/vnc_openstack/test-requirements.txt
Expand Up @@ -28,3 +28,4 @@ discoveryclient
sandesh
sandesh-common
pyOpenSSL
mock
54 changes: 51 additions & 3 deletions src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py
Expand Up @@ -162,7 +162,7 @@ class DBInterface(object):
Q_URL_PREFIX = '/extensions/ct'

def __init__(self, manager, admin_name, admin_password, admin_tenant_name,
api_srvr_ip, api_srvr_port,
api_srvr_ip, api_srvr_port,
api_server_obj=None, user_info=None,
contrail_extensions_enabled=True,
list_optimization_enabled=False,
Expand Down Expand Up @@ -2279,6 +2279,43 @@ def _gw_port_vnc_to_neutron(self, port_obj, port_req_memo):
return rtr_back_refs[0]['uuid']
#end _gw_port_vnc_to_neutron

def _get_router_gw_interface_for_neutron(self, context, router):
# Only admin user can list router gw inteface
if not context.get('is_admin', False):
return

si_ref = (router.get_service_instance_refs() or [None])[0]
if si_ref is None:
# No gateway set on that router
return

# Router's gateway is enabled on the router
# As Contrail model uses a service instance composed of 2 VM for the
# gw stuff, we use the first VMI of the first SI's VM as Neutron router
# gw port
try:
si = self._vnc_lib.service_instance_read(
id=si_ref['uuid'],
fields=['virtual_machine_back_refs'],
)
except NoIdError:
return
# To be sure to always use the same SI's VM, we sort them by theirs
# name
vm_ref = sorted(si.get_virtual_machine_back_refs() or [],
key=lambda ref: ref['to'][-1])[0]
if vm_ref:
# And list right VM's VMIs. Return the first one (sorted by
# name) but SI's VM habitually have only one right interface
right_vmis = []
for vmi in self._virtual_machine_interface_list(
back_ref_id=[vm_ref['uuid']]) or []:
vmi_props = vmi.get_virtual_machine_interface_properties()
if vmi_props and vmi_props.service_interface_type == 'right':
right_vmis.append(vmi)
if right_vmis:
return sorted(right_vmis, key=lambda vmi: vmi.name)[0]

def _port_vnc_to_neutron(self, port_obj, port_req_memo=None):
port_q_dict = {}
extra_dict = {}
Expand Down Expand Up @@ -2439,6 +2476,10 @@ def _port_vnc_to_neutron(self, port_obj, port_req_memo=None):
if rtr_uuid:
port_q_dict['device_id'] = rtr_uuid
port_q_dict['device_owner'] = constants.DEVICE_OWNER_ROUTER_GW
# Neutron router gateway interface is a system resource only
# visible by admin user. Neutron intentionally set the tenant
# id to None for that
port_q_dict['tenant_id'] = ''
else:
port_q_dict['device_id'] = \
port_obj.get_virtual_machine_refs()[0]['to'][-1]
Expand Down Expand Up @@ -4007,7 +4048,7 @@ def port_create(self, context, port_q):
ip_family="v4")
except BadRequest as e:
ipv4_port_delete = True
v4_msg_str = "v4:"+ str(e)
v4_msg_str = "v4:"+ str(e)
err_msg_str += v4_msg_str
except vnc_exc.HttpError as e:
# failure in creating the instance ip. Roll back
Expand Down Expand Up @@ -4226,11 +4267,18 @@ def port_list(self, context=None, filters=None):
for vmi_ref in
router_obj.get_virtual_machine_interface_refs() or []
]
# Read all logical router ports and add it to the list
# Add all router intefraces on private networks
if router_port_ids:
port_objs.extend(self._virtual_machine_interface_list(
obj_uuids=router_port_ids, parent_id=project_ids))

# Add router gateway interface
for router in router_objs:
gw_vmi = self._get_router_gw_interface_for_neutron(context,
router)
if gw_vmi is not None:
port_objs.append(gw_vmi)

# Filter it with project ids if there are.
if project_ids:
port_objs.extend([p for p in port_objs_filtered_by_device_id
Expand Down
139 changes: 135 additions & 4 deletions src/config/vnc_openstack/vnc_openstack/tests/test_basic.py
@@ -1,6 +1,7 @@
import sys
import json
import re
import mock

from testtools import ExpectedException
import webtest.app
Expand Down Expand Up @@ -543,7 +544,8 @@ def test_fixed_ip_conflicts_with_floating_ip(self):
proj_obj = self._vnc_lib.project_read(fq_name=['default-domain', 'default-project'])
sg_q = self.create_resource('security_group', proj_obj.uuid)
net_q = self.create_resource('network', proj_obj.uuid,
extra_res_fields={'router:external': True})
extra_res_fields={'router:external': True,
'port_security_enabled': True})
subnet_q = self.create_resource('subnet', proj_obj.uuid,
extra_res_fields={
'network_id': net_q['id'],
Expand Down Expand Up @@ -579,7 +581,8 @@ def test_empty_floating_ip_body_disassociates(self):
sg_q = self.create_resource('security_group', proj_obj.uuid)

pvt_net_q = self.create_resource('network', proj_obj.uuid,
extra_res_fields={'router:external': True})
extra_res_fields={'router:external': True,
'port_security_enabled': True})
pvt_subnet_q = self.create_resource('subnet', proj_obj.uuid,
extra_res_fields={
'network_id': pvt_net_q['id'],
Expand Down Expand Up @@ -666,7 +669,8 @@ def test_floating_ip_list(self):
def create_net_subnet_port_assoc_fip(i, pub_net_q_list,
has_routers=True):
net_q_list = [self.create_resource('network', proj_objs[i].uuid,
name='network-%s-%s-%s' %(self.id(), i, j)) for j in range(2)]
name='network-%s-%s-%s' %(self.id(), i, j),
extra_res_fields={'port_security_enabled': True}) for j in range(2)]
subnet_q_list = [self.create_resource('subnet', proj_objs[i].uuid,
name='subnet-%s-%s-%s' %(self.id(), i, j),
extra_res_fields={
Expand Down Expand Up @@ -793,7 +797,10 @@ def test_create_fip_w_port_associated_w_another_fip_negative(self):
'ip_version': 4})

# private network
pvt_net_q = self.create_resource('network', proj_id)
pvt_net_q = self.create_resource(
'network', proj_id,
extra_res_fields={'port_security_enabled': True},
)
pvt_subnet_q = self.create_resource('subnet', proj_id,
extra_res_fields=
{'network_id': pvt_net_q['id'],
Expand Down Expand Up @@ -927,6 +934,130 @@ def test_external_network_fip_pool(self):
self.assertEqual(fip_pool_obj.perms2.global_access, PERMS_RWX)
self.delete_resource('network', proj_obj.uuid, net_q['id'])
# end test_external_network_fip_pool

def test_list_router_interfaces(self):
proj_obj = vnc_api.Project('proj-%s' % self.id(), vnc_api.Domain())
self._vnc_lib.project_create(proj_obj)

# public network/subnet
public_net = self.create_resource(
'network',
proj_obj.uuid,
'public-%s' % self.id(),
extra_res_fields={'router:external': True},
)
self.create_resource(
'subnet',
proj_obj.uuid,
'public-%s' % self.id(),
extra_res_fields={
'network_id': public_net['id'],
'cidr': '80.0.0.0/24',
'ip_version': 4,
},
)
public_net_obj = self._vnc_lib.virtual_network_read(
id=public_net['id'])

# private network/subnet
privat_net = self.create_resource(
'network',
proj_obj.uuid,
name='private-%s' % self.id(),
extra_res_fields={'port_security_enabled': False},
)
private_subnet = self.create_resource(
'subnet',
proj_obj.uuid,
'private-%s' % self.id(),
extra_res_fields={
'network_id': privat_net['id'],
'cidr': '10.0.0.0/24',
'ip_version': 4,
},
)

# Router with a gateway on the public network
router = self.create_resource(
'router',
proj_obj.uuid,
name='router-%s' % self.id(),
extra_res_fields={
'external_gateway_info': {
'network_id': public_net['id'],
}
},
)

# Add router interface on the private network/subnet
self.update_resource(
'router',
router['id'],
proj_obj.uuid,
is_admin=True,
operation='ADDINTERFACE',
extra_res_fields={'subnet_id': private_subnet['id']},
)
router_obj = self._vnc_lib.logical_router_read(id=router['id'])

def router_with_fake_si_ref(orig_method, *args, **kwargs):
if 'obj_uuids' in kwargs and kwargs['obj_uuids'] == [router['id']]:
mock_router_si_ref = mock.patch.object(
router_obj,
'get_service_instance_refs',
).start()
mock_router_si_ref.return_value = [{'uuid': 'fake_si_uuid'}]
return [router_obj]
return orig_method(*args, **kwargs)

def fake_si_obj(orig_method, *args, **kwargs):
if 'id' in kwargs and kwargs['id'] == 'fake_si_uuid':
si = mock.Mock()
si.get_virtual_machine_back_refs.return_value = \
[{'to': 'fake_vm_name', 'uuid': 'fake_vm_uuid'}]
return si
return orig_method(*args, **kwargs)

def return_router_gw_interface(orig_method, *args, **kwargs):
if ('back_ref_id' in kwargs and
kwargs['back_ref_id'] == ['fake_vm_uuid']):
fake_gw = vnc_api.VirtualMachineInterface('fake_gw_interface',
proj_obj)
fake_gw.add_virtual_network(public_net_obj)
fake_gw.set_virtual_machine_interface_properties(
vnc_api.VirtualMachineInterfacePropertiesType(
service_interface_type='right',
),
)
fake_gw_id = self._vnc_lib.virtual_machine_interface_create(
fake_gw)
return [self._vnc_lib.virtual_machine_interface_read(
id=fake_gw_id)]
return orig_method(*args, **kwargs)

neutron_api_obj = FakeExtensionManager.get_extension_objects(
'vnc_cfg_api.neutronApi')[0]
neutron_db_obj = neutron_api_obj._npi._cfgdb
with test_common.patch(neutron_db_obj._vnc_lib, 'logical_routers_list',
router_with_fake_si_ref), \
test_common.patch(neutron_db_obj._vnc_lib,
'service_instance_read', fake_si_obj), \
test_common.patch(neutron_db_obj._vnc_lib,
'virtual_machine_interfaces_list',
return_router_gw_interface):
router_interfaces = self.list_resource(
'port',
proj_uuid=proj_obj.uuid,
req_filters={'device_id': [router['id']]},
)
self.assertEqual(len(router_interfaces), 1)
router_interfaces2 = self.list_resource(
'port',
proj_uuid=proj_obj.uuid,
req_filters={'device_id': [router['id']]},
is_admin=True,
)
self.assertEqual(len(router_interfaces2), 2)
# end class TestBasic

class TestExtraFieldsPresenceByKnob(test_case.NeutronBackendTestCase):
Expand Down

0 comments on commit 66058bf

Please sign in to comment.