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:
David Shrewsbury 2015-06-11 15:08:42 -04:00
parent 2f499ab98b
commit 965998c764
6 changed files with 234 additions and 15 deletions

View File

@ -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.

View File

@ -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):

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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)