Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / image / test.py @ ca5528f1

History | View | Annotate | Download (13.2 kB)

1
# Copyright 2012-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
from mock import patch, call
35
from unittest import TestCase
36
from itertools import product
37

    
38
example_image_headers = {
39
    'x-image-meta-id': '3edd4d15-41b4-4a39-9601-015ef56b3bb3',
40
    'x-image-meta-checksum': 'df23837c30889252c0aed80b6f770a53a86',
41
    'x-image-meta-container-format': 'bare',
42
    'x-image-meta-location': 'pithos://a13528163db/con/obj_13.0',
43
    'x-image-meta-disk-format': 'diskdump',
44
    'x-image-meta-is-public': 'True',
45
    'x-image-meta-status': 'available',
46
    'x-image-meta-deleted-at': '',
47
    'x-image-meta-updated-at': '2013-04-11 15:22:39',
48
    'x-image-meta-created-at': '2013-04-11 15:22:37',
49
    'x-image-meta-owner': 'a13529bb3c3db',
50
    'x-image-meta-size': '1073741824',
51
    'x-image-meta-name': 'img_1365686546.0',
52
    'extraheaders': 'should be ignored'
53
}
54
example_images = [
55
    {
56
        "status": "available",
57
        "name": "Archlinux",
58
        "disk_format": "diskdump",
59
        "container_format": "bare",
60
        "id": "b4713f20-3a41-4eaf-81ae-88698c18b3e8",
61
        "size": 752782848},
62
    {
63
        "status": "available",
64
        "name": "maelstrom",
65
        "disk_format": "diskdump",
66
        "container_format": "bare",
67
        "id": "0fb03e45-7d5a-4515-bd4e-e6bbf6457f06",
68
        "size": 2583195644},
69
    {
70
        "status": "available",
71
        "name": "Gardenia",
72
        "disk_format": "diskdump",
73
        "container_format": "bare",
74
        "id": "5963020b-ab74-4e11-bc59-90c494bbdedb",
75
        "size": 2589802496}]
76
imgid = "b4713f20-3a41-4eaf-81ae-88698c18b3e8"
77
example_images_detailed = [
78
    {
79
        "status": "available",
80
        "name": "Archlinux",
81
        "checksum": "1a126aad07475b43cc1959b446344211be13974",
82
        "created_at": "2013-01-28 22:44:54",
83
        "disk_format": "diskdump",
84
        "updated_at": "2013-01-28 22:44:55",
85
        "properties": {
86
            "partition_table": "msdos",
87
            "osfamily": "linux",
88
            "users": "root",
89
            "exclude_task_assignhostname": "yes",
90
            "os": "archlinux",
91
            "root_partition": "1",
92
            "description": "Archlinux base install 2012.12.01"},
93
        "location": "pithos://us3r-I6E-1d/images/archlinux.12.2012",
94
        "container_format": "bare",
95
        "owner": "user163@mail.example.com",
96
        "is_public": True,
97
        "deleted_at": "",
98
        "id": "b4713f20-3a41-4eaf-81ae-88698c18b3e8",
99
        "size": 752782848},
100
    {
101
        "status": "available",
102
        "name": "maelstrom",
103
        "checksum": "b202b8c7030cb22f896c6664ac",
104
        "created_at": "2013-02-13 10:07:42",
105
        "disk_format": "diskdump",
106
        "updated_at": "2013-02-13 10:07:44",
107
        "properties": {
108
            "partition_table": "msdos",
109
            "osfamily": "linux",
110
            "description": "Ubuntu 12.04.1 LTS",
111
            "os": "ubuntu",
112
            "root_partition": "1",
113
            "users": "user"},
114
        "location": "pithos://us3r-@r3n@-1d/images/mls-201302131203.diskdump",
115
        "container_format": "bare",
116
        "owner": "user3@mail.example.com",
117
        "is_public": True,
118
        "deleted_at": "",
119
        "id": "0fb03e45-7d5a-4515-bd4e-e6bbf6457f06",
120
        "size": 2583195648},
121
    {
122
        "status": "available",
123
        "name": "Gardenia",
124
        "checksum": "06d3099815d1f6fada91e80107638b882",
125
        "created_at": "2013-02-13 12:35:21",
126
        "disk_format": "diskdump",
127
        "updated_at": "2013-02-13 12:35:23",
128
        "properties": {
129
            "partition_table": "msdos",
130
            "osfamily": "linux",
131
            "description": "Ubuntu 12.04.2 LTS",
132
            "os": "ubuntu",
133
            "root_partition": "1",
134
            "users": "user"},
135
        "location": "pithos://us3r-E-1d/images/Gardenia-201302131431.diskdump",
136
        "container_format": "bare",
137
        "owner": "user3@mail.example.com",
138
        "is_public": True,
139
        "deleted_at": "",
140
        "id": "5963020b-ab74-4e11-bc59-90c494bbdedb",
141
        "size": 2589802496}]
142

    
143

    
144
class FR(object):
145
    json = example_images
146
    headers = {}
147
    content = json
148
    status = None
149
    status_code = 200
150

    
151
image_pkg = 'kamaki.clients.image.ImageClient'
152

    
153

    
154
class ImageClient(TestCase):
155

    
156
    def assert_dicts_are_equal(self, d1, d2):
157
        for k, v in d1.items():
158
            self.assertTrue(k in d2)
159
            if isinstance(v, dict):
160
                self.assert_dicts_are_equal(v, d2[k])
161
            else:
162
                self.assertEqual(unicode(v), unicode(d2[k]))
163

    
164
    def setUp(self):
165
        self.url = 'http://image.example.com'
166
        self.token = 'an1m@g370k3n=='
167
        from kamaki.clients import image
168
        self.client = image.ImageClient(self.url, self.token)
169

    
170
    def tearDown(self):
171
        FR.json = example_images
172
        FR.status_code = 200
173

    
174
    @patch('%s.get' % image_pkg, return_value=FR())
175
    def test_list_public(self, get):
176
        a_filter = dict(size_max=42)
177
        for args in product((False, True), ({}, a_filter), ('', '-')):
178
            (detail, filters, order) = args
179
            r = self.client.list_public(*args)
180
            filters['sort_dir'] = 'desc' if order.startswith('-') else 'asc'
181
            self.assertEqual(get.mock_calls[-1], call(
182
                '/images/%s' % ('detail' if detail else ''),
183
                async_params=filters, success=200))
184
            for i in range(len(r)):
185
                self.assert_dicts_are_equal(r[i], example_images[i])
186

    
187
    @patch('%s.head' % image_pkg, return_value=FR())
188
    def test_get_meta(self, head):
189
        img0 = example_images[0]
190
        FR.json = img0
191
        img0_headers = {}
192
        for k, v in example_images_detailed[0].items():
193
            img0_headers['x-image-meta-%s' % k] = v
194
        FR.headers = img0_headers
195
        r = self.client.get_meta(img0['id'])
196
        head.assert_called_once_with('/images/%s' % img0['id'], success=200)
197
        self.assertEqual(r['id'], img0['id'])
198
        self.assert_dicts_are_equal(r, example_images_detailed[0])
199

    
200
    @patch('%s.set_header' % image_pkg, return_value=FR())
201
    @patch('%s.post' % image_pkg, return_value=FR())
202
    def test_register(self, post, SH):
203
        img0 = example_images_detailed[0]
204
        FR.headers = example_image_headers
205
        img0_location = img0['location']
206
        img0_name = 'A new img0 name'
207
        prfx = 'x-image-meta-'
208
        proprfx = 'x-image-meta-property-'
209
        keys = [
210
            'id', 'store', 'dist_format', 'container_format',
211
            'size', 'checksum', 'is_public', 'owner']
212
        for args in product(
213
                ('v_id', None), ('v_store', None),
214
                ('v_dist_format', None), ('v_container_format', None),
215
                ('v_size', None), ('v_checksum', None),
216
                ('v_is_public', None), ('v_owner', None)):
217
            params = dict()
218
            async_headers = dict()
219
            props = dict()
220
            for i, k in enumerate(keys):
221
                if args[i]:
222
                    params[k] = args[i]
223
                    async_headers['%s%s' % (prfx, k)] = args[i]
224
                    props['%s%s' % (proprfx, args[i])] = k
225
            async_headers.update(props)
226
        r = self.client.register(
227
            img0_name, img0_location, params=params, properties=props)
228
        expectedict = dict(example_image_headers)
229
        expectedict.pop('extraheaders')
230
        from kamaki.clients.image import _format_image_headers
231
        self.assert_dicts_are_equal(_format_image_headers(expectedict), r)
232
        self.assertEqual(
233
            post.mock_calls[-1],
234
            call('/images/', async_headers=async_headers, success=200))
235
        self.assertEqual(SH.mock_calls[-2:], [
236
            call('X-Image-Meta-Name', img0_name),
237
            call('X-Image-Meta-Location', img0_location)])
238
        img1_location = ('some_uuid', 'some_container', 'some/path')
239
        r = self.client.register(
240
            img0_name, img1_location, params=params, properties=props)
241
        img1_location = 'pithos://%s' % '/'.join(img1_location)
242
        self.assertEqual(SH.mock_calls[-2:], [
243
            call('X-Image-Meta-Name', img0_name),
244
            call('X-Image-Meta-Location', img1_location)])
245

    
246
    @patch('%s.delete' % image_pkg)
247
    def test_unregister(self, delete):
248
        img_id = 'an1m4g3'
249
        self.client.unregister(img_id)
250
        delete.assert_called_once_with('/images/%s' % img_id, success=204)
251

    
252
    @patch('%s.put' % image_pkg, return_value=FR())
253
    def test_set_members(self, put):
254
        members = ['use3r-1d-0', 'us2r-1d-1', 'us3r-1d-2']
255
        self.client.set_members(imgid, members)
256
        put.assert_called_once_with(
257
            '/images/%s/members' % imgid,
258
            json=dict(memberships=[dict(member_id=m) for m in members]),
259
            success=204)
260

    
261
    @patch('%s.get' % image_pkg, return_value=FR())
262
    def test_list_members(self, get):
263
        members = ['use3r-1d-0', 'us2r-1d-1', 'us3r-1d-2']
264
        FR.json = dict(members=members)
265
        r = self.client.list_members(imgid)
266
        get.assert_called_once_with('/images/%s/members' % imgid, success=200)
267
        self.assertEqual(r, members)
268

    
269
    @patch('%s.put' % image_pkg, return_value=FR())
270
    def test_add_member(self, put):
271
        new_member = 'us3r-15-n3w'
272
        self.client.add_member(imgid, new_member)
273
        put.assert_called_once_with(
274
            '/images/%s/members/%s' % (imgid, new_member),
275
            success=204)
276

    
277
    @patch('%s.delete' % image_pkg, return_value=FR())
278
    def test_remove_member(self, delete):
279
        old_member = 'us3r-15-0ld'
280
        self.client.remove_member(imgid, old_member)
281
        delete.assert_called_once_with(
282
            '/images/%s/members/%s' % (imgid, old_member),
283
            success=204)
284

    
285
    @patch('%s.get' % image_pkg, return_value=FR())
286
    def test_list_shared(self, get):
287
        FR.json = dict(shared_images=example_images)
288
        r = self.client.list_shared(imgid)
289
        get.assert_called_once_with('/shared-images/%s' % imgid, success=200)
290
        for i in range(len(r)):
291
            self.assert_dicts_are_equal(r[i], example_images[i])
292

    
293
    @patch('%s.put' % image_pkg, return_value=FR())
294
    @patch('%s.set_header' % image_pkg)
295
    def test_update_image(self, set_header, put):
296
        FR.headers = 'some headers'
297
        hcnt = 0
298
        for args in product(
299
                ('some id', 'other id'),
300
                ('image name', None), ('disk fmt', None), ('cnt format', None),
301
                ('status', None), (True, False, None), ('owner id', None),
302
                (dict(k1='v1', k2='v2'), {})):
303
            r = self.client.update_image(*args[:-1], **args[-1])
304
            (image_id, name, disk_format, container_format,
305
            status, public, owner_id, properties) = args
306
            self.assertEqual(r, FR.headers)
307
            header_calls = [call('Content-Length', 0), ]
308
            prf = 'X-Image-Meta-'
309
            if name:
310
                header_calls.append(call('%sName' % prf, name))
311
            if disk_format:
312
                header_calls.append(call('%sDisk-Format' % prf, disk_format))
313
            if container_format:
314
                header_calls.append(
315
                    call('%sContainer-Format' % prf, container_format))
316
            if status:
317
                header_calls.append(call('%sStatus' % prf, status))
318
            if public is not None:
319
                header_calls.append(call('%sIs-Public' % prf, public))
320
            if owner_id:
321
                header_calls.append(call('%sOwner' % prf, owner_id))
322
            for k, v in properties.items():
323
                header_calls.append(call('%sProperty-%s' % (prf, k), v))
324
            self.assertEqual(
325
                sorted(set_header.mock_calls[hcnt:]), sorted(header_calls))
326
            hcnt = len(set_header.mock_calls)
327
            self.assertEqual(
328
                put.mock_calls[-1], call('/images/%s' % image_id, success=200))
329

    
330

    
331
if __name__ == '__main__':
332
    from sys import argv
333
    from kamaki.clients.test import runTestCase
334
    runTestCase(ImageClient, 'Plankton Client', argv[1:])