__init__.py 11.7 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
    CONSOLE_TYPES = ('vnc', 'vnc-ws', 'vnc-wss')

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

        :param name: (str)

        :param flavor_id: integer id denoting a preset hardware configuration

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

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

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

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

71
        :param project_id: the project where to assign the server
72

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

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

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
        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']
104

105
106
107
108
109
110
111
112
113
114
115
    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
116
    def start_server(self, server_id):
117
118
119
        """Submit a startup request

        :param server_id: integer (str or int)
120
121

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

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

        :param server_id: integer (str or int)
131
132

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

138
    def get_server_console(self, server_id, console_type='vnc'):
139
140
141
        """
        :param server_id: integer (str or int)

142
143
        :param console_type: str (vnc, vnc-ws, vnc-wss, default: vnc)

144
        :returns: (dict) info to set a VNC connection to virtual server
145
        """
146
147
148
        ct = self.CONSOLE_TYPES
        assert console_type in ct, '%s not in %s' % (console_type, ct)
        req = {'console': {'type': console_type}}
149
        r = self.servers_action_post(server_id, json_data=req, success=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
150
        return r.json['console']
151

152
153
154
155
156
    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
157
    def get_server_stats(self, server_id):
158
159
160
161
162
        """
        :param server_id: integer (str or int)

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

166
167
168
169
170
171
172
173
174
    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

175
176
177
178
179
180
181
182
183
184
185
186
    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

187
188
        :max_wait: (int) timeout in secconds

189
190
191
192
193
194
195
196
197
198
199
200
201
        :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)

202

203
204
205
206
# Backwards compatibility
CycladesClient = CycladesComputeClient


207
class CycladesNetworkClient(NetworkClient):
208
209
210
211
212
    """Cyclades Network API extentions"""

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

213
214
215
216
217
    def list_networks(self, detail=None):
        path = path4url('networks', 'detail' if detail else '')
        r = self.get(path, success=200)
        return r.json['networks']

218
    def create_network(self, type, name=None, shared=None, project_id=None):
219
220
221
222
223
        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)
224
225
        if project_id is not None:
            req['network']['project'] = project_id
226
227
        r = self.networks_post(json_data=req, success=201)
        return r.json['network']
228

229
    def reassign_network(self, network_id, project_id, **kwargs):
230
        """POST endpoint_url/networks/<network_id>/action
231
232
233
234

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

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

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

    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))
289
        self.post(path, json=json_data, success=200)
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
319
320
321
322
323
324


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)