Revision 85898ca4

/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
from kamaki.clients import Client, ClientError
35

  
36

  
37
class AstakosClient(Client):
38
    """GRNet Astakos API client"""
39

  
40
    _cache = {}
41

  
42
    def __init__(self, base_url, token):
43
        super(AstakosClient, self).__init__(base_url, token)
44

  
45
    def authenticate(self, token=None):
46
        """Get authentication information and store it in this client
47
        As long as the AstakosClient instance is alive, the latest
48
        authentication information for this token will be available
49

  
50
        :param token: (str) custom token to authenticate
51

  
52
        :returns: (dict) authentication information
53
        """
54
        self.token = token or self.token
55
        self._cache[self.token] = self.get('/im/authenticate').json
56
        return self._cache[self.token]
57

  
58
    def list(self):
59
        """list cached user information"""
60
        r = []
61
        for k, v in self._cache.items():
62
            r.append(dict(v))
63
            r[-1].update(dict(auth_token=k))
64
        return r
65

  
66
    def info(self, token=None):
67
        """Get (cached) user information"""
68
        token_bu = self.token
69
        token = token or self.token
70
        try:
71
            r = self._cache[token]
72
        except KeyError:
73
            r = self.authenticate(token)
74
        self.token = token_bu
75
        return r
76

  
77
    def term(self, key, token=None):
78
        """Get (cached) term, from user credentials"""
79
        return self.info(token).get(key, None)
b/kamaki/clients/astakos/__init__.py
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
from kamaki.clients import Client, ClientError
35

  
36

  
37
class AstakosClient(Client):
38
    """GRNet Astakos API client"""
39

  
40
    _cache = {}
41

  
42
    def __init__(self, base_url, token):
43
        super(AstakosClient, self).__init__(base_url, token)
44

  
45
    def authenticate(self, token=None):
46
        """Get authentication information and store it in this client
47
        As long as the AstakosClient instance is alive, the latest
48
        authentication information for this token will be available
49

  
50
        :param token: (str) custom token to authenticate
51

  
52
        :returns: (dict) authentication information
53
        """
54
        self.token = token or self.token
55
        self._cache[self.token] = self.get('/im/authenticate').json
56
        return self._cache[self.token]
57

  
58
    def list(self):
59
        """list cached user information"""
60
        r = []
61
        for k, v in self._cache.items():
62
            r.append(dict(v))
63
            r[-1].update(dict(auth_token=k))
64
        return r
65

  
66
    def info(self, token=None):
67
        """Get (cached) user information"""
68
        token_bu = self.token
69
        token = token or self.token
70
        try:
71
            r = self._cache[token]
72
        except KeyError:
73
            r = self.authenticate(token)
74
        self.token = token_bu
75
        return r
76

  
77
    def term(self, key, token=None):
78
        """Get (cached) term, from user credentials"""
79
        return self.info(token).get(key, None)
b/kamaki/clients/astakos/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

  
34
from mock import patch
35

  
36
from unittest import TestCase
37
from kamaki.clients.astakos import AstakosClient
38

  
39

  
40
example = dict(
41
        name='Simple Name',
42
        username='User Full Name',
43
        auth_token_expires='1362583796000',
44
        auth_token_created='1359991796000',
45
        email=['user@example.gr'],
46
        id=42,
47
        uuid='aus3r-uu1d-f0r-73s71ng-as7ak0s')
48

  
49
example0 = dict(
50
        name='Simple Name 0',
51
        username='User Full Name 0',
52
        auth_token_expires='1362583796001',
53
        auth_token_created='1359991796001',
54
        email=['user0@example.gr'],
55
        id=32,
56
        uuid='an07h2r-us3r-uu1d-f0r-as7ak0s')
57

  
58

  
59
class FR(object):
60
    json = example
61
    headers = {}
62
    content = json
63
    status = None
64
    status_code = 200
65

  
66
    def release(self):
67
        pass
68

  
69
khttp = 'kamaki.clients.connection.kamakicon.KamakiHTTPConnection'
70

  
71

  
72
class Astakos(TestCase):
73

  
74
    cached = False
75

  
76
    def setUp(self):
77
        self.url = 'https://astakos.example.com'
78
        self.token = 'ast@k0sT0k3n=='
79
        self.client = AstakosClient(self.url, self.token)
80

  
81
    def tearDown(self):
82
        FR.json = example
83

  
84
    @patch('%s.perform_request' % khttp, return_value=FR())
85
    def _authenticate(self, PR):
86
        r = self.client.authenticate()
87
        self.cached = True
88
        return r
89

  
90
    def test_authenticate(self):
91
        r = self._authenticate()
92
        self.assertEqual(self.client.http_client.url, self.url)
93
        self.assertEqual(self.client.http_client.path, '/im/authenticate')
94
        for term, val in example.items():
95
            self.assertTrue(term in r)
96
            self.assertEqual(val, r[term])
97

  
98
    def test_info(self):
99
        if not self.cached:
100
            self._authenticate()
101
            return self.test_info()
102
        self.assertTrue(set(
103
            example.keys()).issubset(self.client.info().keys()))
104

  
105
    def test_get(self):
106
        if not self.cached:
107
            self._authenticate()
108
            return self.test_get()
109
        for term, val in example.items():
110
            self.assertEqual(self.client.term(term, self.token), val)
111
        self.assertTrue(example['email'][0] in self.client.term('email'))
112

  
113
    def test_list(self):
114
        if not self.cached:
115
            self._authenticate
116
        FR.json = example0
117
        self._authenticate()
118
        r = self.client.list()
119
        self.assertTrue(len(r) == 1)
120
        self.assertEqual(r[0]['auth_token'], self.token)
121

  
122
if __name__ == '__main__':
123
    from sys import argv
124
    from kamaki.clients.test import runTestCase
125
    runTestCase(Astakos, 'AstakosClient', argv[1:])
/dev/null
1
# Copyright 2011-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 time import sleep
35

  
36
from kamaki.clients.cyclades_rest_api import CycladesClientApi
37
from kamaki.clients import ClientError
38
from sys import stdout
39

  
40

  
41
class CycladesClient(CycladesClientApi):
42
    """GRNet Cyclades API client"""
43

  
44
    def start_server(self, server_id):
45
        """Submit a startup request
46

  
47
        :param server_id: integer (str or int)
48
        """
49
        req = {'start': {}}
50
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
51
        r.release()
52

  
53
    def shutdown_server(self, server_id):
54
        """Submit a shutdown request
55

  
56
        :param server_id: integer (str or int)
57
        """
58
        req = {'shutdown': {}}
59
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
60
        r.release()
61

  
62
    def get_server_console(self, server_id):
63
        """
64
        :param server_id: integer (str or int)
65

  
66
        :returns: (dict) info to set a VNC connection to VM
67
        """
68
        req = {'console': {'type': 'vnc'}}
69
        r = self.servers_post(server_id, 'action', json_data=req, success=200)
70
        return r.json['console']
71

  
72
    def get_firewall_profile(self, server_id):
73
        """
74
        :param server_id: integer (str or int)
75

  
76
        :returns: (str) ENABLED | DISABLED | PROTECTED
77

  
78
        :raises ClientError: 520 No Firewall Profile
79
        """
80
        r = self.get_server_details(server_id)
81
        try:
82
            return r['attachments']['values'][0]['firewallProfile']
83
        except KeyError:
84
            raise ClientError(
85
                'No Firewall Profile',
86
                details='Server %s is missing a firewall profile' % server_id)
87

  
88
    def set_firewall_profile(self, server_id, profile):
89
        """Set the firewall profile for the public interface of a server
90

  
91
        :param server_id: integer (str or int)
92

  
93
        :param profile: (str) ENABLED | DISABLED | PROTECTED
94
        """
95
        req = {'firewallProfile': {'profile': profile}}
96
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
97
        r.release()
98

  
99
    def list_servers(self, detail=False, changes_since=None):
100
        """
101
        :param detail: (bool) append full server details to each item if true
102

  
103
        :param changes_since: (date)
104

  
105
        :returns: list of server ids and names
106
        """
107
        detail = 'detail' if detail else ''
108
        r = self.servers_get(command=detail, changes_since=changes_since)
109
        return r.json['servers']['values']
110

  
111
    def list_server_nics(self, server_id):
112
        """
113
        :param server_id: integer (str or int)
114

  
115
        :returns: (dict) network interface connections
116
        """
117
        r = self.servers_get(server_id, 'ips')
118
        return r.json['addresses']['values']
119

  
120
    def get_server_stats(self, server_id):
121
        """
122
        :param server_id: integer (str or int)
123

  
124
        :returns: (dict) auto-generated graphs of statistics (urls)
125
        """
126
        r = self.servers_get(server_id, 'stats')
127
        return r.json['stats']
128

  
129
    def list_networks(self, detail=False):
130
        """
131
        :param detail: (bool)
132

  
133
        :returns: (list) id,name if not detail else full info per network
134
        """
135
        detail = 'detail' if detail else ''
136
        r = self.networks_get(command=detail)
137
        return r.json['networks']['values']
138

  
139
    def list_network_nics(self, network_id):
140
        """
141
        :param network_id: integer (str or int)
142

  
143
        :returns: (list)
144
        """
145
        r = self.networks_get(network_id=network_id)
146
        return r.json['network']['attachments']['values']
147

  
148
    def create_network(
149
            self, name,
150
            cidr=None, gateway=None, type=None, dhcp=False):
151
        """
152
        :param name: (str)
153

  
154
        :param cidr: (str)
155

  
156
        :param geteway: (str)
157

  
158
        :param type: (str)
159

  
160
        :param dhcp: (bool)
161

  
162
        :returns: (dict) network detailed info
163
        """
164
        net = dict(name=name)
165
        if cidr:
166
            net['cidr'] = cidr
167
        if gateway:
168
            net['gateway'] = gateway
169
        if type:
170
            net['type'] = type
171
        net['dhcp'] = True if dhcp else False
172
        req = dict(network=net)
173
        r = self.networks_post(json_data=req, success=202)
174
        return r.json['network']
175

  
176
    def get_network_details(self, network_id):
177
        """
178
        :param network_id: integer (str or int)
179

  
180
        :returns: (dict)
181
        """
182
        r = self.networks_get(network_id=network_id)
183
        return r.json['network']
184

  
185
    def update_network_name(self, network_id, new_name):
186
        """
187
        :param network_id: integer (str or int)
188

  
189
        :param new_name: (str)
190
        """
191
        req = {'network': {'name': new_name}}
192
        r = self.networks_put(network_id=network_id, json_data=req)
193
        r.release()
194

  
195
    def delete_network(self, network_id):
196
        """
197
        :param network_id: integer (str or int)
198

  
199
        :raises ClientError: 421 Network in use
200
        """
201
        try:
202
            r = self.networks_delete(network_id)
203
        except ClientError as err:
204
            if err.status == 421:
205
                err.details = [
206
                    'Network may be still connected to at least one server']
207
            raise err
208
        r.release()
209

  
210
    def connect_server(self, server_id, network_id):
211
        """ Connect a server to a network
212

  
213
        :param server_id: integer (str or int)
214

  
215
        :param network_id: integer (str or int)
216
        """
217
        req = {'add': {'serverRef': server_id}}
218
        r = self.networks_post(network_id, 'action', json_data=req)
219
        r.release()
220

  
221
    def disconnect_server(self, server_id, nic_id):
222
        """
223
        :param server_id: integer (str or int)
224

  
225
        :param nic_id: (str)
226
        """
227
        vm_nets = self.list_server_nics(server_id)
228
        num_of_disconnections = 0
229
        for (nic_id, network_id) in [(
230
                net['id'],
231
                net['network_id']) for net in vm_nets if nic_id == net['id']]:
232
            req = {'remove': {'attachment': '%s' % nic_id}}
233
            r = self.networks_post(network_id, 'action', json_data=req)
234
            r.release()
235
            num_of_disconnections += 1
236
        return num_of_disconnections
237

  
238
    def disconnect_network_nics(self, netid):
239
        """
240
        :param netid: integer (str or int)
241
        """
242
        for nic in self.list_network_nics(netid):
243
            req = dict(remove=dict(attachment=nic))
244
            r = self.networks_post(netid, 'action', json_data=req)
245
            r.release()
246

  
247
    def wait_server(
248
            self,
249
            server_id,
250
            current_status='BUILD',
251
            delay=0.5,
252
            max_wait=128,
253
            wait_cb=None):
254
        """Wait for server while its status is current_status
255

  
256
        :param server_id: integer (str or int)
257

  
258
        :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
259

  
260
        :param delay: time interval between retries
261

  
262
        :param wait_cb: if set a progressbar is used to show progress
263

  
264
        :returns: (str) the new mode if succesfull, (bool) False if timed out
265
        """
266
        r = self.get_server_details(server_id)
267
        if r['status'] != current_status:
268
            return r['status']
269
        old_wait = total_wait = 0
270

  
271
        if current_status == 'BUILD':
272
            max_wait = 100
273
            wait_gen = wait_cb(max_wait) if wait_cb else None
274
        elif wait_cb:
275
            wait_gen = wait_cb(1 + max_wait // delay)
276
            wait_gen.next()
277

  
278
        while r['status'] == current_status and total_wait <= max_wait:
279
            if current_status == 'BUILD':
280
                total_wait = int(r['progress'])
281
                if wait_cb:
282
                    for i in range(int(old_wait), int(total_wait)):
283
                        wait_gen.next()
284
                    old_wait = total_wait
285
                else:
286
                    stdout.write('.')
287
                    stdout.flush()
288
            else:
289
                if wait_cb:
290
                    wait_gen.next()
291
                else:
292
                    stdout.write('.')
293
                    stdout.flush()
294
                total_wait += delay
295
            sleep(delay)
296
            r = self.get_server_details(server_id)
297

  
298
        if r['status'] != current_status:
299
            if wait_cb:
300
                try:
301
                    while True:
302
                        wait_gen.next()
303
                except:
304
                    pass
305
            return r['status']
306
        return False
b/kamaki/clients/cyclades/__init__.py
1
# Copyright 2011-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 time import sleep
35

  
36
from kamaki.clients.cyclades_rest_api import CycladesClientApi
37
from kamaki.clients import ClientError
38
from sys import stdout
39

  
40

  
41
class CycladesClient(CycladesClientApi):
42
    """GRNet Cyclades API client"""
43

  
44
    def start_server(self, server_id):
45
        """Submit a startup request
46

  
47
        :param server_id: integer (str or int)
48
        """
49
        req = {'start': {}}
50
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
51
        r.release()
52

  
53
    def shutdown_server(self, server_id):
54
        """Submit a shutdown request
55

  
56
        :param server_id: integer (str or int)
57
        """
58
        req = {'shutdown': {}}
59
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
60
        r.release()
61

  
62
    def get_server_console(self, server_id):
63
        """
64
        :param server_id: integer (str or int)
65

  
66
        :returns: (dict) info to set a VNC connection to VM
67
        """
68
        req = {'console': {'type': 'vnc'}}
69
        r = self.servers_post(server_id, 'action', json_data=req, success=200)
70
        return r.json['console']
71

  
72
    def get_firewall_profile(self, server_id):
73
        """
74
        :param server_id: integer (str or int)
75

  
76
        :returns: (str) ENABLED | DISABLED | PROTECTED
77

  
78
        :raises ClientError: 520 No Firewall Profile
79
        """
80
        r = self.get_server_details(server_id)
81
        try:
82
            return r['attachments']['values'][0]['firewallProfile']
83
        except KeyError:
84
            raise ClientError(
85
                'No Firewall Profile',
86
                details='Server %s is missing a firewall profile' % server_id)
87

  
88
    def set_firewall_profile(self, server_id, profile):
89
        """Set the firewall profile for the public interface of a server
90

  
91
        :param server_id: integer (str or int)
92

  
93
        :param profile: (str) ENABLED | DISABLED | PROTECTED
94
        """
95
        req = {'firewallProfile': {'profile': profile}}
96
        r = self.servers_post(server_id, 'action', json_data=req, success=202)
97
        r.release()
98

  
99
    def list_servers(self, detail=False, changes_since=None):
100
        """
101
        :param detail: (bool) append full server details to each item if true
102

  
103
        :param changes_since: (date)
104

  
105
        :returns: list of server ids and names
106
        """
107
        detail = 'detail' if detail else ''
108
        r = self.servers_get(command=detail, changes_since=changes_since)
109
        return r.json['servers']['values']
110

  
111
    def list_server_nics(self, server_id):
112
        """
113
        :param server_id: integer (str or int)
114

  
115
        :returns: (dict) network interface connections
116
        """
117
        r = self.servers_get(server_id, 'ips')
118
        return r.json['addresses']['values']
119

  
120
    def get_server_stats(self, server_id):
121
        """
122
        :param server_id: integer (str or int)
123

  
124
        :returns: (dict) auto-generated graphs of statistics (urls)
125
        """
126
        r = self.servers_get(server_id, 'stats')
127
        return r.json['stats']
128

  
129
    def list_networks(self, detail=False):
130
        """
131
        :param detail: (bool)
132

  
133
        :returns: (list) id,name if not detail else full info per network
134
        """
135
        detail = 'detail' if detail else ''
136
        r = self.networks_get(command=detail)
137
        return r.json['networks']['values']
138

  
139
    def list_network_nics(self, network_id):
140
        """
141
        :param network_id: integer (str or int)
142

  
143
        :returns: (list)
144
        """
145
        r = self.networks_get(network_id=network_id)
146
        return r.json['network']['attachments']['values']
147

  
148
    def create_network(
149
            self, name,
150
            cidr=None, gateway=None, type=None, dhcp=False):
151
        """
152
        :param name: (str)
153

  
154
        :param cidr: (str)
155

  
156
        :param geteway: (str)
157

  
158
        :param type: (str)
159

  
160
        :param dhcp: (bool)
161

  
162
        :returns: (dict) network detailed info
163
        """
164
        net = dict(name=name)
165
        if cidr:
166
            net['cidr'] = cidr
167
        if gateway:
168
            net['gateway'] = gateway
169
        if type:
170
            net['type'] = type
171
        net['dhcp'] = True if dhcp else False
172
        req = dict(network=net)
173
        r = self.networks_post(json_data=req, success=202)
174
        return r.json['network']
175

  
176
    def get_network_details(self, network_id):
177
        """
178
        :param network_id: integer (str or int)
179

  
180
        :returns: (dict)
181
        """
182
        r = self.networks_get(network_id=network_id)
183
        return r.json['network']
184

  
185
    def update_network_name(self, network_id, new_name):
186
        """
187
        :param network_id: integer (str or int)
188

  
189
        :param new_name: (str)
190
        """
191
        req = {'network': {'name': new_name}}
192
        r = self.networks_put(network_id=network_id, json_data=req)
193
        r.release()
194

  
195
    def delete_network(self, network_id):
196
        """
197
        :param network_id: integer (str or int)
198

  
199
        :raises ClientError: 421 Network in use
200
        """
201
        try:
202
            r = self.networks_delete(network_id)
203
        except ClientError as err:
204
            if err.status == 421:
205
                err.details = [
206
                    'Network may be still connected to at least one server']
207
            raise err
208
        r.release()
209

  
210
    def connect_server(self, server_id, network_id):
211
        """ Connect a server to a network
212

  
213
        :param server_id: integer (str or int)
214

  
215
        :param network_id: integer (str or int)
216
        """
217
        req = {'add': {'serverRef': server_id}}
218
        r = self.networks_post(network_id, 'action', json_data=req)
219
        r.release()
220

  
221
    def disconnect_server(self, server_id, nic_id):
222
        """
223
        :param server_id: integer (str or int)
224

  
225
        :param nic_id: (str)
226
        """
227
        vm_nets = self.list_server_nics(server_id)
228
        num_of_disconnections = 0
229
        for (nic_id, network_id) in [(
230
                net['id'],
231
                net['network_id']) for net in vm_nets if nic_id == net['id']]:
232
            req = {'remove': {'attachment': '%s' % nic_id}}
233
            r = self.networks_post(network_id, 'action', json_data=req)
234
            r.release()
235
            num_of_disconnections += 1
236
        return num_of_disconnections
237

  
238
    def disconnect_network_nics(self, netid):
239
        """
240
        :param netid: integer (str or int)
241
        """
242
        for nic in self.list_network_nics(netid):
243
            req = dict(remove=dict(attachment=nic))
244
            r = self.networks_post(netid, 'action', json_data=req)
245
            r.release()
246

  
247
    def wait_server(
248
            self,
249
            server_id,
250
            current_status='BUILD',
251
            delay=0.5,
252
            max_wait=128,
253
            wait_cb=None):
254
        """Wait for server while its status is current_status
255

  
256
        :param server_id: integer (str or int)
257

  
258
        :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
259

  
260
        :param delay: time interval between retries
261

  
262
        :param wait_cb: if set a progressbar is used to show progress
263

  
264
        :returns: (str) the new mode if succesfull, (bool) False if timed out
265
        """
266
        r = self.get_server_details(server_id)
267
        if r['status'] != current_status:
268
            return r['status']
269
        old_wait = total_wait = 0
270

  
271
        if current_status == 'BUILD':
272
            max_wait = 100
273
            wait_gen = wait_cb(max_wait) if wait_cb else None
274
        elif wait_cb:
275
            wait_gen = wait_cb(1 + max_wait // delay)
276
            wait_gen.next()
277

  
278
        while r['status'] == current_status and total_wait <= max_wait:
279
            if current_status == 'BUILD':
280
                total_wait = int(r['progress'])
281
                if wait_cb:
282
                    for i in range(int(old_wait), int(total_wait)):
283
                        wait_gen.next()
284
                    old_wait = total_wait
285
                else:
286
                    stdout.write('.')
287
                    stdout.flush()
288
            else:
289
                if wait_cb:
290
                    wait_gen.next()
291
                else:
292
                    stdout.write('.')
293
                    stdout.flush()
294
                total_wait += delay
295
            sleep(delay)
296
            r = self.get_server_details(server_id)
297

  
298
        if r['status'] != current_status:
299
            if wait_cb:
300
                try:
301
                    while True:
302
                        wait_gen.next()
303
                except:
304
                    pass
305
            return r['status']
306
        return False
b/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_create_server(self):
154
        self.client.get_image_details = Mock(return_value=img_recv['image'])
155
        with patch.object(Client, 'request', side_effect=ClientError(
156
                'REQUEST ENTITY TOO LARGE',
157
                status=403)):
158
            self.assertRaises(
159
                ClientError,
160
                self.client.create_server,
161
                vm_name, fid, img_ref)
162

  
163
        with patch.object(
164
                self.C,
165
                'perform_request',
166
                return_value=FR()) as perform_req:
167
            self.assertRaises(
168
                ClientError,
169
                self.client.create_server,
170
                vm_name, fid, img_ref)
171
            FR.status_code = 202
172
            r = self.client.create_server(vm_name, fid, img_ref)
173
            self.assertEqual(self.client.http_client.url, self.url)
174
            self.assertEqual(self.client.http_client.path, '/servers')
175
            (method, data, a_headers, a_params) = perform_req.call_args[0]
176
            self.assert_dicts_are_equal(loads(data), vm_send)
177
            self.assert_dicts_are_equal(r, vm_recv['server'])
178
            prsn = 'Personality string (does not work with real servers)'
179
            self.client.create_server(vm_name, fid, img_ref, prsn)
180
            (method, data, a_headers, a_params) = perform_req.call_args[0]
181
            data = loads(data)
182
            self.assertTrue('personality' in data['server'])
183
            self.assertEqual(prsn, data['server']['personality'])
184

  
185
    def test_list_servers(self):
186
        FR.json = vm_list
187
        with patch.object(
188
                self.C,
189
                'perform_request',
190
                return_value=FR()) as perform_req:
191
            r = self.client.list_servers()
192
            self.assertEqual(self.client.http_client.url, self.url)
193
            self.assertEqual(self.client.http_client.path, '/servers')
194
            (method, data, a_headers, a_params) = perform_req.call_args[0]
195
            self.assert_dicts_are_equal(dict(values=r), vm_list['servers'])
196
            r = self.client.list_servers(detail=True)
197
            self.assertEqual(self.client.http_client.url, self.url)
198
            self.assertEqual(self.client.http_client.path, '/servers/detail')
199
        with patch.object(
200
                CycladesClientApi,
201
                'servers_get',
202
                return_value=FR()) as servers_get:
203
            self.client.list_servers(changes_since=True)
204
            self.assertTrue(servers_get.call_args[1]['changes_since'])
205

  
206
    @patch('%s.perform_request' % khttp, return_value=FR())
207
    def test_get_server_details(self, PR):
208
        vm_id = vm_recv['server']['id']
209
        r = self.client.get_server_details(vm_id)
210
        self.assertEqual(self.client.http_client.url, self.url)
211
        self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
212
        self.assert_dicts_are_equal(r, vm_recv['server'])
213

  
214
    @patch('%s.perform_request' % khttp, return_value=FR())
215
    def test_update_server_name(self, PR):
216
        vm_id = vm_recv['server']['id']
217
        new_name = vm_name + '_new'
218
        FR.status_code = 204
219
        self.client.update_server_name(vm_id, new_name)
220
        self.assertEqual(self.client.http_client.url, self.url)
221
        self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
222
        (method, data, a_headers, a_params) = PR.call_args[0]
223
        self.assert_dicts_are_equal(
224
            dict(server=dict(name=new_name)),
225
            loads(data))
226

  
227
    @patch('%s.perform_request' % khttp, return_value=FR())
228
    def test_reboot_server(self, PR):
229
        vm_id = vm_recv['server']['id']
230
        FR.status_code = 202
231
        self.client.reboot_server(vm_id)
232
        self.assertEqual(self.client.http_client.url, self.url)
233
        self.assertEqual(
234
            self.client.http_client.path,
235
            '/servers/%s/action' % vm_id)
236
        (method, data, a_headers, a_params) = PR.call_args[0]
237
        self.assert_dicts_are_equal(
238
            dict(reboot=dict(type='SOFT')),
239
            loads(data))
240

  
241
    @patch('%s.perform_request' % khttp, return_value=FR())
242
    def test_create_server_metadata(self, PR):
243
        vm_id = vm_recv['server']['id']
244
        metadata = dict(m1='v1', m2='v2', m3='v3')
245
        FR.json = dict(meta=vm_recv['server'])
246
        self.assertRaises(
247
            ClientError,
248
            self.client.create_server_metadata,
249
            vm_id, 'key', 'value')
250
        FR.status_code = 201
251
        for k, v in metadata.items():
252
            r = self.client.create_server_metadata(vm_id, k, v)
253
            self.assertEqual(self.client.http_client.url, self.url)
254
            self.assertEqual(
255
                self.client.http_client.path,
256
                '/servers/%s/meta/%s' % (vm_id, k))
257
            (method, data, a_headers, a_params) = PR.call_args[0]
258
            self.assertEqual(dict(meta={k: v}), loads(data))
259
            self.assert_dicts_are_equal(r, vm_recv['server'])
260

  
261
    @patch('%s.perform_request' % khttp, return_value=FR())
262
    def test_get_server_metadata(self, PR):
263
        vm_id = vm_recv['server']['id']
264
        metadata = dict(m1='v1', m2='v2', m3='v3')
265
        FR.json = dict(metadata=dict(values=metadata))
266
        r = self.client.get_server_metadata(vm_id)
267
        self.assertEqual(self.client.http_client.url, self.url)
268
        self.assertEqual(
269
            self.client.http_client.path,
270
            '/servers/%s/meta' % vm_id)
271
        self.assert_dicts_are_equal(r, metadata)
272

  
273
        for k, v in metadata.items():
274
            FR.json = dict(meta={k: v})
275
            r = self.client.get_server_metadata(vm_id, k)
276
            self.assertEqual(self.client.http_client.url, self.url)
277
            self.assertEqual(
278
                self.client.http_client.path,
279
                '/servers/%s/meta/%s' % (vm_id, k))
280
            self.assert_dicts_are_equal(r, {k: v})
281

  
282
    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
283
    def test_update_server_metadata(self, servers_post):
284
        vm_id = vm_recv['server']['id']
285
        metadata = dict(m1='v1', m2='v2', m3='v3')
286
        FR.json = dict(metadata=metadata)
287
        r = self.client.update_server_metadata(vm_id, **metadata)
288
        self.assert_dicts_are_equal(r, metadata)
289
        (called_id, cmd) = servers_post.call_args[0]
290
        self.assertEqual(called_id, vm_id)
291
        self.assertEqual(cmd, 'meta')
292
        data = servers_post.call_args[1]['json_data']
293
        self.assert_dicts_are_equal(data, dict(metadata=metadata))
294

  
295
    @patch('%s.servers_delete' % cyclades_pkg, return_value=FR())
296
    def test_delete_server_metadata(self, servers_delete):
297
        vm_id = vm_recv['server']['id']
298
        key = 'metakey'
299
        self.client.delete_server_metadata(vm_id, key)
300
        self.assertEqual((vm_id, 'meta/' + key), servers_delete.call_args[0])
301

  
302
    @patch('%s.perform_request' % khttp, return_value=FR())
303
    def test_list_flavors(self, PR):
304
        FR.json = flavor_list
305
        r = self.client.list_flavors()
306
        self.assertEqual(self.client.http_client.url, self.url)
307
        self.assertEqual(self.client.http_client.path, '/flavors')
308
        (method, data, a_headers, a_params) = PR.call_args[0]
309
        self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
310
        r = self.client.list_flavors(detail=True)
311
        self.assertEqual(self.client.http_client.url, self.url)
312
        self.assertEqual(self.client.http_client.path, '/flavors/detail')
313

  
314
    @patch('%s.perform_request' % khttp, return_value=FR())
315
    def test_get_flavor_details(self, PR):
316
        FR.json = dict(flavor=flavor_list['flavors'])
317
        r = self.client.get_flavor_details(fid)
318
        self.assertEqual(self.client.http_client.url, self.url)
319
        self.assertEqual(self.client.http_client.path, '/flavors/%s' % fid)
320
        self.assert_dicts_are_equal(r, flavor_list['flavors'])
321

  
322
    @patch('%s.perform_request' % khttp, return_value=FR())
323
    def test_list_images(self, PR):
324
        FR.json = img_list
325
        r = self.client.list_images()
326
        self.assertEqual(self.client.http_client.url, self.url)
327
        self.assertEqual(self.client.http_client.path, '/images')
328
        expected = img_list['images']['values']
329
        for i in range(len(r)):
330
            self.assert_dicts_are_equal(expected[i], r[i])
331
        self.client.list_images(detail=True)
332
        self.assertEqual(self.client.http_client.url, self.url)
333
        self.assertEqual(self.client.http_client.path, '/images/detail')
334

  
335
    @patch('%s.perform_request' % khttp, return_value=FR())
336
    def test_get_image_details(self, PR):
337
        FR.json = img_recv
338
        r = self.client.get_image_details(img_ref)
339
        self.assertEqual(self.client.http_client.url, self.url)
340
        self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
341
        self.assert_dicts_are_equal(r, img_recv['image'])
342

  
343
    @patch('%s.images_get' % cyclades_pkg, return_value=FR())
344
    def test_get_image_metadata(self, IG):
345
        FR.json = dict(metadata=dict(values=img_recv['image']))
346
        r = self.client.get_image_metadata(img_ref)
347
        self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta'))
348
        self.assert_dicts_are_equal(img_recv['image'], r)
349
        FR.json = dict(meta=img_recv['image'])
350
        key = 'somekey'
351
        self.client.get_image_metadata(img_ref, key)
352
        self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta/%s' % key))
353

  
354
    @patch('%s.perform_request' % khttp, return_value=FR())
355
    def test_shutdown_server(self, PR):
356
        vm_id = vm_recv['server']['id']
357
        FR.status_code = 202
358
        self.client.shutdown_server(vm_id)
359
        self.assertEqual(self.client.http_client.url, self.url)
360
        self.assertEqual(
361
            self.client.http_client.path,
362
            '/servers/%s/action' % vm_id)
363
        self.assertEqual(
364
            PR.call_args[0],
365
            ('post',  '{"shutdown": {}}', {}, {}))
366

  
367
    @patch('%s.perform_request' % khttp, return_value=FR())
368
    def test_start_server(self, PR):
369
        vm_id = vm_recv['server']['id']
370
        FR.status_code = 202
371
        self.client.start_server(vm_id)
372
        self.assertEqual(self.client.http_client.url, self.url)
373
        self.assertEqual(
374
            self.client.http_client.path,
375
            '/servers/%s/action' % vm_id)
376
        self.assertEqual(PR.call_args[0], ('post',  '{"start": {}}', {}, {}))
377

  
378
    @patch('%s.perform_request' % khttp, return_value=FR())
379
    def test_get_server_console(self, PR):
380
        cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
381
        FR.json = cnsl
382
        vm_id = vm_recv['server']['id']
383
        r = self.client.get_server_console(vm_id)
384
        self.assertEqual(self.client.http_client.url, self.url)
385
        self.assertEqual(
386
            self.client.http_client.path,
387
            '/servers/%s/action' % vm_id)
388
        self.assert_dicts_are_equal(cnsl['console'], r)
389
        self.assertEqual(
390
            PR.call_args[0],
391
            ('post',  '{"console": {"type": "vnc"}}', {}, {}))
392

  
393
    def test_get_firewall_profile(self):
394
        vm_id = vm_recv['server']['id']
395
        v = 'Some profile'
396
        ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
397
        with patch.object(
398
                CycladesClient,
399
                'get_server_details',
400
                return_value=ret) as GSD:
401
            r = self.client.get_firewall_profile(vm_id)
402
            self.assertEqual(r, v)
403
            self.assertEqual(GSD.call_args[0], (vm_id,))
404
            ret['attachments']['values'][0].pop('firewallProfile')
405
            self.assertRaises(
406
                ClientError,
407
                self.client.get_firewall_profile,
408
                vm_id)
409

  
410
    @patch('%s.perform_request' % khttp, return_value=FR())
411
    def test_set_firewall_profile(self, PR):
412
        vm_id = vm_recv['server']['id']
413
        v = 'Some profile'
414
        FR.status_code = 202
415
        self.client.set_firewall_profile(vm_id, v)
416
        self.assertEqual(self.client.http_client.url, self.url)
417
        self.assertEqual(
418
            self.client.http_client.path,
419
            '/servers/%s/action' % vm_id)
420
        self.assertEqual(PR.call_args[0], (
421
            'post',
422
            '{"firewallProfile": {"profile": "%s"}}' % v,
423
            {},
424
            {}))
425

  
426
    @patch('%s.perform_request' % khttp, return_value=FR())
427
    def test_get_server_stats(self, PR):
428
        vm_id = vm_recv['server']['id']
429
        stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
430
        FR.json = dict(stats=stats)
431
        r = self.client.get_server_stats(vm_id)
432
        self.assertEqual(self.client.http_client.url, self.url)
433
        self.assertEqual(
434
            self.client.http_client.path,
435
            '/servers/%s/stats' % vm_id)
436
        self.assert_dicts_are_equal(stats, r)
437

  
438
    @patch('%s.perform_request' % khttp, return_value=FR())
439
    def test_create_network(self, PR):
440
        net_name = net_send['network']['name']
441
        FR.json = net_recv
442
        FR.status_code = 202
443
        full_args = dict(
444
                cidr='192.168.0.0/24',
445
                gateway='192.168.0.1',
446
                type='MAC_FILTERED',
447
                dhcp=True)
448
        test_args = dict(full_args)
449
        test_args.update(dict(empty=None, full=None))
450
        for arg, val in test_args.items():
451
            kwargs = {} if arg == 'empty' else full_args if (
452
                arg == 'full') else {arg: val}
453
            r = self.client.create_network(net_name, **kwargs)
454
            self.assertEqual(self.client.http_client.url, self.url)
455
            self.assertEqual(
456
                self.client.http_client.path,
457
                '/networks')
458
            self.assert_dicts_are_equal(r, net_recv['network'])
459
            data = PR.call_args[0][1]
460
            expected = dict(network=dict(net_send['network']))
461
            expected['network'].update(kwargs)
462
            self.assert_dicts_are_equal(loads(data), expected)
463

  
464
    @patch('%s.perform_request' % khttp, return_value=FR())
465
    def test_connect_server(self, PR):
466
        vm_id = vm_recv['server']['id']
467
        net_id = net_recv['network']['id']
468
        FR.status_code = 202
469
        self.client.connect_server(vm_id, net_id)
470
        self.assertEqual(self.client.http_client.url, self.url)
471
        self.assertEqual(
472
            self.client.http_client.path,
473
            '/networks/%s/action' % net_id)
474
        self.assertEqual(
475
            PR.call_args[0],
476
            ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
477

  
478
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
479
    def test_disconnect_server(self, NP):
480
        vm_id = vm_recv['server']['id']
481
        net_id = net_recv['network']['id']
482
        nic_id = 'nic-%s-%s' % (net_id, vm_id)
483
        vm_nics = [
484
            dict(id=nic_id, network_id=net_id),
485
            dict(id='another-nic-id', network_id='another-net-id'),
486
            dict(id=nic_id * 2, network_id=net_id * 2)]
487
        with patch.object(
488
                CycladesClient,
489
                'list_server_nics',
490
                return_value=vm_nics) as LSN:
491
            r = self.client.disconnect_server(vm_id, nic_id)
492
            self.assertEqual(r, 1)
493
            self.assertEqual(LSN.call_args[0], (vm_id,))
494
            self.assertEqual(NP.call_args[0], (net_id, 'action'))
495
            self.assertEqual(
496
                NP.call_args[1],
497
                dict(json_data=dict(remove=dict(attachment=nic_id))))
498

  
499
    @patch('%s.perform_request' % khttp, return_value=FR())
500
    def test_list_server_nics(self, PR):
501
        vm_id = vm_recv['server']['id']
502
        nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
503
        FR.json = nics
504
        r = self.client.list_server_nics(vm_id)
505
        self.assertEqual(self.client.http_client.url, self.url)
506
        self.assertEqual(
507
            self.client.http_client.path,
508
            '/servers/%s/ips' % vm_id)
509
        expected = nics['addresses']['values']
510
        for i in range(len(r)):
511
            self.assert_dicts_are_equal(r[i], expected[i])
512

  
513
    @patch('%s.perform_request' % khttp, return_value=FR())
514
    def test_list_networks(self, PR):
515
        FR.json = net_list
516
        r = self.client.list_networks()
517
        self.assertEqual(self.client.http_client.url, self.url)
518
        self.assertEqual(self.client.http_client.path, '/networks')
519
        expected = net_list['networks']['values']
520
        for i in range(len(r)):
521
            self.assert_dicts_are_equal(expected[i], r[i])
522
        self.client.list_networks(detail=True)
523
        self.assertEqual(self.client.http_client.url, self.url)
524
        self.assertEqual(self.client.http_client.path, '/networks/detail')
525

  
526
    @patch('%s.perform_request' % khttp, return_value=FR())
527
    def test_list_network_nics(self, PR):
528
        net_id = net_recv['network']['id']
529
        FR.json = net_recv
530
        r = self.client.list_network_nics(net_id)
531
        self.assertEqual(self.client.http_client.url, self.url)
532
        self.assertEqual(
533
            self.client.http_client.path,
534
            '/networks/%s' % net_id)
535
        expected = net_recv['network']['attachments']['values']
536
        for i in range(len(r)):
537
            self.assert_dicts_are_equal(r[i], expected[i])
538

  
539
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
540
    def test_disconnect_network_nics(self, NP):
541
        net_id = net_recv['network']['id']
542
        nics = ['nic1', 'nic2', 'nic3']
543
        with patch.object(
544
                CycladesClient,
545
                'list_network_nics',
546
                return_value=nics) as lnn:
547
            self.client.disconnect_network_nics(net_id)
548
            lnn.assert_called_once_with(net_id)
549
            for i in range(len(nics)):
550
                expected = call(net_id, 'action', json_data=dict(
551
                    remove=dict(attachment=nics[i])))
552
                self.assertEqual(expected, NP.mock_calls[i])
553

  
554
    @patch('%s.perform_request' % khttp, return_value=FR())
555
    def test_get_network_details(self, PR):
556
        FR.json = net_recv
557
        net_id = net_recv['network']['id']
558
        r = self.client.get_network_details(net_id)
559
        self.assertEqual(self.client.http_client.url, self.url)
560
        self.assertEqual(
561
            self.client.http_client.path,
562
            '/networks/%s' % net_id)
563
        self.assert_dicts_are_equal(r, net_recv['network'])
564

  
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff