Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / cyclades / __init__.py @ f1e45161

History | View | Annotate | Download (9.7 kB)

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 kamaki.clients.cyclades.rest_api import CycladesRestClient
35
from kamaki.clients.network import NetworkClient
36
from kamaki.clients.utils import path4url
37
from kamaki.clients import ClientError, Waiter
38
import json
39

    
40

    
41
class CycladesClient(CycladesRestClient, Waiter):
42
    """Synnefo Cyclades Compute API client"""
43

    
44
    def create_server(
45
            self, name, flavor_id, image_id,
46
            metadata=None, personality=None, networks=None, project=None):
47
        """Submit request to create a new server
48

49
        :param name: (str)
50

51
        :param flavor_id: integer id denoting a preset hardware configuration
52

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

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

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

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

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

70
        :returns: a dict with the new virtual server details
71

72
        :raises ClientError: wraps request errors
73
        """
74
        image = self.get_image_details(image_id)
75
        metadata = metadata or dict()
76
        for key in ('os', 'users'):
77
            try:
78
                metadata[key] = image['metadata'][key]
79
            except KeyError:
80
                pass
81

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

    
87
    def set_firewall_profile(self, server_id, profile, port_id):
88
        """Set the firewall profile for the public interface of a server
89
        :param server_id: integer (str or int)
90
        :param profile: (str) ENABLED | DISABLED | PROTECTED
91
        :param port_id: (str) This port must connect to a public network
92
        :returns: (dict) response headers
93
        """
94
        req = {'firewallProfile': {'profile': profile, 'nic': port_id}}
95
        r = self.servers_action_post(server_id, json_data=req, success=202)
96
        return r.headers
97

    
98
    def start_server(self, server_id):
99
        """Submit a startup request
100

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

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

    
109
    def shutdown_server(self, server_id):
110
        """Submit a shutdown request
111

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

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

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

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

    
130
    def reassign_server(self, server_id, project):
131
        req = {'reassign': {'project': project}}
132
        r = self.servers_action_post(server_id, json_data=req, success=200)
133
        return r.headers
134

    
135
    def get_server_stats(self, server_id):
136
        """
137
        :param server_id: integer (str or int)
138

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

    
144
    def get_server_diagnostics(self, server_id):
145
        """
146
        :param server_id: integer (str or int)
147

148
        :returns: (list)
149
        """
150
        r = self.servers_diagnostics_get(server_id)
151
        return r.json
152

    
153
    def wait_server(
154
            self, server_id,
155
            current_status='BUILD',
156
            delay=1, max_wait=100, wait_cb=None):
157
        """Wait for server while its status is current_status
158

159
        :param server_id: integer (str or int)
160

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

163
        :param delay: time interval between retries
164

165
        :max_wait: (int) timeout in secconds
166

167
        :param wait_cb: if set a progressbar is used to show progress
168

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

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

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

    
180

    
181
class CycladesNetworkClient(NetworkClient):
182
    """Cyclades Network API extentions"""
183

    
184
    network_types = (
185
        'CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
186

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

    
192
    def create_network(self, type, name=None, shared=None, project=None):
193
        req = dict(network=dict(type=type, admin_state_up=True))
194
        if name:
195
            req['network']['name'] = name
196
        if shared not in (None, ):
197
            req['network']['shared'] = bool(shared)
198
        if project is not None:
199
            req['network']['project'] = project
200
        r = self.networks_post(json_data=req, success=201)
201
        return r.json['network']
202

    
203
    def reassign_network(self, network_id, project, **kwargs):
204
        """POST base_url/networks/<network_id>/action
205

206
        :returns: request response
207
        """
208
        path = path4url('networks', network_id, 'action')
209
        req = {'reassign': {'project': project}}
210
        r = self.post(path, json=req, success=200, **kwargs)
211
        return r.headers
212

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

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

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

    
259
    def reassign_floating_ip(self, floating_network_id, project_id):
260
        """Change the project where this ip is charged"""
261
        path = path4url('floatingips', floating_network_id, 'action')
262
        json_data = dict(reassign=dict(project=project_id))
263
        self.post(path, json=json_data, success=202)