Revision 94537e34
/dev/null | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
|
|
36 |
from snf_django.utils.testing import BaseAPITest |
|
37 |
from synnefo.lib.services import get_service_path |
|
38 |
from synnefo.cyclades_settings import cyclades_services |
|
39 |
from synnefo.lib import join_urls |
|
40 |
|
|
41 |
COMPUTE_URL = get_service_path(cyclades_services, 'compute', |
|
42 |
version='v2.0') |
|
43 |
EXTENSIONS_URL = join_urls(COMPUTE_URL, "extensions/") |
|
44 |
|
|
45 |
|
|
46 |
class ExtensionsAPITest(BaseAPITest): |
|
47 |
def test_list(self): |
|
48 |
response = self.get(EXTENSIONS_URL, "user") |
|
49 |
self.assertSuccess(response) |
|
50 |
extensions = json.loads(response.content)["extensions"] |
|
51 |
self.assertEqual(extensions, []) |
|
52 |
|
|
53 |
def test_get(self): |
|
54 |
response = self.get(join_urls(EXTENSIONS_URL, "SNF"), "user") |
|
55 |
self.assertEqual(response.status_code, 404) |
|
56 |
response = self.get(join_urls(EXTENSIONS_URL, "SNF_asfas_da"), "user") |
|
57 |
self.assertEqual(response.status_code, 404) |
|
58 |
response = self.get(join_urls(EXTENSIONS_URL, "SNF-AD"), "user") |
|
59 |
self.assertEqual(response.status_code, 404) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
|
|
36 |
from snf_django.utils.testing import BaseAPITest |
|
37 |
from synnefo.db.models import Flavor |
|
38 |
from synnefo.db.models_factory import FlavorFactory |
|
39 |
from synnefo.lib.services import get_service_path |
|
40 |
from synnefo.cyclades_settings import cyclades_services |
|
41 |
from synnefo.lib import join_urls |
|
42 |
|
|
43 |
|
|
44 |
class FlavorAPITest(BaseAPITest): |
|
45 |
|
|
46 |
def setUp(self): |
|
47 |
self.flavor1 = FlavorFactory() |
|
48 |
self.flavor2 = FlavorFactory(deleted=True) |
|
49 |
self.flavor3 = FlavorFactory() |
|
50 |
self.compute_path = get_service_path(cyclades_services, 'compute', |
|
51 |
version='v2.0') |
|
52 |
|
|
53 |
|
|
54 |
def myget(self, path): |
|
55 |
path = join_urls(self.compute_path, path) |
|
56 |
return self.get(path) |
|
57 |
|
|
58 |
|
|
59 |
def test_flavor_list(self): |
|
60 |
"""Test if the expected list of flavors is returned.""" |
|
61 |
response = self.myget('flavors') |
|
62 |
self.assertSuccess(response) |
|
63 |
|
|
64 |
api_flavors = json.loads(response.content)['flavors'] |
|
65 |
db_flavors = Flavor.objects.filter(deleted=False) |
|
66 |
self.assertEqual(len(api_flavors), len(db_flavors)) |
|
67 |
for api_flavor in api_flavors: |
|
68 |
db_flavor = Flavor.objects.get(id=api_flavor['id']) |
|
69 |
self.assertEqual(api_flavor['id'], db_flavor.id) |
|
70 |
self.assertEqual(api_flavor['name'], db_flavor.name) |
|
71 |
|
|
72 |
def test_flavors_details(self): |
|
73 |
"""Test if the flavors details are returned.""" |
|
74 |
response = self.myget('flavors/detail') |
|
75 |
self.assertSuccess(response) |
|
76 |
|
|
77 |
db_flavors = Flavor.objects.filter(deleted=False) |
|
78 |
api_flavors = json.loads(response.content)['flavors'] |
|
79 |
|
|
80 |
self.assertEqual(len(db_flavors), len(api_flavors)) |
|
81 |
|
|
82 |
for i in range(0, len(db_flavors)): |
|
83 |
api_flavor = api_flavors[i] |
|
84 |
db_flavor = Flavor.objects.get(id=db_flavors[i].id) |
|
85 |
self.assertEqual(api_flavor['vcpus'], db_flavor.cpu) |
|
86 |
self.assertEqual(api_flavor['id'], db_flavor.id) |
|
87 |
self.assertEqual(api_flavor['disk'], db_flavor.disk) |
|
88 |
self.assertEqual(api_flavor['name'], db_flavor.name) |
|
89 |
self.assertEqual(api_flavor['ram'], db_flavor.ram) |
|
90 |
self.assertEqual(api_flavor['SNF:disk_template'], |
|
91 |
db_flavor.disk_template) |
|
92 |
|
|
93 |
def test_flavor_details(self): |
|
94 |
"""Test if the expected flavor is returned.""" |
|
95 |
flavor = self.flavor3 |
|
96 |
|
|
97 |
response = self.myget('flavors/%d' % flavor.id) |
|
98 |
self.assertSuccess(response) |
|
99 |
|
|
100 |
api_flavor = json.loads(response.content)['flavor'] |
|
101 |
db_flavor = Flavor.objects.get(id=flavor.id) |
|
102 |
self.assertEqual(api_flavor['vcpus'], db_flavor.cpu) |
|
103 |
self.assertEqual(api_flavor['id'], db_flavor.id) |
|
104 |
self.assertEqual(api_flavor['disk'], db_flavor.disk) |
|
105 |
self.assertEqual(api_flavor['name'], db_flavor.name) |
|
106 |
self.assertEqual(api_flavor['ram'], db_flavor.ram) |
|
107 |
self.assertEqual(api_flavor['SNF:disk_template'], |
|
108 |
db_flavor.disk_template) |
|
109 |
|
|
110 |
def test_deleted_flavor_details(self): |
|
111 |
"""Test that API returns details for deleted flavors""" |
|
112 |
flavor = self.flavor2 |
|
113 |
response = self.myget('flavors/%d' % flavor.id) |
|
114 |
self.assertSuccess(response) |
|
115 |
api_flavor = json.loads(response.content)['flavor'] |
|
116 |
self.assertEquals(api_flavor['name'], flavor.name) |
|
117 |
|
|
118 |
def test_deleted_flavors_list(self): |
|
119 |
"""Test that deleted flavors do not appear to flavors list""" |
|
120 |
response = self.myget('flavors') |
|
121 |
self.assertSuccess(response) |
|
122 |
api_flavors = json.loads(response.content)['flavors'] |
|
123 |
self.assertEqual(len(api_flavors), 2) |
|
124 |
|
|
125 |
def test_deleted_flavors_details(self): |
|
126 |
"""Test that deleted flavors do not appear to flavors detail list""" |
|
127 |
FlavorFactory(deleted=True) |
|
128 |
response = self.myget('flavors/detail') |
|
129 |
self.assertSuccess(response) |
|
130 |
api_flavors = json.loads(response.content)['flavors'] |
|
131 |
self.assertEqual(len(api_flavors), 2) |
|
132 |
|
|
133 |
def test_wrong_flavor(self): |
|
134 |
"""Test 404 result when requesting a flavor that does not exist.""" |
|
135 |
|
|
136 |
# XXX: flavors/22 below fails for no apparent reason |
|
137 |
response = self.myget('flavors/%d' % 23) |
|
138 |
self.assertItemNotFound(response) |
|
139 |
|
|
140 |
def test_catch_wrong_api_paths(self, *args): |
|
141 |
response = self.myget('nonexistent') |
|
142 |
self.assertEqual(response.status_code, 400) |
|
143 |
try: |
|
144 |
error = json.loads(response.content) |
|
145 |
except ValueError: |
|
146 |
self.assertTrue(False) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
|
|
36 |
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder |
|
37 |
from synnefo.db.models import FloatingIP |
|
38 |
from synnefo.db.models_factory import (FloatingIPFactory, NetworkFactory, |
|
39 |
VirtualMachineFactory, |
|
40 |
NetworkInterfaceFactory, |
|
41 |
BackendNetworkFactory) |
|
42 |
from mock import patch, Mock |
|
43 |
from functools import partial |
|
44 |
|
|
45 |
from synnefo.cyclades_settings import cyclades_services |
|
46 |
from synnefo.lib.services import get_service_path |
|
47 |
from synnefo.lib import join_urls |
|
48 |
|
|
49 |
|
|
50 |
compute_path = get_service_path(cyclades_services, "compute", version="v2.0") |
|
51 |
URL = join_urls(compute_path, "os-floating-ips") |
|
52 |
NETWORKS_URL = join_urls(compute_path, "networks") |
|
53 |
SERVERS_URL = join_urls(compute_path, "servers") |
|
54 |
|
|
55 |
FloatingIPPoolFactory = partial(NetworkFactory, public=True, deleted=False, |
|
56 |
floating_ip_pool=True) |
|
57 |
|
|
58 |
|
|
59 |
class FloatingIPAPITest(BaseAPITest): |
|
60 |
def test_no_floating_ip(self): |
|
61 |
response = self.get(URL) |
|
62 |
self.assertSuccess(response) |
|
63 |
self.assertEqual(json.loads(response.content)["floating_ips"], []) |
|
64 |
|
|
65 |
def test_list_ips(self): |
|
66 |
ip = FloatingIPFactory(userid="user1") |
|
67 |
FloatingIPFactory(userid="user1", deleted=True) |
|
68 |
with mocked_quotaholder(): |
|
69 |
response = self.get(URL, "user1") |
|
70 |
self.assertSuccess(response) |
|
71 |
api_ip = json.loads(response.content)["floating_ips"][0] |
|
72 |
self.assertEqual(api_ip, |
|
73 |
{"instance_id": str(ip.machine.id), "ip": ip.ipv4, |
|
74 |
"fixed_ip": None, "id": str(ip.id), "pool": |
|
75 |
str(ip.network.id)}) |
|
76 |
|
|
77 |
def test_get_ip(self): |
|
78 |
ip = FloatingIPFactory(userid="user1") |
|
79 |
with mocked_quotaholder(): |
|
80 |
response = self.get(URL + "/%s" % ip.id, "user1") |
|
81 |
self.assertSuccess(response) |
|
82 |
api_ip = json.loads(response.content)["floating_ip"] |
|
83 |
self.assertEqual(api_ip, |
|
84 |
{"instance_id": str(ip.machine.id), "ip": ip.ipv4, |
|
85 |
"fixed_ip": None, "id": str(ip.id), "pool": |
|
86 |
str(ip.network.id)}) |
|
87 |
|
|
88 |
def test_wrong_user(self): |
|
89 |
ip = FloatingIPFactory(userid="user1") |
|
90 |
with mocked_quotaholder(): |
|
91 |
response = self.delete(URL + "/%s" % ip.id, "user2") |
|
92 |
self.assertItemNotFound(response) |
|
93 |
|
|
94 |
def test_deleted_ip(self): |
|
95 |
ip = FloatingIPFactory(userid="user1", deleted=True) |
|
96 |
with mocked_quotaholder(): |
|
97 |
response = self.delete(URL + "/%s" % ip.id, "user1") |
|
98 |
self.assertItemNotFound(response) |
|
99 |
|
|
100 |
def test_reserve(self): |
|
101 |
net = FloatingIPPoolFactory(userid="test_user", |
|
102 |
subnet="192.168.2.0/24", |
|
103 |
gateway=None) |
|
104 |
request = {'pool': net.id} |
|
105 |
with mocked_quotaholder(): |
|
106 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
107 |
self.assertSuccess(response) |
|
108 |
ip = FloatingIP.objects.get() |
|
109 |
self.assertEqual(ip.ipv4, "192.168.2.1") |
|
110 |
self.assertEqual(ip.machine, None) |
|
111 |
self.assertEqual(ip.network, net) |
|
112 |
self.assertEqual(json.loads(response.content)["floating_ip"], |
|
113 |
{"instance_id": None, "ip": "192.168.2.1", |
|
114 |
"fixed_ip": None, "id": "1", "pool": "1"}) |
|
115 |
|
|
116 |
def test_reserve_no_pool(self): |
|
117 |
# No networks |
|
118 |
with mocked_quotaholder(): |
|
119 |
response = self.post(URL, "test_user", json.dumps({}), "json") |
|
120 |
self.assertFault(response, 413, "overLimit") |
|
121 |
# Full network |
|
122 |
FloatingIPPoolFactory(userid="test_user", |
|
123 |
subnet="192.168.2.0/32", |
|
124 |
gateway=None) |
|
125 |
with mocked_quotaholder(): |
|
126 |
response = self.post(URL, "test_user", json.dumps({}), "json") |
|
127 |
self.assertFault(response, 413, "overLimit") |
|
128 |
# Success |
|
129 |
net2 = FloatingIPPoolFactory(userid="test_user", |
|
130 |
subnet="192.168.2.0/24", |
|
131 |
gateway=None) |
|
132 |
with mocked_quotaholder(): |
|
133 |
response = self.post(URL, "test_user", json.dumps({}), "json") |
|
134 |
self.assertSuccess(response) |
|
135 |
self.assertEqual(json.loads(response.content)["floating_ip"], |
|
136 |
{"instance_id": None, "ip": "192.168.2.1", |
|
137 |
"fixed_ip": None, "id": "1", "pool": str(net2.id)}) |
|
138 |
|
|
139 |
def test_reserve_full(self): |
|
140 |
net = FloatingIPPoolFactory(userid="test_user", |
|
141 |
subnet="192.168.2.0/32") |
|
142 |
request = {'pool': net.id} |
|
143 |
with mocked_quotaholder(): |
|
144 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
145 |
self.assertEqual(response.status_code, 413) |
|
146 |
|
|
147 |
def test_reserve_with_address(self): |
|
148 |
net = FloatingIPPoolFactory(userid="test_user", |
|
149 |
subnet="192.168.2.0/24") |
|
150 |
request = {'pool': net.id, "address": "192.168.2.10"} |
|
151 |
with mocked_quotaholder(): |
|
152 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
153 |
self.assertSuccess(response) |
|
154 |
self.assertEqual(json.loads(response.content)["floating_ip"], |
|
155 |
{"instance_id": None, "ip": "192.168.2.10", |
|
156 |
"fixed_ip": None, "id": "1", "pool": "1"}) |
|
157 |
|
|
158 |
# Already reserved |
|
159 |
FloatingIPFactory(network=net, ipv4="192.168.2.3") |
|
160 |
request = {'pool': net.id, "address": "192.168.2.3"} |
|
161 |
with mocked_quotaholder(): |
|
162 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
163 |
self.assertFault(response, 409, "conflict") |
|
164 |
|
|
165 |
# Already used |
|
166 |
pool = net.get_pool() |
|
167 |
pool.reserve("192.168.2.5") |
|
168 |
pool.save() |
|
169 |
# ..by another_user |
|
170 |
nic = NetworkInterfaceFactory(network=net, ipv4="192.168.2.5", |
|
171 |
machine__userid="test2") |
|
172 |
request = {'pool': net.id, "address": "192.168.2.5"} |
|
173 |
with mocked_quotaholder(): |
|
174 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
175 |
self.assertFault(response, 409, "conflict") |
|
176 |
# ..and by him |
|
177 |
nic.delete() |
|
178 |
NetworkInterfaceFactory(network=net, ipv4="192.168.2.5", |
|
179 |
machine__userid="test_user") |
|
180 |
request = {'pool': net.id, "address": "192.168.2.5"} |
|
181 |
with mocked_quotaholder(): |
|
182 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
183 |
self.assertSuccess(response) |
|
184 |
|
|
185 |
# Address out of pool |
|
186 |
request = {'pool': net.id, "address": "192.168.3.5"} |
|
187 |
with mocked_quotaholder(): |
|
188 |
response = self.post(URL, "test_user", json.dumps(request), "json") |
|
189 |
self.assertBadRequest(response) |
|
190 |
|
|
191 |
def test_release_in_use(self): |
|
192 |
ip = FloatingIPFactory() |
|
193 |
vm = ip.machine |
|
194 |
vm.operstate = "ACTIVE" |
|
195 |
vm.userid = ip.userid |
|
196 |
vm.save() |
|
197 |
vm.nics.create(index=0, ipv4=ip.ipv4, network=ip.network, |
|
198 |
state="ACTIVE") |
|
199 |
with mocked_quotaholder(): |
|
200 |
response = self.delete(URL + "/%s" % ip.id, ip.userid) |
|
201 |
self.assertFault(response, 409, "conflict") |
|
202 |
# Also send a notification to remove the NIC and assert that FIP is in |
|
203 |
# use until notification from ganeti arrives |
|
204 |
request = {"removeFloatingIp": {"address": ip.ipv4}} |
|
205 |
url = SERVERS_URL + "/%s/action" % vm.id |
|
206 |
with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c: |
|
207 |
c().ModifyInstance.return_value = 10 |
|
208 |
response = self.post(url, vm.userid, json.dumps(request), |
|
209 |
"json") |
|
210 |
self.assertEqual(response.status_code, 202) |
|
211 |
with mocked_quotaholder(): |
|
212 |
response = self.delete(URL + "/%s" % ip.id, ip.userid) |
|
213 |
self.assertFault(response, 409, "conflict") |
|
214 |
|
|
215 |
def test_release(self): |
|
216 |
ip = FloatingIPFactory(machine=None) |
|
217 |
with mocked_quotaholder(): |
|
218 |
response = self.delete(URL + "/%s" % ip.id, ip.userid) |
|
219 |
self.assertSuccess(response) |
|
220 |
ips_after = FloatingIP.objects.filter(id=ip.id) |
|
221 |
self.assertEqual(len(ips_after), 0) |
|
222 |
|
|
223 |
@patch("synnefo.logic.backend", Mock()) |
|
224 |
def test_delete_network_with_floating_ips(self): |
|
225 |
ip = FloatingIPFactory(machine=None, network__flavor="IP_LESS_ROUTED") |
|
226 |
net = ip.network |
|
227 |
# Can not remove network with floating IPs |
|
228 |
with mocked_quotaholder(): |
|
229 |
response = self.delete(NETWORKS_URL + "/%s" % net.id, |
|
230 |
net.userid) |
|
231 |
self.assertFault(response, 421, "networkInUse") |
|
232 |
# But we can with only deleted Floating Ips |
|
233 |
ip.deleted = True |
|
234 |
ip.save() |
|
235 |
with mocked_quotaholder(): |
|
236 |
response = self.delete(NETWORKS_URL + "/%s" % net.id, |
|
237 |
net.userid) |
|
238 |
self.assertSuccess(response) |
|
239 |
|
|
240 |
|
|
241 |
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools") |
|
242 |
|
|
243 |
|
|
244 |
class FloatingIPPoolsAPITest(BaseAPITest): |
|
245 |
def test_no_pool(self): |
|
246 |
response = self.get(POOLS_URL) |
|
247 |
self.assertSuccess(response) |
|
248 |
self.assertEqual(json.loads(response.content)["floating_ip_pools"], []) |
|
249 |
|
|
250 |
def test_list_pools(self): |
|
251 |
net = FloatingIPPoolFactory(subnet="192.168.0.0/30", |
|
252 |
gateway="192.168.0.1") |
|
253 |
NetworkFactory(public=True, deleted=True) |
|
254 |
NetworkFactory(public=False, deleted=False) |
|
255 |
NetworkFactory(public=True, deleted=False) |
|
256 |
response = self.get(POOLS_URL) |
|
257 |
self.assertSuccess(response) |
|
258 |
self.assertEqual(json.loads(response.content)["floating_ip_pools"], |
|
259 |
[{"name": str(net.id), "size": 4, "free": 1}]) |
|
260 |
|
|
261 |
|
|
262 |
class FloatingIPActionsTest(BaseAPITest): |
|
263 |
def setUp(self): |
|
264 |
vm = VirtualMachineFactory() |
|
265 |
vm.operstate = "ACTIVE" |
|
266 |
vm.save() |
|
267 |
self.vm = vm |
|
268 |
|
|
269 |
def test_bad_request(self): |
|
270 |
url = SERVERS_URL + "/%s/action" % self.vm.id |
|
271 |
response = self.post(url, self.vm.userid, json.dumps({}), "json") |
|
272 |
self.assertBadRequest(response) |
|
273 |
response = self.post(url, self.vm.userid, |
|
274 |
json.dumps({"addFloatingIp": {}}), |
|
275 |
"json") |
|
276 |
self.assertBadRequest(response) |
|
277 |
|
|
278 |
@patch('synnefo.logic.rapi_pool.GanetiRapiClient') |
|
279 |
def test_add_floating_ip(self, mock): |
|
280 |
# Not exists |
|
281 |
url = SERVERS_URL + "/%s/action" % self.vm.id |
|
282 |
request = {"addFloatingIp": {"address": "10.0.0.1"}} |
|
283 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
284 |
self.assertItemNotFound(response) |
|
285 |
# In use |
|
286 |
vm1 = VirtualMachineFactory() |
|
287 |
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=vm1) |
|
288 |
BackendNetworkFactory(network=ip1.network, backend=vm1.backend, |
|
289 |
operstate='ACTIVE') |
|
290 |
request = {"addFloatingIp": {"address": ip1.ipv4}} |
|
291 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
292 |
self.assertFault(response, 409, "conflict") |
|
293 |
# Success |
|
294 |
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None) |
|
295 |
BackendNetworkFactory(network=ip1.network, backend=self.vm.backend, |
|
296 |
operstate='ACTIVE') |
|
297 |
request = {"addFloatingIp": {"address": ip1.ipv4}} |
|
298 |
mock().ModifyInstance.return_value = 1 |
|
299 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
300 |
self.assertEqual(response.status_code, 202) |
|
301 |
ip1_after = FloatingIP.objects.get(id=ip1.id) |
|
302 |
self.assertEqual(ip1_after.machine, self.vm) |
|
303 |
self.assertTrue(ip1_after.in_use()) |
|
304 |
self.vm.nics.create(ipv4=ip1_after.ipv4, network=ip1_after.network, |
|
305 |
state="ACTIVE", index=0) |
|
306 |
response = self.get(SERVERS_URL + "/%s" % self.vm.id, |
|
307 |
self.vm.userid) |
|
308 |
self.assertSuccess(response) |
|
309 |
nic = json.loads(response.content)["server"]["attachments"][0] |
|
310 |
self.assertEqual(nic["OS-EXT-IPS:type"], "floating") |
|
311 |
|
|
312 |
@patch('synnefo.logic.rapi_pool.GanetiRapiClient') |
|
313 |
def test_remove_floating_ip(self, mock): |
|
314 |
# Not exists |
|
315 |
url = SERVERS_URL + "/%s/action" % self.vm.id |
|
316 |
request = {"removeFloatingIp": {"address": "10.0.0.1"}} |
|
317 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
318 |
self.assertItemNotFound(response) |
|
319 |
# Not In Use |
|
320 |
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None) |
|
321 |
request = {"removeFloatingIp": {"address": ip1.ipv4}} |
|
322 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
323 |
self.assertItemNotFound(response) |
|
324 |
# Success |
|
325 |
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=self.vm) |
|
326 |
NetworkInterfaceFactory(machine=self.vm, ipv4=ip1.ipv4) |
|
327 |
request = {"removeFloatingIp": {"address": ip1.ipv4}} |
|
328 |
mock().ModifyInstance.return_value = 2 |
|
329 |
response = self.post(url, self.vm.userid, json.dumps(request), "json") |
|
330 |
self.assertEqual(response.status_code, 202) |
|
331 |
# Yet used. Wait for the callbacks |
|
332 |
ip1_after = FloatingIP.objects.get(id=ip1.id) |
|
333 |
self.assertEqual(ip1_after.machine, self.vm) |
|
334 |
self.assertTrue(ip1_after.in_use()) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
|
|
36 |
from snf_django.lib.api import faults |
|
37 |
from snf_django.utils.testing import BaseAPITest |
|
38 |
from synnefo.lib.services import get_service_path |
|
39 |
from synnefo.cyclades_settings import cyclades_services |
|
40 |
from synnefo.lib import join_urls |
|
41 |
|
|
42 |
from mock import patch |
|
43 |
from functools import wraps |
|
44 |
|
|
45 |
|
|
46 |
def assert_backend_closed(func): |
|
47 |
"""Decorator for ensuring that ImageBackend is returned to pool.""" |
|
48 |
@wraps(func) |
|
49 |
def wrapper(self, backend): |
|
50 |
result = func(self, backend) |
|
51 |
if backend.called is True: |
|
52 |
backend.return_value.close.asssert_called |
|
53 |
return result |
|
54 |
return wrapper |
|
55 |
|
|
56 |
|
|
57 |
class ComputeAPITest(BaseAPITest): |
|
58 |
def setUp(self, *args, **kwargs): |
|
59 |
super(ComputeAPITest, self).setUp(*args, **kwargs) |
|
60 |
self.compute_path = get_service_path(cyclades_services, 'compute', |
|
61 |
version='v2.0') |
|
62 |
def myget(self, path, *args, **kwargs): |
|
63 |
path = join_urls(self.compute_path, path) |
|
64 |
return self.get(path, *args, **kwargs) |
|
65 |
|
|
66 |
def myput(self, path, *args, **kwargs): |
|
67 |
path = join_urls(self.compute_path, path) |
|
68 |
return self.put(path, *args, **kwargs) |
|
69 |
|
|
70 |
def mypost(self, path, *args, **kwargs): |
|
71 |
path = join_urls(self.compute_path, path) |
|
72 |
return self.post(path, *args, **kwargs) |
|
73 |
|
|
74 |
def mydelete(self, path, *args, **kwargs): |
|
75 |
path = join_urls(self.compute_path, path) |
|
76 |
return self.delete(path, *args, **kwargs) |
|
77 |
|
|
78 |
|
|
79 |
@patch('synnefo.plankton.backend.ImageBackend') |
|
80 |
class ImageAPITest(ComputeAPITest): |
|
81 |
@assert_backend_closed |
|
82 |
def test_create_image(self, mimage): |
|
83 |
"""Test that create image is not implemented""" |
|
84 |
response = self.mypost('images/', 'user', json.dumps(''), 'json') |
|
85 |
self.assertEqual(response.status_code, 501) |
|
86 |
|
|
87 |
@assert_backend_closed |
|
88 |
def test_list_images(self, mimage): |
|
89 |
"""Test that expected list of images is returned""" |
|
90 |
images = [{'id': 1, 'name': 'image-1'}, |
|
91 |
{'id': 2, 'name': 'image-2'}, |
|
92 |
{'id': 3, 'name': 'image-3'}] |
|
93 |
mimage().list_images.return_value = images |
|
94 |
response = self.myget('images', 'user') |
|
95 |
self.assertSuccess(response) |
|
96 |
api_images = json.loads(response.content)['images'] |
|
97 |
self.assertEqual(images, api_images) |
|
98 |
|
|
99 |
@assert_backend_closed |
|
100 |
def test_list_images_detail(self, mimage): |
|
101 |
images = [{'id': 1, |
|
102 |
'name': 'image-1', |
|
103 |
'status':'available', |
|
104 |
'created_at': '2012-11-26 11:52:54', |
|
105 |
'updated_at': '2012-12-26 11:52:54', |
|
106 |
'owner': 'user1', |
|
107 |
'deleted_at': '', |
|
108 |
'properties': {'foo':'bar'}}, |
|
109 |
{'id': 2, |
|
110 |
'name': 'image-2', |
|
111 |
'status': 'deleted', |
|
112 |
'created_at': '2012-11-26 11:52:54', |
|
113 |
'updated_at': '2012-12-26 11:52:54', |
|
114 |
'owner': 'user1', |
|
115 |
'deleted_at': '2012-12-27 11:52:54', |
|
116 |
'properties': ''}, |
|
117 |
{'id': 3, |
|
118 |
'name': 'image-3', |
|
119 |
'status': 'available', |
|
120 |
'created_at': '2012-11-26 11:52:54', |
|
121 |
'deleted_at': '', |
|
122 |
'updated_at': '2012-12-26 11:52:54', |
|
123 |
'owner': 'user1', |
|
124 |
'properties': ''}] |
|
125 |
result_images = [ |
|
126 |
{'id': 1, |
|
127 |
'name': 'image-1', |
|
128 |
'status':'ACTIVE', |
|
129 |
'progress': 100, |
|
130 |
'created': '2012-11-26T11:52:54+00:00', |
|
131 |
'updated': '2012-12-26T11:52:54+00:00', |
|
132 |
'user_id': 'user1', |
|
133 |
'tenant_id': 'user1', |
|
134 |
'metadata': {'foo':'bar'}}, |
|
135 |
{'id': 2, |
|
136 |
'name': 'image-2', |
|
137 |
'status': 'DELETED', |
|
138 |
'progress': 0, |
|
139 |
'user_id': 'user1', |
|
140 |
'tenant_id': 'user1', |
|
141 |
'created': '2012-11-26T11:52:54+00:00', |
|
142 |
'updated': '2012-12-26T11:52:54+00:00', |
|
143 |
'metadata': {}}, |
|
144 |
{'id': 3, |
|
145 |
'name': 'image-3', |
|
146 |
'status': 'ACTIVE', |
|
147 |
'progress': 100, |
|
148 |
'user_id': 'user1', |
|
149 |
'tenant_id': 'user1', |
|
150 |
'created': '2012-11-26T11:52:54+00:00', |
|
151 |
'updated': '2012-12-26T11:52:54+00:00', |
|
152 |
'metadata': {}}] |
|
153 |
mimage().list_images.return_value = images |
|
154 |
response = self.myget('images/detail', 'user') |
|
155 |
self.assertSuccess(response) |
|
156 |
api_images = json.loads(response.content)['images'] |
|
157 |
self.assertEqual(len(result_images), len(api_images)) |
|
158 |
map(lambda image: image.pop("links"), api_images) |
|
159 |
self.assertEqual(result_images, api_images) |
|
160 |
|
|
161 |
@assert_backend_closed |
|
162 |
def test_list_images_detail_since(self, mimage): |
|
163 |
from datetime import datetime, timedelta |
|
164 |
from time import sleep |
|
165 |
old_time = datetime.now() |
|
166 |
new_time = old_time + timedelta(seconds=0.1) |
|
167 |
sleep(0.1) |
|
168 |
images = [ |
|
169 |
{'id': 1, |
|
170 |
'name': 'image-1', |
|
171 |
'status':'available', |
|
172 |
'progress': 100, |
|
173 |
'created_at': old_time.isoformat(), |
|
174 |
'deleted_at': '', |
|
175 |
'updated_at': old_time.isoformat(), |
|
176 |
'owner': 'user1', |
|
177 |
'properties': ''}, |
|
178 |
{'id': 2, |
|
179 |
'name': 'image-2', |
|
180 |
'status': 'deleted', |
|
181 |
'progress': 0, |
|
182 |
'owner': 'user2', |
|
183 |
'created_at': new_time.isoformat(), |
|
184 |
'updated_at': new_time.isoformat(), |
|
185 |
'deleted_at': new_time.isoformat(), |
|
186 |
'properties': ''}] |
|
187 |
mimage().list_images.return_value = images |
|
188 |
response =\ |
|
189 |
self.myget('images/detail?changes-since=%sUTC' % new_time) |
|
190 |
self.assertSuccess(response) |
|
191 |
api_images = json.loads(response.content)['images'] |
|
192 |
self.assertEqual(1, len(api_images)) |
|
193 |
|
|
194 |
@assert_backend_closed |
|
195 |
def test_get_image_details(self, mimage): |
|
196 |
image = {'id': 42, |
|
197 |
'name': 'image-1', |
|
198 |
'status': 'available', |
|
199 |
'created_at': '2012-11-26 11:52:54', |
|
200 |
'updated_at': '2012-12-26 11:52:54', |
|
201 |
'deleted_at': '', |
|
202 |
'owner': 'user1', |
|
203 |
'properties': {'foo': 'bar'}} |
|
204 |
result_image = \ |
|
205 |
{'id': 42, |
|
206 |
'name': 'image-1', |
|
207 |
'status': 'ACTIVE', |
|
208 |
'progress': 100, |
|
209 |
'created': '2012-11-26T11:52:54+00:00', |
|
210 |
'updated': '2012-12-26T11:52:54+00:00', |
|
211 |
'user_id': 'user1', |
|
212 |
'tenant_id': 'user1', |
|
213 |
'metadata': {'foo': 'bar'}} |
|
214 |
mimage.return_value.get_image.return_value = image |
|
215 |
response = self.myget('images/42', 'user') |
|
216 |
self.assertSuccess(response) |
|
217 |
api_image = json.loads(response.content)['image'] |
|
218 |
api_image.pop("links") |
|
219 |
self.assertEqual(api_image, result_image) |
|
220 |
|
|
221 |
@assert_backend_closed |
|
222 |
def test_invalid_image(self, mimage): |
|
223 |
mimage.return_value.get_image.side_effect = faults.ItemNotFound('Image not found') |
|
224 |
response = self.myget('images/42', 'user') |
|
225 |
self.assertItemNotFound(response) |
|
226 |
|
|
227 |
@assert_backend_closed |
|
228 |
def test_delete_image(self, mimage): |
|
229 |
response = self.mydelete("images/42", "user") |
|
230 |
self.assertEqual(response.status_code, 204) |
|
231 |
mimage.return_value.unregister.assert_called_once_with('42') |
|
232 |
mimage.return_value._delete.assert_not_called('42') |
|
233 |
|
|
234 |
@assert_backend_closed |
|
235 |
def test_catch_wrong_api_paths(self, *args): |
|
236 |
response = self.myget('nonexistent') |
|
237 |
self.assertEqual(response.status_code, 400) |
|
238 |
try: |
|
239 |
error = json.loads(response.content) |
|
240 |
except ValueError: |
|
241 |
self.assertTrue(False) |
|
242 |
|
|
243 |
@assert_backend_closed |
|
244 |
def test_method_not_allowed(self, *args): |
|
245 |
# /images/ allows only POST, GET |
|
246 |
response = self.myput('images', '', '') |
|
247 |
self.assertMethodNotAllowed(response) |
|
248 |
response = self.mydelete('images') |
|
249 |
self.assertMethodNotAllowed(response) |
|
250 |
|
|
251 |
# /images/<imgid>/ allows only GET, DELETE |
|
252 |
response = self.mypost("images/42") |
|
253 |
self.assertMethodNotAllowed(response) |
|
254 |
response = self.myput('images/42', '', '') |
|
255 |
self.assertMethodNotAllowed(response) |
|
256 |
|
|
257 |
# /images/<imgid>/metadata/ allows only POST, GET |
|
258 |
response = self.myput('images/42/metadata', '', '') |
|
259 |
self.assertMethodNotAllowed(response) |
|
260 |
response = self.mydelete('images/42/metadata') |
|
261 |
self.assertMethodNotAllowed(response) |
|
262 |
|
|
263 |
# /images/<imgid>/metadata/ allows only POST, GET |
|
264 |
response = self.myput('images/42/metadata', '', '') |
|
265 |
self.assertMethodNotAllowed(response) |
|
266 |
response = self.mydelete('images/42/metadata') |
|
267 |
self.assertMethodNotAllowed(response) |
|
268 |
|
|
269 |
# /images/<imgid>/metadata/<key> allows only PUT, GET, DELETE |
|
270 |
response = self.mypost('images/42/metadata/foo') |
|
271 |
self.assertMethodNotAllowed(response) |
|
272 |
|
|
273 |
|
|
274 |
@patch('synnefo.plankton.backend.ImageBackend') |
|
275 |
class ImageMetadataAPITest(ComputeAPITest): |
|
276 |
def setUp(self): |
|
277 |
self.image = {'id': 42, |
|
278 |
'name': 'image-1', |
|
279 |
'status': 'available', |
|
280 |
'created_at': '2012-11-26 11:52:54', |
|
281 |
'updated_at': '2012-12-26 11:52:54', |
|
282 |
'deleted_at': '', |
|
283 |
'properties': {'foo': 'bar', 'foo2': 'bar2'}} |
|
284 |
self.result_image = \ |
|
285 |
{'id': 42, |
|
286 |
'name': 'image-1', |
|
287 |
'status': 'ACTIVE', |
|
288 |
'progress': 100, |
|
289 |
'created': '2012-11-26T11:52:54+00:00', |
|
290 |
'updated': '2012-12-26T11:52:54+00:00', |
|
291 |
'metadata': {'foo': 'bar'}} |
|
292 |
super(ImageMetadataAPITest, self).setUp() |
|
293 |
|
|
294 |
@assert_backend_closed |
|
295 |
def test_list_metadata(self, backend): |
|
296 |
backend.return_value.get_image.return_value = self.image |
|
297 |
response = self.myget('images/42/metadata', 'user') |
|
298 |
self.assertSuccess(response) |
|
299 |
meta = json.loads(response.content)['metadata'] |
|
300 |
self.assertEqual(meta, self.image['properties']) |
|
301 |
|
|
302 |
@assert_backend_closed |
|
303 |
def test_get_metadata(self, backend): |
|
304 |
backend.return_value.get_image.return_value = self.image |
|
305 |
response = self.myget('images/42/metadata/foo', 'user') |
|
306 |
self.assertSuccess(response) |
|
307 |
meta = json.loads(response.content)['meta'] |
|
308 |
self.assertEqual(meta['foo'], 'bar') |
|
309 |
|
|
310 |
@assert_backend_closed |
|
311 |
def test_get_invalid_metadata(self, backend): |
|
312 |
backend.return_value.get_image.return_value = self.image |
|
313 |
response = self.myget('images/42/metadata/not_found', 'user') |
|
314 |
self.assertItemNotFound(response) |
|
315 |
|
|
316 |
def test_delete_metadata_item(self, backend): |
|
317 |
backend.return_value.get_image.return_value = self.image |
|
318 |
response = self.mydelete('images/42/metadata/foo', 'user') |
|
319 |
self.assertEqual(response.status_code, 204) |
|
320 |
backend.return_value.update_metadata.assert_called_once_with('42', {'properties': {'foo2': |
|
321 |
'bar2'}}) |
|
322 |
|
|
323 |
@assert_backend_closed |
|
324 |
def test_create_metadata_item(self, backend): |
|
325 |
backend.return_value.get_image.return_value = self.image |
|
326 |
request = {'meta': {'foo3': 'bar3'}} |
|
327 |
response = self.myput('images/42/metadata/foo3', 'user', |
|
328 |
json.dumps(request), 'json') |
|
329 |
self.assertEqual(response.status_code, 201) |
|
330 |
backend.return_value.update_metadata.assert_called_once_with('42', |
|
331 |
{'properties': |
|
332 |
{'foo': 'bar', 'foo2': 'bar2', 'foo3': 'bar3'}}) |
|
333 |
|
|
334 |
@assert_backend_closed |
|
335 |
def test_create_metadata_malformed_1(self, backend): |
|
336 |
backend.return_value.get_image.return_value = self.image |
|
337 |
request = {'met': {'foo3': 'bar3'}} |
|
338 |
response = self.myput('images/42/metadata/foo3', 'user', |
|
339 |
json.dumps(request), 'json') |
|
340 |
self.assertBadRequest(response) |
|
341 |
|
|
342 |
@assert_backend_closed |
|
343 |
def test_create_metadata_malformed_2(self, backend): |
|
344 |
backend.return_value.get_image.return_value = self.image |
|
345 |
request = {'metadata': [('foo3', 'bar3')]} |
|
346 |
response = self.myput('images/42/metadata/foo3', 'user', |
|
347 |
json.dumps(request), 'json') |
|
348 |
self.assertBadRequest(response) |
|
349 |
|
|
350 |
@assert_backend_closed |
|
351 |
def test_create_metadata_malformed_3(self, backend): |
|
352 |
backend.return_value.get_image.return_value = self.image |
|
353 |
request = {'met': {'foo3': 'bar3', 'foo4': 'bar4'}} |
|
354 |
response = self.myput('images/42/metadata/foo3', 'user', |
|
355 |
json.dumps(request), 'json') |
|
356 |
self.assertBadRequest(response) |
|
357 |
|
|
358 |
@assert_backend_closed |
|
359 |
def test_create_metadata_malformed_4(self, backend): |
|
360 |
backend.return_value.get_image.return_value = self.image |
|
361 |
request = {'met': {'foo3': 'bar3'}} |
|
362 |
response = self.myput('images/42/metadata/foo4', 'user', |
|
363 |
json.dumps(request), 'json') |
|
364 |
self.assertBadRequest(response) |
|
365 |
|
|
366 |
@assert_backend_closed |
|
367 |
def test_update_metadata_item(self, backend): |
|
368 |
backend.return_value.get_image.return_value = self.image |
|
369 |
request = {'metadata': {'foo': 'bar_new', 'foo4': 'bar4'}} |
|
370 |
response = self.mypost('images/42/metadata', 'user', |
|
371 |
json.dumps(request), 'json') |
|
372 |
self.assertEqual(response.status_code, 201) |
|
373 |
backend.return_value.update_metadata.assert_called_once_with('42', |
|
374 |
{'properties': |
|
375 |
{'foo': 'bar_new', 'foo2': 'bar2', 'foo4': 'bar4'} |
|
376 |
}) |
|
377 |
|
|
378 |
@assert_backend_closed |
|
379 |
def test_update_metadata_malformed(self, backend): |
|
380 |
backend.return_value.get_image.return_value = self.image |
|
381 |
request = {'meta': {'foo': 'bar_new', 'foo4': 'bar4'}} |
|
382 |
response = self.mypost('images/42/metadata', 'user', |
|
383 |
json.dumps(request), 'json') |
|
384 |
self.assertBadRequest(response) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
from mock import patch |
|
36 |
|
|
37 |
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder |
|
38 |
from synnefo.db.models import Network, NetworkInterface |
|
39 |
from synnefo.db import models_factory as mfactory |
|
40 |
from synnefo.cyclades_settings import cyclades_services |
|
41 |
from synnefo.lib.services import get_service_path |
|
42 |
from synnefo.lib import join_urls |
|
43 |
|
|
44 |
|
|
45 |
class ComputeAPITest(BaseAPITest): |
|
46 |
def __init__(self, *args, **kwargs): |
|
47 |
super(ComputeAPITest, self).__init__(*args, **kwargs) |
|
48 |
self.compute_path = get_service_path(cyclades_services, 'compute', |
|
49 |
version='v2.0') |
|
50 |
|
|
51 |
def myget(self, path, *args, **kwargs): |
|
52 |
path = join_urls(self.compute_path, path) |
|
53 |
return self.get(path, *args, **kwargs) |
|
54 |
|
|
55 |
def myput(self, path, *args, **kwargs): |
|
56 |
path = join_urls(self.compute_path, path) |
|
57 |
return self.put(path, *args, **kwargs) |
|
58 |
|
|
59 |
def mypost(self, path, *args, **kwargs): |
|
60 |
path = join_urls(self.compute_path, path) |
|
61 |
return self.post(path, *args, **kwargs) |
|
62 |
|
|
63 |
def mydelete(self, path, *args, **kwargs): |
|
64 |
path = join_urls(self.compute_path, path) |
|
65 |
return self.delete(path, *args, **kwargs) |
|
66 |
|
|
67 |
|
|
68 |
@patch('synnefo.logic.rapi_pool.GanetiRapiClient') |
|
69 |
class NetworkAPITest(ComputeAPITest): |
|
70 |
def setUp(self): |
|
71 |
self.mac_prefixes = mfactory.MacPrefixPoolTableFactory() |
|
72 |
self.bridges = mfactory.BridgePoolTableFactory(base="link") |
|
73 |
self.user = 'dummy-user' |
|
74 |
self.net1 = mfactory.NetworkFactory(userid=self.user) |
|
75 |
self.vm1 = mfactory.VirtualMachineFactory(userid=self.user) |
|
76 |
self.nic1 = mfactory.NetworkInterfaceFactory(network=self.net1, |
|
77 |
machine=self.vm1) |
|
78 |
self.nic2 = mfactory.NetworkInterfaceFactory(network=self.net1, |
|
79 |
machine=self.vm1) |
|
80 |
self.net2 = mfactory.NetworkFactory(userid=self.user) |
|
81 |
self.nic3 = mfactory.NetworkInterfaceFactory(network=self.net2) |
|
82 |
super(NetworkAPITest, self).setUp() |
|
83 |
|
|
84 |
def assertNetworksEqual(self, db_net, api_net, detail=False): |
|
85 |
self.assertEqual(str(db_net.id), api_net["id"]) |
|
86 |
self.assertEqual(db_net.name, api_net['name']) |
|
87 |
if detail: |
|
88 |
self.assertEqual(db_net.state, api_net['status']) |
|
89 |
self.assertEqual(db_net.flavor, api_net['type']) |
|
90 |
self.assertEqual(db_net.subnet, api_net['cidr']) |
|
91 |
self.assertEqual(db_net.subnet6, api_net['cidr6']) |
|
92 |
self.assertEqual(db_net.gateway, api_net['gateway']) |
|
93 |
self.assertEqual(db_net.gateway6, api_net['gateway6']) |
|
94 |
self.assertEqual(db_net.dhcp, api_net['dhcp']) |
|
95 |
self.assertEqual(db_net.public, api_net['public']) |
|
96 |
db_nics = ["nic-%d-%d" % (nic.machine.id, nic.index) for nic in |
|
97 |
db_net.nics.filter(machine__userid=db_net.userid)] |
|
98 |
self.assertEqual(db_nics, api_net['attachments']) |
|
99 |
|
|
100 |
def test_create_network_1(self, mrapi): |
|
101 |
request = { |
|
102 |
'network': {'name': 'foo', "type": "MAC_FILTERED"} |
|
103 |
} |
|
104 |
with mocked_quotaholder(): |
|
105 |
response = self.mypost('networks/', 'user1', |
|
106 |
json.dumps(request), 'json') |
|
107 |
self.assertEqual(response.status_code, 202) |
|
108 |
db_networks = Network.objects.filter(userid='user1') |
|
109 |
self.assertEqual(len(db_networks), 1) |
|
110 |
db_net = db_networks[0] |
|
111 |
api_net = json.loads(response.content)['network'] |
|
112 |
self.assertNetworksEqual(db_net, api_net) |
|
113 |
mrapi.CreateNetwork.assert_called() |
|
114 |
mrapi.ConnectNetwork.assert_called() |
|
115 |
|
|
116 |
def test_invalid_data_1(self, mrapi): |
|
117 |
"""Test invalid flavor""" |
|
118 |
request = { |
|
119 |
'network': {'name': 'foo', 'type': 'LoLo'} |
|
120 |
} |
|
121 |
response = self.mypost('networks/', 'user1', |
|
122 |
json.dumps(request), 'json') |
|
123 |
self.assertBadRequest(response) |
|
124 |
self.assertEqual(len(Network.objects.filter(userid='user1')), 0) |
|
125 |
|
|
126 |
def test_invalid_data_2(self, mrapi): |
|
127 |
"""Test invalid data/subnet""" |
|
128 |
request = { |
|
129 |
'network': {'name': 'foo', |
|
130 |
'cidr': '10.0.0.0/8', "type": |
|
131 |
"MAC_FILTERED"} |
|
132 |
} |
|
133 |
response = self.mypost('networks/', 'user1', |
|
134 |
json.dumps(request), 'json') |
|
135 |
self.assertFault(response, 413, "overLimit") |
|
136 |
|
|
137 |
def test_invalid_data_3(self, mrapi): |
|
138 |
"""Test unauthorized to create public network""" |
|
139 |
request = { |
|
140 |
'network': {'name': 'foo', |
|
141 |
"public": "True", |
|
142 |
"type": "MAC_FILTERED"} |
|
143 |
} |
|
144 |
response = self.mypost('networks/', 'user1', |
|
145 |
json.dumps(request), 'json') |
|
146 |
self.assertFault(response, 403, "forbidden") |
|
147 |
|
|
148 |
def test_invalid_data_4(self, mrapi): |
|
149 |
"""Test unauthorized to create network not in settings""" |
|
150 |
request = { |
|
151 |
'network': {'name': 'foo', 'type': 'CUSTOM'} |
|
152 |
} |
|
153 |
response = self.mypost('networks/', 'user1', |
|
154 |
json.dumps(request), 'json') |
|
155 |
self.assertFault(response, 403, "forbidden") |
|
156 |
|
|
157 |
def test_invalid_subnet(self, mrapi): |
|
158 |
"""Test invalid subnet""" |
|
159 |
request = { |
|
160 |
'network': {'name': 'foo', |
|
161 |
'cidr': '10.0.0.10/27', |
|
162 |
"type": "MAC_FILTERED"} |
|
163 |
} |
|
164 |
response = self.mypost('networks/', 'user1', |
|
165 |
json.dumps(request), 'json') |
|
166 |
self.assertBadRequest(response) |
|
167 |
|
|
168 |
def test_invalid_gateway_1(self, mrapi): |
|
169 |
request = { |
|
170 |
'network': {'name': 'foo', |
|
171 |
'cidr': '10.0.0.0/28', |
|
172 |
'gateway': '10.0.0.0.300'} |
|
173 |
} |
|
174 |
response = self.mypost('networks/', 'user1', |
|
175 |
json.dumps(request), 'json') |
|
176 |
self.assertBadRequest(response) |
|
177 |
|
|
178 |
def test_invalid_gateway_2(self, mrapi): |
|
179 |
request = { |
|
180 |
'network': {'name': 'foo', |
|
181 |
'cidr': '10.0.0.0/28', |
|
182 |
'gateway': '10.2.0.1'} |
|
183 |
} |
|
184 |
response = self.mypost('networks/', 'user1', |
|
185 |
json.dumps(request), 'json') |
|
186 |
self.assertBadRequest(response) |
|
187 |
|
|
188 |
def test_invalid_network6(self, mrapi): |
|
189 |
request = { |
|
190 |
'network': {'name': 'foo', |
|
191 |
'cidr': '10.0.0.0/28', |
|
192 |
'subnet6': '10.0.0.0/28', |
|
193 |
'gateway': '10.2.0.1'} |
|
194 |
} |
|
195 |
response = self.mypost('networks/', 'user1', |
|
196 |
json.dumps(request), 'json') |
|
197 |
self.assertBadRequest(response) |
|
198 |
|
|
199 |
def test_invalid_gateway6(self, mrapi): |
|
200 |
request = { |
|
201 |
'network': {'name': 'foo', |
|
202 |
'cidr': '10.0.0.0/28', |
|
203 |
'subnet6': '2001:0db8:0123:4567:89ab:cdef:1234:5678', |
|
204 |
'gateway': '10.2.0.1'} |
|
205 |
} |
|
206 |
response = self.mypost('networks/', 'user1', |
|
207 |
json.dumps(request), 'json') |
|
208 |
self.assertBadRequest(response) |
|
209 |
|
|
210 |
def test_list_networks(self, mrapi): |
|
211 |
"""Test that expected list of networks is returned.""" |
|
212 |
# Create a deleted network |
|
213 |
mfactory.NetworkFactory(userid=self.user, deleted=True) |
|
214 |
|
|
215 |
response = self.myget('networks/', self.user) |
|
216 |
self.assertSuccess(response) |
|
217 |
|
|
218 |
db_nets = Network.objects.filter(userid=self.user, deleted=False) |
|
219 |
api_nets = json.loads(response.content)["networks"] |
|
220 |
|
|
221 |
self.assertEqual(len(db_nets), len(api_nets)) |
|
222 |
for api_net in api_nets: |
|
223 |
net_id = api_net['id'] |
|
224 |
self.assertNetworksEqual(Network.objects.get(id=net_id), api_net) |
|
225 |
|
|
226 |
def test_list_networks_detail(self, mrapi): |
|
227 |
"""Test that expected networks details are returned.""" |
|
228 |
# Create a deleted network |
|
229 |
mfactory.NetworkFactory(userid=self.user, deleted=True) |
|
230 |
|
|
231 |
response = self.myget('networks/detail', self.user) |
|
232 |
self.assertSuccess(response) |
|
233 |
|
|
234 |
db_nets = Network.objects.filter(userid=self.user, deleted=False) |
|
235 |
api_nets = json.loads(response.content)["networks"] |
|
236 |
|
|
237 |
self.assertEqual(len(db_nets), len(api_nets)) |
|
238 |
for api_net in api_nets: |
|
239 |
net_id = api_net['id'] |
|
240 |
self.assertNetworksEqual(Network.objects.get(id=net_id), api_net, |
|
241 |
detail=True) |
|
242 |
|
|
243 |
def test_get_network_building_nics(self, mrapi): |
|
244 |
net = mfactory.NetworkFactory() |
|
245 |
machine = mfactory.VirtualMachineFactory(userid=net.userid) |
|
246 |
mfactory.NetworkInterfaceFactory(network=net, machine=machine, |
|
247 |
state="BUILDING") |
|
248 |
response = self.myget('networks/%d' % net.id, net.userid) |
|
249 |
self.assertSuccess(response) |
|
250 |
api_net = json.loads(response.content)["network"] |
|
251 |
self.assertEqual(len(api_net["attachments"]), 0) |
|
252 |
|
|
253 |
def test_network_details_1(self, mrapi): |
|
254 |
"""Test that expected details for a network are returned""" |
|
255 |
response = self.myget('networks/%d' % self.net1.id, self.net1.userid) |
|
256 |
self.assertSuccess(response) |
|
257 |
api_net = json.loads(response.content)["network"] |
|
258 |
self.assertNetworksEqual(self.net1, api_net, detail=True) |
|
259 |
|
|
260 |
def test_invalid_network(self, mrapi): |
|
261 |
"""Test details for non-existing network.""" |
|
262 |
response = self.myget('networks/%d' % 42, self.net1.userid) |
|
263 |
self.assertItemNotFound(response) |
|
264 |
|
|
265 |
def test_rename_network(self, mrapi): |
|
266 |
request = {'network': {'name': "new_name"}} |
|
267 |
response = self.myput('networks/%d' % self.net2.id, |
|
268 |
self.net2.userid, json.dumps(request), 'json') |
|
269 |
self.assertEqual(response.status_code, 204) |
|
270 |
self.assertEqual(Network.objects.get(id=self.net2.id).name, "new_name") |
|
271 |
# Check invalid |
|
272 |
request = {'name': "new_name"} |
|
273 |
response = self.myput('networks/%d' % self.net2.id, |
|
274 |
self.net2.userid, json.dumps(request), 'json') |
|
275 |
self.assertBadRequest(response) |
|
276 |
|
|
277 |
def test_rename_deleted_network(self, mrapi): |
|
278 |
net = mfactory.NetworkFactory(deleted=True) |
|
279 |
request = {'network': {'name': "new_name"}} |
|
280 |
response = self.myput('networks/%d' % net.id, |
|
281 |
net.userid, json.dumps(request), 'json') |
|
282 |
self.assertBadRequest(response) |
|
283 |
|
|
284 |
def test_rename_public_network(self, mrapi): |
|
285 |
net = mfactory.NetworkFactory(public=True) |
|
286 |
request = {'network': {'name': "new_name"}} |
|
287 |
response = self.myput('networks/%d' % net.id, |
|
288 |
self.net2.userid, json.dumps(request), 'json') |
|
289 |
self.assertFault(response, 403, 'forbidden') |
|
290 |
|
|
291 |
def test_delete_network(self, mrapi): |
|
292 |
net = mfactory.NetworkFactory(deleted=False, state='ACTIVE', |
|
293 |
link="link-10") |
|
294 |
with mocked_quotaholder(): |
|
295 |
response = self.mydelete('networks/%d' % net.id, net.userid) |
|
296 |
self.assertEqual(response.status_code, 204) |
|
297 |
net = Network.objects.get(id=net.id, userid=net.userid) |
|
298 |
self.assertEqual(net.action, 'DESTROY') |
|
299 |
mrapi.DeleteNetwork.assert_called() |
|
300 |
|
|
301 |
def test_delete_public_network(self, mrapi): |
|
302 |
net = mfactory.NetworkFactory(public=True) |
|
303 |
response = self.mydelete('networks/%d' % net.id, self.net2.userid) |
|
304 |
self.assertFault(response, 403, 'forbidden') |
|
305 |
self.assertFalse(mrapi.called) |
|
306 |
|
|
307 |
def test_delete_deleted_network(self, mrapi): |
|
308 |
net = mfactory.NetworkFactory(deleted=True) |
|
309 |
response = self.mydelete('networks/%d' % net.id, net.userid) |
|
310 |
self.assertBadRequest(response) |
|
311 |
|
|
312 |
def test_delete_network_in_use(self, mrapi): |
|
313 |
net = self.net1 |
|
314 |
response = self.mydelete('networks/%d' % net.id, net.userid) |
|
315 |
self.assertFault(response, 421, 'networkInUse') |
|
316 |
self.assertFalse(mrapi.called) |
|
317 |
|
|
318 |
def test_add_nic(self, mrapi): |
|
319 |
user = 'userr' |
|
320 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
321 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
322 |
mrapi().ModifyInstance.return_value = 1 |
|
323 |
request = {'add': {'serverRef': vm.id}} |
|
324 |
response = self.mypost('networks/%d/action' % net.id, |
|
325 |
net.userid, json.dumps(request), 'json') |
|
326 |
self.assertEqual(response.status_code, 202) |
|
327 |
|
|
328 |
def test_add_nic_to_deleted_network(self, mrapi): |
|
329 |
user = 'userr' |
|
330 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user, |
|
331 |
operstate="ACTIVE") |
|
332 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user, |
|
333 |
deleted=True) |
|
334 |
request = {'add': {'serverRef': vm.id}} |
|
335 |
response = self.mypost('networks/%d/action' % net.id, |
|
336 |
net.userid, json.dumps(request), 'json') |
|
337 |
self.assertBadRequest(response) |
|
338 |
self.assertFalse(mrapi.called) |
|
339 |
|
|
340 |
def test_add_nic_to_public_network(self, mrapi): |
|
341 |
user = 'userr' |
|
342 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
343 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user, public=True) |
|
344 |
request = {'add': {'serverRef': vm.id}} |
|
345 |
response = self.mypost('networks/%d/action' % net.id, |
|
346 |
net.userid, json.dumps(request), 'json') |
|
347 |
self.assertFault(response, 403, 'forbidden') |
|
348 |
self.assertFalse(mrapi.called) |
|
349 |
|
|
350 |
def test_add_nic_malformed_1(self, mrapi): |
|
351 |
user = 'userr' |
|
352 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
353 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
354 |
request = {'add': {'serveRef': vm.id}} |
|
355 |
response = self.mypost('networks/%d/action' % net.id, |
|
356 |
net.userid, json.dumps(request), 'json') |
|
357 |
self.assertBadRequest(response) |
|
358 |
self.assertFalse(mrapi.called) |
|
359 |
|
|
360 |
def test_add_nic_malformed_2(self, mrapi): |
|
361 |
user = 'userr' |
|
362 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
363 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
364 |
request = {'add': {'serveRef': [vm.id, 22]}} |
|
365 |
response = self.mypost('networks/%d/action' % net.id, |
|
366 |
net.userid, json.dumps(request), 'json') |
|
367 |
self.assertBadRequest(response) |
|
368 |
self.assertFalse(mrapi.called) |
|
369 |
|
|
370 |
def test_add_nic_not_active(self, mrapi): |
|
371 |
"""Test connecting VM to non-active network""" |
|
372 |
user = 'dummy' |
|
373 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
374 |
net = mfactory.NetworkFactory(state='PENDING', subnet='10.0.0.0/31', |
|
375 |
userid=user) |
|
376 |
request = {'add': {'serverRef': vm.id}} |
|
377 |
response = self.mypost('networks/%d/action' % net.id, |
|
378 |
net.userid, json.dumps(request), 'json') |
|
379 |
# Test that returns BuildInProgress |
|
380 |
self.assertEqual(response.status_code, 409) |
|
381 |
self.assertFalse(mrapi.called) |
|
382 |
|
|
383 |
def test_add_nic_full_network(self, mrapi): |
|
384 |
"""Test connecting VM to a full network""" |
|
385 |
user = 'userr' |
|
386 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user, |
|
387 |
operstate="STARTED") |
|
388 |
net = mfactory.NetworkFactory(state='ACTIVE', subnet='10.0.0.0/30', |
|
389 |
userid=user, dhcp=True) |
|
390 |
pool = net.get_pool() |
|
391 |
while not pool.empty(): |
|
392 |
pool.get() |
|
393 |
pool.save() |
|
394 |
pool = net.get_pool() |
|
395 |
self.assertTrue(pool.empty()) |
|
396 |
request = {'add': {'serverRef': vm.id}} |
|
397 |
response = self.mypost('networks/%d/action' % net.id, |
|
398 |
net.userid, json.dumps(request), 'json') |
|
399 |
# Test that returns OverLimit |
|
400 |
self.assertEqual(response.status_code, 413) |
|
401 |
self.assertFalse(mrapi.called) |
|
402 |
|
|
403 |
def test_remove_nic(self, mrapi): |
|
404 |
user = 'userr' |
|
405 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user, |
|
406 |
operstate="ACTIVE") |
|
407 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
408 |
nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net) |
|
409 |
mrapi().ModifyInstance.return_value = 1 |
|
410 |
request = {'remove': {'attachment': 'nic-%s-%s' % (vm.id, nic.index)}} |
|
411 |
response = self.mypost('networks/%d/action' % net.id, |
|
412 |
net.userid, json.dumps(request), 'json') |
|
413 |
self.assertEqual(response.status_code, 202) |
|
414 |
self.assertTrue(NetworkInterface.objects.get(id=nic.id).dirty) |
|
415 |
vm.task = None |
|
416 |
vm.task_job_id = None |
|
417 |
vm.save() |
|
418 |
# Remove dirty nic |
|
419 |
response = self.mypost('networks/%d/action' % net.id, |
|
420 |
net.userid, json.dumps(request), 'json') |
|
421 |
self.assertFault(response, 409, 'buildInProgress') |
|
422 |
|
|
423 |
def test_remove_nic_malformed(self, mrapi): |
|
424 |
user = 'userr' |
|
425 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
426 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
427 |
nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net) |
|
428 |
request = {'remove': |
|
429 |
{'att234achment': 'nic-%s-%s' % (vm.id, nic.index)} |
|
430 |
} |
|
431 |
response = self.mypost('networks/%d/action' % net.id, |
|
432 |
net.userid, json.dumps(request), 'json') |
|
433 |
self.assertBadRequest(response) |
|
434 |
|
|
435 |
def test_remove_nic_malformed_2(self, mrapi): |
|
436 |
user = 'userr' |
|
437 |
vm = mfactory.VirtualMachineFactory(name='yo', userid=user) |
|
438 |
net = mfactory.NetworkFactory(state='ACTIVE', userid=user) |
|
439 |
request = {'remove': |
|
440 |
{'attachment': 'nic-%s' % vm.id} |
|
441 |
} |
|
442 |
response = self.mypost('networks/%d/action' % net.id, |
|
443 |
net.userid, json.dumps(request), 'json') |
|
444 |
self.assertBadRequest(response) |
|
445 |
|
|
446 |
def test_catch_wrong_api_paths(self, *args): |
|
447 |
response = self.myget('nonexistent') |
|
448 |
self.assertEqual(response.status_code, 400) |
|
449 |
try: |
|
450 |
error = json.loads(response.content) |
|
451 |
except ValueError: |
|
452 |
self.assertTrue(False) |
|
453 |
|
|
454 |
def test_method_not_allowed(self, *args): |
|
455 |
# /networks/ allows only POST, GET |
|
456 |
response = self.myput('networks', '', '') |
|
457 |
self.assertMethodNotAllowed(response) |
|
458 |
response = self.mydelete('networks') |
|
459 |
self.assertMethodNotAllowed(response) |
|
460 |
|
|
461 |
# /networks/<srvid>/ allows only GET, PUT, DELETE |
|
462 |
response = self.mypost("networks/42") |
|
463 |
self.assertMethodNotAllowed(response) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import json |
|
35 |
from copy import deepcopy |
|
36 |
|
|
37 |
from snf_django.utils.testing import (BaseAPITest, mocked_quotaholder, |
|
38 |
override_settings) |
|
39 |
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata, |
|
40 |
FloatingIP) |
|
41 |
from synnefo.db import models_factory as mfactory |
|
42 |
from synnefo.logic.utils import get_rsapi_state |
|
43 |
from synnefo.cyclades_settings import cyclades_services |
|
44 |
from synnefo.lib.services import get_service_path |
|
45 |
from synnefo.lib import join_urls |
|
46 |
from synnefo import settings |
|
47 |
|
|
48 |
from mock import patch, Mock |
|
49 |
|
|
50 |
|
|
51 |
class ComputeAPITest(BaseAPITest): |
|
52 |
def __init__(self, *args, **kwargs): |
|
53 |
super(ComputeAPITest, self).__init__(*args, **kwargs) |
|
54 |
self.compute_path = get_service_path(cyclades_services, 'compute', |
|
55 |
version='v2.0') |
|
56 |
|
|
57 |
def myget(self, path, *args, **kwargs): |
|
58 |
path = join_urls(self.compute_path, path) |
|
59 |
return self.get(path, *args, **kwargs) |
|
60 |
|
|
61 |
def myput(self, path, *args, **kwargs): |
|
62 |
path = join_urls(self.compute_path, path) |
|
63 |
return self.put(path, *args, **kwargs) |
|
64 |
|
|
65 |
def mypost(self, path, *args, **kwargs): |
|
66 |
path = join_urls(self.compute_path, path) |
|
67 |
return self.post(path, *args, **kwargs) |
|
68 |
|
|
69 |
def mydelete(self, path, *args, **kwargs): |
|
70 |
path = join_urls(self.compute_path, path) |
Also available in: Unified diff