Add create method for secgroup rule
Adds the creation method for security group rules. Rules, as returned from Nova, are normalized to look more like Neutron rule definitions. Change-Id: I4b1fa8b3067997a3a87de1ca0e8c924ae0b69f2a
This commit is contained in:
parent
2f499ab98b
commit
965998c764
@ -2521,6 +2521,126 @@ class OpenStackCloud(object):
|
||||
"Unavailable feature: security groups"
|
||||
)
|
||||
|
||||
def create_security_group_rule(self,
|
||||
secgroup_name_or_id,
|
||||
port_range_min=None,
|
||||
port_range_max=None,
|
||||
protocol=None,
|
||||
remote_ip_prefix=None,
|
||||
remote_group_id=None,
|
||||
direction='ingress',
|
||||
ethertype='IPv4'):
|
||||
"""Create a new security group rule
|
||||
|
||||
:param string secgroup_name_or_id:
|
||||
The security group name or ID to associate with this security
|
||||
group rule. If a non-unique group name is given, an exception
|
||||
is raised.
|
||||
:param int port_range_min:
|
||||
The minimum port number in the range that is matched by the
|
||||
security group rule. If the protocol is TCP or UDP, this value
|
||||
must be less than or equal to the port_range_max attribute value.
|
||||
If nova is used by the cloud provider for security groups, then
|
||||
a value of None will be transformed to -1.
|
||||
:param int port_range_max:
|
||||
The maximum port number in the range that is matched by the
|
||||
security group rule. The port_range_min attribute constrains the
|
||||
port_range_max attribute. If nova is used by the cloud provider
|
||||
for security groups, then a value of None will be transformed
|
||||
to -1.
|
||||
:param string protocol:
|
||||
The protocol that is matched by the security group rule. Valid
|
||||
values are None, tcp, udp, and icmp.
|
||||
:param string remote_ip_prefix:
|
||||
The remote IP prefix to be associated with this security group
|
||||
rule. This attribute matches the specified IP prefix as the
|
||||
source IP address of the IP packet.
|
||||
:param string remote_group_id:
|
||||
The remote group ID to be associated with this security group
|
||||
rule.
|
||||
:param string direction:
|
||||
Ingress or egress: The direction in which the security group
|
||||
rule is applied. For a compute instance, an ingress security
|
||||
group rule is applied to incoming (ingress) traffic for that
|
||||
instance. An egress rule is applied to traffic leaving the
|
||||
instance.
|
||||
:param string ethertype:
|
||||
Must be IPv4 or IPv6, and addresses represented in CIDR must
|
||||
match the ingress or egress rules.
|
||||
|
||||
:returns: A dict representing the new security group rule.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
|
||||
secgroup = self.get_security_group(secgroup_name_or_id)
|
||||
if not secgroup:
|
||||
raise OpenStackCloudException(
|
||||
"Security group %s not found." % secgroup_name_or_id)
|
||||
|
||||
if self.secgroup_source == 'neutron':
|
||||
# NOTE: Nova accepts -1 port numbers, but Neutron accepts None
|
||||
# as the equivalent value.
|
||||
rule_def = {
|
||||
'security_group_id': secgroup['id'],
|
||||
'port_range_min':
|
||||
None if port_range_min == -1 else port_range_min,
|
||||
'port_range_max':
|
||||
None if port_range_max == -1 else port_range_max,
|
||||
'protocol': protocol,
|
||||
'remote_ip_prefix': remote_ip_prefix,
|
||||
'remote_group_id': remote_group_id,
|
||||
'direction': direction,
|
||||
'ethertype': ethertype
|
||||
}
|
||||
|
||||
try:
|
||||
rule = self.manager.submitTask(
|
||||
_tasks.NeutronSecurityGroupRuleCreate(
|
||||
body={'security_group_rule': rule_def})
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.debug("neutron failed to create security group rule",
|
||||
exc_info=True)
|
||||
raise OpenStackCloudException(
|
||||
"failed to create security group rule: {msg}".format(
|
||||
msg=str(e)))
|
||||
return rule['security_group_rule']
|
||||
|
||||
elif self.secgroup_source == 'nova':
|
||||
# NOTE: Neutron accepts None for ports, but Nova accepts -1
|
||||
# as the equivalent value.
|
||||
if port_range_min is None:
|
||||
port_range_min = -1
|
||||
if port_range_max is None:
|
||||
port_range_max = -1
|
||||
try:
|
||||
rule = meta.obj_to_dict(
|
||||
self.manager.submitTask(
|
||||
_tasks.NovaSecurityGroupRuleCreate(
|
||||
parent_group_id=secgroup['id'],
|
||||
ip_protocol=protocol,
|
||||
from_port=port_range_min,
|
||||
to_port=port_range_max,
|
||||
cidr=remote_ip_prefix,
|
||||
group_id=remote_group_id
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.debug("nova failed to create security group rule",
|
||||
exc_info=True)
|
||||
raise OpenStackCloudException(
|
||||
"failed to create security group rule: {msg}".format(
|
||||
msg=str(e)))
|
||||
return _utils.normalize_nova_secgroup_rules([rule])[0]
|
||||
|
||||
# Security groups not supported
|
||||
else:
|
||||
raise OpenStackCloudUnavailableFeature(
|
||||
"Unavailable feature: security groups"
|
||||
)
|
||||
|
||||
|
||||
class OperatorCloud(OpenStackCloud):
|
||||
"""Represent a privileged/operator connection to an OpenStack Cloud.
|
||||
|
@ -212,6 +212,11 @@ class NeutronSecurityGroupUpdate(task_manager.Task):
|
||||
return client.neutron_client.update_security_group(**self.args)
|
||||
|
||||
|
||||
class NeutronSecurityGroupRuleCreate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.neutron_client.create_security_group_rule(**self.args)
|
||||
|
||||
|
||||
class NovaSecurityGroupList(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.nova_client.security_groups.list()
|
||||
@ -232,6 +237,11 @@ class NovaSecurityGroupUpdate(task_manager.Task):
|
||||
return client.nova_client.security_groups.update(**self.args)
|
||||
|
||||
|
||||
class NovaSecurityGroupRuleCreate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.nova_client.security_group_rules.create(**self.args)
|
||||
|
||||
|
||||
# TODO: Do this with neutron instead of nova if possible
|
||||
class FloatingIPList(task_manager.Task):
|
||||
def main(self, client):
|
||||
|
@ -121,9 +121,6 @@ def normalize_nova_secgroups(groups):
|
||||
security group dicts as returned from neutron. This does not make them
|
||||
look exactly the same, but it's pretty close.
|
||||
|
||||
Note that nova uses -1 for non-specific port values, but neutron
|
||||
represents these with None.
|
||||
|
||||
:param list groups: A list of security group dicts.
|
||||
|
||||
:returns: A list of normalized dicts.
|
||||
@ -131,16 +128,28 @@ def normalize_nova_secgroups(groups):
|
||||
return [{'id': g['id'],
|
||||
'name': g['name'],
|
||||
'description': g['description'],
|
||||
'security_group_rules': [{
|
||||
'id': r['id'],
|
||||
'direction': 'ingress',
|
||||
'ethertype': 'IPv4',
|
||||
'port_range_min':
|
||||
None if r['from_port'] == -1 else r['from_port'],
|
||||
'port_range_max':
|
||||
None if r['to_port'] == -1 else r['to_port'],
|
||||
'protocol': r['ip_protocol'],
|
||||
'remote_ip_prefix': r['ip_range'].get('cidr', None),
|
||||
'security_group_id': r['parent_group_id'],
|
||||
} for r in g['rules']]
|
||||
'security_group_rules': normalize_nova_secgroup_rules(g['rules'])
|
||||
} for g in groups]
|
||||
|
||||
|
||||
def normalize_nova_secgroup_rules(rules):
|
||||
"""Normalize the structure of nova security group rules
|
||||
|
||||
Note that nova uses -1 for non-specific port values, but neutron
|
||||
represents these with None.
|
||||
|
||||
:param list rules: A list of security group rule dicts.
|
||||
|
||||
:returns: A list of normalized dicts.
|
||||
"""
|
||||
return [{'id': r['id'],
|
||||
'direction': 'ingress',
|
||||
'ethertype': 'IPv4',
|
||||
'port_range_min':
|
||||
None if r['from_port'] == -1 else r['from_port'],
|
||||
'port_range_max':
|
||||
None if r['to_port'] == -1 else r['to_port'],
|
||||
'protocol': r['ip_protocol'],
|
||||
'remote_ip_prefix': r['ip_range'].get('cidr', None),
|
||||
'security_group_id': r['parent_group_id']
|
||||
} for r in rules]
|
||||
|
@ -97,3 +97,15 @@ class FakeSecgroup(object):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.rules = rules
|
||||
|
||||
|
||||
class FakeNovaSecgroupRule(object):
|
||||
def __init__(self, id, from_port=None, to_port=None, ip_protocol=None,
|
||||
cidr=None, parent_group_id=None):
|
||||
self.id = id
|
||||
self.from_port = from_port
|
||||
self.to_port = to_port
|
||||
self.ip_protocol = ip_protocol
|
||||
if cidr:
|
||||
self.ip_range = {'cidr': cidr}
|
||||
self.parent_group_id = parent_group_id
|
||||
|
@ -99,3 +99,16 @@ class TestUtils(base.TestCase):
|
||||
retval = _utils.normalize_nova_secgroups([nova_secgroup])[0]
|
||||
self.assertIsNone(retval['security_group_rules'][0]['port_range_min'])
|
||||
self.assertIsNone(retval['security_group_rules'][0]['port_range_max'])
|
||||
|
||||
def test_normalize_nova_secgroup_rules(self):
|
||||
nova_rules = [
|
||||
dict(id='123', from_port=80, to_port=81, ip_protocol='tcp',
|
||||
ip_range={'cidr': '0.0.0.0/0'}, parent_group_id='xyz123')
|
||||
]
|
||||
expected = [
|
||||
dict(id='123', direction='ingress', ethertype='IPv4',
|
||||
port_range_min=80, port_range_max=81, protocol='tcp',
|
||||
remote_ip_prefix='0.0.0.0/0', security_group_id='xyz123')
|
||||
]
|
||||
retval = _utils.normalize_nova_secgroup_rules(nova_rules)
|
||||
self.assertEqual(expected, retval)
|
||||
|
@ -200,3 +200,58 @@ class TestSecurityGroups(base.TestCase):
|
||||
'doesNotExist', bad_arg='')
|
||||
self.assertFalse(mock_neutron.create_security_group.called)
|
||||
self.assertFalse(mock_nova.security_groups.create.called)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'get_security_group')
|
||||
@mock.patch.object(shade.OpenStackCloud, 'neutron_client')
|
||||
def test_create_security_group_rule_neutron(self, mock_neutron, mock_get):
|
||||
self.cloud.secgroup_source = 'neutron'
|
||||
args = dict(
|
||||
port_range_min=-1,
|
||||
port_range_max=40000,
|
||||
protocol='tcp',
|
||||
remote_ip_prefix='0.0.0.0/0',
|
||||
remote_group_id='456',
|
||||
direction='egress',
|
||||
ethertype='IPv6'
|
||||
)
|
||||
mock_get.return_value = {'id': 'abc'}
|
||||
self.cloud.create_security_group_rule(secgroup_name_or_id='abc',
|
||||
**args)
|
||||
|
||||
# For neutron, -1 port should be converted to None
|
||||
args['port_range_min'] = None
|
||||
args['security_group_id'] = 'abc'
|
||||
|
||||
mock_neutron.create_security_group_rule.assert_called_once_with(
|
||||
body={'security_group_rule': args}
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'get_security_group')
|
||||
@mock.patch.object(shade.OpenStackCloud, 'nova_client')
|
||||
def test_create_security_group_rule_nova(self, mock_nova, mock_get):
|
||||
self.cloud.secgroup_source = 'nova'
|
||||
|
||||
new_rule = fakes.FakeNovaSecgroupRule(
|
||||
id='xyz', from_port=-1, to_port=2000, ip_protocol='tcp',
|
||||
cidr='1.2.3.4/32')
|
||||
mock_nova.security_group_rules.create.return_value = new_rule
|
||||
mock_get.return_value = {'id': 'abc'}
|
||||
|
||||
self.cloud.create_security_group_rule(
|
||||
'abc', port_range_max=2000, protocol='tcp',
|
||||
remote_ip_prefix='1.2.3.4/32', remote_group_id='123')
|
||||
|
||||
mock_nova.security_group_rules.create.assert_called_once_with(
|
||||
parent_group_id='abc', ip_protocol='tcp', from_port=-1,
|
||||
to_port=2000, cidr='1.2.3.4/32', group_id='123'
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'neutron_client')
|
||||
@mock.patch.object(shade.OpenStackCloud, 'nova_client')
|
||||
def test_create_security_group_rule_none(self, mock_nova, mock_neutron):
|
||||
self.cloud.secgroup_source = None
|
||||
self.assertRaises(shade.OpenStackCloudUnavailableFeature,
|
||||
self.cloud.create_security_group_rule,
|
||||
'')
|
||||
self.assertFalse(mock_neutron.create_security_group.called)
|
||||
self.assertFalse(mock_nova.security_groups.create.called)
|
||||
|
Loading…
Reference in New Issue
Block a user