Merge lp:~tpatil/nova/add-remove-securitygroup-instance into lp:~hudson-openstack/nova/trunk
- add-remove-securitygroup-instance
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jesse Andrews (community) | Approve | ||
Vish Ishaya (community) | Approve | ||
Review via email: mp+71967@code.launchpad.net |
Commit message
Description of the change
Added OS APIs to associate/
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:/
Jesse Andrews (anotherjesse) wrote : | # |
We need to update https:/
Jesse Andrews (anotherjesse) wrote : | # |
As noted by bcwaldon & blamar in https:/
166 +
167 + def get_actions(self):
168 + """Return the actions the extension adds, as required by contract."""
169 + actions = [
170 + extensions.
171 + self._add_
172 + extensions.
173 + self._remove_
174 + ]
175 +
176 + return actions
Other than that LGTM
Tushar Patil (tpatil) wrote : | # |
> As noted by bcwaldon & blamar in
> https:/
> 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.
> "addFloatingIp",
> 171 + self._add_
> 172 + extensions.
> "removeFloatingIp",
> 173 +
> self._remove_
> 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.
Preview Diff
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): |
Awesome stuff.
Nice tests.