Add network update operation support(1)

1. What is the problem
Tricircle does not support network update operation now.

2. What is the solution to the problem
Implement related functions

3. What the features need to be implemented to the Tricircle
Add name,description,shared,admin_state_up attribute updates support,
where updating name only takes effect on the central pod.

Change-Id: Ifd0bceeb70ee5408d3357cfc990c9423a6e94237
This commit is contained in:
xiulin yin 2016-12-06 16:24:10 +08:00
parent 07468d25c2
commit 61cb169437
6 changed files with 267 additions and 12 deletions

View File

@ -87,6 +87,7 @@ JT_ROUTER = 'router'
JT_ROUTER_SETUP = 'router_setup'
JT_PORT_DELETE = 'port_delete'
JT_SEG_RULE_SETUP = 'seg_rule_setup'
JT_NETWORK_UPDATE = 'update_network'
# network type
NT_LOCAL = 'local'

View File

@ -118,7 +118,7 @@ class GlanceResourceHandle(ResourceHandle):
class NeutronResourceHandle(ResourceHandle):
service_type = cons.ST_NEUTRON
support_resource = {
'network': LIST | CREATE | DELETE | GET,
'network': LIST | CREATE | DELETE | GET | UPDATE,
'subnet': LIST | CREATE | DELETE | GET | UPDATE,
'port': LIST | CREATE | DELETE | GET,
'router': LIST | CREATE | DELETE | ACTION | GET | UPDATE,

View File

@ -97,3 +97,9 @@ class XJobAPI(object):
self.client.prepare(exchange='openstack').cast(
ctxt, 'configure_security_group_rules',
payload={constants.JT_SEG_RULE_SETUP: project_id})
def update_network(self, ctxt, network_id, pod_id):
combine_id = '%s#%s' % (pod_id, network_id)
self.client.prepare(exchange='openstack').cast(
ctxt, 'update_network',
payload={constants.JT_NETWORK_UPDATE: combine_id})

View File

@ -301,14 +301,45 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
self.type_manager.release_network_segments(context, network_id)
super(TricirclePlugin, self).delete_network(context, network_id)
def _raise_if_updates_external_attribute(self, attrs):
"""Raise exception if external attributes are present.
This method is used for plugins that do not support
updating external attributes.
"""
if validators.is_attr_set(attrs.get(external_net.EXTERNAL)):
msg = _("Plugin does not support updating network's "
"router:external attribute")
raise exceptions.InvalidInput(error_message=msg)
def update_network(self, context, network_id, network):
"""update top network
update top network and trigger asynchronous job via RPC to update
bottom network
:param context: neutron context
:param network_id: top network id
:param network: updated body
:return: updated network
"""
net_data = network[attributes.NETWORK]
provider._raise_if_updates_provider_attributes(net_data)
self._raise_if_updates_external_attribute(net_data)
net = super(TricirclePlugin, self).update_network(
context, network_id, network)
self.type_manager.extend_network_dict_provider(context, net)
return net
with context.session.begin():
net = super(TricirclePlugin, self).update_network(context,
network_id,
network)
t_ctx = t_context.get_context_from_neutron_context(context)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, network_id, t_constants.RT_NETWORK)
if mappings:
self.xjob_handler.update_network(
t_ctx, network_id, t_constants.POD_NOT_SPECIFIED)
self.type_manager.extend_network_dict_provider(context, net)
return net
def get_network(self, context, network_id, fields=None):
net = super(TricirclePlugin, self).get_network(context, network_id,

View File

@ -25,14 +25,19 @@ from sqlalchemy.orm import exc
from sqlalchemy.sql import elements
import neutron_lib.constants as q_constants
import neutron_lib.exceptions as q_lib_exc
from neutron_lib.plugins import directory
import neutron.api.v2.attributes as neutron_attributes
import neutron.conf.common as q_config
from neutron.db import db_base_plugin_common
from neutron.db import db_base_plugin_v2
from neutron.db import ipam_pluggable_backend
from neutron.db import l3_db
from neutron.db import models_v2
from neutron.db import rbac_db_models as rbac_db
from neutron.extensions import availability_zone as az_ext
from neutron.ipam import driver
from neutron.ipam import requests
@ -74,6 +79,7 @@ TOP_EXTNETS = []
TOP_FLOATINGIPS = []
TOP_SGS = []
TOP_SG_RULES = []
TOP_NETWORK_RBAC = []
BOTTOM1_NETS = []
BOTTOM1_SUBNETS = []
BOTTOM1_PORTS = []
@ -107,7 +113,8 @@ RES_MAP = {'networks': TOP_NETS,
'externalnetworks': TOP_EXTNETS,
'floatingips': TOP_FLOATINGIPS,
'securitygroups': TOP_SGS,
'securitygrouprules': TOP_SG_RULES}
'securitygrouprules': TOP_SG_RULES,
'networkrbacs': TOP_NETWORK_RBAC}
SUBNET_INFOS = {}
TEST_TENANT_ID = 'test_tenant_id'
@ -154,6 +161,24 @@ class FakeIpamSubnet(driver.Subnet):
self._subnet['pools'])
class FakeNetworkRBAC(object):
def __init__(self, **kwargs):
self.__tablename__ = 'networkrbacs'
self.project_id = kwargs['tenant_id']
self.id = uuidutils.generate_uuid()
self.target_tenant = kwargs['target_tenant']
self.action = kwargs['action']
network = kwargs['network']
self.object_id = network['id']
def _as_dict(self):
return {'porject_id': self.project_id,
'id': self.id,
'target_tenant': self.target_tenant,
'action': self.action,
'object': self.object_id}
class FakePool(driver.Pool):
def allocate_subnet(self, subnet_request):
if isinstance(subnet_request, requests.SpecificSubnetRequest):
@ -197,6 +222,8 @@ class DotDict(dict):
self[key] = value
def __getattr__(self, item):
if item == 'rbac_entries':
return []
return self.get(item)
@ -376,6 +403,19 @@ class FakeClient(object):
def delete_networks(self, ctx, net_id):
self.delete_resources('network', ctx, net_id)
def update_networks(self, ctx, net_id, network):
net_data = network[neutron_attributes.NETWORK]
if self.pod_name == 'pod_1':
bottom_nets = BOTTOM1_NETS
else:
bottom_nets = BOTTOM2_NETS
for net in bottom_nets:
if net['id'] == net_id:
net['description'] = net_data['description']
net['admin_state_up'] = net_data['admin_state_up']
net['shared'] = net_data['shared']
def list_subnets(self, ctx, filters=None):
return self.list_resources('subnet', ctx, filters)
@ -815,6 +855,14 @@ class FakeSession(object):
net['external'] = True
net['router:external'] = True
break
if model_obj.__tablename__ == 'networkrbacs':
if (model_dict['action'] == 'access_as_shared' and
model_dict['target_tenant'] == '*'):
for net in TOP_NETS:
if net['id'] == model_dict['object']:
net['shared'] = True
break
link_models(model_obj, model_dict,
'routerports', 'router_id',
'routers', 'id', 'attached_ports')
@ -840,7 +888,7 @@ class FakeSession(object):
delete_model(res_list, model_obj)
class FakeXManager(xmanager.XManager):
class FakeBaseManager(xmanager.XManager):
def __init__(self, fake_plugin):
self.clients = {constants.TOP: client.Client()}
self.job_handles = {
@ -848,16 +896,29 @@ class FakeXManager(xmanager.XManager):
constants.JT_ROUTER_SETUP: self.setup_bottom_router,
constants.JT_PORT_DELETE: self.delete_server_port}
self.helper = FakeHelper(fake_plugin)
self.xjob_handler = FakeBaseRPCAPI()
def _get_client(self, pod_name=None):
return FakeClient(pod_name)
class FakeXManager(FakeBaseManager):
def __init__(self, fake_plugin):
super(FakeXManager, self).__init__(fake_plugin)
self.xjob_handler = FakeBaseRPCAPI(fake_plugin)
class FakeBaseRPCAPI(object):
def __init__(self, fake_plugin):
self.xmanager = FakeBaseManager(fake_plugin)
def configure_extra_routes(self, ctxt, router_id):
pass
def update_network(self, ctxt, network_id, pod_id):
combine_id = '%s#%s' % (pod_id, network_id)
self.xmanager.update_network(
ctxt, payload={constants.JT_NETWORK_UPDATE: combine_id})
class FakeRPCAPI(FakeBaseRPCAPI):
def __init__(self, fake_plugin):
@ -1263,6 +1324,98 @@ class PluginTest(unittest.TestCase,
'availability_zone_hints': ['az_name_1', 'az_name_2']}}
fake_plugin.create_network(neutron_context, network)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(context, 'get_context_from_neutron_context')
@patch.object(rbac_db, 'NetworkRBAC', new=FakeNetworkRBAC)
def test_update_network(self, mock_context):
tenant_id = TEST_TENANT_ID
self._basic_pod_route_setup()
t_ctx = context.get_db_context()
t_net_id, _, b_net_id, _ = self._prepare_network_test(tenant_id,
t_ctx, 'pod_1',
1)
fake_plugin = FakePlugin()
fake_client = FakeClient('pod_1')
neutron_context = FakeNeutronContext()
mock_context.return_value = t_ctx
update_body = {
'network': {
'name': 'new_name',
'description': 'new_description',
'admin_state_up': True,
'shared': True}
}
fake_plugin.update_network(neutron_context, t_net_id, update_body)
top_net = fake_plugin.get_network(neutron_context, t_net_id)
self.assertEqual(top_net['name'], update_body['network']['name'])
self.assertEqual(top_net['description'],
update_body['network']['description'])
self.assertEqual(top_net['admin_state_up'],
update_body['network']['admin_state_up'])
self.assertEqual(top_net['shared'], True)
bottom_net = fake_client.get_networks(t_ctx, b_net_id)
# name is set to top resource id, which is used by lock_handle to
# retrieve bottom/local resources that have been created but not
# registered in the resource routing table, so it's not allowed to
# be updated
self.assertEqual(bottom_net['name'], t_net_id)
self.assertEqual(bottom_net['description'],
update_body['network']['description'])
self.assertEqual(bottom_net['admin_state_up'],
update_body['network']['admin_state_up'])
self.assertEqual(bottom_net['shared'], True)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(context, 'get_context_from_neutron_context')
@patch.object(rbac_db, 'NetworkRBAC', new=FakeNetworkRBAC)
def test_update_network_external_attr(self, mock_context):
tenant_id = TEST_TENANT_ID
self._basic_pod_route_setup()
t_ctx = context.get_db_context()
t_net_id, _, _, _ = self._prepare_network_test(tenant_id, t_ctx,
'pod_1', 1)
fake_plugin = FakePlugin()
neutron_context = FakeNeutronContext()
mock_context.return_value = t_ctx
update_body = {
'network': {
'router:external': True
}
}
self.assertRaises(q_lib_exc.InvalidInput, fake_plugin.update_network,
neutron_context, t_net_id, update_body)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(context, 'get_context_from_neutron_context')
@patch.object(rbac_db, 'NetworkRBAC', new=FakeNetworkRBAC)
def test_update_network_provider_attrs(self, mock_context):
tenant_id = TEST_TENANT_ID
self._basic_pod_route_setup()
t_ctx = context.get_db_context()
t_net_id, _, _, _ = self._prepare_network_test(tenant_id, t_ctx,
'pod_1', 1)
fake_plugin = FakePlugin()
neutron_context = FakeNeutronContext()
mock_context.return_value = t_ctx
provider_attrs = {'provider:network_type': 'vlan',
'provider:physical_network': 'br-vlan',
'provider:segmentation_id': 1234}
for key, value in provider_attrs.items():
update_body = {
'network': {
key: value
}
}
self.assertRaises(q_lib_exc.InvalidInput,
fake_plugin.update_network,
neutron_context, t_net_id, update_body)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
@ -1330,7 +1483,7 @@ class PluginTest(unittest.TestCase,
self.assertEqual(bottom_entry_map['port']['bottom_id'], b_port_id)
@staticmethod
def _prepare_router_test(tenant_id, ctx, pod_name, index):
def _prepare_network_test(tenant_id, ctx, pod_name, index):
t_net_id = uuidutils.generate_uuid()
t_subnet_id = uuidutils.generate_uuid()
b_net_id = uuidutils.generate_uuid()
@ -1341,7 +1494,10 @@ class PluginTest(unittest.TestCase,
t_net = {
'id': t_net_id,
'name': 'top_net_%d' % index,
'tenant_id': tenant_id
'tenant_id': tenant_id,
'description': 'description',
'admin_state_up': False,
'shared': False
}
t_subnet = {
'id': t_subnet_id,
@ -1368,7 +1524,10 @@ class PluginTest(unittest.TestCase,
b_net = {
'id': b_net_id,
'name': t_net_id,
'tenant_id': tenant_id
'tenant_id': tenant_id,
'description': 'description',
'admin_state_up': False,
'shared': False
}
b_subnet = {
'id': b_subnet_id,
@ -1403,6 +1562,12 @@ class PluginTest(unittest.TestCase,
'pod_id': pod_id,
'project_id': tenant_id,
'resource_type': constants.RT_SUBNET})
return t_net_id, t_subnet_id, b_net_id, b_subnet_id
def _prepare_router_test(self, tenant_id, ctx, pod_name, index):
(t_net_id, t_subnet_id, b_net_id,
b_subnet_id) = self._prepare_network_test(tenant_id, ctx, pod_name,
index)
if len(TOP_ROUTERS) == 0:
t_router_id = uuidutils.generate_uuid()

View File

@ -149,7 +149,8 @@ class XManager(PeriodicTasks):
constants.JT_ROUTER: self.configure_extra_routes,
constants.JT_ROUTER_SETUP: self.setup_bottom_router,
constants.JT_PORT_DELETE: self.delete_server_port,
constants.JT_SEG_RULE_SETUP: self.configure_security_group_rules}
constants.JT_SEG_RULE_SETUP: self.configure_security_group_rules,
constants.JT_NETWORK_UPDATE: self.update_network}
self.helper = helper.NetworkHelper()
self.xjob_handler = xrpcapi.XJobAPI()
super(XManager, self).__init__()
@ -769,3 +770,54 @@ class XManager(PeriodicTasks):
rule_body['security_group_rules'].append(add_rule)
self._safe_create_security_group_rule(
ctx, client, rule_body)
@_job_handle(constants.JT_NETWORK_UPDATE)
def update_network(self, ctx, payload):
"""update bottom network
if bottom pod id equal to POD_NOT_SPECIFIED, dispatch jobs for every
mapped bottom pod via RPC, otherwise update network in the specified
pod.
:param ctx: tricircle context
:param payload: dict whose key is JT_NETWORK_UPDATE and value
is "top_network_id#bottom_pod_id"
:return: None
"""
(b_pod_id, t_network_id) = payload[
constants.JT_NETWORK_UPDATE].split('#')
if b_pod_id == constants.POD_NOT_SPECIFIED:
mappings = db_api.get_bottom_mappings_by_top_id(
ctx, t_network_id, constants.RT_NETWORK)
b_pods = [mapping[0] for mapping in mappings]
for b_pod in b_pods:
self.xjob_handler.update_network(ctx, t_network_id,
b_pod['pod_id'])
return
t_client = self._get_client()
t_network = t_client.get_networks(ctx, t_network_id)
if not t_network:
return
b_pod = db_api.get_pod(ctx, b_pod_id)
b_pod_name = b_pod['pod_name']
b_client = self._get_client(pod_name=b_pod_name)
b_network_id = db_api.get_bottom_id_by_top_id_pod_name(
ctx, t_network_id, b_pod_name, constants.RT_NETWORK)
# name is not allowed to be updated, because it is used by
# lock_handle to retrieve bottom/local resources that have been
# created but not registered in the resource routing table
body = {
'network': {
'description': t_network['description'],
'admin_state_up': t_network['admin_state_up'],
'shared': t_network['shared']
}
}
try:
b_client.update_networks(ctx, b_network_id, body)
except q_cli_exceptions.NotFound:
LOG.error(_LE('network: %(net_id)s not found,'
'pod name: %(name)s'),
{'net_id': b_network_id, 'name': b_pod_name})