__init__.py 11.4 KB
Newer Older
1
# Copyright 2011-2014 GRNET S.A. All rights reserved.
Giorgos Verigakis's avatar
Giorgos Verigakis committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

34
35
from kamaki.clients.cyclades.rest_api import (
    CycladesComputeRestClient, CycladesBlockStorageRestClient)
36
from kamaki.clients.network import NetworkClient
37
from kamaki.clients.utils import path4url
Stavros Sachtouris's avatar
Stavros Sachtouris committed
38
from kamaki.clients import ClientError, Waiter
39

40

41
class CycladesComputeClient(CycladesComputeRestClient, Waiter):
42
    """Synnefo Cyclades Compute API client"""
43

44
45
    def create_server(
            self, name, flavor_id, image_id,
46
47
            metadata=None, personality=None, networks=None, project_id=None,
            response_headers=dict(location=None)):
48
49
50
51
52
53
        """Submit request to create a new server

        :param name: (str)

        :param flavor_id: integer id denoting a preset hardware configuration

54
        :param image_id: (str) id denoting the OS image to run on virt. server
55
56
57
58

        :param metadata: (dict) vm metadata updated by os/users image metadata

        :param personality: a list of (file path, file contents) tuples,
59
            describing files to be injected into virtual server upon creation
60

61
62
        :param networks: (list of dicts) Networks to connect to, list this:
            "networks": [
63
64
65
            {"uuid": <network_uuid>},
            {"uuid": <network_uuid>, "fixed_ip": address},
            {"port": <port_id>}, ...]
66
67
            ATTENTION: Empty list is different to None. None means ' do not
            mention it', empty list means 'automatically get an ip'
68

69
        :param project_id: the project where to assign the server
70

71
        :returns: a dict with the new virtual server details
72
73
74
75
76
77
78

        :raises ClientError: wraps request errors
        """
        image = self.get_image_details(image_id)
        metadata = metadata or dict()
        for key in ('os', 'users'):
            try:
79
                metadata.setdefault(key, image['metadata'][key])
80
81
82
            except KeyError:
                pass

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
        req = {'server': {
            'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}}

        if metadata:
            req['server']['metadata'] = metadata

        if personality:
            req['server']['personality'] = personality

        if networks is not None:
            req['server']['networks'] = networks

        if project_id is not None:
            req['server']['project'] = project_id

        r = self.servers_post(json_data=req, success=(202, ))
        for k, v in response_headers.items():
            response_headers[k] = r.headers.get(k, v)
        return r.json['server']
102

103
104
105
106
107
108
109
110
111
112
113
    def set_firewall_profile(self, server_id, profile, port_id):
        """Set the firewall profile for the public interface of a server
        :param server_id: integer (str or int)
        :param profile: (str) ENABLED | DISABLED | PROTECTED
        :param port_id: (str) This port must connect to a public network
        :returns: (dict) response headers
        """
        req = {'firewallProfile': {'profile': profile, 'nic': port_id}}
        r = self.servers_action_post(server_id, json_data=req, success=202)
        return r.headers

Giorgos Verigakis's avatar
Giorgos Verigakis committed
114
    def start_server(self, server_id):
115
116
117
        """Submit a startup request

        :param server_id: integer (str or int)
118
119

        :returns: (dict) response headers
120
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
121
        req = {'start': {}}
122
        r = self.servers_action_post(server_id, json_data=req, success=202)
123
        return r.headers
124

Giorgos Verigakis's avatar
Giorgos Verigakis committed
125
    def shutdown_server(self, server_id):
126
127
128
        """Submit a shutdown request

        :param server_id: integer (str or int)
129
130

        :returns: (dict) response headers
131
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
132
        req = {'shutdown': {}}
133
        r = self.servers_action_post(server_id, json_data=req, success=202)
134
        return r.headers
135

Giorgos Verigakis's avatar
Giorgos Verigakis committed
136
    def get_server_console(self, server_id):
137
138
139
        """
        :param server_id: integer (str or int)

140
        :returns: (dict) info to set a VNC connection to virtual server
141
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
142
        req = {'console': {'type': 'vnc'}}
143
        r = self.servers_action_post(server_id, json_data=req, success=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
144
        return r.json['console']
145

146
147
148
149
150
    def reassign_server(self, server_id, project):
        req = {'reassign': {'project': project}}
        r = self.servers_action_post(server_id, json_data=req, success=200)
        return r.headers

Giorgos Verigakis's avatar
Giorgos Verigakis committed
151
    def get_server_stats(self, server_id):
152
153
154
155
156
        """
        :param server_id: integer (str or int)

        :returns: (dict) auto-generated graphs of statistics (urls)
        """
157
        r = self.servers_stats_get(server_id)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
158
        return r.json['stats']
159

160
161
162
163
164
165
166
167
168
    def get_server_diagnostics(self, server_id):
        """
        :param server_id: integer (str or int)

        :returns: (list)
        """
        r = self.servers_diagnostics_get(server_id)
        return r.json

169
170
171
172
173
174
175
176
177
178
179
180
    def wait_server(
            self, server_id,
            current_status='BUILD',
            delay=1, max_wait=100, wait_cb=None):
        """Wait for server while its status is current_status

        :param server_id: integer (str or int)

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

        :param delay: time interval between retries

181
182
        :max_wait: (int) timeout in secconds

183
184
185
186
187
188
189
190
191
192
193
194
195
        :param wait_cb: if set a progressbar is used to show progress

        :returns: (str) the new mode if succesfull, (bool) False if timed out
        """

        def get_status(self, server_id):
            r = self.get_server_details(server_id)
            return r['status'], (r.get('progress', None) if (
                            current_status in ('BUILD', )) else None)

        return self._wait(
            server_id, current_status, get_status, delay, max_wait, wait_cb)

196

197
198
199
200
# Backwards compatibility
CycladesClient = CycladesComputeClient


201
class CycladesNetworkClient(NetworkClient):
202
203
204
205
206
    """Cyclades Network API extentions"""

    network_types = (
        'CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')

207
208
209
210
211
    def list_networks(self, detail=None):
        path = path4url('networks', 'detail' if detail else '')
        r = self.get(path, success=200)
        return r.json['networks']

212
    def create_network(self, type, name=None, shared=None, project_id=None):
213
214
215
216
217
        req = dict(network=dict(type=type, admin_state_up=True))
        if name:
            req['network']['name'] = name
        if shared not in (None, ):
            req['network']['shared'] = bool(shared)
218
219
        if project_id is not None:
            req['network']['project'] = project_id
220
221
        r = self.networks_post(json_data=req, success=201)
        return r.json['network']
222

223
    def reassign_network(self, network_id, project_id, **kwargs):
224
        """POST endpoint_url/networks/<network_id>/action
225
226
227
228

        :returns: request response
        """
        path = path4url('networks', network_id, 'action')
229
        req = {'reassign': {'project': project_id}}
230
        r = self.post(path, json=req, success=200, **kwargs)
231
232
        return r.headers

233
234
235
236
237
    def list_ports(self, detail=None):
        path = path4url('ports', 'detail' if detail else '')
        r = self.get(path, success=200)
        return r.json['ports']

Stavros Sachtouris's avatar
Stavros Sachtouris committed
238
    def create_port(
239
240
            self, network_id,
            device_id=None, security_groups=None, name=None, fixed_ips=None):
241
242
243
        """
        :param fixed_ips: (list of dicts) [{"ip_address": IPv4}, ...]
        """
244
245
246
        port = dict(network_id=network_id)
        if device_id:
            port['device_id'] = device_id
247
248
        if security_groups:
            port['security_groups'] = security_groups
Stavros Sachtouris's avatar
Stavros Sachtouris committed
249
250
        if name:
            port['name'] = name
251
        if fixed_ips:
252
253
            for fixed_ip in fixed_ips or []:
                if not 'ip_address' in fixed_ip:
254
                    raise ValueError('Invalid fixed_ip [%s]' % fixed_ip)
255
            port['fixed_ips'] = fixed_ips
256
        r = self.ports_post(json_data=dict(port=port), success=201)
257
        return r.json['port']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
258

259
    def create_floatingip(
260
261
            self,
            floating_network_id=None, floating_ip_address='', project_id=None):
262
263
264
        """
        :param floating_network_id: if not provided, it is assigned
            automatically by the service
265
266
        :param floating_ip_address: only if the IP is availabel in network pool
        :param project_id: specific project to get resource quotas from
267
268
269
270
271
272
        """
        floatingip = {}
        if floating_network_id:
            floatingip['floating_network_id'] = floating_network_id
        if floating_ip_address:
            floatingip['floating_ip_address'] = floating_ip_address
273
        if project_id is not None:
274
            floatingip['project'] = project_id
275
276
277
        r = self.floatingips_post(
            json_data=dict(floatingip=floatingip), success=200)
        return r.json['floatingip']
278
279
280
281
282

    def reassign_floating_ip(self, floating_network_id, project_id):
        """Change the project where this ip is charged"""
        path = path4url('floatingips', floating_network_id, 'action')
        json_data = dict(reassign=dict(project=project_id))
283
        self.post(path, json=json_data, success=200)
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318


class CycladesBlockStorageClient(CycladesBlockStorageRestClient):
    """Cyclades Block Storage REST API Client"""

    def create_volume(
            self, size, server_id, display_name,
            display_description=None,
            snapshot_id=None,
            imageRef=None,
            volume_type=None,
            metadata=None,
            project=None):
        """:returns: (dict) new volumes' details"""
        r = self.volumes_post(
            size, server_id, display_name,
            display_description=display_description,
            snapshot_id=snapshot_id,
            imageRef=imageRef,
            volume_type=volume_type,
            metadata=metadata,
            project=project)
        return r.json

    def reassign_volume(self, volume_id, project):
        self.volumes_action_post(volume_id, {"reassign": {"project": project}})

    def create_snapshot(
            self, volume_id, display_name,
            force=None, display_description=None):
        return super(CycladesBlockStorageClient, self).create_snapshot(
            volume_id,
            display_name=display_name,
            force=force,
            display_description=display_description)