__init__.py 9.75 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
            metadata=None, personality=None, networks=None, project=None):
46
47
48
49
50
51
        """Submit request to create a new server

        :param name: (str)

        :param flavor_id: integer id denoting a preset hardware configuration

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

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

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

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

67
68
        :param project: the project where to assign the server

69
        :returns: a dict with the new virtual server details
70
71
72
73
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:
                metadata[key] = image['metadata'][key]
            except KeyError:
                pass

81
        return super(CycladesComputeClient, self).create_server(
82
            name, flavor_id, image_id,
83
84
            metadata=metadata, personality=personality, networks=networks,
            project=project)
85

86
87
88
89
90
91
92
93
94
95
96
    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
97
    def start_server(self, server_id):
98
99
100
        """Submit a startup request

        :param server_id: integer (str or int)
101
102

        :returns: (dict) response headers
103
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
104
        req = {'start': {}}
105
        r = self.servers_action_post(server_id, json_data=req, success=202)
106
        return r.headers
107

Giorgos Verigakis's avatar
Giorgos Verigakis committed
108
    def shutdown_server(self, server_id):
109
110
111
        """Submit a shutdown request

        :param server_id: integer (str or int)
112
113

        :returns: (dict) response headers
114
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
115
        req = {'shutdown': {}}
116
        r = self.servers_action_post(server_id, json_data=req, success=202)
117
        return r.headers
118

Giorgos Verigakis's avatar
Giorgos Verigakis committed
119
    def get_server_console(self, server_id):
120
121
122
        """
        :param server_id: integer (str or int)

123
        :returns: (dict) info to set a VNC connection to virtual server
124
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
125
        req = {'console': {'type': 'vnc'}}
126
        r = self.servers_action_post(server_id, json_data=req, success=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
127
        return r.json['console']
128

129
130
131
132
133
    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
134
    def get_server_stats(self, server_id):
135
136
137
138
139
        """
        :param server_id: integer (str or int)

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

143
144
145
146
147
148
149
150
151
    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

152
153
154
155
156
157
158
159
160
161
162
163
    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

164
165
        :max_wait: (int) timeout in secconds

166
167
168
169
170
171
172
173
174
175
176
177
178
        :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)

179

180
181
182
183
# Backwards compatibility
CycladesClient = CycladesComputeClient


184
class CycladesNetworkClient(NetworkClient):
185
186
187
188
189
    """Cyclades Network API extentions"""

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

190
191
192
193
194
    def list_networks(self, detail=None):
        path = path4url('networks', 'detail' if detail else '')
        r = self.get(path, success=200)
        return r.json['networks']

195
    def create_network(self, type, name=None, shared=None, project=None):
196
197
198
199
200
        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)
201
202
        if project is not None:
            req['network']['project'] = project
203
204
        r = self.networks_post(json_data=req, success=201)
        return r.json['network']
205

206
    def reassign_network(self, network_id, project, **kwargs):
207
208
209
210
211
212
        """POST base_url/networks/<network_id>/action

        :returns: request response
        """
        path = path4url('networks', network_id, 'action')
        req = {'reassign': {'project': project}}
213
        r = self.post(path, json=req, success=200, **kwargs)
214
215
        return r.headers

216
217
218
219
220
    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
221
    def create_port(
222
223
            self, network_id,
            device_id=None, security_groups=None, name=None, fixed_ips=None):
224
225
226
        """
        :param fixed_ips: (list of dicts) [{"ip_address": IPv4}, ...]
        """
227
228
229
        port = dict(network_id=network_id)
        if device_id:
            port['device_id'] = device_id
230
231
        if security_groups:
            port['security_groups'] = security_groups
Stavros Sachtouris's avatar
Stavros Sachtouris committed
232
233
        if name:
            port['name'] = name
234
        if fixed_ips:
235
236
            for fixed_ip in fixed_ips or []:
                if not 'ip_address' in fixed_ip:
237
                    raise ValueError('Invalid fixed_ip [%s]' % fixed_ip)
238
            port['fixed_ips'] = fixed_ips
239
        r = self.ports_post(json_data=dict(port=port), success=201)
240
        return r.json['port']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
241

242
    def create_floatingip(
243
244
            self,
            floating_network_id=None, floating_ip_address='', project_id=None):
245
246
247
        """
        :param floating_network_id: if not provided, it is assigned
            automatically by the service
248
249
        :param floating_ip_address: only if the IP is availabel in network pool
        :param project_id: specific project to get resource quotas from
250
251
252
253
254
255
        """
        floatingip = {}
        if floating_network_id:
            floatingip['floating_network_id'] = floating_network_id
        if floating_ip_address:
            floatingip['floating_ip_address'] = floating_ip_address
256
        if project_id is not None:
257
            floatingip['project'] = project_id
258
259
260
        r = self.floatingips_post(
            json_data=dict(floatingip=floatingip), success=200)
        return r.json['floatingip']
261
262
263
264
265

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