Default security group update

1. What is the problem?
Currently when users update the default security group it
will raise an exception. So we can't update default security
group. When we create or delete rules it uses a synchronous way to
create or delete the related rules in bottom pod. In order
to accelerate response we need to use an asynchronous way.

2. What is the solution to the problem?
Enable default security group to support add rules and
delete rules. Using Xjob to create or delete related rules
in bottom pod.

3. What the features need to be implemented to the Tricircle
to realize the solution?
Allow users to update default security group.

Change-Id: Ibf896f15390b656bda6d438bd3176e4b15dd359c
This commit is contained in:
lyman-xu 2017-10-10 14:51:07 +08:00
parent 8a81cebe14
commit 25ada0602e
6 changed files with 128 additions and 108 deletions

View File

@ -0,0 +1,3 @@
---
features:
- Support updating default security group using asynchronous methods.

View File

@ -44,6 +44,14 @@ class CentralizedSNATPortNotFound(exceptions.NotFound):
message = _('Centralized snat port for subnet %(subnet_id)s not found')
class SecurityGroupNotFound(exceptions.NotFound):
message = _('Security group for %(sg_id)s not found')
class SecurityGroupRuleNotFound(exceptions.NotFound):
message = _('Security group rule for id %(rule_id)s not found')
class NetAttachedToNonLocalRouter(exceptions.Conflict):
message = _('Network %(network_id)s has already been attached to non '
'local router %(router_id)s')

View File

@ -14,29 +14,17 @@
# under the License.
from neutron.db import securitygroups_db
import neutronclient.common.exceptions as q_exceptions
from tricircle.common import constants
from tricircle.common import context
import tricircle.db.api as db_api
from tricircle.common import xrpcapi
import tricircle.network.exceptions as n_exceptions
class TricircleSecurityGroupMixin(securitygroups_db.SecurityGroupDbMixin):
@staticmethod
def _safe_create_security_group_rule(t_context, client, body):
try:
client.create_security_group_rules(t_context, body)
except q_exceptions.Conflict:
return
@staticmethod
def _safe_delete_security_group_rule(t_context, client, _id):
try:
client.delete_security_group_rules(t_context, _id)
except q_exceptions.NotFound:
return
def __init__(self):
super(TricircleSecurityGroupMixin, self).__init__()
self.xjob_handler = xrpcapi.XJobAPI()
@staticmethod
def _compare_rule(rule1, rule2):
@ -52,57 +40,43 @@ class TricircleSecurityGroupMixin(securitygroups_db.SecurityGroupDbMixin):
raise n_exceptions.RemoteGroupNotSupported()
sg_id = rule['security_group_id']
sg = self.get_security_group(q_context, sg_id)
if sg['name'] == 'default':
raise n_exceptions.DefaultGroupUpdateNotSupported()
if not sg:
raise n_exceptions.SecurityGroupNotFound(sg_id=sg_id)
new_rule = super(TricircleSecurityGroupMixin,
self).create_security_group_rule(q_context,
security_group_rule)
t_context = context.get_context_from_neutron_context(q_context)
mappings = db_api.get_bottom_mappings_by_top_id(
t_context, sg_id, constants.RT_SG)
try:
for pod, b_sg_id in mappings:
client = self._get_client(pod['region_name'])
rule['security_group_id'] = b_sg_id
self._safe_create_security_group_rule(
t_context, client, {'security_group_rule': rule})
self.xjob_handler.configure_security_group_rules(
t_context, rule['project_id'])
except Exception:
super(TricircleSecurityGroupMixin,
self).delete_security_group_rule(q_context, new_rule['id'])
raise n_exceptions.BottomPodOperationFailure(
resource='security group rule', region_name=pod['region_name'])
resource='security group rule', region_name='')
return new_rule
def delete_security_group_rule(self, q_context, _id):
rule = self.get_security_group_rule(q_context, _id)
if not rule:
raise n_exceptions.SecurityGroupRuleNotFound(rule_id=_id)
if rule['remote_group_id']:
raise n_exceptions.RemoteGroupNotSupported()
sg_id = rule['security_group_id']
sg = self.get_security_group(q_context, sg_id)
if sg['name'] == 'default':
raise n_exceptions.DefaultGroupUpdateNotSupported()
t_context = context.get_context_from_neutron_context(q_context)
mappings = db_api.get_bottom_mappings_by_top_id(
t_context, sg_id, constants.RT_SG)
try:
for pod, b_sg_id in mappings:
client = self._get_client(pod['region_name'])
rule['security_group_id'] = b_sg_id
b_sg = client.get_security_groups(t_context, b_sg_id)
for b_rule in b_sg['security_group_rules']:
if not self._compare_rule(b_rule, rule):
continue
self._safe_delete_security_group_rule(t_context, client,
b_rule['id'])
break
except Exception:
raise n_exceptions.BottomPodOperationFailure(
resource='security group rule', region_name=pod['region_name'])
if not sg:
raise n_exceptions.SecurityGroupNotFound(sg_id=sg_id)
super(TricircleSecurityGroupMixin,
self).delete_security_group_rule(q_context, _id)
t_context = context.get_context_from_neutron_context(q_context)
try:
self.xjob_handler.configure_security_group_rules(
t_context, rule['project_id'])
except Exception:
raise n_exceptions.BottomPodOperationFailure(
resource='security group rule', region_name='')

View File

@ -442,22 +442,32 @@ class FakeClient(test_utils.FakeClient):
def delete_floatingips(self, ctx, _id):
self.delete_resources('floatingip', ctx, _id)
@staticmethod
def _compare_rule(rule1, rule2):
for key in ('direction', 'remote_ip_prefix', 'protocol', 'ethertype',
'port_range_max', 'port_range_min'):
if rule1[key] != rule2[key]:
return False
return True
def create_security_group_rules(self, ctx, body):
sg_id = body['security_group_rule']['security_group_id']
sg_id = body['security_group_rules'][0]['security_group_id']
res_list = self._res_map[self.region_name]['security_group']
for sg in res_list:
if sg['id'] == sg_id:
target_sg = sg
new_rule = copy.copy(body['security_group_rule'])
new_rules = copy.copy(body['security_group_rules'])
match_found = False
for rule in target_sg['security_group_rules']:
old_rule = copy.copy(rule)
if new_rule == old_rule:
match_found = True
break
for new_rule in new_rules:
for rule in target_sg['security_group_rules']:
if self._compare_rule(rule, new_rule):
match_found = True
break
if not match_found:
new_rule['id'] = uuidutils.generate_uuid()
if match_found:
raise q_exceptions.Conflict()
target_sg['security_group_rules'].append(body['security_group_rule'])
target_sg['security_group_rules'].extend(body['security_group_rules'])
def delete_security_group_rules(self, ctx, rule_id):
res_list = self._res_map[self.region_name]['security_group']
@ -477,6 +487,9 @@ class FakeClient(test_utils.FakeClient):
def get_security_group(self, context, _id, fields=None, tenant_id=None):
pass
def list_security_groups(self, ctx, sg_filters):
return self.list_resources('security_group', ctx, sg_filters)
def update_floatingip_dict(fip_dict, update_dict):
if not update_dict.get('port_id'):
@ -586,7 +599,8 @@ class FakeRPCAPI(FakeBaseRPCAPI):
pass
def configure_security_group_rules(self, ctxt, project_id):
pass
self.xmanager.configure_security_group_rules(
ctxt, payload={constants.JT_SEG_RULE_SETUP: project_id})
def setup_shadow_ports(self, ctxt, project_id, pod_id, net_id):
combine_id = '%s#%s' % (pod_id, net_id)
@ -3314,19 +3328,6 @@ class PluginTest(unittest.TestCase,
'pod_id_1', TOP_SGS,
TOP_SG_RULES, BOTTOM1_SGS)
@patch.object(context, 'get_context_from_neutron_context')
def test_handle_default_sg_invalid_input(self, mock_context):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
self._test_handle_default_sg_invalid_input(fake_plugin, q_ctx, t_ctx,
'pod_id_1', TOP_SGS,
TOP_SG_RULES, BOTTOM1_SGS)
@patch.object(FakeClient, 'create_security_group_rules')
@patch.object(context, 'get_context_from_neutron_context')
def test_create_security_group_rule_exception(self, mock_context,
@ -3358,6 +3359,19 @@ class PluginTest(unittest.TestCase,
fake_plugin, q_ctx, t_ctx, 'pod_id_1', TOP_SGS, TOP_SG_RULES,
BOTTOM1_SGS)
@patch.object(context, 'get_context_from_neutron_context')
def test_update_default_sg(self, mock_context):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
self._test_update_default_sg(fake_plugin, q_ctx, t_ctx,
'pod_id_1', TOP_SGS,
TOP_SG_RULES, BOTTOM1_SGS)
@patch.object(FakeBaseRPCAPI, 'setup_shadow_ports')
@patch.object(FakeClient, 'update_ports')
@patch.object(context, 'get_context_from_neutron_context')

View File

@ -37,6 +37,14 @@ class TricircleSecurityGroupTestMixin(object):
'port_range_min': None,
'ethertype': 'IPv4'}
@staticmethod
def _compare_rule(rule1, rule2):
for key in ('direction', 'remote_ip_prefix', 'protocol', 'ethertype',
'port_range_max', 'port_range_min'):
if rule1[key] != rule2[key] and str(rule1[key]) != str(rule2[key]):
return False
return True
def _test_create_security_group_rule(self, plugin, q_ctx, t_ctx, pod_id,
top_sgs, bottom1_sgs):
t_sg_id = uuidutils.generate_uuid()
@ -67,9 +75,6 @@ class TricircleSecurityGroupTestMixin(object):
self.assertEqual(1, len(bottom1_sgs[0]['security_group_rules']))
b_rule = bottom1_sgs[0]['security_group_rules'][0]
self.assertEqual(b_sg_id, b_rule['security_group_id'])
rule['security_group_rule'].pop('security_group_id', None)
b_rule.pop('security_group_id', None)
self.assertEqual(rule['security_group_rule'], b_rule)
def _test_delete_security_group_rule(self, plugin, q_ctx, t_ctx, pod_id,
top_sgs, top_rules, bottom1_sgs):
@ -149,41 +154,6 @@ class TricircleSecurityGroupTestMixin(object):
self.assertRaises(exceptions.RemoteGroupNotSupported,
plugin.delete_security_group_rule, q_ctx, t_rule1_id)
def _test_handle_default_sg_invalid_input(self, plugin, q_ctx, t_ctx,
pod_id, top_sgs, top_rules,
bottom1_sgs):
t_sg_id = uuidutils.generate_uuid()
t_rule1_id = uuidutils.generate_uuid()
t_rule2_id = uuidutils.generate_uuid()
b_sg_id = uuidutils.generate_uuid()
project_id = 'test_prject_id'
t_rule1 = self._build_test_rule(
t_rule1_id, t_sg_id, project_id, '10.0.0.0/24')
t_rule2 = self._build_test_rule(
t_rule2_id, t_sg_id, project_id, '10.0.1.0/24')
t_sg = {'id': t_sg_id, 'name': 'default', 'description': '',
'tenant_id': project_id,
'security_group_rules': [t_rule1]}
b_sg = {'id': b_sg_id, 'name': t_sg_id, 'description': '',
'tenant_id': project_id,
'security_group_rules': []}
top_sgs.append(t_sg)
top_rules.append(t_rule1)
bottom1_sgs.append(b_sg)
route1 = {
'top_id': t_sg_id,
'pod_id': pod_id,
'bottom_id': b_sg_id,
'resource_type': constants.RT_SG}
with t_ctx.session.begin():
core.create_resource(t_ctx, models.ResourceRouting, route1)
self.assertRaises(exceptions.DefaultGroupUpdateNotSupported,
plugin.create_security_group_rule, q_ctx,
{'security_group_rule': t_rule2})
self.assertRaises(exceptions.DefaultGroupUpdateNotSupported,
plugin.delete_security_group_rule, q_ctx, t_rule1_id)
def _test_create_security_group_rule_exception(
self, plugin, q_ctx, t_ctx, pod_id, top_sgs, bottom1_sgs):
t_sg_id = uuidutils.generate_uuid()
@ -242,3 +212,51 @@ class TricircleSecurityGroupTestMixin(object):
self.assertRaises(exceptions.BottomPodOperationFailure,
plugin.delete_security_group_rule, q_ctx, t_rule_id)
def _test_update_default_sg(self, plugin, q_ctx, t_ctx,
pod_id, top_sgs, top_rules,
bottom1_sgs):
t_sg_id = uuidutils.generate_uuid()
t_rule1_id = uuidutils.generate_uuid()
t_rule2_id = uuidutils.generate_uuid()
b_sg_id = uuidutils.generate_uuid()
project_id = 'test_prject_id'
t_rule1 = self._build_test_rule(
t_rule1_id, t_sg_id, project_id, '10.0.0.0/24')
t_sg = {'id': t_sg_id, 'name': 'default', 'description': '',
'tenant_id': project_id,
'security_group_rules': [t_rule1]}
b_sg = {'id': b_sg_id, 'name': 'default', 'description': '',
'tenant_id': project_id,
'security_group_rules': []}
top_sgs.append(t_sg)
top_rules.append(t_rule1)
bottom1_sgs.append(b_sg)
route1 = {
'top_id': t_sg_id,
'pod_id': pod_id,
'bottom_id': b_sg_id,
'resource_type': constants.RT_SG}
with t_ctx.session.begin():
core.create_resource(t_ctx, models.ResourceRouting, route1)
t_rule2 = {
'security_group_rule': self._build_test_rule(
t_rule2_id, t_sg_id, project_id, '10.0.1.0/24')}
plugin.create_security_group_rule(q_ctx, t_rule2)
self.assertEqual(len(top_sgs[0]['security_group_rules']),
len(bottom1_sgs[0]['security_group_rules']))
for i in range(len(bottom1_sgs[0]['security_group_rules'])):
self.assertTrue(self._compare_rule(
bottom1_sgs[0]['security_group_rules'][i],
top_sgs[0]['security_group_rules'][i]))
plugin.delete_security_group_rule(q_ctx, t_rule1_id)
self.assertEqual(len(bottom1_sgs[0]['security_group_rules']),
len(top_sgs[0]['security_group_rules']))
for i in range(len(bottom1_sgs[0]['security_group_rules'])):
self.assertTrue(self._compare_rule(
bottom1_sgs[0]['security_group_rules'][i],
top_sgs[0]['security_group_rules'][i]))

View File

@ -456,6 +456,9 @@ class FakeSession(object):
def delete(self, model_obj):
unlink_models(self.resource_store.store_map['routers'], model_obj,
'router_id', 'id', 'attached_ports', 'port_id', 'id')
unlink_models(self.resource_store.store_map['securitygroups'],
model_obj, 'security_group_id', 'id',
'security_group_rules', 'id', 'id')
self._cascade_delete(model_obj, 'port_id', 'ipallocations', 'id')
key = self.delete_hook(model_obj)
for res_list in self.resource_store.store_map.values():