Merge lp:~tpatil/nova/os-security-groups into lp:~hudson-openstack/nova/trunk

Proposed by Tushar Patil
Status: Merged
Approved by: Jesse Andrews
Approved revision: 1369
Merged at revision: 1417
Proposed branch: lp:~tpatil/nova/os-security-groups
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1337 lines (+1249/-5)
6 files modified
nova/api/openstack/contrib/security_groups.py (+466/-0)
nova/api/openstack/extensions.py (+9/-2)
nova/db/api.py (+5/-0)
nova/exception.py (+4/-0)
nova/tests/api/openstack/contrib/test_security_groups.py (+761/-0)
nova/tests/api/openstack/test_extensions.py (+4/-3)
To merge this branch: bzr merge lp:~tpatil/nova/os-security-groups
Reviewer Review Type Date Requested Status
Jesse Andrews (community) Approve
Tushar Patil (community) Needs Fixing
Ed Leafe (community) Approve
Review via email: mp+70375@code.launchpad.net

Description of the change

Support for management of security groups in OS API as a new extension.

To post a comment you must log in.
Revision history for this message
Ed Leafe (ed-leafe) wrote :

In '_format_security_group_rule()' and '_format_security_group()', you should avoid the use of single-character variable names. I can sort of guess what they represent, but it's better to use descriptive names.

There are several 'standards' for the indentation of continued lines, but right-alignment is not one of them. It does nothing for either consistency of indentation or readability. The preferred method is to indent two level (i.e., 8 spaces) on a continued line; you can also indent more than that if you wish to align the text with something on the line above.

One specific case is line 322: it's a continuation line, but is only indented one level, making it the same indentation level as the block that follows. While the Python interpreter can make sense of this, it is visually inaccurate to humans reading the code.

Lines 642-6 actually outdent continued lines. That should never happen.

I'm concerned about the security of some of the methods. You check for context.is_admin in the index() method to limit the returned values, but it looks like destructive methods like 'delete()' can be invoked on any ID whether one is an admin or not, with no restriction on project. Can you explain where the ability to interact with a security group you do not have rights to is enforced?

The two methods '_validate_security_group_name()' and '_validate_security_group_description()' use the same logic and is essentially duplicated code. That logic should be refactored out into a single method that checks the value that is called by those methods. It could also be greatly simplified:

def validate_name(self, value, typ):
    # NOTE: 'typ' will be either 'name' or 'description', depending on the caller.
    try:
        val = value.strip()
    except AttributeError:
        msg = _("Security group %s is not a string or unicode") % typ
        raise exc.HTTPBadRequest(explanation=msg)
    if not val:
        msg = _("Security group %s cannot be empty.") % typ
        raise exc.HTTPBadRequest(explanation=msg)
    if len(val) > 255:
        msg = _("Security group %s should not be greater than 255 characters.") % typ
        raise exc.HTTPBadRequest(explanation=msg)

The localization is incorrect in lines 227-8. Since localized strings are extracted from the script file, not during runtime, the text to be localized will never be included. The lines should be re-written as:
msg = _("Authorize security group ingress %s")
LOG.audit(msg, security_group['name'], context=context)

review: Needs Fixing
Revision history for this message
Tushar Patil (tpatil) wrote :

Along with the review comments, I am also going to add code in the Create Server API to take security group as an additional parameter to launch the server with the specified security group.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

it should be 1+ security groups. You can also do that in another patch. No need to make the first one more complicated.

On Aug 5, 2011, at 10:22 AM, Tushar Patil wrote:

> Along with the review comments, I am also going to add code in the Create Server API to take security group as an additional parameter to launch the server with the specified security group.
> --
> https://code.launchpad.net/~tpatil/nova/os-security-groups/+merge/70375
> You are subscribed to branch lp:nova.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Bonus points in the other patch for add_security_group to instance and delete_security_group_from_instance

Revision history for this message
Tushar Patil (tpatil) wrote :

> it should be 1+ security groups. You can also do that in another patch. No
> need to make the first one more complicated.
>
> On Aug 5, 2011, at 10:22 AM, Tushar Patil wrote:
>
> > Along with the review comments, I am also going to add code in the Create
> Server API to take security group as an additional parameter to launch the
> server with the specified security group.
Ok, I will add this code in another patch.

Revision history for this message
Tushar Patil (tpatil) wrote :

> I'm concerned about the security of some of the methods. You check for
> context.is_admin in the index() method to limit the returned values, but it
> looks like destructive methods like 'delete()' can be invoked on any ID
> whether one is an admin or not, with no restriction on project. Can you
> explain where the ability to interact with a security group you do not have
> rights to is enforced?
>
Before calling security_group_destroy, security_group_get call is made to check if the security group actually belongs to the concern project. If not, then it will throw SecurityGroupNotFound exception.
For admin users, project id check is not required.
You can check the implementation of "security_group_get" method in nova/db/sqlalchemy/api.py.

I have fixed all your review comments.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

The changes look good; thanks for pointing me to the filtering in sqlalchemy/api.py - that's what I was looking for.

One minor problem: lines 210 and 337 still have the localization function call around 'msg'; it's not needed, and should be removed. Only literal strings ever need to be localized.

review: Needs Fixing
Revision history for this message
Tushar Patil (tpatil) wrote :

> The changes look good; thanks for pointing me to the filtering in
> sqlalchemy/api.py - that's what I was looking for.
>
> One minor problem: lines 210 and 337 still have the localization function call
> around 'msg'; it's not needed, and should be removed. Only literal strings
> ever need to be localized.
I should have noticed this before. Anyway I have fixed it now.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

Looks great now.

review: Approve
Revision history for this message
Tushar Patil (tpatil) wrote :

> Bonus points in the other patch for add_security_group to instance and
> delete_security_group_from_instance
Seems like a good idea. Also I think it will be good to add one more method to list instances using a specified security group.

Revision history for this message
Anthony Young (sleepsonthefloor) wrote :

I see that this convention from the ec2 code made it to this branch.

    if context.is_admin:
         groups = db.security_group_get_all(context)

My preference would be to not list everything for admin users by default, as that makes it hard to use admin accounts as users. I'd rather use a separate parameter to specify that you in fact want a complete list, or have a separate admin api call. That said, I think that servers api behaves like this right now (I don't like that either :)

Revision history for this message
Tushar Patil (tpatil) wrote :

> I see that this convention from the ec2 code made it to this branch.
>
> if context.is_admin:
> groups = db.security_group_get_all(context)
>
> My preference would be to not list everything for admin users by default, as
> that makes it hard to use admin accounts as users. I'd rather use a separate
> parameter to specify that you in fact want a complete list, or have a separate
> admin api call. That said, I think that servers api behaves like this right
> now (I don't like that either :)

You are right, I have copied this code from Openstack EC2 API and I also agree with you that there should be separate admin API to fetch all security groups.

For now, I will simply remove this code and will add admin API to fetch all security groups sometime later after diablo timeframe.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

> For now, I will simply remove this code and will add admin API to fetch all security groups sometime later after diablo timeframe.

This is looking good. I will fire this off once you have made this change.

Revision history for this message
Tushar Patil (tpatil) wrote :

> Bonus points in the other patch for add_security_group to instance and
> delete_security_group_from_instance
I have already implemented both these methods but until this branch doesn't get merged into trunk I cannot create another branch because of dependency.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

> I have already implemented both these methods but until this branch doesn't
> get merged into trunk I cannot create another branch because of dependency.

Sure, just add the is_admin change and we'll get this one in. Then you can propose your other branches.

Revision history for this message
Tushar Patil (tpatil) wrote :

os-keypairs branch is merged into trunk and this is implemented as an extension, so this has impact on my branch as it will break test_extensions unit testcases. I will again merge trunk changes into my branch and make sure all unit testcases are executed properly.

review: Needs Fixing
Revision history for this message
Tushar Patil (tpatil) wrote :

I have fixed broken unit test-cases and is_admin change.
Please review my branch.

Revision history for this message
Jesse Andrews (anotherjesse) wrote :

Added support to python-novaclient here:

  https://github.com/rackspace/python-novaclient/pull/71

LGTM

review: Approve
Revision history for this message
Jesse Andrews (anotherjesse) wrote :

perhaps the ResourceExtension should be:

  os-security-groups
  os-security-group-rules

the keypairs, quotas, floating ips, ... have had dashes instead of underscores and prefixed with os-

Revision history for this message
Tushar Patil (tpatil) wrote :

> perhaps the ResourceExtension should be:
>
> os-security-groups
> os-security-group-rules
>
> the keypairs, quotas, floating ips, ... have had dashes instead of underscores
> and prefixed with os-

OK, I will prefix os- to the newly added extensions and make changes at the relevant places.

Revision history for this message
Tushar Patil (tpatil) wrote :

Prefixed with os- for the newly added extensions. Please review.

Revision history for this message
Jesse Andrews (anotherjesse) wrote :

lgtm - with ed's prior approve I think we have a merge! checking with vish

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'nova/api/openstack/contrib/security_groups.py'
--- nova/api/openstack/contrib/security_groups.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/security_groups.py 2011-08-12 00:06:25 +0000
@@ -0,0 +1,466 @@
1# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""The security groups extension."""
17
18import netaddr
19import urllib
20from webob import exc
21import webob
22
23from nova import compute
24from nova import db
25from nova import exception
26from nova import flags
27from nova import log as logging
28from nova.api.openstack import common
29from nova.api.openstack import extensions
30from nova.api.openstack import wsgi
31
32
33from xml.dom import minidom
34
35
36LOG = logging.getLogger("nova.api.contrib.security_groups")
37FLAGS = flags.FLAGS
38
39
40class SecurityGroupController(object):
41 """The Security group API controller for the OpenStack API."""
42
43 def __init__(self):
44 self.compute_api = compute.API()
45 super(SecurityGroupController, self).__init__()
46
47 def _format_security_group_rule(self, context, rule):
48 sg_rule = {}
49 sg_rule['id'] = rule.id
50 sg_rule['parent_group_id'] = rule.parent_group_id
51 sg_rule['ip_protocol'] = rule.protocol
52 sg_rule['from_port'] = rule.from_port
53 sg_rule['to_port'] = rule.to_port
54 sg_rule['group'] = {}
55 sg_rule['ip_range'] = {}
56 if rule.group_id:
57 source_group = db.security_group_get(context, rule.group_id)
58 sg_rule['group'] = {'name': source_group.name,
59 'tenant_id': source_group.project_id}
60 else:
61 sg_rule['ip_range'] = {'cidr': rule.cidr}
62 return sg_rule
63
64 def _format_security_group(self, context, group):
65 security_group = {}
66 security_group['id'] = group.id
67 security_group['description'] = group.description
68 security_group['name'] = group.name
69 security_group['tenant_id'] = group.project_id
70 security_group['rules'] = []
71 for rule in group.rules:
72 security_group['rules'] += [self._format_security_group_rule(
73 context, rule)]
74 return security_group
75
76 def show(self, req, id):
77 """Return data about the given security group."""
78 context = req.environ['nova.context']
79 try:
80 id = int(id)
81 security_group = db.security_group_get(context, id)
82 except ValueError:
83 msg = _("Security group id is not integer")
84 return exc.HTTPBadRequest(explanation=msg)
85 except exception.NotFound as exp:
86 return exc.HTTPNotFound(explanation=unicode(exp))
87
88 return {'security_group': self._format_security_group(context,
89 security_group)}
90
91 def delete(self, req, id):
92 """Delete a security group."""
93 context = req.environ['nova.context']
94 try:
95 id = int(id)
96 security_group = db.security_group_get(context, id)
97 except ValueError:
98 msg = _("Security group id is not integer")
99 return exc.HTTPBadRequest(explanation=msg)
100 except exception.SecurityGroupNotFound as exp:
101 return exc.HTTPNotFound(explanation=unicode(exp))
102
103 LOG.audit(_("Delete security group %s"), id, context=context)
104 db.security_group_destroy(context, security_group.id)
105
106 return exc.HTTPAccepted()
107
108 def index(self, req):
109 """Returns a list of security groups"""
110 context = req.environ['nova.context']
111
112 self.compute_api.ensure_default_security_group(context)
113 groups = db.security_group_get_by_project(context,
114 context.project_id)
115 limited_list = common.limited(groups, req)
116 result = [self._format_security_group(context, group)
117 for group in limited_list]
118
119 return {'security_groups':
120 list(sorted(result,
121 key=lambda k: (k['tenant_id'], k['name'])))}
122
123 def create(self, req, body):
124 """Creates a new security group."""
125 context = req.environ['nova.context']
126 if not body:
127 return exc.HTTPUnprocessableEntity()
128
129 security_group = body.get('security_group', None)
130
131 if security_group is None:
132 return exc.HTTPUnprocessableEntity()
133
134 group_name = security_group.get('name', None)
135 group_description = security_group.get('description', None)
136
137 self._validate_security_group_property(group_name, "name")
138 self._validate_security_group_property(group_description,
139 "description")
140 group_name = group_name.strip()
141 group_description = group_description.strip()
142
143 LOG.audit(_("Create Security Group %s"), group_name, context=context)
144 self.compute_api.ensure_default_security_group(context)
145 if db.security_group_exists(context, context.project_id, group_name):
146 msg = _('Security group %s already exists') % group_name
147 raise exc.HTTPBadRequest(explanation=msg)
148
149 group = {'user_id': context.user_id,
150 'project_id': context.project_id,
151 'name': group_name,
152 'description': group_description}
153 group_ref = db.security_group_create(context, group)
154
155 return {'security_group': self._format_security_group(context,
156 group_ref)}
157
158 def _validate_security_group_property(self, value, typ):
159 """ typ will be either 'name' or 'description',
160 depending on the caller
161 """
162 try:
163 val = value.strip()
164 except AttributeError:
165 msg = _("Security group %s is not a string or unicode") % typ
166 raise exc.HTTPBadRequest(explanation=msg)
167 if not val:
168 msg = _("Security group %s cannot be empty.") % typ
169 raise exc.HTTPBadRequest(explanation=msg)
170 if len(val) > 255:
171 msg = _("Security group %s should not be greater "
172 "than 255 characters.") % typ
173 raise exc.HTTPBadRequest(explanation=msg)
174
175
176class SecurityGroupRulesController(SecurityGroupController):
177
178 def create(self, req, body):
179 context = req.environ['nova.context']
180
181 if not body:
182 raise exc.HTTPUnprocessableEntity()
183
184 if not 'security_group_rule' in body:
185 raise exc.HTTPUnprocessableEntity()
186
187 self.compute_api.ensure_default_security_group(context)
188
189 sg_rule = body['security_group_rule']
190 parent_group_id = sg_rule.get('parent_group_id', None)
191 try:
192 parent_group_id = int(parent_group_id)
193 security_group = db.security_group_get(context, parent_group_id)
194 except ValueError:
195 msg = _("Parent group id is not integer")
196 return exc.HTTPBadRequest(explanation=msg)
197 except exception.NotFound as exp:
198 msg = _("Security group (%s) not found") % parent_group_id
199 return exc.HTTPNotFound(explanation=msg)
200
201 msg = _("Authorize security group ingress %s")
202 LOG.audit(msg, security_group['name'], context=context)
203
204 try:
205 values = self._rule_args_to_dict(context,
206 to_port=sg_rule.get('to_port'),
207 from_port=sg_rule.get('from_port'),
208 parent_group_id=sg_rule.get('parent_group_id'),
209 ip_protocol=sg_rule.get('ip_protocol'),
210 cidr=sg_rule.get('cidr'),
211 group_id=sg_rule.get('group_id'))
212 except Exception as exp:
213 raise exc.HTTPBadRequest(explanation=unicode(exp))
214
215 if values is None:
216 msg = _("Not enough parameters to build a "
217 "valid rule.")
218 raise exc.HTTPBadRequest(explanation=msg)
219
220 values['parent_group_id'] = security_group.id
221
222 if self._security_group_rule_exists(security_group, values):
223 msg = _('This rule already exists in group %s') % parent_group_id
224 raise exc.HTTPBadRequest(explanation=msg)
225
226 security_group_rule = db.security_group_rule_create(context, values)
227
228 self.compute_api.trigger_security_group_rules_refresh(context,
229 security_group_id=security_group['id'])
230
231 return {'security_group_rule': self._format_security_group_rule(
232 context,
233 security_group_rule)}
234
235 def _security_group_rule_exists(self, security_group, values):
236 """Indicates whether the specified rule values are already
237 defined in the given security group.
238 """
239 for rule in security_group.rules:
240 if 'group_id' in values:
241 if rule['group_id'] == values['group_id']:
242 return True
243 else:
244 is_duplicate = True
245 for key in ('cidr', 'from_port', 'to_port', 'protocol'):
246 if rule[key] != values[key]:
247 is_duplicate = False
248 break
249 if is_duplicate:
250 return True
251 return False
252
253 def _rule_args_to_dict(self, context, to_port=None, from_port=None,
254 parent_group_id=None, ip_protocol=None,
255 cidr=None, group_id=None):
256 values = {}
257
258 if group_id:
259 try:
260 parent_group_id = int(parent_group_id)
261 group_id = int(group_id)
262 except ValueError:
263 msg = _("Parent or group id is not integer")
264 raise exception.InvalidInput(reason=msg)
265
266 if parent_group_id == group_id:
267 msg = _("Parent group id and group id cannot be same")
268 raise exception.InvalidInput(reason=msg)
269
270 values['group_id'] = group_id
271 #check if groupId exists
272 db.security_group_get(context, group_id)
273 elif cidr:
274 # If this fails, it throws an exception. This is what we want.
275 try:
276 cidr = urllib.unquote(cidr).decode()
277 netaddr.IPNetwork(cidr)
278 except Exception:
279 raise exception.InvalidCidr(cidr=cidr)
280 values['cidr'] = cidr
281 else:
282 values['cidr'] = '0.0.0.0/0'
283
284 if ip_protocol and from_port and to_port:
285
286 try:
287 from_port = int(from_port)
288 to_port = int(to_port)
289 except ValueError:
290 raise exception.InvalidPortRange(from_port=from_port,
291 to_port=to_port)
292 ip_protocol = str(ip_protocol)
293 if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
294 raise exception.InvalidIpProtocol(protocol=ip_protocol)
295 if ((min(from_port, to_port) < -1) or
296 (max(from_port, to_port) > 65535)):
297 raise exception.InvalidPortRange(from_port=from_port,
298 to_port=to_port)
299
300 values['protocol'] = ip_protocol
301 values['from_port'] = from_port
302 values['to_port'] = to_port
303 else:
304 # If cidr based filtering, protocol and ports are mandatory
305 if 'cidr' in values:
306 return None
307
308 return values
309
310 def delete(self, req, id):
311 context = req.environ['nova.context']
312
313 self.compute_api.ensure_default_security_group(context)
314 try:
315 id = int(id)
316 rule = db.security_group_rule_get(context, id)
317 except ValueError:
318 msg = _("Rule id is not integer")
319 return exc.HTTPBadRequest(explanation=msg)
320 except exception.NotFound as exp:
321 msg = _("Rule (%s) not found") % id
322 return exc.HTTPNotFound(explanation=msg)
323
324 group_id = rule.parent_group_id
325 self.compute_api.ensure_default_security_group(context)
326 security_group = db.security_group_get(context, group_id)
327
328 msg = _("Revoke security group ingress %s")
329 LOG.audit(msg, security_group['name'], context=context)
330
331 db.security_group_rule_destroy(context, rule['id'])
332 self.compute_api.trigger_security_group_rules_refresh(context,
333 security_group_id=security_group['id'])
334
335 return exc.HTTPAccepted()
336
337
338class Security_groups(extensions.ExtensionDescriptor):
339 def get_name(self):
340 return "SecurityGroups"
341
342 def get_alias(self):
343 return "security_groups"
344
345 def get_description(self):
346 return "Security group support"
347
348 def get_namespace(self):
349 return "http://docs.openstack.org/ext/securitygroups/api/v1.1"
350
351 def get_updated(self):
352 return "2011-07-21T00:00:00+00:00"
353
354 def get_resources(self):
355 resources = []
356
357 metadata = _get_metadata()
358 body_serializers = {
359 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
360 xmlns=wsgi.XMLNS_V11),
361 }
362 serializer = wsgi.ResponseSerializer(body_serializers, None)
363
364 body_deserializers = {
365 'application/xml': SecurityGroupXMLDeserializer(),
366 }
367 deserializer = wsgi.RequestDeserializer(body_deserializers)
368
369 res = extensions.ResourceExtension('os-security-groups',
370 controller=SecurityGroupController(),
371 deserializer=deserializer,
372 serializer=serializer)
373
374 resources.append(res)
375
376 body_deserializers = {
377 'application/xml': SecurityGroupRulesXMLDeserializer(),
378 }
379 deserializer = wsgi.RequestDeserializer(body_deserializers)
380
381 res = extensions.ResourceExtension('os-security-group-rules',
382 controller=SecurityGroupRulesController(),
383 deserializer=deserializer,
384 serializer=serializer)
385 resources.append(res)
386 return resources
387
388
389class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer):
390 """
391 Deserializer to handle xml-formatted security group requests.
392 """
393 def create(self, string):
394 """Deserialize an xml-formatted security group create request"""
395 dom = minidom.parseString(string)
396 security_group = {}
397 sg_node = self.find_first_child_named(dom,
398 'security_group')
399 if sg_node is not None:
400 if sg_node.hasAttribute('name'):
401 security_group['name'] = sg_node.getAttribute('name')
402 desc_node = self.find_first_child_named(sg_node,
403 "description")
404 if desc_node:
405 security_group['description'] = self.extract_text(desc_node)
406 return {'body': {'security_group': security_group}}
407
408
409class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer):
410 """
411 Deserializer to handle xml-formatted security group requests.
412 """
413
414 def create(self, string):
415 """Deserialize an xml-formatted security group create request"""
416 dom = minidom.parseString(string)
417 security_group_rule = self._extract_security_group_rule(dom)
418 return {'body': {'security_group_rule': security_group_rule}}
419
420 def _extract_security_group_rule(self, node):
421 """Marshal the security group rule attribute of a parsed request"""
422 sg_rule = {}
423 sg_rule_node = self.find_first_child_named(node,
424 'security_group_rule')
425 if sg_rule_node is not None:
426 ip_protocol_node = self.find_first_child_named(sg_rule_node,
427 "ip_protocol")
428 if ip_protocol_node is not None:
429 sg_rule['ip_protocol'] = self.extract_text(ip_protocol_node)
430
431 from_port_node = self.find_first_child_named(sg_rule_node,
432 "from_port")
433 if from_port_node is not None:
434 sg_rule['from_port'] = self.extract_text(from_port_node)
435
436 to_port_node = self.find_first_child_named(sg_rule_node, "to_port")
437 if to_port_node is not None:
438 sg_rule['to_port'] = self.extract_text(to_port_node)
439
440 parent_group_id_node = self.find_first_child_named(sg_rule_node,
441 "parent_group_id")
442 if parent_group_id_node is not None:
443 sg_rule['parent_group_id'] = self.extract_text(
444 parent_group_id_node)
445
446 group_id_node = self.find_first_child_named(sg_rule_node,
447 "group_id")
448 if group_id_node is not None:
449 sg_rule['group_id'] = self.extract_text(group_id_node)
450
451 cidr_node = self.find_first_child_named(sg_rule_node, "cidr")
452 if cidr_node is not None:
453 sg_rule['cidr'] = self.extract_text(cidr_node)
454
455 return sg_rule
456
457
458def _get_metadata():
459 metadata = {
460 "attributes": {
461 "security_group": ["id", "tenant_id", "name"],
462 "rule": ["id", "parent_group_id"],
463 "security_group_rule": ["id", "parent_group_id"],
464 }
465 }
466 return metadata
0467
=== modified file 'nova/api/openstack/extensions.py'
--- nova/api/openstack/extensions.py 2011-08-09 22:46:57 +0000
+++ nova/api/openstack/extensions.py 2011-08-12 00:06:25 +0000
@@ -266,9 +266,13 @@
266 for resource in ext_mgr.get_resources():266 for resource in ext_mgr.get_resources():
267 LOG.debug(_('Extended resource: %s'),267 LOG.debug(_('Extended resource: %s'),
268 resource.collection)268 resource.collection)
269 if resource.serializer is None:
270 resource.serializer = serializer
271
269 mapper.resource(resource.collection, resource.collection,272 mapper.resource(resource.collection, resource.collection,
270 controller=wsgi.Resource(273 controller=wsgi.Resource(
271 resource.controller, serializer=serializer),274 resource.controller, resource.deserializer,
275 resource.serializer),
272 collection=resource.collection_actions,276 collection=resource.collection_actions,
273 member=resource.member_actions,277 member=resource.member_actions,
274 parent_resource=resource.parent)278 parent_resource=resource.parent)
@@ -461,7 +465,8 @@
461 """Add top level resources to the OpenStack API in nova."""465 """Add top level resources to the OpenStack API in nova."""
462466
463 def __init__(self, collection, controller, parent=None,467 def __init__(self, collection, controller, parent=None,
464 collection_actions=None, member_actions=None):468 collection_actions=None, member_actions=None,
469 deserializer=None, serializer=None):
465 if not collection_actions:470 if not collection_actions:
466 collection_actions = {}471 collection_actions = {}
467 if not member_actions:472 if not member_actions:
@@ -471,6 +476,8 @@
471 self.parent = parent476 self.parent = parent
472 self.collection_actions = collection_actions477 self.collection_actions = collection_actions
473 self.member_actions = member_actions478 self.member_actions = member_actions
479 self.deserializer = deserializer
480 self.serializer = serializer
474481
475482
476class ExtensionsXMLSerializer(wsgi.XMLDictSerializer):483class ExtensionsXMLSerializer(wsgi.XMLDictSerializer):
477484
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2011-07-07 12:56:15 +0000
+++ nova/db/api.py 2011-08-12 00:06:25 +0000
@@ -1102,6 +1102,11 @@
1102 return IMPL.security_group_rule_destroy(context, security_group_rule_id)1102 return IMPL.security_group_rule_destroy(context, security_group_rule_id)
11031103
11041104
1105def security_group_rule_get(context, security_group_rule_id):
1106 """Gets a security group rule."""
1107 return IMPL.security_group_rule_get(context, security_group_rule_id)
1108
1109
1105###################1110###################
11061111
11071112
11081113
=== modified file 'nova/exception.py'
--- nova/exception.py 2011-08-09 12:47:47 +0000
+++ nova/exception.py 2011-08-12 00:06:25 +0000
@@ -209,6 +209,10 @@
209 message = _("Invalid content type %(content_type)s.")209 message = _("Invalid content type %(content_type)s.")
210210
211211
212class InvalidCidr(Invalid):
213 message = _("Invalid cidr %(cidr)s.")
214
215
212# Cannot be templated as the error syntax varies.216# Cannot be templated as the error syntax varies.
213# msg needs to be constructed when raised.217# msg needs to be constructed when raised.
214class InvalidParameterValue(Invalid):218class InvalidParameterValue(Invalid):
215219
=== added file 'nova/tests/api/openstack/contrib/test_security_groups.py'
--- nova/tests/api/openstack/contrib/test_security_groups.py 1970-01-01 00:00:00 +0000
+++ nova/tests/api/openstack/contrib/test_security_groups.py 2011-08-12 00:06:25 +0000
@@ -0,0 +1,761 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17import json
18import unittest
19import webob
20from xml.dom import minidom
21
22from nova import test
23from nova.api.openstack.contrib import security_groups
24from nova.tests.api.openstack import fakes
25
26
27def _get_create_request_json(body_dict):
28 req = webob.Request.blank('/v1.1/os-security-groups')
29 req.headers['Content-Type'] = 'application/json'
30 req.method = 'POST'
31 req.body = json.dumps(body_dict)
32 return req
33
34
35def _create_security_group_json(security_group):
36 body_dict = _create_security_group_request_dict(security_group)
37 request = _get_create_request_json(body_dict)
38 response = request.get_response(fakes.wsgi_app())
39 return response
40
41
42def _create_security_group_request_dict(security_group):
43 sg = {}
44 if security_group is not None:
45 name = security_group.get('name', None)
46 description = security_group.get('description', None)
47 if name:
48 sg['name'] = security_group['name']
49 if description:
50 sg['description'] = security_group['description']
51 return {'security_group': sg}
52
53
54class TestSecurityGroups(test.TestCase):
55 def setUp(self):
56 super(TestSecurityGroups, self).setUp()
57
58 def tearDown(self):
59 super(TestSecurityGroups, self).tearDown()
60
61 def _create_security_group_request_dict(self, security_group):
62 sg = {}
63 if security_group is not None:
64 name = security_group.get('name', None)
65 description = security_group.get('description', None)
66 if name:
67 sg['name'] = security_group['name']
68 if description:
69 sg['description'] = security_group['description']
70 return {'security_group': sg}
71
72 def _format_create_xml_request_body(self, body_dict):
73 sg = body_dict['security_group']
74 body_parts = []
75 body_parts.extend([
76 '<?xml version="1.0" encoding="UTF-8"?>',
77 '<security_group xmlns="http://docs.openstack.org/ext/'
78 'securitygroups/api/v1.1"',
79 ' name="%s">' % (sg['name'])])
80 if 'description' in sg:
81 body_parts.append('<description>%s</description>'
82 % sg['description'])
83 body_parts.append('</security_group>')
84 return ''.join(body_parts)
85
86 def _get_create_request_xml(self, body_dict):
87 req = webob.Request.blank('/v1.1/os-security-groups')
88 req.headers['Content-Type'] = 'application/xml'
89 req.content_type = 'application/xml'
90 req.accept = 'application/xml'
91 req.method = 'POST'
92 req.body = self._format_create_xml_request_body(body_dict)
93 return req
94
95 def _create_security_group_xml(self, security_group):
96 body_dict = self._create_security_group_request_dict(security_group)
97 request = self._get_create_request_xml(body_dict)
98 response = request.get_response(fakes.wsgi_app())
99 return response
100
101 def _delete_security_group(self, id):
102 request = webob.Request.blank('/v1.1/os-security-groups/%s'
103 % id)
104 request.method = 'DELETE'
105 response = request.get_response(fakes.wsgi_app())
106 return response
107
108 def test_create_security_group_json(self):
109 security_group = {}
110 security_group['name'] = "test"
111 security_group['description'] = "group-description"
112 response = _create_security_group_json(security_group)
113 res_dict = json.loads(response.body)
114 self.assertEqual(res_dict['security_group']['name'], "test")
115 self.assertEqual(res_dict['security_group']['description'],
116 "group-description")
117 self.assertEquals(response.status_int, 200)
118
119 def test_create_security_group_xml(self):
120 security_group = {}
121 security_group['name'] = "test"
122 security_group['description'] = "group-description"
123 response = \
124 self._create_security_group_xml(security_group)
125
126 self.assertEquals(response.status_int, 200)
127 dom = minidom.parseString(response.body)
128 sg = dom.childNodes[0]
129 self.assertEquals(sg.nodeName, 'security_group')
130 self.assertEqual(security_group['name'], sg.getAttribute('name'))
131
132 def test_create_security_group_with_no_name_json(self):
133 security_group = {}
134 security_group['description'] = "group-description"
135 response = _create_security_group_json(security_group)
136 self.assertEquals(response.status_int, 400)
137
138 def test_create_security_group_with_no_description_json(self):
139 security_group = {}
140 security_group['name'] = "test"
141 response = _create_security_group_json(security_group)
142 self.assertEquals(response.status_int, 400)
143
144 def test_create_security_group_with_blank_name_json(self):
145 security_group = {}
146 security_group['name'] = ""
147 security_group['description'] = "group-description"
148 response = _create_security_group_json(security_group)
149 self.assertEquals(response.status_int, 400)
150
151 def test_create_security_group_with_whitespace_name_json(self):
152 security_group = {}
153 security_group['name'] = " "
154 security_group['description'] = "group-description"
155 response = _create_security_group_json(security_group)
156 self.assertEquals(response.status_int, 400)
157
158 def test_create_security_group_with_blank_description_json(self):
159 security_group = {}
160 security_group['name'] = "test"
161 security_group['description'] = ""
162 response = _create_security_group_json(security_group)
163 self.assertEquals(response.status_int, 400)
164
165 def test_create_security_group_with_whitespace_description_json(self):
166 security_group = {}
167 security_group['name'] = "name"
168 security_group['description'] = " "
169 response = _create_security_group_json(security_group)
170 self.assertEquals(response.status_int, 400)
171
172 def test_create_security_group_with_duplicate_name_json(self):
173 security_group = {}
174 security_group['name'] = "test"
175 security_group['description'] = "group-description"
176 response = _create_security_group_json(security_group)
177
178 self.assertEquals(response.status_int, 200)
179 response = _create_security_group_json(security_group)
180 self.assertEquals(response.status_int, 400)
181
182 def test_create_security_group_with_no_body_json(self):
183 request = _get_create_request_json(body_dict=None)
184 response = request.get_response(fakes.wsgi_app())
185 self.assertEquals(response.status_int, 422)
186
187 def test_create_security_group_with_no_security_group(self):
188 body_dict = {}
189 body_dict['no-securityGroup'] = None
190 request = _get_create_request_json(body_dict)
191 response = request.get_response(fakes.wsgi_app())
192 self.assertEquals(response.status_int, 422)
193
194 def test_create_security_group_above_255_characters_name_json(self):
195 security_group = {}
196 security_group['name'] = ("1234567890123456"
197 "1234567890123456789012345678901234567890"
198 "1234567890123456789012345678901234567890"
199 "1234567890123456789012345678901234567890"
200 "1234567890123456789012345678901234567890"
201 "1234567890123456789012345678901234567890"
202 "1234567890123456789012345678901234567890")
203 security_group['description'] = "group-description"
204 response = _create_security_group_json(security_group)
205
206 self.assertEquals(response.status_int, 400)
207
208 def test_create_security_group_above_255_characters_description_json(self):
209 security_group = {}
210 security_group['name'] = "test"
211 security_group['description'] = ("1234567890123456"
212 "1234567890123456789012345678901234567890"
213 "1234567890123456789012345678901234567890"
214 "1234567890123456789012345678901234567890"
215 "1234567890123456789012345678901234567890"
216 "1234567890123456789012345678901234567890"
217 "1234567890123456789012345678901234567890")
218 response = _create_security_group_json(security_group)
219 self.assertEquals(response.status_int, 400)
220
221 def test_create_security_group_non_string_name_json(self):
222 security_group = {}
223 security_group['name'] = 12
224 security_group['description'] = "group-description"
225 response = _create_security_group_json(security_group)
226 self.assertEquals(response.status_int, 400)
227
228 def test_create_security_group_non_string_description_json(self):
229 security_group = {}
230 security_group['name'] = "test"
231 security_group['description'] = 12
232 response = _create_security_group_json(security_group)
233 self.assertEquals(response.status_int, 400)
234
235 def test_get_security_group_list(self):
236 security_group = {}
237 security_group['name'] = "test"
238 security_group['description'] = "group-description"
239 response = _create_security_group_json(security_group)
240
241 req = webob.Request.blank('/v1.1/os-security-groups')
242 req.headers['Content-Type'] = 'application/json'
243 req.method = 'GET'
244 response = req.get_response(fakes.wsgi_app())
245 res_dict = json.loads(response.body)
246
247 expected = {'security_groups': [
248 {'id': 1,
249 'name':"default",
250 'tenant_id': "fake",
251 "description":"default",
252 "rules": []
253 },
254 ]
255 }
256 expected['security_groups'].append(
257 {
258 'id': 2,
259 'name': "test",
260 'tenant_id': "fake",
261 "description": "group-description",
262 "rules": []
263 }
264 )
265 self.assertEquals(response.status_int, 200)
266 self.assertEquals(res_dict, expected)
267
268 def test_get_security_group_by_id(self):
269 security_group = {}
270 security_group['name'] = "test"
271 security_group['description'] = "group-description"
272 response = _create_security_group_json(security_group)
273
274 res_dict = json.loads(response.body)
275 req = webob.Request.blank('/v1.1/os-security-groups/%s' %
276 res_dict['security_group']['id'])
277 req.headers['Content-Type'] = 'application/json'
278 req.method = 'GET'
279 response = req.get_response(fakes.wsgi_app())
280 res_dict = json.loads(response.body)
281
282 expected = {
283 'security_group': {
284 'id': 2,
285 'name': "test",
286 'tenant_id': "fake",
287 'description': "group-description",
288 'rules': []
289 }
290 }
291 self.assertEquals(response.status_int, 200)
292 self.assertEquals(res_dict, expected)
293
294 def test_get_security_group_by_invalid_id(self):
295 req = webob.Request.blank('/v1.1/os-security-groups/invalid')
296 req.headers['Content-Type'] = 'application/json'
297 req.method = 'GET'
298 response = req.get_response(fakes.wsgi_app())
299 self.assertEquals(response.status_int, 400)
300
301 def test_get_security_group_by_non_existing_id(self):
302 req = webob.Request.blank('/v1.1/os-security-groups/111111111')
303 req.headers['Content-Type'] = 'application/json'
304 req.method = 'GET'
305 response = req.get_response(fakes.wsgi_app())
306 self.assertEquals(response.status_int, 404)
307
308 def test_delete_security_group_by_id(self):
309 security_group = {}
310 security_group['name'] = "test"
311 security_group['description'] = "group-description"
312 response = _create_security_group_json(security_group)
313 security_group = json.loads(response.body)['security_group']
314 response = self._delete_security_group(security_group['id'])
315 self.assertEquals(response.status_int, 202)
316
317 response = self._delete_security_group(security_group['id'])
318 self.assertEquals(response.status_int, 404)
319
320 def test_delete_security_group_by_invalid_id(self):
321 response = self._delete_security_group('invalid')
322 self.assertEquals(response.status_int, 400)
323
324 def test_delete_security_group_by_non_existing_id(self):
325 response = self._delete_security_group(11111111)
326 self.assertEquals(response.status_int, 404)
327
328
329class TestSecurityGroupRules(test.TestCase):
330 def setUp(self):
331 super(TestSecurityGroupRules, self).setUp()
332 security_group = {}
333 security_group['name'] = "authorize-revoke"
334 security_group['description'] = ("Security group created for "
335 " authorize-revoke testing")
336 response = _create_security_group_json(security_group)
337 security_group = json.loads(response.body)
338 self.parent_security_group = security_group['security_group']
339
340 rules = {
341 "security_group_rule": {
342 "ip_protocol": "tcp",
343 "from_port": "22",
344 "to_port": "22",
345 "parent_group_id": self.parent_security_group['id'],
346 "cidr": "10.0.0.0/24"
347 }
348 }
349 res = self._create_security_group_rule_json(rules)
350 self.assertEquals(res.status_int, 200)
351 self.security_group_rule = json.loads(res.body)['security_group_rule']
352
353 def tearDown(self):
354 super(TestSecurityGroupRules, self).tearDown()
355
356 def _create_security_group_rule_json(self, rules):
357 request = webob.Request.blank('/v1.1/os-security-group-rules')
358 request.headers['Content-Type'] = 'application/json'
359 request.method = 'POST'
360 request.body = json.dumps(rules)
361 response = request.get_response(fakes.wsgi_app())
362 return response
363
364 def _delete_security_group_rule(self, id):
365 request = webob.Request.blank('/v1.1/os-security-group-rules/%s'
366 % id)
367 request.method = 'DELETE'
368 response = request.get_response(fakes.wsgi_app())
369 return response
370
371 def test_create_by_cidr_json(self):
372 rules = {
373 "security_group_rule": {
374 "ip_protocol": "tcp",
375 "from_port": "22",
376 "to_port": "22",
377 "parent_group_id": 2,
378 "cidr": "10.2.3.124/24"
379 }
380 }
381
382 response = self._create_security_group_rule_json(rules)
383 security_group_rule = json.loads(response.body)['security_group_rule']
384 self.assertEquals(response.status_int, 200)
385 self.assertNotEquals(security_group_rule['id'], 0)
386 self.assertEquals(security_group_rule['parent_group_id'], 2)
387 self.assertEquals(security_group_rule['ip_range']['cidr'],
388 "10.2.3.124/24")
389
390 def test_create_by_group_id_json(self):
391 rules = {
392 "security_group_rule": {
393 "ip_protocol": "tcp",
394 "from_port": "22",
395 "to_port": "22",
396 "group_id": "1",
397 "parent_group_id": "%s"
398 % self.parent_security_group['id'],
399 }
400 }
401
402 response = self._create_security_group_rule_json(rules)
403 self.assertEquals(response.status_int, 200)
404 security_group_rule = json.loads(response.body)['security_group_rule']
405 self.assertNotEquals(security_group_rule['id'], 0)
406 self.assertEquals(security_group_rule['parent_group_id'], 2)
407
408 def test_create_add_existing_rules_json(self):
409 rules = {
410 "security_group_rule": {
411 "ip_protocol": "tcp",
412 "from_port": "22",
413 "to_port": "22",
414 "cidr": "10.0.0.0/24",
415 "parent_group_id": "%s" % self.parent_security_group['id'],
416 }
417 }
418
419 response = self._create_security_group_rule_json(rules)
420 self.assertEquals(response.status_int, 400)
421
422 def test_create_with_no_body_json(self):
423 request = webob.Request.blank('/v1.1/os-security-group-rules')
424 request.headers['Content-Type'] = 'application/json'
425 request.method = 'POST'
426 request.body = json.dumps(None)
427 response = request.get_response(fakes.wsgi_app())
428 self.assertEquals(response.status_int, 422)
429
430 def test_create_with_no_security_group_rule_in_body_json(self):
431 request = webob.Request.blank('/v1.1/os-security-group-rules')
432 request.headers['Content-Type'] = 'application/json'
433 request.method = 'POST'
434 body_dict = {'test': "test"}
435 request.body = json.dumps(body_dict)
436 response = request.get_response(fakes.wsgi_app())
437 self.assertEquals(response.status_int, 422)
438
439 def test_create_with_invalid_parent_group_id_json(self):
440 rules = {
441 "security_group_rule": {
442 "ip_protocol": "tcp",
443 "from_port": "22",
444 "to_port": "22",
445 "parent_group_id": "invalid"
446 }
447 }
448
449 response = self._create_security_group_rule_json(rules)
450 self.assertEquals(response.status_int, 400)
451
452 def test_create_with_non_existing_parent_group_id_json(self):
453 rules = {
454 "security_group_rule": {
455 "ip_protocol": "tcp",
456 "from_port": "22",
457 "to_port": "22",
458 "group_id": "invalid",
459 "parent_group_id": "1111111111111"
460 }
461 }
462
463 response = self._create_security_group_rule_json(rules)
464 self.assertEquals(response.status_int, 404)
465
466 def test_create_with_invalid_protocol_json(self):
467 rules = {
468 "security_group_rule": {
469 "ip_protocol": "invalid-protocol",
470 "from_port": "22",
471 "to_port": "22",
472 "cidr": "10.2.2.0/24",
473 "parent_group_id": "%s" % self.parent_security_group['id'],
474 }
475 }
476
477 response = self._create_security_group_rule_json(rules)
478 self.assertEquals(response.status_int, 400)
479
480 def test_create_with_no_protocol_json(self):
481 rules = {
482 "security_group_rule": {
483 "from_port": "22",
484 "to_port": "22",
485 "cidr": "10.2.2.0/24",
486 "parent_group_id": "%s" % self.parent_security_group['id'],
487 }
488 }
489
490 response = self._create_security_group_rule_json(rules)
491 self.assertEquals(response.status_int, 400)
492
493 def test_create_with_invalid_from_port_json(self):
494 rules = {
495 "security_group_rule": {
496 "ip_protocol": "tcp",
497 "from_port": "666666",
498 "to_port": "22",
499 "cidr": "10.2.2.0/24",
500 "parent_group_id": "%s" % self.parent_security_group['id'],
501 }
502 }
503
504 response = self._create_security_group_rule_json(rules)
505 self.assertEquals(response.status_int, 400)
506
507 def test_create_with_invalid_to_port_json(self):
508 rules = {
509 "security_group_rule": {
510 "ip_protocol": "tcp",
511 "from_port": "22",
512 "to_port": "666666",
513 "cidr": "10.2.2.0/24",
514 "parent_group_id": "%s" % self.parent_security_group['id'],
515 }
516 }
517
518 response = self._create_security_group_rule_json(rules)
519 self.assertEquals(response.status_int, 400)
520
521 def test_create_with_non_numerical_from_port_json(self):
522 rules = {
523 "security_group_rule": {
524 "ip_protocol": "tcp",
525 "from_port": "invalid",
526 "to_port": "22",
527 "cidr": "10.2.2.0/24",
528 "parent_group_id": "%s" % self.parent_security_group['id'],
529 }
530 }
531
532 response = self._create_security_group_rule_json(rules)
533 self.assertEquals(response.status_int, 400)
534
535 def test_create_with_non_numerical_to_port_json(self):
536 rules = {
537 "security_group_rule": {
538 "ip_protocol": "tcp",
539 "from_port": "22",
540 "to_port": "invalid",
541 "cidr": "10.2.2.0/24",
542 "parent_group_id": "%s" % self.parent_security_group['id'],
543 }
544 }
545
546 response = self._create_security_group_rule_json(rules)
547 self.assertEquals(response.status_int, 400)
548
549 def test_create_with_no_to_port_json(self):
550 rules = {
551 "security_group_rule": {
552 "ip_protocol": "tcp",
553 "from_port": "22",
554 "cidr": "10.2.2.0/24",
555 "parent_group_id": "%s" % self.parent_security_group['id'],
556 }
557 }
558
559 response = self._create_security_group_rule_json(rules)
560 self.assertEquals(response.status_int, 400)
561
562 def test_create_with_invalid_cidr_json(self):
563 rules = {
564 "security_group_rule": {
565 "ip_protocol": "tcp",
566 "from_port": "22",
567 "to_port": "22",
568 "cidr": "10.2.22222.0/24",
569 "parent_group_id": "%s" % self.parent_security_group['id'],
570 }
571 }
572
573 response = self._create_security_group_rule_json(rules)
574 self.assertEquals(response.status_int, 400)
575
576 def test_create_with_no_cidr_group_json(self):
577 rules = {
578 "security_group_rule": {
579 "ip_protocol": "tcp",
580 "from_port": "22",
581 "to_port": "22",
582 "parent_group_id": "%s" % self.parent_security_group['id'],
583 }
584 }
585
586 response = self._create_security_group_rule_json(rules)
587 security_group_rule = json.loads(response.body)['security_group_rule']
588 self.assertEquals(response.status_int, 200)
589 self.assertNotEquals(security_group_rule['id'], 0)
590 self.assertEquals(security_group_rule['parent_group_id'],
591 self.parent_security_group['id'])
592 self.assertEquals(security_group_rule['ip_range']['cidr'],
593 "0.0.0.0/0")
594
595 def test_create_with_invalid_group_id_json(self):
596 rules = {
597 "security_group_rule": {
598 "ip_protocol": "tcp",
599 "from_port": "22",
600 "to_port": "22",
601 "group_id": "invalid",
602 "parent_group_id": "%s" % self.parent_security_group['id'],
603 }
604 }
605
606 response = self._create_security_group_rule_json(rules)
607 self.assertEquals(response.status_int, 400)
608
609 def test_create_with_empty_group_id_json(self):
610 rules = {
611 "security_group_rule": {
612 "ip_protocol": "tcp",
613 "from_port": "22",
614 "to_port": "22",
615 "group_id": "invalid",
616 "parent_group_id": "%s" % self.parent_security_group['id'],
617 }
618 }
619
620 response = self._create_security_group_rule_json(rules)
621 self.assertEquals(response.status_int, 400)
622
623 def test_create_with_invalid_group_id_json(self):
624 rules = {
625 "security_group_rule": {
626 "ip_protocol": "tcp",
627 "from_port": "22",
628 "to_port": "22",
629 "group_id": "222222",
630 "parent_group_id": "%s" % self.parent_security_group['id'],
631 }
632 }
633
634 response = self._create_security_group_rule_json(rules)
635 self.assertEquals(response.status_int, 400)
636
637 def test_create_rule_with_same_group_parent_id_json(self):
638 rules = {
639 "security_group_rule": {
640 "ip_protocol": "tcp",
641 "from_port": "22",
642 "to_port": "22",
643 "group_id": "%s" % self.parent_security_group['id'],
644 "parent_group_id": "%s" % self.parent_security_group['id'],
645 }
646 }
647
648 response = self._create_security_group_rule_json(rules)
649 self.assertEquals(response.status_int, 400)
650
651 def test_delete(self):
652 response = self._delete_security_group_rule(
653 self.security_group_rule['id'])
654 self.assertEquals(response.status_int, 202)
655
656 response = self._delete_security_group_rule(
657 self.security_group_rule['id'])
658 self.assertEquals(response.status_int, 404)
659
660 def test_delete_invalid_rule_id(self):
661 response = self._delete_security_group_rule('invalid')
662 self.assertEquals(response.status_int, 400)
663
664 def test_delete_non_existing_rule_id(self):
665 response = self._delete_security_group_rule(22222222222222)
666 self.assertEquals(response.status_int, 404)
667
668
669class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase):
670
671 def setUp(self):
672 self.deserializer = security_groups.SecurityGroupRulesXMLDeserializer()
673
674 def test_create_request(self):
675 serial_request = """
676<security_group_rule>
677 <parent_group_id>12</parent_group_id>
678 <from_port>22</from_port>
679 <to_port>22</to_port>
680 <group_id></group_id>
681 <ip_protocol>tcp</ip_protocol>
682 <cidr>10.0.0.0/24</cidr>
683</security_group_rule>"""
684 request = self.deserializer.deserialize(serial_request, 'create')
685 expected = {
686 "security_group_rule": {
687 "parent_group_id": "12",
688 "from_port": "22",
689 "to_port": "22",
690 "ip_protocol": "tcp",
691 "group_id": "",
692 "cidr": "10.0.0.0/24",
693 },
694 }
695 self.assertEquals(request['body'], expected)
696
697 def test_create_no_protocol_request(self):
698 serial_request = """
699<security_group_rule>
700 <parent_group_id>12</parent_group_id>
701 <from_port>22</from_port>
702 <to_port>22</to_port>
703 <group_id></group_id>
704 <cidr>10.0.0.0/24</cidr>
705</security_group_rule>"""
706 request = self.deserializer.deserialize(serial_request, 'create')
707 expected = {
708 "security_group_rule": {
709 "parent_group_id": "12",
710 "from_port": "22",
711 "to_port": "22",
712 "group_id": "",
713 "cidr": "10.0.0.0/24",
714 },
715 }
716 self.assertEquals(request['body'], expected)
717
718
719class TestSecurityGroupXMLDeserializer(unittest.TestCase):
720
721 def setUp(self):
722 self.deserializer = security_groups.SecurityGroupXMLDeserializer()
723
724 def test_create_request(self):
725 serial_request = """
726<security_group name="test">
727 <description>test</description>
728</security_group>"""
729 request = self.deserializer.deserialize(serial_request, 'create')
730 expected = {
731 "security_group": {
732 "name": "test",
733 "description": "test",
734 },
735 }
736 self.assertEquals(request['body'], expected)
737
738 def test_create_no_description_request(self):
739 serial_request = """
740<security_group name="test">
741</security_group>"""
742 request = self.deserializer.deserialize(serial_request, 'create')
743 expected = {
744 "security_group": {
745 "name": "test",
746 },
747 }
748 self.assertEquals(request['body'], expected)
749
750 def test_create_no_name_request(self):
751 serial_request = """
752<security_group>
753<description>test</description>
754</security_group>"""
755 request = self.deserializer.deserialize(serial_request, 'create')
756 expected = {
757 "security_group": {
758 "description": "test",
759 },
760 }
761 self.assertEquals(request['body'], expected)
0762
=== modified file 'nova/tests/api/openstack/test_extensions.py'
--- nova/tests/api/openstack/test_extensions.py 2011-08-10 19:08:35 +0000
+++ nova/tests/api/openstack/test_extensions.py 2011-08-12 00:06:25 +0000
@@ -97,7 +97,8 @@
97 names = [x['name'] for x in data['extensions']]97 names = [x['name'] for x in data['extensions']]
98 names.sort()98 names.sort()
99 self.assertEqual(names, ["FlavorExtraSpecs", "Floating_ips",99 self.assertEqual(names, ["FlavorExtraSpecs", "Floating_ips",
100 "Fox In Socks", "Hosts", "Keypairs", "Multinic", "Volumes"])100 "Fox In Socks", "Hosts", "Keypairs", "Multinic", "SecurityGroups",
101 "Volumes"])
101102
102 # Make sure that at least Fox in Sox is correct.103 # Make sure that at least Fox in Sox is correct.
103 (fox_ext,) = [104 (fox_ext,) = [
@@ -108,7 +109,7 @@
108 'updated': '2011-01-22T13:25:27-06:00',109 'updated': '2011-01-22T13:25:27-06:00',
109 'description': 'The Fox In Socks Extension',110 'description': 'The Fox In Socks Extension',
110 'alias': 'FOXNSOX',111 'alias': 'FOXNSOX',
111 'links': [],112 'links': []
112 },113 },
113 )114 )
114115
@@ -144,7 +145,7 @@
144145
145 # Make sure we have all the extensions.146 # Make sure we have all the extensions.
146 exts = root.findall('{0}extension'.format(NS))147 exts = root.findall('{0}extension'.format(NS))
147 self.assertEqual(len(exts), 7)148 self.assertEqual(len(exts), 8)
148149
149 # Make sure that at least Fox in Sox is correct.150 # Make sure that at least Fox in Sox is correct.
150 (fox_ext,) = [x for x in exts if x.get('alias') == 'FOXNSOX']151 (fox_ext,) = [x for x in exts if x.get('alias') == 'FOXNSOX']