Finetest and move Compute.delete_server_metadata
[kamaki] / kamaki / clients / cyclades / test.py
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 from mock import patch, Mock, call
34 from unittest import TestCase
35 from json import loads
36
37 from kamaki.clients import Client, ClientError
38 from kamaki.clients.cyclades import CycladesClient
39 from kamaki.clients.cyclades_rest_api import CycladesClientApi
40
41 img_ref = "1m4g3-r3f3r3nc3"
42 vm_name = "my new VM"
43 fid = 42
44 vm_send = dict(server=dict(
45     flavorRef=fid,
46     name=vm_name,
47     imageRef=img_ref,
48     metadata=dict(os="debian", users="root")))
49 vm_recv = dict(server=dict(
50     status="BUILD",
51     updated="2013-03-01T10:04:00.637152+00:00",
52     hostId="",
53     name=vm_name,
54     imageRef=img_ref,
55     created="2013-03-01T10:04:00.087324+00:00",
56     flavorRef=fid,
57     adminPass="n0n3sh@11p@55",
58     suspended=False,
59     progress=0,
60     id=31173,
61     metadata=dict(values=dict(os="debian", users="root"))))
62 img_recv = dict(image=dict(
63     status="ACTIVE",
64     updated="2013-02-26T11:10:14+00:00",
65     name="Debian Base",
66     created="2013-02-26T11:03:29+00:00",
67     progress=100,
68     id=img_ref,
69     metadata=dict(values=dict(
70         partition_table="msdos",
71         kernel="2.6.32",
72         osfamily="linux",
73         users="root",
74         gui="No GUI",
75         sortorder="1",
76         os="debian",
77         root_partition="1",
78         description="Debian 6.0.7 (Squeeze) Base System"))))
79 vm_list = dict(servers=dict(values=[
80     dict(name='n1', id=1),
81     dict(name='n2', id=2)]))
82 flavor_list = dict(flavors=dict(values=[
83         dict(id=41, name="C1R1024D20"),
84         dict(id=42, name="C1R1024D40"),
85         dict(id=43, name="C1R1028D20")]))
86 img_list = dict(images=dict(values=[
87     dict(name="maelstrom", id="0fb03e45-7d5a-4515-bd4e-e6bbf6457f06"),
88     dict(name="edx_saas", id="1357163d-5fd8-488e-a117-48734c526206"),
89     dict(name="Debian_Wheezy_Base", id="1f8454f0-8e3e-4b6c-ab8e-5236b728dffe"),
90     dict(name="CentOS", id="21894b48-c805-4568-ac8b-7d4bb8eb533d"),
91     dict(name="Ubuntu Desktop", id="37bc522c-c479-4085-bfb9-464f9b9e2e31"),
92     dict(name="Ubuntu 12.10", id="3a24fef9-1a8c-47d1-8f11-e07bd5e544fd"),
93     dict(name="Debian Base", id="40ace203-6254-4e17-a5cb-518d55418a7d"),
94     dict(name="ubuntu_bundled", id="5336e265-5c7c-4127-95cb-2bf832a79903")]))
95 net_send = dict(network=dict(dhcp=False, name='someNet'))
96 net_recv = dict(network=dict(
97     status="PENDING",
98     updated="2013-03-05T15:04:51.758780+00:00",
99     name="someNet",
100     created="2013-03-05T15:04:51.758728+00:00",
101     cidr6=None,
102     id="2130",
103     gateway6=None,
104     public=False,
105     dhcp=False,
106     cidr="192.168.1.0/24",
107     type="MAC_FILTERED",
108     gateway=None,
109     attachments=dict(values=[dict(name='att1'), dict(name='att2')])))
110 net_list = dict(networks=dict(values=[
111     dict(id=1, name='n1'),
112     dict(id=2, name='n2'),
113     dict(id=3, name='n3')]))
114
115
116 class FR(object):
117     """FR stands for Fake Response"""
118     json = vm_recv
119     headers = {}
120     content = json
121     status = None
122     status_code = 200
123
124     def release(self):
125         pass
126
127 khttp = 'kamaki.clients.connection.kamakicon.KamakiHTTPConnection'
128 cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
129
130
131 class Cyclades(TestCase):
132
133     def assert_dicts_are_equal(self, d1, d2):
134         for k, v in d1.items():
135             self.assertTrue(k in d2)
136             if isinstance(v, dict):
137                 self.assert_dicts_are_equal(v, d2[k])
138             else:
139                 self.assertEqual(unicode(v), unicode(d2[k]))
140
141     """Set up a Cyclades thorough test"""
142     def setUp(self):
143         self.url = 'http://cyclades.example.com'
144         self.token = 'cyc14d3s70k3n'
145         self.client = CycladesClient(self.url, self.token)
146         from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
147         self.C = KamakiHTTPConnection
148
149     def tearDown(self):
150         FR.status_code = 200
151         FR.json = vm_recv
152
153     def test_list_servers(self):
154         FR.json = vm_list
155         with patch.object(
156                 self.C,
157                 'perform_request',
158                 return_value=FR()) as perform_req:
159             r = self.client.list_servers()
160             self.assertEqual(self.client.http_client.url, self.url)
161             self.assertEqual(self.client.http_client.path, '/servers')
162             (method, data, a_headers, a_params) = perform_req.call_args[0]
163             self.assert_dicts_are_equal(dict(values=r), vm_list['servers'])
164             r = self.client.list_servers(detail=True)
165             self.assertEqual(self.client.http_client.url, self.url)
166             self.assertEqual(self.client.http_client.path, '/servers/detail')
167         with patch.object(
168                 CycladesClientApi,
169                 'servers_get',
170                 return_value=FR()) as servers_get:
171             self.client.list_servers(changes_since=True)
172             self.assertTrue(servers_get.call_args[1]['changes_since'])
173
174     @patch('%s.perform_request' % khttp, return_value=FR())
175     def test_list_flavors(self, PR):
176         FR.json = flavor_list
177         r = self.client.list_flavors()
178         self.assertEqual(self.client.http_client.url, self.url)
179         self.assertEqual(self.client.http_client.path, '/flavors')
180         (method, data, a_headers, a_params) = PR.call_args[0]
181         self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
182         r = self.client.list_flavors(detail=True)
183         self.assertEqual(self.client.http_client.url, self.url)
184         self.assertEqual(self.client.http_client.path, '/flavors/detail')
185
186     @patch('%s.perform_request' % khttp, return_value=FR())
187     def test_get_flavor_details(self, PR):
188         FR.json = dict(flavor=flavor_list['flavors'])
189         r = self.client.get_flavor_details(fid)
190         self.assertEqual(self.client.http_client.url, self.url)
191         self.assertEqual(self.client.http_client.path, '/flavors/%s' % fid)
192         self.assert_dicts_are_equal(r, flavor_list['flavors'])
193
194     @patch('%s.perform_request' % khttp, return_value=FR())
195     def test_list_images(self, PR):
196         FR.json = img_list
197         r = self.client.list_images()
198         self.assertEqual(self.client.http_client.url, self.url)
199         self.assertEqual(self.client.http_client.path, '/images')
200         expected = img_list['images']['values']
201         for i in range(len(r)):
202             self.assert_dicts_are_equal(expected[i], r[i])
203         self.client.list_images(detail=True)
204         self.assertEqual(self.client.http_client.url, self.url)
205         self.assertEqual(self.client.http_client.path, '/images/detail')
206
207     @patch('%s.perform_request' % khttp, return_value=FR())
208     def test_get_image_details(self, PR):
209         FR.json = img_recv
210         r = self.client.get_image_details(img_ref)
211         self.assertEqual(self.client.http_client.url, self.url)
212         self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
213         self.assert_dicts_are_equal(r, img_recv['image'])
214
215     @patch('%s.images_get' % cyclades_pkg, return_value=FR())
216     def test_get_image_metadata(self, IG):
217         FR.json = dict(metadata=dict(values=img_recv['image']))
218         r = self.client.get_image_metadata(img_ref)
219         self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta'))
220         self.assert_dicts_are_equal(img_recv['image'], r)
221         FR.json = dict(meta=img_recv['image'])
222         key = 'somekey'
223         self.client.get_image_metadata(img_ref, key)
224         self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta/%s' % key))
225
226     @patch('%s.perform_request' % khttp, return_value=FR())
227     def test_shutdown_server(self, PR):
228         vm_id = vm_recv['server']['id']
229         FR.status_code = 202
230         self.client.shutdown_server(vm_id)
231         self.assertEqual(self.client.http_client.url, self.url)
232         self.assertEqual(
233             self.client.http_client.path,
234             '/servers/%s/action' % vm_id)
235         self.assertEqual(
236             PR.call_args[0],
237             ('post',  '{"shutdown": {}}', {}, {}))
238
239     @patch('%s.perform_request' % khttp, return_value=FR())
240     def test_start_server(self, PR):
241         vm_id = vm_recv['server']['id']
242         FR.status_code = 202
243         self.client.start_server(vm_id)
244         self.assertEqual(self.client.http_client.url, self.url)
245         self.assertEqual(
246             self.client.http_client.path,
247             '/servers/%s/action' % vm_id)
248         self.assertEqual(PR.call_args[0], ('post',  '{"start": {}}', {}, {}))
249
250     @patch('%s.perform_request' % khttp, return_value=FR())
251     def test_get_server_console(self, PR):
252         cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
253         FR.json = cnsl
254         vm_id = vm_recv['server']['id']
255         r = self.client.get_server_console(vm_id)
256         self.assertEqual(self.client.http_client.url, self.url)
257         self.assertEqual(
258             self.client.http_client.path,
259             '/servers/%s/action' % vm_id)
260         self.assert_dicts_are_equal(cnsl['console'], r)
261         self.assertEqual(
262             PR.call_args[0],
263             ('post',  '{"console": {"type": "vnc"}}', {}, {}))
264
265     def test_get_firewall_profile(self):
266         vm_id = vm_recv['server']['id']
267         v = 'Some profile'
268         ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
269         with patch.object(
270                 CycladesClient,
271                 'get_server_details',
272                 return_value=ret) as GSD:
273             r = self.client.get_firewall_profile(vm_id)
274             self.assertEqual(r, v)
275             self.assertEqual(GSD.call_args[0], (vm_id,))
276             ret['attachments']['values'][0].pop('firewallProfile')
277             self.assertRaises(
278                 ClientError,
279                 self.client.get_firewall_profile,
280                 vm_id)
281
282     @patch('%s.perform_request' % khttp, return_value=FR())
283     def test_set_firewall_profile(self, PR):
284         vm_id = vm_recv['server']['id']
285         v = 'Some profile'
286         FR.status_code = 202
287         self.client.set_firewall_profile(vm_id, v)
288         self.assertEqual(self.client.http_client.url, self.url)
289         self.assertEqual(
290             self.client.http_client.path,
291             '/servers/%s/action' % vm_id)
292         self.assertEqual(PR.call_args[0], (
293             'post',
294             '{"firewallProfile": {"profile": "%s"}}' % v,
295             {},
296             {}))
297
298     @patch('%s.perform_request' % khttp, return_value=FR())
299     def test_get_server_stats(self, PR):
300         vm_id = vm_recv['server']['id']
301         stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
302         FR.json = dict(stats=stats)
303         r = self.client.get_server_stats(vm_id)
304         self.assertEqual(self.client.http_client.url, self.url)
305         self.assertEqual(
306             self.client.http_client.path,
307             '/servers/%s/stats' % vm_id)
308         self.assert_dicts_are_equal(stats, r)
309
310     @patch('%s.perform_request' % khttp, return_value=FR())
311     def test_create_network(self, PR):
312         net_name = net_send['network']['name']
313         FR.json = net_recv
314         FR.status_code = 202
315         full_args = dict(
316                 cidr='192.168.0.0/24',
317                 gateway='192.168.0.1',
318                 type='MAC_FILTERED',
319                 dhcp=True)
320         test_args = dict(full_args)
321         test_args.update(dict(empty=None, full=None))
322         for arg, val in test_args.items():
323             kwargs = {} if arg == 'empty' else full_args if (
324                 arg == 'full') else {arg: val}
325             r = self.client.create_network(net_name, **kwargs)
326             self.assertEqual(self.client.http_client.url, self.url)
327             self.assertEqual(
328                 self.client.http_client.path,
329                 '/networks')
330             self.assert_dicts_are_equal(r, net_recv['network'])
331             data = PR.call_args[0][1]
332             expected = dict(network=dict(net_send['network']))
333             expected['network'].update(kwargs)
334             self.assert_dicts_are_equal(loads(data), expected)
335
336     @patch('%s.perform_request' % khttp, return_value=FR())
337     def test_connect_server(self, PR):
338         vm_id = vm_recv['server']['id']
339         net_id = net_recv['network']['id']
340         FR.status_code = 202
341         self.client.connect_server(vm_id, net_id)
342         self.assertEqual(self.client.http_client.url, self.url)
343         self.assertEqual(
344             self.client.http_client.path,
345             '/networks/%s/action' % net_id)
346         self.assertEqual(
347             PR.call_args[0],
348             ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
349
350     @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
351     def test_disconnect_server(self, NP):
352         vm_id = vm_recv['server']['id']
353         net_id = net_recv['network']['id']
354         nic_id = 'nic-%s-%s' % (net_id, vm_id)
355         vm_nics = [
356             dict(id=nic_id, network_id=net_id),
357             dict(id='another-nic-id', network_id='another-net-id'),
358             dict(id=nic_id * 2, network_id=net_id * 2)]
359         with patch.object(
360                 CycladesClient,
361                 'list_server_nics',
362                 return_value=vm_nics) as LSN:
363             r = self.client.disconnect_server(vm_id, nic_id)
364             self.assertEqual(r, 1)
365             self.assertEqual(LSN.call_args[0], (vm_id,))
366             self.assertEqual(NP.call_args[0], (net_id, 'action'))
367             self.assertEqual(
368                 NP.call_args[1],
369                 dict(json_data=dict(remove=dict(attachment=nic_id))))
370
371     @patch('%s.perform_request' % khttp, return_value=FR())
372     def test_list_server_nics(self, PR):
373         vm_id = vm_recv['server']['id']
374         nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
375         FR.json = nics
376         r = self.client.list_server_nics(vm_id)
377         self.assertEqual(self.client.http_client.url, self.url)
378         self.assertEqual(
379             self.client.http_client.path,
380             '/servers/%s/ips' % vm_id)
381         expected = nics['addresses']['values']
382         for i in range(len(r)):
383             self.assert_dicts_are_equal(r[i], expected[i])
384
385     @patch('%s.perform_request' % khttp, return_value=FR())
386     def test_list_networks(self, PR):
387         FR.json = net_list
388         r = self.client.list_networks()
389         self.assertEqual(self.client.http_client.url, self.url)
390         self.assertEqual(self.client.http_client.path, '/networks')
391         expected = net_list['networks']['values']
392         for i in range(len(r)):
393             self.assert_dicts_are_equal(expected[i], r[i])
394         self.client.list_networks(detail=True)
395         self.assertEqual(self.client.http_client.url, self.url)
396         self.assertEqual(self.client.http_client.path, '/networks/detail')
397
398     @patch('%s.perform_request' % khttp, return_value=FR())
399     def test_list_network_nics(self, PR):
400         net_id = net_recv['network']['id']
401         FR.json = net_recv
402         r = self.client.list_network_nics(net_id)
403         self.assertEqual(self.client.http_client.url, self.url)
404         self.assertEqual(
405             self.client.http_client.path,
406             '/networks/%s' % net_id)
407         expected = net_recv['network']['attachments']['values']
408         for i in range(len(r)):
409             self.assert_dicts_are_equal(r[i], expected[i])
410
411     @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
412     def test_disconnect_network_nics(self, NP):
413         net_id = net_recv['network']['id']
414         nics = ['nic1', 'nic2', 'nic3']
415         with patch.object(
416                 CycladesClient,
417                 'list_network_nics',
418                 return_value=nics) as lnn:
419             self.client.disconnect_network_nics(net_id)
420             lnn.assert_called_once_with(net_id)
421             for i in range(len(nics)):
422                 expected = call(net_id, 'action', json_data=dict(
423                     remove=dict(attachment=nics[i])))
424                 self.assertEqual(expected, NP.mock_calls[i])
425
426     @patch('%s.perform_request' % khttp, return_value=FR())
427     def test_get_network_details(self, PR):
428         FR.json = net_recv
429         net_id = net_recv['network']['id']
430         r = self.client.get_network_details(net_id)
431         self.assertEqual(self.client.http_client.url, self.url)
432         self.assertEqual(
433             self.client.http_client.path,
434             '/networks/%s' % net_id)
435         self.assert_dicts_are_equal(r, net_recv['network'])
436
437     @patch('%s.perform_request' % khttp, return_value=FR())
438     def test_update_network_name(self, PR):
439         net_id = net_recv['network']['id']
440         new_name = '%s_new' % net_id
441         FR.status_code = 204
442         self.client.update_network_name(net_id, new_name)
443         self.assertEqual(self.client.http_client.url, self.url)
444         self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
445         (method, data, a_headers, a_params) = PR.call_args[0]
446         self.assert_dicts_are_equal(
447             dict(network=dict(name=new_name)),
448             loads(data))
449
450     @patch('%s.perform_request' % khttp, return_value=FR())
451     def test_delete_server(self, PR):
452         vm_id = vm_recv['server']['id']
453         FR.status_code = 204
454         self.client.delete_server(vm_id)
455         self.assertEqual(self.client.http_client.url, self.url)
456         self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
457
458     @patch('%s.perform_request' % khttp, return_value=FR())
459     def test_delete_image(self, PR):
460         FR.status_code = 204
461         self.client.delete_image(img_ref)
462         self.assertEqual(self.client.http_client.url, self.url)
463         self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
464
465     @patch('%s.perform_request' % khttp, return_value=FR())
466     def test_delete_network(self, PR):
467         net_id = net_recv['network']['id']
468         FR.status_code = 204
469         self.client.delete_network(net_id)
470         self.assertEqual(self.client.http_client.url, self.url)
471         self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
472
473     @patch('%s.perform_request' % khttp, return_value=FR())
474     def test_create_image_metadata(self, PR):
475         metadata = dict(m1='v1', m2='v2', m3='v3')
476         FR.json = dict(meta=img_recv['image'])
477         self.assertRaises(
478             ClientError,
479             self.client.create_image_metadata,
480             img_ref, 'key', 'value')
481         FR.status_code = 201
482         for k, v in metadata.items():
483             r = self.client.create_image_metadata(img_ref, k, v)
484             self.assertEqual(self.client.http_client.url, self.url)
485             self.assertEqual(
486                 self.client.http_client.path,
487                 '/images/%s/meta/%s' % (img_ref, k))
488             (method, data, a_headers, a_params) = PR.call_args[0]
489             self.assertEqual(dict(meta={k: v}), loads(data))
490             self.assert_dicts_are_equal(r, img_recv['image'])
491
492     @patch('%s.images_post' % cyclades_pkg, return_value=FR())
493     def test_update_image_metadata(self, images_post):
494         metadata = dict(m1='v1', m2='v2', m3='v3')
495         FR.json = dict(metadata=metadata)
496         r = self.client.update_image_metadata(img_ref, **metadata)
497         self.assert_dicts_are_equal(r, metadata)
498         (called_id, cmd) = images_post.call_args[0]
499         self.assertEqual(called_id, img_ref)
500         self.assertEqual(cmd, 'meta')
501         data = images_post.call_args[1]['json_data']
502         self.assert_dicts_are_equal(data, dict(metadata=metadata))
503
504     @patch('%s.images_delete' % cyclades_pkg, return_value=FR())
505     def test_delete_image_metadata(self, images_delete):
506         key = 'metakey'
507         self.client.delete_image_metadata(img_ref, key)
508         self.assertEqual(
509             (img_ref, '/meta/' + key),
510             images_delete.call_args[0])
511
512 if __name__ == '__main__':
513     from sys import argv
514     from kamaki.clients.test import runTestCase
515     runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])