__init__.py 10.2 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
from kamaki.clients.cyclades.rest_api import CycladesComputeRestClient
35
from kamaki.clients.network import NetworkClient
36
from kamaki.clients.utils import path4url
Stavros Sachtouris's avatar
Stavros Sachtouris committed
37
from kamaki.clients import ClientError, Waiter
38

39

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

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

        :param name: (str)

        :param flavor_id: integer id denoting a preset hardware configuration

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

145
146
147
148
149
    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
150
    def get_server_stats(self, server_id):
151
152
153
154
155
        """
        :param server_id: integer (str or int)

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

159
160
161
162
163
164
165
166
167
    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

168
169
170
171
172
173
174
175
176
177
178
179
    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

180
181
        :max_wait: (int) timeout in secconds

182
183
184
185
186
187
188
189
190
191
192
193
194
        :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)

195

196
197
198
199
# Backwards compatibility
CycladesClient = CycladesComputeClient


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

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

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

211
    def create_network(self, type, name=None, shared=None, project_id=None):
212
213
214
215
216
        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)
217
218
        if project_id is not None:
            req['network']['project'] = project_id
219
220
        r = self.networks_post(json_data=req, success=201)
        return r.json['network']
221

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

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

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

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

    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))
282
        self.post(path, json=json_data, success=200)