Central and local plugin (part 2, l3 functionality)

1. What is the problem
Necessary changes for local plugin and central plugin to boot
a virtual machine have been submitted in this patch[1]. As the
next step, we need to add l3 functionality to the local and
central plugin.

2. What is the solution to the problem
Several changes in local plugin.
(1) Before creating local subnet, local plugin sends request to
central plugin to create a "reserved gateway port", and use the
ip address of this port as the gateway ip of the local subnet.
(2) When local plugin receives network or subnet creation request,
if the request contains "name" parameter and the name is a UUID,
local plugin uses the name as the id of the local network or
subnet.

3. What the features need to be implemented to the Tricircle
   to realize the solution
With this patch, users can connect virtual machines booted
directly via the local Nova server in different networks with
a router.

[1] https://review.openstack.org/375281

Change-Id: I12094f30804c0bad2f74e0ff510ac26bd217cfd4
This commit is contained in:
zhiyuan_cai 2016-09-19 16:57:25 +08:00 committed by joehuang
parent b0789882eb
commit ff7f86fa01
14 changed files with 283 additions and 63 deletions

View File

@ -46,16 +46,9 @@ OVS_BRIDGE_MAPPINGS=bridge:br-bridge
Q_ENABLE_TRICIRCLE=True
enable_plugin tricircle https://github.com/openstack/tricircle/
# Tricircle Services
enable_service t-api
enable_service t-ngw
enable_service t-cgw
enable_service t-job
# Use Neutron instead of nova-network
disable_service n-net
enable_service q-svc
enable_service q-svc1
enable_service q-dhcp
enable_service q-agt
enable_service q-l3
@ -68,3 +61,22 @@ disable_service n-obj
disable_service c-bak
disable_service tempest
disable_service horizon
CENTRAL_REGION_NAME=CentralRegion
TRICIRCLE_NEUTRON_PORT=20001
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
core_plugin=tricircle.network.local_plugin.TricirclePlugin
[client]
admin_username=admin
admin_password=$ADMIN_PASSWORD
admin_tenant=demo
auto_refresh_endpoint=True
top_pod_name=$CENTRAL_REGION_NAME
[tricircle]
real_core_plugin=neutron.plugins.ml2.plugin.Ml2Plugin
central_neutron_url=http://127.0.0.1:$TRICIRCLE_NEUTRON_PORT

View File

@ -38,12 +38,11 @@ NEUTRON_CREATE_INITIAL_NETWORKS=False
Q_USE_PROVIDERNET_FOR_PUBLIC=True
HOST_IP=10.250.201.25
REGION_NAME=Pod2
REGION_NAME=RegionTwo
KEYSTONE_REGION_NAME=RegionOne
SERVICE_HOST=$HOST_IP
KEYSTONE_SERVICE_HOST=10.250.201.24
KEYSTONE_AUTH_HOST=10.250.201.24
GLANCE_SERVICE_HOST=10.250.201.24
Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS=(network_vlan_ranges=bridge:2001:3000,extern:3001:4000)
OVS_BRIDGE_MAPPINGS=bridge:br-bridge,extern:br-ext
@ -60,8 +59,26 @@ enable_service c-vol
enable_service c-sch
disable_service n-obj
disable_service g-api
disable_service g-reg
disable_service c-bak
disable_service tempest
disable_service horizon
CENTRAL_REGION_NAME=CentralRegion
TRICIRCLE_NEUTRON_PORT=20001
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
core_plugin=tricircle.network.local_plugin.TricirclePlugin
[client]
admin_username=admin
admin_password=$ADMIN_PASSWORD
admin_tenant=demo
auto_refresh_endpoint=True
top_pod_name=$CENTRAL_REGION_NAME
[tricircle]
real_core_plugin=neutron.plugins.ml2.plugin.Ml2Plugin
# change the ip to the ip of the machine hosting central Neutron server
central_neutron_url=http://10.250.201.24:$TRICIRCLE_NEUTRON_PORT

View File

@ -35,10 +35,6 @@ LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver
Q_ENABLE_TRICIRCLE=True
enable_plugin tricircle https://github.com/openstack/tricircle/
# Tricircle Services
enable_service t-api
enable_service t-job
# Use Neutron instead of nova-network
disable_service n-net
enable_service q-svc

View File

@ -86,7 +86,6 @@ function configure_tricircle_xjob {
echo "Configuring Tricircle xjob"
init_common_tricircle_conf $TRICIRCLE_XJOB_CONF
iniset $TRICIRCLE_XJOB_CONF DEFAULT enable_api_gateway False
setup_colorized_logging $TRICIRCLE_XJOB_CONF DEFAULT
fi

View File

@ -59,6 +59,7 @@ ns_bridge_port_name = 'ns_bridge_port_%s_%s_%s'
dhcp_port_name = 'dhcp_port_%s' # subnet_id
interface_port_name = 'interface_%s_%s' # b_pod_id t_subnet_id
interface_port_device_id = 'reserved_gateway_port'
MAX_INT = 0x7FFFFFFF
expire_time = datetime.datetime(2000, 1, 1)

View File

@ -18,6 +18,7 @@ import six
import pecan
from oslo_log import log as logging
from oslo_utils import uuidutils
from tricircle.common import constants as cons
import tricircle.common.exceptions as t_exceptions
@ -126,6 +127,21 @@ def get_bottom_network_name(network):
return '%s#%s' % (network['id'], network['name'])
def get_id_from_name(_type, name):
if _type == cons.RT_NETWORK:
tokens = name.split('#')
if len(tokens) == 2:
id_candidate = tokens[1]
else:
id_candidate = tokens[0]
else:
id_candidate = name
if uuidutils.is_uuid_like(id_candidate):
return id_candidate
else:
return None
def format_error(code, message, error_type=None):
error_type_map = {400: 'badRequest',
403: 'forbidden',

View File

@ -57,7 +57,6 @@ def get_pod(context, pod_id):
def list_pods(context, filters=None, sorts=None):
with context.session.begin():
return core.query_resource(context, models.Pod, filters or [],
sorts or [])
@ -116,7 +115,6 @@ def get_pod_service_configuration(context, config_id):
def list_pod_service_configurations(context, filters=None, sorts=None):
with context.session.begin():
return core.query_resource(context, models.PodServiceConfiguration,
filters or [], sorts or [])

View File

@ -386,6 +386,19 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
context, subnet_id, subnet)
def create_port(self, context, port):
port_body = port['port']
if port_body['device_id'] == t_constants.interface_port_device_id:
_, region_name, subnet_id = port_body['name'].split('_')
gateway_port_body = self.helper.get_create_interface_body(
port_body['tenant_id'], port_body['network_id'], region_name,
subnet_id)
t_ctx = t_context.get_context_from_neutron_context(context)
pod = db_api.get_pod_by_name(t_ctx, region_name)
_, t_gateway_id = self.helper.prepare_top_element(
t_ctx, context, port_body['tenant_id'], pod,
{'id': port_body['name']}, t_constants.RT_PORT,
gateway_port_body)
return super(TricirclePlugin, self).get_port(context, t_gateway_id)
db_port = super(TricirclePlugin, self).create_port_db(context, port)
return self._make_port_dict(db_port)
@ -414,6 +427,20 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
db_api.create_resource_mapping(t_ctx, resource_id, resource_id,
pod['pod_id'], res['tenant_id'],
resource_type)
interfaces = super(TricirclePlugin, self).get_ports(
context,
{'network_id': [res['network_id']],
'device_owner': [constants.DEVICE_OWNER_ROUTER_INTF]})
interfaces = [inf for inf in interfaces if inf['device_id']]
if interfaces:
# request may be come from service, we use an admin context
# to run the xjob
admin_context = t_context.get_admin_context()
self.xjob_handler.setup_bottom_router(
admin_context, res['network_id'],
interfaces[0]['device_id'], pod['pod_id'])
return res
def delete_port(self, context, port_id, l3_port_check=True):

View File

@ -32,3 +32,8 @@ class BottomPodOperationFailure(exceptions.NeutronException):
class DhcpPortNotFound(exceptions.NotFound):
message = _('Dhcp port for subnet %(subnet_id)s not found')
class GatewayPortNotFound(exceptions.NotFound):
message = _('Gateway port for subnet %(subnet_id)s and region %(region)s '
'not found')

View File

@ -28,7 +28,9 @@ from tricircle.common import client # noqa
import tricircle.common.constants as t_constants
import tricircle.common.context as t_context
from tricircle.common.i18n import _
from tricircle.common import resource_handle
import tricircle.common.utils as t_utils
import tricircle.network.exceptions as t_exceptions
from tricircle.network import helper
@ -59,6 +61,18 @@ class TricirclePlugin(plugin.Ml2Plugin):
self.neutron_handle.endpoint_url = \
cfg.CONF.tricircle.central_neutron_url
def start_rpc_listeners(self):
return self.core_plugin.start_rpc_listeners()
def start_rpc_state_reports_listener(self):
return self.core_plugin.start_rpc_state_reports_listener()
def rpc_workers_supported(self):
return self.core_plugin.rpc_workers_supported()
def rpc_state_report_workers_supported(self):
return self.core_plugin.rpc_state_report_workers_supported()
@staticmethod
def _adapt_network_body(network):
network_type = network.get('provider:network_type')
@ -147,6 +161,43 @@ class TricirclePlugin(plugin.Ml2Plugin):
dhcp_port_body['port']['id'] = t_ports[0]['id']
self.core_plugin.create_port(q_ctx, dhcp_port_body)
def _ensure_gateway_port(self, t_ctx, t_subnet):
region_name = cfg.CONF.nova.region_name
gateway_port_name = t_constants.interface_port_name % (region_name,
t_subnet['id'])
gateway_port_body = {
'port': {'tenant_id': t_subnet['tenant_id'],
'admin_state_up': True,
'name': gateway_port_name,
'network_id': t_subnet['network_id'],
'device_id': t_constants.interface_port_device_id}}
try:
return self.neutron_handle.handle_create(
t_ctx, t_constants.RT_PORT, gateway_port_body)
except Exception:
raw_client = self.neutron_handle._get_client(t_ctx)
params = {'name': gateway_port_name}
t_ports = raw_client.list_ports(**params)['ports']
if not t_ports:
raise t_exceptions.GatewayPortNotFound(
subnet_id=t_subnet['id'], region=region_name)
return t_ports[0]
def create_network(self, context, network):
# this method is overwritten for bottom bridge network and external
# network creation, for internal network, get_network and get_networks
# will do the trick
net_body = network['network']
self._adapt_network_body(net_body)
if net_body['name']:
net_id = t_utils.get_id_from_name(t_constants.RT_NETWORK,
net_body['name'])
if net_id:
net_body['id'] = net_id
b_network = self.core_plugin.create_network(context,
{'network': net_body})
return b_network
def get_network(self, context, _id, fields=None):
try:
b_network = self.core_plugin.get_network(context, _id, fields)
@ -174,12 +225,14 @@ class TricirclePlugin(plugin.Ml2Plugin):
return self.core_plugin.get_networks(
context, filters, fields, sorts, limit, marker, page_reverse)
b_networks = self.core_plugin.get_networks(
context, filters, fields, sorts, limit, marker, page_reverse)
for b_network in b_networks:
b_full_networks = self.core_plugin.get_networks(
context, filters, None, sorts, limit, marker, page_reverse)
b_networks = []
for b_network in b_full_networks:
subnet_ids = self._ensure_subnet(context, b_network, False)
if subnet_ids:
b_network['subnets'] = subnet_ids
b_networks.append(self._fields(b_network, fields))
if len(b_networks) == len(filters['id']):
return b_networks
@ -206,6 +259,31 @@ class TricirclePlugin(plugin.Ml2Plugin):
b_networks.append(self._fields(b_network, fields))
return b_networks
def create_subnet(self, context, subnet):
# this method is overwritten for bottom bridge subnet and external
# subnet creation, for internal subnet, get_subnet and get_subnets
# will do the trick
subnet_body = subnet['subnet']
if subnet_body['name']:
subnet_id = t_utils.get_id_from_name(t_constants.RT_SUBNET,
subnet_body['name'])
if subnet_id:
subnet_body['id'] = subnet_id
b_subnet = self.core_plugin.create_subnet(context,
{'subnet': subnet_body})
return b_subnet
def _create_bottom_subnet(self, t_ctx, q_ctx, t_subnet):
gateway_port = self._ensure_gateway_port(t_ctx, t_subnet)
subnet_body = helper.NetworkHelper.get_create_subnet_body(
gateway_port['tenant_id'], t_subnet, t_subnet['network_id'],
gateway_port['fixed_ips'][0]['ip_address'])['subnet']
t_subnet['gateway_ip'] = subnet_body['gateway_ip']
t_subnet['allocation_pools'] = subnet_body['allocation_pools']
b_subnet = self.core_plugin.create_subnet(q_ctx, {'subnet': t_subnet})
return b_subnet
def get_subnet(self, context, _id, fields=None):
t_ctx = t_context.get_context_from_neutron_context(context)
try:
@ -214,11 +292,9 @@ class TricirclePlugin(plugin.Ml2Plugin):
t_subnet = self.neutron_handle.handle_get(t_ctx, 'subnet', _id)
if not t_subnet:
raise q_exceptions.SubnetNotFound(subnet_id=_id)
b_subnet = self.core_plugin.create_subnet(context,
{'subnet': t_subnet})
b_subnet = self._create_bottom_subnet(t_ctx, context, t_subnet)
if b_subnet['enable_dhcp']:
self._ensure_subnet_dhcp_port(t_ctx, context, b_subnet)
return self._fields(b_subnet, fields)
def get_subnets(self, context, filters=None, fields=None, sorts=None,
@ -232,10 +308,13 @@ class TricirclePlugin(plugin.Ml2Plugin):
context, filters, fields, sorts, limit, marker, page_reverse)
t_ctx = t_context.get_context_from_neutron_context(context)
b_subnets = self.core_plugin.get_subnets(
context, filters, fields, sorts, limit, marker, page_reverse)
for b_subnet in b_subnets:
b_full_subnets = self.core_plugin.get_subnets(
context, filters, None, sorts, limit, marker, page_reverse)
b_subnets = []
for b_subnet in b_full_subnets:
if b_subnet['enable_dhcp']:
self._ensure_subnet_dhcp_port(t_ctx, context, b_subnet)
b_subnets.append(self._fields(b_subnet, fields))
if len(b_subnets) == len(filters['id']):
return b_subnets
@ -251,13 +330,19 @@ class TricirclePlugin(plugin.Ml2Plugin):
missing_subnets = [subnet for subnet in t_subnets if (
subnet['id'] in missing_id_set)]
for subnet in missing_subnets:
b_subnet = self.core_plugin.create_subnet(
context, {'subnet': subnet})
b_subnet = self._create_bottom_subnet(t_ctx, context, subnet)
if b_subnet['enable_dhcp']:
self._ensure_subnet_dhcp_port(t_ctx, context, b_subnet)
b_subnets.append(self._fields(b_subnet, fields))
return b_subnets
@staticmethod
def _is_special_port(port):
return port.get('device_owner') in (
q_constants.DEVICE_OWNER_ROUTER_INTF,
q_constants.DEVICE_OWNER_FLOATINGIP,
q_constants.DEVICE_OWNER_ROUTER_GW)
def create_port(self, context, port):
port_body = port['port']
network_id = port_body['network_id']
@ -268,12 +353,13 @@ class TricirclePlugin(plugin.Ml2Plugin):
raw_client = self.neutron_handle._get_client(t_ctx)
if port_body['fixed_ips'] is not q_constants.ATTR_NOT_SPECIFIED:
if not self._is_special_port(port_body):
fixed_ip = port_body['fixed_ips'][0]
ip_address = fixed_ip.get('ip_address')
if not ip_address:
# dhcp agent may request to create a dhcp port without
# specifying ip address, we just raise an exception to reject
# this request
# specifying ip address, we just raise an exception to
# reject this request
raise q_exceptions.InvalidIpForNetwork(ip_address='None')
params = {'fixed_ips': 'ip_address=%s' % ip_address}
t_ports = raw_client.list_ports(**params)['ports']
@ -281,12 +367,20 @@ class TricirclePlugin(plugin.Ml2Plugin):
raise q_exceptions.InvalidIpForNetwork(
ip_address=fixed_ip['ip_address'])
t_port = t_ports[0]
else:
t_port = port_body
else:
self._adapt_port_body_for_client(port['port'])
t_port = raw_client.create_port(port)['port']
if not self._is_special_port(port_body):
subnet_id = t_port['fixed_ips'][0]['subnet_id']
# get_subnet will create bottom subnet if it doesn't exist
self.get_subnet(context, subnet_id)
for field in ('name', 'device_id'):
if port_body.get(field):
t_port[field] = port_body[field]
b_port = self.core_plugin.create_port(context, {'port': t_port})
return b_port

View File

@ -1020,9 +1020,11 @@ class PluginTest(unittest.TestCase,
core.initialize()
core.ModelBase.metadata.create_all(core.get_engine())
cfg.CONF.register_opts(q_config.core_opts)
cfg.CONF.register_opts(plugin.tricircle_opts)
plugin_path = \
'tricircle.tests.unit.network.test_central_plugin.FakePlugin'
cfg.CONF.set_override('core_plugin', plugin_path)
cfg.CONF.set_override('enable_api_gateway', True)
self.context = context.Context()
self.save_method = manager.NeutronManager._get_default_service_plugins
manager.NeutronManager._get_default_service_plugins = mock.Mock()
@ -2110,16 +2112,16 @@ class PluginTest(unittest.TestCase,
b_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, net['id'], 'pod_1', constants.RT_NETWORK)
calls = [mock.call(t_ctx,
{'floatingip': {
'floating_network_id': b_bridge_net_id,
'floating_ip_address': '100.128.0.3',
'port_id': b_port_id}}),
mock.call(t_ctx,
{'floatingip': {
'floating_network_id': b_ext_net_id,
'floating_ip_address': fip[
'floating_ip_address'],
'port_id': ns_bridge_port['id']}}),
mock.call(t_ctx,
{'floatingip': {
'floating_network_id': b_bridge_net_id,
'floating_ip_address': '100.128.0.3',
'port_id': b_port_id}})]
'port_id': ns_bridge_port['id']}})]
mock_create.assert_has_calls(calls)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)

View File

@ -19,6 +19,7 @@ from mock import patch
import six
import unittest
from oslo_config import cfg
from oslo_utils import uuidutils
import neutron_lib.constants as q_constants
@ -106,6 +107,9 @@ class FakeCorePlugin(object):
create_resource('port', False, port['port'])
return port['port']
def update_port(self, context, _id, port):
pass
def get_port(self, context, _id, fields=None):
return get_resource('port', False, _id)
@ -180,6 +184,15 @@ class FakeNeutronHandle(object):
def handle_get(self, context, _type, _id):
return get_resource(_type, True, _id)
def handle_create(self, context, _type, body):
if _type == 'port':
return FakeClient().create_port(body)['port']
create_resource(_type, True, body[_type])
return body[_type]
def handle_update(self, context, _type, _id, body):
pass
class FakePlugin(plugin.TricirclePlugin):
def __init__(self):
@ -207,6 +220,9 @@ class PluginTest(unittest.TestCase):
'name': 'subnet1',
'network_id': network_id,
'cidr': '10.0.1.0/24',
'ip_version': 4,
'allocation_pools': [{'start': '10.0.1.2',
'end': '10.0.1.254'}],
'enable_dhcp': True}
t_port = {'id': port_id,
'tenant_id': self.tenant_id,
@ -232,10 +248,26 @@ class PluginTest(unittest.TestCase):
b_port = get_resource('port', False, port['id'])
b_net.pop('project_id')
b_subnet.pop('project_id')
pool = subnet.pop('allocation_pools')[0]
b_pools = b_subnet.pop('allocation_pools')
b_gateway_ip = b_subnet.pop('gateway_ip')
def ip_to_digit(ip):
return int(ip[ip.rindex('.') + 1:])
pool_range = range(ip_to_digit(pool['start']),
ip_to_digit(pool['end']) + 1)
b_pool_range1 = range(ip_to_digit(b_pools[0]['start']),
ip_to_digit(b_pools[0]['end']) + 1)
b_pool_range2 = range(ip_to_digit(b_pools[1]['start']),
ip_to_digit(b_pools[1]['end']) + 1)
b_pool_range = b_pool_range1 + [
ip_to_digit(b_gateway_ip)] + b_pool_range2
port.pop('name')
b_port.pop('name')
self.assertDictEqual(net, b_net)
self.assertDictEqual(subnet, b_subnet)
self.assertEqual(pool_range, b_pool_range)
self.assertEqual('vlan', b_net_type)
self.assertDictEqual(port, b_port)
@ -329,6 +361,19 @@ class PluginTest(unittest.TestCase):
b_port.pop('project_id')
self.assertDictEqual(t_ports[i], b_port)
@patch.object(t_context, 'get_context_from_neutron_context')
@patch.object(FakeNeutronHandle, 'handle_update')
def test_update_port(self, mock_update, mock_context):
cfg.CONF.set_override('region_name', 'Pod1', 'nova')
mock_context.return_value = self.context
update_body = {'port': {'device_owner': 'compute:None',
'binding:host_id': 'fake_host'}}
port_id = 'fake_port_id'
self.plugin.update_port(self.context, port_id, update_body)
mock_update.assert_called_once_with(
self.context, 'port', port_id,
{'port': {'binding:profile': {'region': 'Pod1'}}})
def tearDown(self):
for res in RES_LIST:
del res[:]

View File

@ -385,9 +385,13 @@ class XManager(PeriodicTasks):
t_subnet_id = t_port['fixed_ips'][0]['subnet_id']
t_subnet = t_client.get_subnets(ctx, t_subnet_id)
if CONF.enable_api_gateway:
(b_net_id,
subnet_map) = self.helper.prepare_bottom_network_subnets(
ctx, q_ctx, project_id, b_pod, t_net, [t_subnet])
else:
(b_net_id,
subnet_map) = (t_net['id'], {t_subnet['id']: t_subnet['id']})
# the gateway ip of bottom subnet is set to the ip of t_port, so
# we just attach the bottom subnet to the bottom router and neutron
@ -456,13 +460,14 @@ class XManager(PeriodicTasks):
_, b_ns_bridge_port_id = self.helper.prepare_bottom_element(
ctx, project_id, b_ext_pod, t_ns_bridge_port,
constants.RT_PORT, port_body)
self._safe_create_bottom_floatingip(
ctx, b_ext_pod, b_ext_client, b_ext_net_id, add_fip,
b_ns_bridge_port_id)
# swap these two lines
self._safe_create_bottom_floatingip(
ctx, b_pod, b_client, b_ns_bridge_net_id,
t_ns_bridge_port['fixed_ips'][0]['ip_address'],
b_int_port_id)
self._safe_create_bottom_floatingip(
ctx, b_ext_pod, b_ext_client, b_ext_net_id, add_fip,
b_ns_bridge_port_id)
else:
self._safe_create_bottom_floatingip(
ctx, b_pod, b_client, b_ext_net_id, add_fip,

View File

@ -55,7 +55,10 @@ common_opts = [
help=_("Running job is considered expires after this time, in"
" seconds")),
cfg.FloatOpt('worker_sleep_time', default=0.1,
help=_("Seconds a worker sleeps after one run in a loop"))
help=_("Seconds a worker sleeps after one run in a loop")),
cfg.BoolOpt('enable_api_gateway',
default=False,
help=_('Whether the Nova API gateway is enabled'))
]
service_opts = [