diff --git a/shade/_normalize.py b/shade/_normalize.py index 6faf853b3..78a228c6a 100644 --- a/shade/_normalize.py +++ b/shade/_normalize.py @@ -97,6 +97,78 @@ class Normalizer(object): new_image['is_public'] = is_public return new_image + def _normalize_secgroups(self, groups): + """Normalize the structure of security groups + + This makes security group dicts, as returned from nova, look like the + security group dicts as returned from neutron. This does not make them + look exactly the same, but it's pretty close. + + :param list groups: A list of security group dicts. + + :returns: A list of normalized dicts. + """ + ret = [] + for group in groups: + ret.append(self._normalize_secgroup(group)) + return ret + + def _normalize_secgroup(self, group): + + rules = group.pop('security_group_rules', None) + if not rules and 'rules' in group: + rules = group.pop('rules') + group['security_group_rules'] = self._normalize_secgroup_rules(rules) + # neutron sets these. we don't care about it, but let's be the same + project_id = group.get('project_id', group.get('tenant_id', '')) + group['tenant_id'] = project_id + group['project_id'] = project_id + group['location'] = self.current_location + return munch.Munch(group) + + def _normalize_secgroup_rules(self, 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. + """ + ret = [] + for rule in rules: + ret.append(self._normalize_secgroup_rule(rule)) + return ret + + def _normalize_secgroup_rule(self, rule): + ret = munch.Munch() + ret['id'] = rule['id'] + ret['location'] = self.current_location + ret['direction'] = rule.get('direction', 'ingress') + ret['ethertype'] = rule.get('ethertype', 'IPv4') + port_range_min = rule.get( + 'port_range_min', rule.get('from_port', None)) + if port_range_min == -1: + port_range_min = None + ret['port_range_min'] = port_range_min + port_range_max = rule.get( + 'port_range_max', rule.get('to_port', None)) + if port_range_max == -1: + port_range_max = None + ret['port_range_max'] = port_range_max + ret['protocol'] = rule.get('protocol', rule.get('ip_protocol')) + ret['remote_ip_prefix'] = rule.get( + 'remote_ip_prefix', rule.get('ip_range', {}).get('cidr', None)) + ret['security_group_id'] = rule.get( + 'security_group_id', rule.get('parent_group_id')) + ret['remote_group_id'] = rule.get('remote_group_id') + project_id = rule.get('project_id', rule.get('tenant_id', '')) + ret['tenant_id'] = project_id + ret['project_id'] = project_id + ret['remote_group_id'] = rule.get('remote_group_id') + return ret + def _normalize_servers(self, servers): # Here instead of _utils because we need access to region and cloud # name from the cloud object diff --git a/shade/_utils.py b/shade/_utils.py index f4b4e6445..017378202 100644 --- a/shade/_utils.py +++ b/shade/_utils.py @@ -182,49 +182,6 @@ def normalize_keystone_services(services): return meta.obj_list_to_dict(ret) -def normalize_nova_secgroups(groups): - """Normalize the structure of nova security groups - - This makes security group dicts, as returned from nova, look like the - security group dicts as returned from neutron. This does not make them - look exactly the same, but it's pretty close. - - :param list groups: A list of security group dicts. - - :returns: A list of normalized dicts. - """ - ret = [{'id': g['id'], - 'name': g['name'], - 'description': g['description'], - 'security_group_rules': normalize_nova_secgroup_rules(g['rules']) - } for g in groups] - return meta.obj_list_to_dict(ret) - - -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. - """ - ret = [{'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] - return meta.obj_list_to_dict(ret) - - def normalize_nova_floating_ips(ips): """Normalize the structure of Neutron floating IPs diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 71077f08d..b06473e5c 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -1510,7 +1510,7 @@ class OpenStackCloud(_normalize.Normalizer): groups = self.manager.submit_task( _tasks.ServerListSecurityGroups(server=server['id'])) - return _utils.normalize_nova_secgroups(groups) + return self._normalize_secgroups(groups) def list_security_groups(self): """List all available security groups. @@ -1524,12 +1524,13 @@ class OpenStackCloud(_normalize.Normalizer): "Unavailable feature: security groups" ) + groups = [] # Handle neutron security groups if self._use_neutron_secgroups(): # Neutron returns dicts, so no need to convert objects here. with _utils.neutron_exceptions( "Error fetching security group list"): - return self.manager.submit_task( + groups = self.manager.submit_task( _tasks.NeutronSecurityGroupList())['security_groups'] # Handle nova security groups @@ -1537,7 +1538,7 @@ class OpenStackCloud(_normalize.Normalizer): with _utils.shade_exceptions("Error fetching security group list"): groups = self.manager.submit_task( _tasks.NovaSecurityGroupList()) - return _utils.normalize_nova_secgroups(groups) + return self._normalize_secgroups(groups) def list_servers(self, detailed=False): """List all available servers. @@ -5703,6 +5704,7 @@ class OpenStackCloud(_normalize.Normalizer): "Unavailable feature: security groups" ) + group = None if self._use_neutron_secgroups(): with _utils.neutron_exceptions( "Error creating security group {0}".format(name)): @@ -5710,9 +5712,7 @@ class OpenStackCloud(_normalize.Normalizer): _tasks.NeutronSecurityGroupCreate( body=dict(security_group=dict(name=name, description=description)) - ) - ) - return group['security_group'] + ))['security_group'] else: with _utils.shade_exceptions( @@ -5723,7 +5723,7 @@ class OpenStackCloud(_normalize.Normalizer): name=name, description=description ) ) - return _utils.normalize_nova_secgroups([group])[0] + return self._normalize_secgroup(group) def delete_security_group(self, name_or_id): """Delete a security group @@ -5785,9 +5785,9 @@ class OpenStackCloud(_normalize.Normalizer): "Unavailable feature: security groups" ) - secgroup = self.get_security_group(name_or_id) + group = self.get_security_group(name_or_id) - if secgroup is None: + if group is None: raise OpenStackCloudException( "Security group %s not found." % name_or_id) @@ -5796,10 +5796,9 @@ class OpenStackCloud(_normalize.Normalizer): "Error updating security group {0}".format(name_or_id)): group = self.manager.submit_task( _tasks.NeutronSecurityGroupUpdate( - security_group=secgroup['id'], + security_group=group['id'], body={'security_group': kwargs}) - ) - return group['security_group'] + )['security_group'] else: with _utils.shade_exceptions( @@ -5807,9 +5806,9 @@ class OpenStackCloud(_normalize.Normalizer): group=name_or_id)): group = self.manager.submit_task( _tasks.NovaSecurityGroupUpdate( - group=secgroup['id'], **kwargs) + group=group['id'], **kwargs) ) - return _utils.normalize_nova_secgroups([group])[0] + return self._normalize_secgroup(group) def create_security_group_rule(self, secgroup_name_or_id, @@ -5895,7 +5894,7 @@ class OpenStackCloud(_normalize.Normalizer): _tasks.NeutronSecurityGroupRuleCreate( body={'security_group_rule': rule_def}) ) - return rule['security_group_rule'] + return self._normalize_secgroup_rule(rule['security_group_rule']) else: # NOTE: Neutron accepts None for protocol. Nova does not. @@ -5938,7 +5937,7 @@ class OpenStackCloud(_normalize.Normalizer): group_id=remote_group_id ) ) - return _utils.normalize_nova_secgroup_rules([rule])[0] + return self._normalize_secgroup_rule(rule) def delete_security_group_rule(self, rule_id): """Delete a security group rule diff --git a/shade/tests/unit/test__utils.py b/shade/tests/unit/test__utils.py index 3ef87fcb4..02bfffd0c 100644 --- a/shade/tests/unit/test__utils.py +++ b/shade/tests/unit/test__utils.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import mock import testtools from shade import _utils @@ -79,7 +80,7 @@ class TestUtils(base.TestCase): }}) self.assertEqual([el2, el3], ret) - def test_normalize_nova_secgroups(self): + def test_normalize_secgroups(self): nova_secgroup = dict( id='abc123', name='nova_secgroup', @@ -94,17 +95,38 @@ class TestUtils(base.TestCase): id='abc123', name='nova_secgroup', description='A Nova security group', + tenant_id='', + project_id='', + location=dict( + region_name='RegionOne', + project=dict( + domain_name=None, + id=mock.ANY, + domain_id=None, + name='admin'), + cloud='_test_cloud_'), security_group_rules=[ 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') + remote_ip_prefix='0.0.0.0/0', security_group_id='xyz123', + tenant_id='', + project_id='', + remote_group_id=None, + location=dict( + region_name='RegionOne', + project=dict( + domain_name=None, + id=mock.ANY, + domain_id=None, + name='admin'), + cloud='_test_cloud_')) ] ) - retval = _utils.normalize_nova_secgroups([nova_secgroup])[0] + retval = self.cloud._normalize_secgroup(nova_secgroup) self.assertEqual(expected, retval) - def test_normalize_nova_secgroups_negone_port(self): + def test_normalize_secgroups_negone_port(self): nova_secgroup = dict( id='abc123', name='nova_secgroup', @@ -115,11 +137,11 @@ class TestUtils(base.TestCase): ] ) - retval = _utils.normalize_nova_secgroups([nova_secgroup])[0] + retval = self.cloud._normalize_secgroup(nova_secgroup) 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): + def test_normalize_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') @@ -127,9 +149,18 @@ class TestUtils(base.TestCase): 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') + remote_ip_prefix='0.0.0.0/0', security_group_id='xyz123', + tenant_id='', project_id='', remote_group_id=None, + location=dict( + region_name='RegionOne', + project=dict( + domain_name=None, + id=mock.ANY, + domain_id=None, + name='admin'), + cloud='_test_cloud_')) ] - retval = _utils.normalize_nova_secgroup_rules(nova_rules) + retval = self.cloud._normalize_secgroup_rules(nova_rules) self.assertEqual(expected, retval) def test_normalize_volumes_v1(self): diff --git a/shade/tests/unit/test_security_groups.py b/shade/tests/unit/test_security_groups.py index 9bfc1946c..f36882df5 100644 --- a/shade/tests/unit/test_security_groups.py +++ b/shade/tests/unit/test_security_groups.py @@ -352,9 +352,9 @@ class TestSecurityGroups(base.TestCase): secgroup_name_or_id='nova-sec-group', direction='egress') - @mock.patch.object(shade._utils, 'normalize_nova_secgroups') + @mock.patch.object(shade.OpenStackCloud, '_normalize_secgroups') @mock.patch.object(shade.OpenStackCloud, 'nova_client') - def test_list_server_security_groups(self, mock_nova, mock_norm): + def test_list_server_security_groups_nova(self, mock_nova, mock_norm): self.has_neutron = False server = dict(id='server_id') self.cloud.list_server_security_groups(server)