Merge lp:~tpatil/nova/add-remove-securitygroup-instance into lp:~hudson-openstack/nova/trunk

Proposed by Tushar Patil
Status: Merged
Approved by: Jesse Andrews
Approved revision: 1428
Merged at revision: 1469
Proposed branch: lp:~tpatil/nova/add-remove-securitygroup-instance
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 698 lines (+503/-21)
7 files modified
nova/api/openstack/contrib/security_groups.py (+98/-21)
nova/api/openstack/create_instance_helper.py (+31/-0)
nova/compute/api.py (+72/-0)
nova/db/api.py (+6/-0)
nova/db/sqlalchemy/api.py (+15/-0)
nova/exception.py (+10/-0)
nova/tests/api/openstack/contrib/test_security_groups.py (+271/-0)
To merge this branch: bzr merge lp:~tpatil/nova/add-remove-securitygroup-instance
Reviewer Review Type Date Requested Status
Jesse Andrews (community) Approve
Vish Ishaya (community) Approve
Review via email: mp+71967@code.launchpad.net

Description of the change

Added OS APIs to associate/disassociate security groups to/from instances.

I will add views to return list of security groups associated with the servers later after this branch is merged into trunk. The reason I will do this later is because my previous merge proposal (https://code.launchpad.net/~tpatil/nova/add-options-network-create-os-apis/+merge/68292) is dependent on this work. In this merge proposal I have added a new extension which still uses default OS v1.1 controllers and views, but I am going to override views in this extension to send extra information like security groups.

To post a comment you must log in.
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Awesome stuff.

Nice tests.

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

We need to update https://github.com/rackspace/python-novaclient/pull/71 to take advantage of this :)

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

As noted by bcwaldon & blamar in https://code.launchpad.net/~cloudbuilders/nova/os-floating-ips-redux/+merge/71922 we should switch the add/remove to being server actions like:

166 +
167 + def get_actions(self):
168 + """Return the actions the extension adds, as required by contract."""
169 + actions = [
170 + extensions.ActionExtension("servers", "addFloatingIp",
171 + self._add_floating_ip),
172 + extensions.ActionExtension("servers", "removeFloatingIp",
173 + self._remove_floating_ip),
174 + ]
175 +
176 + return actions

Other than that LGTM

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

> As noted by bcwaldon & blamar in
> https://code.launchpad.net/~cloudbuilders/nova/os-floating-ips-
> redux/+merge/71922 we should switch the add/remove to being server actions
> like:
>
> 166 +
> 167 + def get_actions(self):
> 168 + """Return the actions the extension adds, as required by
> contract."""
> 169 + actions = [
> 170 + extensions.ActionExtension("servers",
> "addFloatingIp",
> 171 + self._add_floating_ip),
> 172 + extensions.ActionExtension("servers",
> "removeFloatingIp",
> 173 +
> self._remove_floating_ip),
> 174 + ]
> 175 +
> 176 + return actions
>
> Other than that LGTM
I have implemented add/remove security groups as server actions and fixed all relevant unit tests.
Please review.

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

++ l g t m

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/openstack/contrib/security_groups.py'
--- nova/api/openstack/contrib/security_groups.py 2011-08-12 00:04:33 +0000
+++ nova/api/openstack/contrib/security_groups.py 2011-08-20 22:39:52 +0000
@@ -25,10 +25,11 @@
25from nova import exception25from nova import exception
26from nova import flags26from nova import flags
27from nova import log as logging27from nova import log as logging
28from nova import rpc
28from nova.api.openstack import common29from nova.api.openstack import common
29from nova.api.openstack import extensions30from nova.api.openstack import extensions
30from nova.api.openstack import wsgi31from nova.api.openstack import wsgi
3132from nova.compute import power_state
3233
33from xml.dom import minidom34from xml.dom import minidom
3435
@@ -73,33 +74,28 @@
73 context, rule)]74 context, rule)]
74 return security_group75 return security_group
7576
77 def _get_security_group(self, context, id):
78 try:
79 id = int(id)
80 security_group = db.security_group_get(context, id)
81 except ValueError:
82 msg = _("Security group id should be integer")
83 raise exc.HTTPBadRequest(explanation=msg)
84 except exception.NotFound as exp:
85 raise exc.HTTPNotFound(explanation=unicode(exp))
86 return security_group
87
76 def show(self, req, id):88 def show(self, req, id):
77 """Return data about the given security group."""89 """Return data about the given security group."""
78 context = req.environ['nova.context']90 context = req.environ['nova.context']
79 try:91 security_group = self._get_security_group(context, id)
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,92 return {'security_group': self._format_security_group(context,
89 security_group)}93 security_group)}
9094
91 def delete(self, req, id):95 def delete(self, req, id):
92 """Delete a security group."""96 """Delete a security group."""
93 context = req.environ['nova.context']97 context = req.environ['nova.context']
94 try:98 security_group = self._get_security_group(context, id)
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)99 LOG.audit(_("Delete security group %s"), id, context=context)
104 db.security_group_destroy(context, security_group.id)100 db.security_group_destroy(context, security_group.id)
105101
@@ -226,9 +222,9 @@
226 security_group_rule = db.security_group_rule_create(context, values)222 security_group_rule = db.security_group_rule_create(context, values)
227223
228 self.compute_api.trigger_security_group_rules_refresh(context,224 self.compute_api.trigger_security_group_rules_refresh(context,
229 security_group_id=security_group['id'])225 security_group_id=security_group['id'])
230226
231 return {'security_group_rule': self._format_security_group_rule(227 return {"security_group_rule": self._format_security_group_rule(
232 context,228 context,
233 security_group_rule)}229 security_group_rule)}
234230
@@ -336,6 +332,11 @@
336332
337333
338class Security_groups(extensions.ExtensionDescriptor):334class Security_groups(extensions.ExtensionDescriptor):
335
336 def __init__(self):
337 self.compute_api = compute.API()
338 super(Security_groups, self).__init__()
339
339 def get_name(self):340 def get_name(self):
340 return "SecurityGroups"341 return "SecurityGroups"
341342
@@ -351,6 +352,82 @@
351 def get_updated(self):352 def get_updated(self):
352 return "2011-07-21T00:00:00+00:00"353 return "2011-07-21T00:00:00+00:00"
353354
355 def _addSecurityGroup(self, input_dict, req, instance_id):
356 context = req.environ['nova.context']
357
358 try:
359 body = input_dict['addSecurityGroup']
360 group_name = body['name']
361 instance_id = int(instance_id)
362 except ValueError:
363 msg = _("Server id should be integer")
364 raise exc.HTTPBadRequest(explanation=msg)
365 except TypeError:
366 msg = _("Missing parameter dict")
367 raise webob.exc.HTTPBadRequest(explanation=msg)
368 except KeyError:
369 msg = _("Security group not specified")
370 raise webob.exc.HTTPBadRequest(explanation=msg)
371
372 if not group_name or group_name.strip() == '':
373 msg = _("Security group name cannot be empty")
374 raise webob.exc.HTTPBadRequest(explanation=msg)
375
376 try:
377 self.compute_api.add_security_group(context, instance_id,
378 group_name)
379 except exception.SecurityGroupNotFound as exp:
380 return exc.HTTPNotFound(explanation=unicode(exp))
381 except exception.InstanceNotFound as exp:
382 return exc.HTTPNotFound(explanation=unicode(exp))
383 except exception.Invalid as exp:
384 return exc.HTTPBadRequest(explanation=unicode(exp))
385
386 return exc.HTTPAccepted()
387
388 def _removeSecurityGroup(self, input_dict, req, instance_id):
389 context = req.environ['nova.context']
390
391 try:
392 body = input_dict['removeSecurityGroup']
393 group_name = body['name']
394 instance_id = int(instance_id)
395 except ValueError:
396 msg = _("Server id should be integer")
397 raise exc.HTTPBadRequest(explanation=msg)
398 except TypeError:
399 msg = _("Missing parameter dict")
400 raise webob.exc.HTTPBadRequest(explanation=msg)
401 except KeyError:
402 msg = _("Security group not specified")
403 raise webob.exc.HTTPBadRequest(explanation=msg)
404
405 if not group_name or group_name.strip() == '':
406 msg = _("Security group name cannot be empty")
407 raise webob.exc.HTTPBadRequest(explanation=msg)
408
409 try:
410 self.compute_api.remove_security_group(context, instance_id,
411 group_name)
412 except exception.SecurityGroupNotFound as exp:
413 return exc.HTTPNotFound(explanation=unicode(exp))
414 except exception.InstanceNotFound as exp:
415 return exc.HTTPNotFound(explanation=unicode(exp))
416 except exception.Invalid as exp:
417 return exc.HTTPBadRequest(explanation=unicode(exp))
418
419 return exc.HTTPAccepted()
420
421 def get_actions(self):
422 """Return the actions the extensions adds"""
423 actions = [
424 extensions.ActionExtension("servers", "addSecurityGroup",
425 self._addSecurityGroup),
426 extensions.ActionExtension("servers", "removeSecurityGroup",
427 self._removeSecurityGroup)
428 ]
429 return actions
430
354 def get_resources(self):431 def get_resources(self):
355 resources = []432 resources = []
356433
357434
=== modified file 'nova/api/openstack/create_instance_helper.py'
--- nova/api/openstack/create_instance_helper.py 2011-08-18 20:41:02 +0000
+++ nova/api/openstack/create_instance_helper.py 2011-08-20 22:39:52 +0000
@@ -111,6 +111,15 @@
111 if personality:111 if personality:
112 injected_files = self._get_injected_files(personality)112 injected_files = self._get_injected_files(personality)
113113
114 sg_names = []
115 security_groups = server_dict.get('security_groups')
116 if security_groups is not None:
117 sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
118 if not sg_names:
119 sg_names.append('default')
120
121 sg_names = list(set(sg_names))
122
114 try:123 try:
115 flavor_id = self.controller._flavor_id_from_req_data(body)124 flavor_id = self.controller._flavor_id_from_req_data(body)
116 except ValueError as error:125 except ValueError as error:
@@ -164,6 +173,7 @@
164 reservation_id=reservation_id,173 reservation_id=reservation_id,
165 min_count=min_count,174 min_count=min_count,
166 max_count=max_count,175 max_count=max_count,
176 security_group=sg_names,
167 user_data=user_data,177 user_data=user_data,
168 availability_zone=availability_zone))178 availability_zone=availability_zone))
169 except quota.QuotaError as error:179 except quota.QuotaError as error:
@@ -174,6 +184,8 @@
174 except exception.FlavorNotFound as error:184 except exception.FlavorNotFound as error:
175 msg = _("Invalid flavorRef provided.")185 msg = _("Invalid flavorRef provided.")
176 raise exc.HTTPBadRequest(explanation=msg)186 raise exc.HTTPBadRequest(explanation=msg)
187 except exception.SecurityGroupNotFound as error:
188 raise exc.HTTPBadRequest(explanation=unicode(error))
177 # Let the caller deal with unhandled exceptions.189 # Let the caller deal with unhandled exceptions.
178190
179 def _handle_quota_error(self, error):191 def _handle_quota_error(self, error):
@@ -465,6 +477,10 @@
465 if personality is not None:477 if personality is not None:
466 server["personality"] = personality478 server["personality"] = personality
467479
480 security_groups = self._extract_security_groups(server_node)
481 if security_groups is not None:
482 server["security_groups"] = security_groups
483
468 return server484 return server
469485
470 def _extract_personality(self, server_node):486 def _extract_personality(self, server_node):
@@ -481,3 +497,18 @@
481 return personality497 return personality
482 else:498 else:
483 return None499 return None
500
501 def _extract_security_groups(self, server_node):
502 """Marshal the security_groups attribute of a parsed request"""
503 node = self.find_first_child_named(server_node, "security_groups")
504 if node is not None:
505 security_groups = []
506 for sg_node in self.find_children_named(node, "security_group"):
507 item = {}
508 name_node = self.find_first_child_named(sg_node, "name")
509 if name_node:
510 item["name"] = self.extract_text(name_node)
511 security_groups.append(item)
512 return security_groups
513 else:
514 return None
484515
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-08-19 18:37:39 +0000
+++ nova/compute/api.py 2011-08-20 22:39:52 +0000
@@ -613,6 +613,78 @@
613 self.db.queue_get_for(context, FLAGS.compute_topic, host),613 self.db.queue_get_for(context, FLAGS.compute_topic, host),
614 {'method': 'refresh_provider_fw_rules', 'args': {}})614 {'method': 'refresh_provider_fw_rules', 'args': {}})
615615
616 def _is_security_group_associated_with_server(self, security_group,
617 instance_id):
618 """Check if the security group is already associated
619 with the instance. If Yes, return True.
620 """
621
622 if not security_group:
623 return False
624
625 instances = security_group.get('instances')
626 if not instances:
627 return False
628
629 inst_id = None
630 for inst_id in (instance['id'] for instance in instances \
631 if instance_id == instance['id']):
632 return True
633
634 return False
635
636 def add_security_group(self, context, instance_id, security_group_name):
637 """Add security group to the instance"""
638 security_group = db.security_group_get_by_name(context,
639 context.project_id,
640 security_group_name)
641 # check if the server exists
642 inst = db.instance_get(context, instance_id)
643 #check if the security group is associated with the server
644 if self._is_security_group_associated_with_server(security_group,
645 instance_id):
646 raise exception.SecurityGroupExistsForInstance(
647 security_group_id=security_group['id'],
648 instance_id=instance_id)
649
650 #check if the instance is in running state
651 if inst['state'] != power_state.RUNNING:
652 raise exception.InstanceNotRunning(instance_id=instance_id)
653
654 db.instance_add_security_group(context.elevated(),
655 instance_id,
656 security_group['id'])
657 rpc.cast(context,
658 db.queue_get_for(context, FLAGS.compute_topic, inst['host']),
659 {"method": "refresh_security_group_rules",
660 "args": {"security_group_id": security_group['id']}})
661
662 def remove_security_group(self, context, instance_id, security_group_name):
663 """Remove the security group associated with the instance"""
664 security_group = db.security_group_get_by_name(context,
665 context.project_id,
666 security_group_name)
667 # check if the server exists
668 inst = db.instance_get(context, instance_id)
669 #check if the security group is associated with the server
670 if not self._is_security_group_associated_with_server(security_group,
671 instance_id):
672 raise exception.SecurityGroupNotExistsForInstance(
673 security_group_id=security_group['id'],
674 instance_id=instance_id)
675
676 #check if the instance is in running state
677 if inst['state'] != power_state.RUNNING:
678 raise exception.InstanceNotRunning(instance_id=instance_id)
679
680 db.instance_remove_security_group(context.elevated(),
681 instance_id,
682 security_group['id'])
683 rpc.cast(context,
684 db.queue_get_for(context, FLAGS.compute_topic, inst['host']),
685 {"method": "refresh_security_group_rules",
686 "args": {"security_group_id": security_group['id']}})
687
616 @scheduler_api.reroute_compute("update")688 @scheduler_api.reroute_compute("update")
617 def update(self, context, instance_id, **kwargs):689 def update(self, context, instance_id, **kwargs):
618 """Updates the instance in the datastore.690 """Updates the instance in the datastore.
619691
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2011-08-12 19:00:48 +0000
+++ nova/db/api.py 2011-08-20 22:39:52 +0000
@@ -570,6 +570,12 @@
570 security_group_id)570 security_group_id)
571571
572572
573def instance_remove_security_group(context, instance_id, security_group_id):
574 """Disassociate the given security group from the given instance."""
575 return IMPL.instance_remove_security_group(context, instance_id,
576 security_group_id)
577
578
573def instance_action_create(context, values):579def instance_action_create(context, values):
574 """Create an instance action from the values dictionary."""580 """Create an instance action from the values dictionary."""
575 return IMPL.instance_action_create(context, values)581 return IMPL.instance_action_create(context, values)
576582
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2011-08-18 21:57:52 +0000
+++ nova/db/sqlalchemy/api.py 2011-08-20 22:39:52 +0000
@@ -1502,6 +1502,19 @@
15021502
15031503
1504@require_context1504@require_context
1505def instance_remove_security_group(context, instance_id, security_group_id):
1506 """Disassociate the given security group from the given instance"""
1507 session = get_session()
1508
1509 session.query(models.SecurityGroupInstanceAssociation).\
1510 filter_by(instance_id=instance_id).\
1511 filter_by(security_group_id=security_group_id).\
1512 update({'deleted': True,
1513 'deleted_at': utils.utcnow(),
1514 'updated_at': literal_column('updated_at')})
1515
1516
1517@require_context
1505def instance_action_create(context, values):1518def instance_action_create(context, values):
1506 """Create an instance action from the values dictionary."""1519 """Create an instance action from the values dictionary."""
1507 action_ref = models.InstanceActions()1520 action_ref = models.InstanceActions()
@@ -2437,6 +2450,7 @@
2437 filter_by(deleted=can_read_deleted(context),).\2450 filter_by(deleted=can_read_deleted(context),).\
2438 filter_by(id=security_group_id).\2451 filter_by(id=security_group_id).\
2439 options(joinedload_all('rules')).\2452 options(joinedload_all('rules')).\
2453 options(joinedload_all('instances')).\
2440 first()2454 first()
2441 else:2455 else:
2442 result = session.query(models.SecurityGroup).\2456 result = session.query(models.SecurityGroup).\
@@ -2444,6 +2458,7 @@
2444 filter_by(id=security_group_id).\2458 filter_by(id=security_group_id).\
2445 filter_by(project_id=context.project_id).\2459 filter_by(project_id=context.project_id).\
2446 options(joinedload_all('rules')).\2460 options(joinedload_all('rules')).\
2461 options(joinedload_all('instances')).\
2447 first()2462 first()
2448 if not result:2463 if not result:
2449 raise exception.SecurityGroupNotFound(2464 raise exception.SecurityGroupNotFound(
24502465
=== modified file 'nova/exception.py'
--- nova/exception.py 2011-08-16 12:47:35 +0000
+++ nova/exception.py 2011-08-20 22:39:52 +0000
@@ -541,6 +541,16 @@
541 message = _("Security group with rule %(rule_id)s not found.")541 message = _("Security group with rule %(rule_id)s not found.")
542542
543543
544class SecurityGroupExistsForInstance(Invalid):
545 message = _("Security group %(security_group_id)s is already associated"
546 " with the instance %(instance_id)s")
547
548
549class SecurityGroupNotExistsForInstance(Invalid):
550 message = _("Security group %(security_group_id)s is not associated with"
551 " the instance %(instance_id)s")
552
553
544class MigrationNotFound(NotFound):554class MigrationNotFound(NotFound):
545 message = _("Migration %(migration_id)s could not be found.")555 message = _("Migration %(migration_id)s could not be found.")
546556
547557
=== modified file 'nova/tests/api/openstack/contrib/test_security_groups.py'
--- nova/tests/api/openstack/contrib/test_security_groups.py 2011-08-12 00:04:33 +0000
+++ nova/tests/api/openstack/contrib/test_security_groups.py 2011-08-20 22:39:52 +0000
@@ -15,10 +15,13 @@
15# under the License.15# under the License.
1616
17import json17import json
18import mox
19import nova
18import unittest20import unittest
19import webob21import webob
20from xml.dom import minidom22from xml.dom import minidom
2123
24from nova import exception
22from nova import test25from nova import test
23from nova.api.openstack.contrib import security_groups26from nova.api.openstack.contrib import security_groups
24from nova.tests.api.openstack import fakes27from nova.tests.api.openstack import fakes
@@ -51,6 +54,28 @@
51 return {'security_group': sg}54 return {'security_group': sg}
5255
5356
57def return_server(context, server_id):
58 return {'id': server_id, 'state': 0x01, 'host': "localhost"}
59
60
61def return_non_running_server(context, server_id):
62 return {'id': server_id, 'state': 0x02,
63 'host': "localhost"}
64
65
66def return_security_group(context, project_id, group_name):
67 return {'id': 1, 'name': group_name, "instances": [
68 {'id': 1}]}
69
70
71def return_security_group_without_instances(context, project_id, group_name):
72 return {'id': 1, 'name': group_name}
73
74
75def return_server_nonexistant(context, server_id):
76 raise exception.InstanceNotFound(instance_id=server_id)
77
78
54class TestSecurityGroups(test.TestCase):79class TestSecurityGroups(test.TestCase):
55 def setUp(self):80 def setUp(self):
56 super(TestSecurityGroups, self).setUp()81 super(TestSecurityGroups, self).setUp()
@@ -325,6 +350,252 @@
325 response = self._delete_security_group(11111111)350 response = self._delete_security_group(11111111)
326 self.assertEquals(response.status_int, 404)351 self.assertEquals(response.status_int, 404)
327352
353 def test_associate_by_non_existing_security_group_name(self):
354 body = dict(addSecurityGroup=dict(name='non-existing'))
355 req = webob.Request.blank('/v1.1/servers/1/action')
356 req.headers['Content-Type'] = 'application/json'
357 req.method = 'POST'
358 req.body = json.dumps(body)
359 response = req.get_response(fakes.wsgi_app())
360 self.assertEquals(response.status_int, 404)
361
362 def test_associate_by_invalid_server_id(self):
363 body = dict(addSecurityGroup=dict(name='test'))
364 self.stubs.Set(nova.db, 'security_group_get_by_name',
365 return_security_group)
366 req = webob.Request.blank('/v1.1/servers/invalid/action')
367 req.headers['Content-Type'] = 'application/json'
368 req.method = 'POST'
369 req.body = json.dumps(body)
370 response = req.get_response(fakes.wsgi_app())
371 self.assertEquals(response.status_int, 400)
372
373 def test_associate_without_body(self):
374 req = webob.Request.blank('/v1.1/servers/1/action')
375 body = dict(addSecurityGroup=None)
376 self.stubs.Set(nova.db, 'instance_get', return_server)
377 req.headers['Content-Type'] = 'application/json'
378 req.method = 'POST'
379 req.body = json.dumps(body)
380 response = req.get_response(fakes.wsgi_app())
381 self.assertEquals(response.status_int, 400)
382
383 def test_associate_no_security_group_name(self):
384 req = webob.Request.blank('/v1.1/servers/1/action')
385 body = dict(addSecurityGroup=dict())
386 self.stubs.Set(nova.db, 'instance_get', return_server)
387 req.headers['Content-Type'] = 'application/json'
388 req.method = 'POST'
389 req.body = json.dumps(body)
390 response = req.get_response(fakes.wsgi_app())
391 self.assertEquals(response.status_int, 400)
392
393 def test_associate_security_group_name_with_whitespaces(self):
394 req = webob.Request.blank('/v1.1/servers/1/action')
395 body = dict(addSecurityGroup=dict(name=" "))
396 self.stubs.Set(nova.db, 'instance_get', return_server)
397 req.headers['Content-Type'] = 'application/json'
398 req.method = 'POST'
399 req.body = json.dumps(body)
400 response = req.get_response(fakes.wsgi_app())
401 self.assertEquals(response.status_int, 400)
402
403 def test_associate_non_existing_instance(self):
404 self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant)
405 body = dict(addSecurityGroup=dict(name="test"))
406 self.stubs.Set(nova.db, 'security_group_get_by_name',
407 return_security_group)
408 req = webob.Request.blank('/v1.1/servers/10000/action')
409 req.headers['Content-Type'] = 'application/json'
410 req.method = 'POST'
411 req.body = json.dumps(body)
412 response = req.get_response(fakes.wsgi_app())
413 self.assertEquals(response.status_int, 404)
414
415 def test_associate_non_running_instance(self):
416 self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
417 self.stubs.Set(nova.db, 'security_group_get_by_name',
418 return_security_group_without_instances)
419 body = dict(addSecurityGroup=dict(name="test"))
420 req = webob.Request.blank('/v1.1/servers/1/action')
421 req.headers['Content-Type'] = 'application/json'
422 req.method = 'POST'
423 req.body = json.dumps(body)
424 response = req.get_response(fakes.wsgi_app())
425 self.assertEquals(response.status_int, 400)
426
427 def test_associate_already_associated_security_group_to_instance(self):
428 self.stubs.Set(nova.db, 'instance_get', return_server)
429 self.stubs.Set(nova.db, 'security_group_get_by_name',
430 return_security_group)
431 body = dict(addSecurityGroup=dict(name="test"))
432 req = webob.Request.blank('/v1.1/servers/1/action')
433 req.headers['Content-Type'] = 'application/json'
434 req.method = 'POST'
435 req.body = json.dumps(body)
436 response = req.get_response(fakes.wsgi_app())
437 self.assertEquals(response.status_int, 400)
438
439 def test_associate(self):
440 self.stubs.Set(nova.db, 'instance_get', return_server)
441 self.mox.StubOutWithMock(nova.db, 'instance_add_security_group')
442 nova.db.instance_add_security_group(mox.IgnoreArg(),
443 mox.IgnoreArg(),
444 mox.IgnoreArg())
445 self.stubs.Set(nova.db, 'security_group_get_by_name',
446 return_security_group_without_instances)
447 self.mox.ReplayAll()
448
449 body = dict(addSecurityGroup=dict(name="test"))
450 req = webob.Request.blank('/v1.1/servers/1/action')
451 req.headers['Content-Type'] = 'application/json'
452 req.method = 'POST'
453 req.body = json.dumps(body)
454 response = req.get_response(fakes.wsgi_app())
455 self.assertEquals(response.status_int, 202)
456
457 def test_associate_xml(self):
458 self.stubs.Set(nova.db, 'instance_get', return_server)
459 self.mox.StubOutWithMock(nova.db, 'instance_add_security_group')
460 nova.db.instance_add_security_group(mox.IgnoreArg(),
461 mox.IgnoreArg(),
462 mox.IgnoreArg())
463 self.stubs.Set(nova.db, 'security_group_get_by_name',
464 return_security_group_without_instances)
465 self.mox.ReplayAll()
466
467 req = webob.Request.blank('/v1.1/servers/1/action')
468 req.headers['Content-Type'] = 'application/xml'
469 req.method = 'POST'
470 req.body = """<addSecurityGroup>
471 <name>test</name>
472 </addSecurityGroup>"""
473 response = req.get_response(fakes.wsgi_app())
474 self.assertEquals(response.status_int, 202)
475
476 def test_disassociate_by_non_existing_security_group_name(self):
477 body = dict(removeSecurityGroup=dict(name='non-existing'))
478 req = webob.Request.blank('/v1.1/servers/1/action')
479 req.headers['Content-Type'] = 'application/json'
480 req.method = 'POST'
481 req.body = json.dumps(body)
482 response = req.get_response(fakes.wsgi_app())
483 self.assertEquals(response.status_int, 404)
484
485 def test_disassociate_by_invalid_server_id(self):
486 body = dict(removeSecurityGroup=dict(name='test'))
487 self.stubs.Set(nova.db, 'security_group_get_by_name',
488 return_security_group)
489 req = webob.Request.blank('/v1.1/servers/invalid/action')
490 req.headers['Content-Type'] = 'application/json'
491 req.method = 'POST'
492 req.body = json.dumps(body)
493 response = req.get_response(fakes.wsgi_app())
494 self.assertEquals(response.status_int, 400)
495
496 def test_disassociate_without_body(self):
497 req = webob.Request.blank('/v1.1/servers/1/action')
498 body = dict(removeSecurityGroup=None)
499 self.stubs.Set(nova.db, 'instance_get', return_server)
500 req.headers['Content-Type'] = 'application/json'
501 req.method = 'POST'
502 req.body = json.dumps(body)
503 response = req.get_response(fakes.wsgi_app())
504 self.assertEquals(response.status_int, 400)
505
506 def test_disassociate_no_security_group_name(self):
507 req = webob.Request.blank('/v1.1/servers/1/action')
508 body = dict(removeSecurityGroup=dict())
509 self.stubs.Set(nova.db, 'instance_get', return_server)
510 req.headers['Content-Type'] = 'application/json'
511 req.method = 'POST'
512 req.body = json.dumps(body)
513 response = req.get_response(fakes.wsgi_app())
514 self.assertEquals(response.status_int, 400)
515
516 def test_disassociate_security_group_name_with_whitespaces(self):
517 req = webob.Request.blank('/v1.1/servers/1/action')
518 body = dict(removeSecurityGroup=dict(name=" "))
519 self.stubs.Set(nova.db, 'instance_get', return_server)
520 req.headers['Content-Type'] = 'application/json'
521 req.method = 'POST'
522 req.body = json.dumps(body)
523 response = req.get_response(fakes.wsgi_app())
524 self.assertEquals(response.status_int, 400)
525
526 def test_disassociate_non_existing_instance(self):
527 self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant)
528 body = dict(removeSecurityGroup=dict(name="test"))
529 self.stubs.Set(nova.db, 'security_group_get_by_name',
530 return_security_group)
531 req = webob.Request.blank('/v1.1/servers/10000/action')
532 req.headers['Content-Type'] = 'application/json'
533 req.method = 'POST'
534 req.body = json.dumps(body)
535 response = req.get_response(fakes.wsgi_app())
536 self.assertEquals(response.status_int, 404)
537
538 def test_disassociate_non_running_instance(self):
539 self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
540 self.stubs.Set(nova.db, 'security_group_get_by_name',
541 return_security_group)
542 body = dict(removeSecurityGroup=dict(name="test"))
543 req = webob.Request.blank('/v1.1/servers/1/action')
544 req.headers['Content-Type'] = 'application/json'
545 req.method = 'POST'
546 req.body = json.dumps(body)
547 response = req.get_response(fakes.wsgi_app())
548 self.assertEquals(response.status_int, 400)
549
550 def test_disassociate_already_associated_security_group_to_instance(self):
551 self.stubs.Set(nova.db, 'instance_get', return_server)
552 self.stubs.Set(nova.db, 'security_group_get_by_name',
553 return_security_group_without_instances)
554 body = dict(removeSecurityGroup=dict(name="test"))
555 req = webob.Request.blank('/v1.1/servers/1/action')
556 req.headers['Content-Type'] = 'application/json'
557 req.method = 'POST'
558 req.body = json.dumps(body)
559 response = req.get_response(fakes.wsgi_app())
560 self.assertEquals(response.status_int, 400)
561
562 def test_disassociate(self):
563 self.stubs.Set(nova.db, 'instance_get', return_server)
564 self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group')
565 nova.db.instance_remove_security_group(mox.IgnoreArg(),
566 mox.IgnoreArg(),
567 mox.IgnoreArg())
568 self.stubs.Set(nova.db, 'security_group_get_by_name',
569 return_security_group)
570 self.mox.ReplayAll()
571
572 body = dict(removeSecurityGroup=dict(name="test"))
573 req = webob.Request.blank('/v1.1/servers/1/action')
574 req.headers['Content-Type'] = 'application/json'
575 req.method = 'POST'
576 req.body = json.dumps(body)
577 response = req.get_response(fakes.wsgi_app())
578 self.assertEquals(response.status_int, 202)
579
580 def test_disassociate_xml(self):
581 self.stubs.Set(nova.db, 'instance_get', return_server)
582 self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group')
583 nova.db.instance_remove_security_group(mox.IgnoreArg(),
584 mox.IgnoreArg(),
585 mox.IgnoreArg())
586 self.stubs.Set(nova.db, 'security_group_get_by_name',
587 return_security_group)
588 self.mox.ReplayAll()
589
590 req = webob.Request.blank('/v1.1/servers/1/action')
591 req.headers['Content-Type'] = 'application/xml'
592 req.method = 'POST'
593 req.body = """<removeSecurityGroup>
594 <name>test</name>
595 </removeSecurityGroup>"""
596 response = req.get_response(fakes.wsgi_app())
597 self.assertEquals(response.status_int, 202)
598
328599
329class TestSecurityGroupRules(test.TestCase):600class TestSecurityGroupRules(test.TestCase):
330 def setUp(self):601 def setUp(self):