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
1=== modified file 'nova/api/openstack/contrib/security_groups.py'
2--- nova/api/openstack/contrib/security_groups.py 2011-08-12 00:04:33 +0000
3+++ nova/api/openstack/contrib/security_groups.py 2011-08-20 22:39:52 +0000
4@@ -25,10 +25,11 @@
5 from nova import exception
6 from nova import flags
7 from nova import log as logging
8+from nova import rpc
9 from nova.api.openstack import common
10 from nova.api.openstack import extensions
11 from nova.api.openstack import wsgi
12-
13+from nova.compute import power_state
14
15 from xml.dom import minidom
16
17@@ -73,33 +74,28 @@
18 context, rule)]
19 return security_group
20
21+ def _get_security_group(self, context, id):
22+ try:
23+ id = int(id)
24+ security_group = db.security_group_get(context, id)
25+ except ValueError:
26+ msg = _("Security group id should be integer")
27+ raise exc.HTTPBadRequest(explanation=msg)
28+ except exception.NotFound as exp:
29+ raise exc.HTTPNotFound(explanation=unicode(exp))
30+ return security_group
31+
32 def show(self, req, id):
33 """Return data about the given security group."""
34 context = req.environ['nova.context']
35- try:
36- id = int(id)
37- security_group = db.security_group_get(context, id)
38- except ValueError:
39- msg = _("Security group id is not integer")
40- return exc.HTTPBadRequest(explanation=msg)
41- except exception.NotFound as exp:
42- return exc.HTTPNotFound(explanation=unicode(exp))
43-
44+ security_group = self._get_security_group(context, id)
45 return {'security_group': self._format_security_group(context,
46 security_group)}
47
48 def delete(self, req, id):
49 """Delete a security group."""
50 context = req.environ['nova.context']
51- try:
52- id = int(id)
53- security_group = db.security_group_get(context, id)
54- except ValueError:
55- msg = _("Security group id is not integer")
56- return exc.HTTPBadRequest(explanation=msg)
57- except exception.SecurityGroupNotFound as exp:
58- return exc.HTTPNotFound(explanation=unicode(exp))
59-
60+ security_group = self._get_security_group(context, id)
61 LOG.audit(_("Delete security group %s"), id, context=context)
62 db.security_group_destroy(context, security_group.id)
63
64@@ -226,9 +222,9 @@
65 security_group_rule = db.security_group_rule_create(context, values)
66
67 self.compute_api.trigger_security_group_rules_refresh(context,
68- security_group_id=security_group['id'])
69+ security_group_id=security_group['id'])
70
71- return {'security_group_rule': self._format_security_group_rule(
72+ return {"security_group_rule": self._format_security_group_rule(
73 context,
74 security_group_rule)}
75
76@@ -336,6 +332,11 @@
77
78
79 class Security_groups(extensions.ExtensionDescriptor):
80+
81+ def __init__(self):
82+ self.compute_api = compute.API()
83+ super(Security_groups, self).__init__()
84+
85 def get_name(self):
86 return "SecurityGroups"
87
88@@ -351,6 +352,82 @@
89 def get_updated(self):
90 return "2011-07-21T00:00:00+00:00"
91
92+ def _addSecurityGroup(self, input_dict, req, instance_id):
93+ context = req.environ['nova.context']
94+
95+ try:
96+ body = input_dict['addSecurityGroup']
97+ group_name = body['name']
98+ instance_id = int(instance_id)
99+ except ValueError:
100+ msg = _("Server id should be integer")
101+ raise exc.HTTPBadRequest(explanation=msg)
102+ except TypeError:
103+ msg = _("Missing parameter dict")
104+ raise webob.exc.HTTPBadRequest(explanation=msg)
105+ except KeyError:
106+ msg = _("Security group not specified")
107+ raise webob.exc.HTTPBadRequest(explanation=msg)
108+
109+ if not group_name or group_name.strip() == '':
110+ msg = _("Security group name cannot be empty")
111+ raise webob.exc.HTTPBadRequest(explanation=msg)
112+
113+ try:
114+ self.compute_api.add_security_group(context, instance_id,
115+ group_name)
116+ except exception.SecurityGroupNotFound as exp:
117+ return exc.HTTPNotFound(explanation=unicode(exp))
118+ except exception.InstanceNotFound as exp:
119+ return exc.HTTPNotFound(explanation=unicode(exp))
120+ except exception.Invalid as exp:
121+ return exc.HTTPBadRequest(explanation=unicode(exp))
122+
123+ return exc.HTTPAccepted()
124+
125+ def _removeSecurityGroup(self, input_dict, req, instance_id):
126+ context = req.environ['nova.context']
127+
128+ try:
129+ body = input_dict['removeSecurityGroup']
130+ group_name = body['name']
131+ instance_id = int(instance_id)
132+ except ValueError:
133+ msg = _("Server id should be integer")
134+ raise exc.HTTPBadRequest(explanation=msg)
135+ except TypeError:
136+ msg = _("Missing parameter dict")
137+ raise webob.exc.HTTPBadRequest(explanation=msg)
138+ except KeyError:
139+ msg = _("Security group not specified")
140+ raise webob.exc.HTTPBadRequest(explanation=msg)
141+
142+ if not group_name or group_name.strip() == '':
143+ msg = _("Security group name cannot be empty")
144+ raise webob.exc.HTTPBadRequest(explanation=msg)
145+
146+ try:
147+ self.compute_api.remove_security_group(context, instance_id,
148+ group_name)
149+ except exception.SecurityGroupNotFound as exp:
150+ return exc.HTTPNotFound(explanation=unicode(exp))
151+ except exception.InstanceNotFound as exp:
152+ return exc.HTTPNotFound(explanation=unicode(exp))
153+ except exception.Invalid as exp:
154+ return exc.HTTPBadRequest(explanation=unicode(exp))
155+
156+ return exc.HTTPAccepted()
157+
158+ def get_actions(self):
159+ """Return the actions the extensions adds"""
160+ actions = [
161+ extensions.ActionExtension("servers", "addSecurityGroup",
162+ self._addSecurityGroup),
163+ extensions.ActionExtension("servers", "removeSecurityGroup",
164+ self._removeSecurityGroup)
165+ ]
166+ return actions
167+
168 def get_resources(self):
169 resources = []
170
171
172=== modified file 'nova/api/openstack/create_instance_helper.py'
173--- nova/api/openstack/create_instance_helper.py 2011-08-18 20:41:02 +0000
174+++ nova/api/openstack/create_instance_helper.py 2011-08-20 22:39:52 +0000
175@@ -111,6 +111,15 @@
176 if personality:
177 injected_files = self._get_injected_files(personality)
178
179+ sg_names = []
180+ security_groups = server_dict.get('security_groups')
181+ if security_groups is not None:
182+ sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
183+ if not sg_names:
184+ sg_names.append('default')
185+
186+ sg_names = list(set(sg_names))
187+
188 try:
189 flavor_id = self.controller._flavor_id_from_req_data(body)
190 except ValueError as error:
191@@ -164,6 +173,7 @@
192 reservation_id=reservation_id,
193 min_count=min_count,
194 max_count=max_count,
195+ security_group=sg_names,
196 user_data=user_data,
197 availability_zone=availability_zone))
198 except quota.QuotaError as error:
199@@ -174,6 +184,8 @@
200 except exception.FlavorNotFound as error:
201 msg = _("Invalid flavorRef provided.")
202 raise exc.HTTPBadRequest(explanation=msg)
203+ except exception.SecurityGroupNotFound as error:
204+ raise exc.HTTPBadRequest(explanation=unicode(error))
205 # Let the caller deal with unhandled exceptions.
206
207 def _handle_quota_error(self, error):
208@@ -465,6 +477,10 @@
209 if personality is not None:
210 server["personality"] = personality
211
212+ security_groups = self._extract_security_groups(server_node)
213+ if security_groups is not None:
214+ server["security_groups"] = security_groups
215+
216 return server
217
218 def _extract_personality(self, server_node):
219@@ -481,3 +497,18 @@
220 return personality
221 else:
222 return None
223+
224+ def _extract_security_groups(self, server_node):
225+ """Marshal the security_groups attribute of a parsed request"""
226+ node = self.find_first_child_named(server_node, "security_groups")
227+ if node is not None:
228+ security_groups = []
229+ for sg_node in self.find_children_named(node, "security_group"):
230+ item = {}
231+ name_node = self.find_first_child_named(sg_node, "name")
232+ if name_node:
233+ item["name"] = self.extract_text(name_node)
234+ security_groups.append(item)
235+ return security_groups
236+ else:
237+ return None
238
239=== modified file 'nova/compute/api.py'
240--- nova/compute/api.py 2011-08-19 18:37:39 +0000
241+++ nova/compute/api.py 2011-08-20 22:39:52 +0000
242@@ -613,6 +613,78 @@
243 self.db.queue_get_for(context, FLAGS.compute_topic, host),
244 {'method': 'refresh_provider_fw_rules', 'args': {}})
245
246+ def _is_security_group_associated_with_server(self, security_group,
247+ instance_id):
248+ """Check if the security group is already associated
249+ with the instance. If Yes, return True.
250+ """
251+
252+ if not security_group:
253+ return False
254+
255+ instances = security_group.get('instances')
256+ if not instances:
257+ return False
258+
259+ inst_id = None
260+ for inst_id in (instance['id'] for instance in instances \
261+ if instance_id == instance['id']):
262+ return True
263+
264+ return False
265+
266+ def add_security_group(self, context, instance_id, security_group_name):
267+ """Add security group to the instance"""
268+ security_group = db.security_group_get_by_name(context,
269+ context.project_id,
270+ security_group_name)
271+ # check if the server exists
272+ inst = db.instance_get(context, instance_id)
273+ #check if the security group is associated with the server
274+ if self._is_security_group_associated_with_server(security_group,
275+ instance_id):
276+ raise exception.SecurityGroupExistsForInstance(
277+ security_group_id=security_group['id'],
278+ instance_id=instance_id)
279+
280+ #check if the instance is in running state
281+ if inst['state'] != power_state.RUNNING:
282+ raise exception.InstanceNotRunning(instance_id=instance_id)
283+
284+ db.instance_add_security_group(context.elevated(),
285+ instance_id,
286+ security_group['id'])
287+ rpc.cast(context,
288+ db.queue_get_for(context, FLAGS.compute_topic, inst['host']),
289+ {"method": "refresh_security_group_rules",
290+ "args": {"security_group_id": security_group['id']}})
291+
292+ def remove_security_group(self, context, instance_id, security_group_name):
293+ """Remove the security group associated with the instance"""
294+ security_group = db.security_group_get_by_name(context,
295+ context.project_id,
296+ security_group_name)
297+ # check if the server exists
298+ inst = db.instance_get(context, instance_id)
299+ #check if the security group is associated with the server
300+ if not self._is_security_group_associated_with_server(security_group,
301+ instance_id):
302+ raise exception.SecurityGroupNotExistsForInstance(
303+ security_group_id=security_group['id'],
304+ instance_id=instance_id)
305+
306+ #check if the instance is in running state
307+ if inst['state'] != power_state.RUNNING:
308+ raise exception.InstanceNotRunning(instance_id=instance_id)
309+
310+ db.instance_remove_security_group(context.elevated(),
311+ instance_id,
312+ security_group['id'])
313+ rpc.cast(context,
314+ db.queue_get_for(context, FLAGS.compute_topic, inst['host']),
315+ {"method": "refresh_security_group_rules",
316+ "args": {"security_group_id": security_group['id']}})
317+
318 @scheduler_api.reroute_compute("update")
319 def update(self, context, instance_id, **kwargs):
320 """Updates the instance in the datastore.
321
322=== modified file 'nova/db/api.py'
323--- nova/db/api.py 2011-08-12 19:00:48 +0000
324+++ nova/db/api.py 2011-08-20 22:39:52 +0000
325@@ -570,6 +570,12 @@
326 security_group_id)
327
328
329+def instance_remove_security_group(context, instance_id, security_group_id):
330+ """Disassociate the given security group from the given instance."""
331+ return IMPL.instance_remove_security_group(context, instance_id,
332+ security_group_id)
333+
334+
335 def instance_action_create(context, values):
336 """Create an instance action from the values dictionary."""
337 return IMPL.instance_action_create(context, values)
338
339=== modified file 'nova/db/sqlalchemy/api.py'
340--- nova/db/sqlalchemy/api.py 2011-08-18 21:57:52 +0000
341+++ nova/db/sqlalchemy/api.py 2011-08-20 22:39:52 +0000
342@@ -1502,6 +1502,19 @@
343
344
345 @require_context
346+def instance_remove_security_group(context, instance_id, security_group_id):
347+ """Disassociate the given security group from the given instance"""
348+ session = get_session()
349+
350+ session.query(models.SecurityGroupInstanceAssociation).\
351+ filter_by(instance_id=instance_id).\
352+ filter_by(security_group_id=security_group_id).\
353+ update({'deleted': True,
354+ 'deleted_at': utils.utcnow(),
355+ 'updated_at': literal_column('updated_at')})
356+
357+
358+@require_context
359 def instance_action_create(context, values):
360 """Create an instance action from the values dictionary."""
361 action_ref = models.InstanceActions()
362@@ -2437,6 +2450,7 @@
363 filter_by(deleted=can_read_deleted(context),).\
364 filter_by(id=security_group_id).\
365 options(joinedload_all('rules')).\
366+ options(joinedload_all('instances')).\
367 first()
368 else:
369 result = session.query(models.SecurityGroup).\
370@@ -2444,6 +2458,7 @@
371 filter_by(id=security_group_id).\
372 filter_by(project_id=context.project_id).\
373 options(joinedload_all('rules')).\
374+ options(joinedload_all('instances')).\
375 first()
376 if not result:
377 raise exception.SecurityGroupNotFound(
378
379=== modified file 'nova/exception.py'
380--- nova/exception.py 2011-08-16 12:47:35 +0000
381+++ nova/exception.py 2011-08-20 22:39:52 +0000
382@@ -541,6 +541,16 @@
383 message = _("Security group with rule %(rule_id)s not found.")
384
385
386+class SecurityGroupExistsForInstance(Invalid):
387+ message = _("Security group %(security_group_id)s is already associated"
388+ " with the instance %(instance_id)s")
389+
390+
391+class SecurityGroupNotExistsForInstance(Invalid):
392+ message = _("Security group %(security_group_id)s is not associated with"
393+ " the instance %(instance_id)s")
394+
395+
396 class MigrationNotFound(NotFound):
397 message = _("Migration %(migration_id)s could not be found.")
398
399
400=== modified file 'nova/tests/api/openstack/contrib/test_security_groups.py'
401--- nova/tests/api/openstack/contrib/test_security_groups.py 2011-08-12 00:04:33 +0000
402+++ nova/tests/api/openstack/contrib/test_security_groups.py 2011-08-20 22:39:52 +0000
403@@ -15,10 +15,13 @@
404 # under the License.
405
406 import json
407+import mox
408+import nova
409 import unittest
410 import webob
411 from xml.dom import minidom
412
413+from nova import exception
414 from nova import test
415 from nova.api.openstack.contrib import security_groups
416 from nova.tests.api.openstack import fakes
417@@ -51,6 +54,28 @@
418 return {'security_group': sg}
419
420
421+def return_server(context, server_id):
422+ return {'id': server_id, 'state': 0x01, 'host': "localhost"}
423+
424+
425+def return_non_running_server(context, server_id):
426+ return {'id': server_id, 'state': 0x02,
427+ 'host': "localhost"}
428+
429+
430+def return_security_group(context, project_id, group_name):
431+ return {'id': 1, 'name': group_name, "instances": [
432+ {'id': 1}]}
433+
434+
435+def return_security_group_without_instances(context, project_id, group_name):
436+ return {'id': 1, 'name': group_name}
437+
438+
439+def return_server_nonexistant(context, server_id):
440+ raise exception.InstanceNotFound(instance_id=server_id)
441+
442+
443 class TestSecurityGroups(test.TestCase):
444 def setUp(self):
445 super(TestSecurityGroups, self).setUp()
446@@ -325,6 +350,252 @@
447 response = self._delete_security_group(11111111)
448 self.assertEquals(response.status_int, 404)
449
450+ def test_associate_by_non_existing_security_group_name(self):
451+ body = dict(addSecurityGroup=dict(name='non-existing'))
452+ req = webob.Request.blank('/v1.1/servers/1/action')
453+ req.headers['Content-Type'] = 'application/json'
454+ req.method = 'POST'
455+ req.body = json.dumps(body)
456+ response = req.get_response(fakes.wsgi_app())
457+ self.assertEquals(response.status_int, 404)
458+
459+ def test_associate_by_invalid_server_id(self):
460+ body = dict(addSecurityGroup=dict(name='test'))
461+ self.stubs.Set(nova.db, 'security_group_get_by_name',
462+ return_security_group)
463+ req = webob.Request.blank('/v1.1/servers/invalid/action')
464+ req.headers['Content-Type'] = 'application/json'
465+ req.method = 'POST'
466+ req.body = json.dumps(body)
467+ response = req.get_response(fakes.wsgi_app())
468+ self.assertEquals(response.status_int, 400)
469+
470+ def test_associate_without_body(self):
471+ req = webob.Request.blank('/v1.1/servers/1/action')
472+ body = dict(addSecurityGroup=None)
473+ self.stubs.Set(nova.db, 'instance_get', return_server)
474+ req.headers['Content-Type'] = 'application/json'
475+ req.method = 'POST'
476+ req.body = json.dumps(body)
477+ response = req.get_response(fakes.wsgi_app())
478+ self.assertEquals(response.status_int, 400)
479+
480+ def test_associate_no_security_group_name(self):
481+ req = webob.Request.blank('/v1.1/servers/1/action')
482+ body = dict(addSecurityGroup=dict())
483+ self.stubs.Set(nova.db, 'instance_get', return_server)
484+ req.headers['Content-Type'] = 'application/json'
485+ req.method = 'POST'
486+ req.body = json.dumps(body)
487+ response = req.get_response(fakes.wsgi_app())
488+ self.assertEquals(response.status_int, 400)
489+
490+ def test_associate_security_group_name_with_whitespaces(self):
491+ req = webob.Request.blank('/v1.1/servers/1/action')
492+ body = dict(addSecurityGroup=dict(name=" "))
493+ self.stubs.Set(nova.db, 'instance_get', return_server)
494+ req.headers['Content-Type'] = 'application/json'
495+ req.method = 'POST'
496+ req.body = json.dumps(body)
497+ response = req.get_response(fakes.wsgi_app())
498+ self.assertEquals(response.status_int, 400)
499+
500+ def test_associate_non_existing_instance(self):
501+ self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant)
502+ body = dict(addSecurityGroup=dict(name="test"))
503+ self.stubs.Set(nova.db, 'security_group_get_by_name',
504+ return_security_group)
505+ req = webob.Request.blank('/v1.1/servers/10000/action')
506+ req.headers['Content-Type'] = 'application/json'
507+ req.method = 'POST'
508+ req.body = json.dumps(body)
509+ response = req.get_response(fakes.wsgi_app())
510+ self.assertEquals(response.status_int, 404)
511+
512+ def test_associate_non_running_instance(self):
513+ self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
514+ self.stubs.Set(nova.db, 'security_group_get_by_name',
515+ return_security_group_without_instances)
516+ body = dict(addSecurityGroup=dict(name="test"))
517+ req = webob.Request.blank('/v1.1/servers/1/action')
518+ req.headers['Content-Type'] = 'application/json'
519+ req.method = 'POST'
520+ req.body = json.dumps(body)
521+ response = req.get_response(fakes.wsgi_app())
522+ self.assertEquals(response.status_int, 400)
523+
524+ def test_associate_already_associated_security_group_to_instance(self):
525+ self.stubs.Set(nova.db, 'instance_get', return_server)
526+ self.stubs.Set(nova.db, 'security_group_get_by_name',
527+ return_security_group)
528+ body = dict(addSecurityGroup=dict(name="test"))
529+ req = webob.Request.blank('/v1.1/servers/1/action')
530+ req.headers['Content-Type'] = 'application/json'
531+ req.method = 'POST'
532+ req.body = json.dumps(body)
533+ response = req.get_response(fakes.wsgi_app())
534+ self.assertEquals(response.status_int, 400)
535+
536+ def test_associate(self):
537+ self.stubs.Set(nova.db, 'instance_get', return_server)
538+ self.mox.StubOutWithMock(nova.db, 'instance_add_security_group')
539+ nova.db.instance_add_security_group(mox.IgnoreArg(),
540+ mox.IgnoreArg(),
541+ mox.IgnoreArg())
542+ self.stubs.Set(nova.db, 'security_group_get_by_name',
543+ return_security_group_without_instances)
544+ self.mox.ReplayAll()
545+
546+ body = dict(addSecurityGroup=dict(name="test"))
547+ req = webob.Request.blank('/v1.1/servers/1/action')
548+ req.headers['Content-Type'] = 'application/json'
549+ req.method = 'POST'
550+ req.body = json.dumps(body)
551+ response = req.get_response(fakes.wsgi_app())
552+ self.assertEquals(response.status_int, 202)
553+
554+ def test_associate_xml(self):
555+ self.stubs.Set(nova.db, 'instance_get', return_server)
556+ self.mox.StubOutWithMock(nova.db, 'instance_add_security_group')
557+ nova.db.instance_add_security_group(mox.IgnoreArg(),
558+ mox.IgnoreArg(),
559+ mox.IgnoreArg())
560+ self.stubs.Set(nova.db, 'security_group_get_by_name',
561+ return_security_group_without_instances)
562+ self.mox.ReplayAll()
563+
564+ req = webob.Request.blank('/v1.1/servers/1/action')
565+ req.headers['Content-Type'] = 'application/xml'
566+ req.method = 'POST'
567+ req.body = """<addSecurityGroup>
568+ <name>test</name>
569+ </addSecurityGroup>"""
570+ response = req.get_response(fakes.wsgi_app())
571+ self.assertEquals(response.status_int, 202)
572+
573+ def test_disassociate_by_non_existing_security_group_name(self):
574+ body = dict(removeSecurityGroup=dict(name='non-existing'))
575+ req = webob.Request.blank('/v1.1/servers/1/action')
576+ req.headers['Content-Type'] = 'application/json'
577+ req.method = 'POST'
578+ req.body = json.dumps(body)
579+ response = req.get_response(fakes.wsgi_app())
580+ self.assertEquals(response.status_int, 404)
581+
582+ def test_disassociate_by_invalid_server_id(self):
583+ body = dict(removeSecurityGroup=dict(name='test'))
584+ self.stubs.Set(nova.db, 'security_group_get_by_name',
585+ return_security_group)
586+ req = webob.Request.blank('/v1.1/servers/invalid/action')
587+ req.headers['Content-Type'] = 'application/json'
588+ req.method = 'POST'
589+ req.body = json.dumps(body)
590+ response = req.get_response(fakes.wsgi_app())
591+ self.assertEquals(response.status_int, 400)
592+
593+ def test_disassociate_without_body(self):
594+ req = webob.Request.blank('/v1.1/servers/1/action')
595+ body = dict(removeSecurityGroup=None)
596+ self.stubs.Set(nova.db, 'instance_get', return_server)
597+ req.headers['Content-Type'] = 'application/json'
598+ req.method = 'POST'
599+ req.body = json.dumps(body)
600+ response = req.get_response(fakes.wsgi_app())
601+ self.assertEquals(response.status_int, 400)
602+
603+ def test_disassociate_no_security_group_name(self):
604+ req = webob.Request.blank('/v1.1/servers/1/action')
605+ body = dict(removeSecurityGroup=dict())
606+ self.stubs.Set(nova.db, 'instance_get', return_server)
607+ req.headers['Content-Type'] = 'application/json'
608+ req.method = 'POST'
609+ req.body = json.dumps(body)
610+ response = req.get_response(fakes.wsgi_app())
611+ self.assertEquals(response.status_int, 400)
612+
613+ def test_disassociate_security_group_name_with_whitespaces(self):
614+ req = webob.Request.blank('/v1.1/servers/1/action')
615+ body = dict(removeSecurityGroup=dict(name=" "))
616+ self.stubs.Set(nova.db, 'instance_get', return_server)
617+ req.headers['Content-Type'] = 'application/json'
618+ req.method = 'POST'
619+ req.body = json.dumps(body)
620+ response = req.get_response(fakes.wsgi_app())
621+ self.assertEquals(response.status_int, 400)
622+
623+ def test_disassociate_non_existing_instance(self):
624+ self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant)
625+ body = dict(removeSecurityGroup=dict(name="test"))
626+ self.stubs.Set(nova.db, 'security_group_get_by_name',
627+ return_security_group)
628+ req = webob.Request.blank('/v1.1/servers/10000/action')
629+ req.headers['Content-Type'] = 'application/json'
630+ req.method = 'POST'
631+ req.body = json.dumps(body)
632+ response = req.get_response(fakes.wsgi_app())
633+ self.assertEquals(response.status_int, 404)
634+
635+ def test_disassociate_non_running_instance(self):
636+ self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
637+ self.stubs.Set(nova.db, 'security_group_get_by_name',
638+ return_security_group)
639+ body = dict(removeSecurityGroup=dict(name="test"))
640+ req = webob.Request.blank('/v1.1/servers/1/action')
641+ req.headers['Content-Type'] = 'application/json'
642+ req.method = 'POST'
643+ req.body = json.dumps(body)
644+ response = req.get_response(fakes.wsgi_app())
645+ self.assertEquals(response.status_int, 400)
646+
647+ def test_disassociate_already_associated_security_group_to_instance(self):
648+ self.stubs.Set(nova.db, 'instance_get', return_server)
649+ self.stubs.Set(nova.db, 'security_group_get_by_name',
650+ return_security_group_without_instances)
651+ body = dict(removeSecurityGroup=dict(name="test"))
652+ req = webob.Request.blank('/v1.1/servers/1/action')
653+ req.headers['Content-Type'] = 'application/json'
654+ req.method = 'POST'
655+ req.body = json.dumps(body)
656+ response = req.get_response(fakes.wsgi_app())
657+ self.assertEquals(response.status_int, 400)
658+
659+ def test_disassociate(self):
660+ self.stubs.Set(nova.db, 'instance_get', return_server)
661+ self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group')
662+ nova.db.instance_remove_security_group(mox.IgnoreArg(),
663+ mox.IgnoreArg(),
664+ mox.IgnoreArg())
665+ self.stubs.Set(nova.db, 'security_group_get_by_name',
666+ return_security_group)
667+ self.mox.ReplayAll()
668+
669+ body = dict(removeSecurityGroup=dict(name="test"))
670+ req = webob.Request.blank('/v1.1/servers/1/action')
671+ req.headers['Content-Type'] = 'application/json'
672+ req.method = 'POST'
673+ req.body = json.dumps(body)
674+ response = req.get_response(fakes.wsgi_app())
675+ self.assertEquals(response.status_int, 202)
676+
677+ def test_disassociate_xml(self):
678+ self.stubs.Set(nova.db, 'instance_get', return_server)
679+ self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group')
680+ nova.db.instance_remove_security_group(mox.IgnoreArg(),
681+ mox.IgnoreArg(),
682+ mox.IgnoreArg())
683+ self.stubs.Set(nova.db, 'security_group_get_by_name',
684+ return_security_group)
685+ self.mox.ReplayAll()
686+
687+ req = webob.Request.blank('/v1.1/servers/1/action')
688+ req.headers['Content-Type'] = 'application/xml'
689+ req.method = 'POST'
690+ req.body = """<removeSecurityGroup>
691+ <name>test</name>
692+ </removeSecurityGroup>"""
693+ response = req.get_response(fakes.wsgi_app())
694+ self.assertEquals(response.status_int, 202)
695+
696
697 class TestSecurityGroupRules(test.TestCase):
698 def setUp(self):