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:
parent
07468d25c2
commit
61cb169437
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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})
|
||||
|
|
Loading…
Reference in New Issue