Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.6 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

    
39

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

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

48
        :param name: (str)
49

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

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

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

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

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

67
        :returns: a dict with the new virtual server details
68

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

    
79
        return super(CycladesClient, self).create_server(
80
            name, flavor_id, image_id,
81
            metadata=metadata, personality=personality, networks=networks)
82

    
83
    def start_server(self, server_id):
84
        """Submit a startup request
85

86
        :param server_id: integer (str or int)
87

88
        :returns: (dict) response headers
89
        """
90
        req = {'start': {}}
91
        r = self.servers_action_post(server_id, json_data=req, success=202)
92
        return r.headers
93

    
94
    def shutdown_server(self, server_id):
95
        """Submit a shutdown request
96

97
        :param server_id: integer (str or int)
98

99
        :returns: (dict) response headers
100
        """
101
        req = {'shutdown': {}}
102
        r = self.servers_action_post(server_id, json_data=req, success=202)
103
        return r.headers
104

    
105
    def get_server_console(self, server_id):
106
        """
107
        :param server_id: integer (str or int)
108

109
        :returns: (dict) info to set a VNC connection to virtual server
110
        """
111
        req = {'console': {'type': 'vnc'}}
112
        r = self.servers_action_post(server_id, json_data=req, success=200)
113
        return r.json['console']
114

    
115
    def get_firewall_profile(self, server_id):
116
        """
117
        :param server_id: integer (str or int)
118

119
        :returns: (str) ENABLED | DISABLED | PROTECTED
120

121
        :raises ClientError: 520 No Firewall Profile
122
        """
123
        r = self.get_server_details(server_id)
124
        try:
125
            return r['attachments'][0]['firewallProfile']
126
        except KeyError:
127
            raise ClientError(
128
                'No Firewall Profile',
129
                details='Server %s is missing a firewall profile' % server_id)
130

    
131
    def set_firewall_profile(self, server_id, profile):
132
        """Set the firewall profile for the public interface of a server
133

134
        :param server_id: integer (str or int)
135

136
        :param profile: (str) ENABLED | DISABLED | PROTECTED
137

138
        :returns: (dict) response headers
139
        """
140
        req = {'firewallProfile': {'profile': profile}}
141
        r = self.servers_action_post(server_id, json_data=req, success=202)
142
        return r.headers
143

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

148
        :returns: (dict) network interface connections
149
        """
150
        r = self.servers_ips_get(server_id)
151
        return r.json['attachments']
152

    
153
    def get_server_stats(self, server_id):
154
        """
155
        :param server_id: integer (str or int)
156

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

    
162
    def wait_server(
163
            self, server_id,
164
            current_status='BUILD',
165
            delay=1, max_wait=100, wait_cb=None):
166
        """Wait for server while its status is current_status
167

168
        :param server_id: integer (str or int)
169

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

172
        :param delay: time interval between retries
173

174
        :max_wait: (int) timeout in secconds
175

176
        :param wait_cb: if set a progressbar is used to show progress
177

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

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

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

    
189
    def wait_firewall(
190
            self, server_id,
191
            current_status='DISABLED', delay=1, max_wait=100, wait_cb=None):
192
        """Wait while the public network firewall status is current_status
193

194
        :param server_id: integer (str or int)
195

196
        :param current_status: (str) DISABLED | ENABLED | PROTECTED
197

198
        :param delay: time interval between retries
199

200
        :max_wait: (int) timeout in secconds
201

202
        :param wait_cb: if set a progressbar is used to show progress
203

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

    
207
        def get_status(self, server_id):
208
            return self.get_firewall_profile(server_id), None
209

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

    
213

    
214
class CycladesNetworkClient(NetworkClient):
215
    """Cyclades Network API extentions"""
216

    
217
    network_types = (
218
        'CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
219

    
220
    def list_networks(self, detail=None):
221
        path = path4url('networks', 'detail' if detail else '')
222
        r = self.get(path, success=200)
223
        return r.json['networks']
224

    
225
    def create_network(self, type, name=None, shared=None):
226
        req = dict(network=dict(type=type, admin_state_up=True))
227
        if name:
228
            req['network']['name'] = name
229
        if shared not in (None, ):
230
            req['network']['shared'] = bool(shared)
231
        r = self.networks_post(json_data=req, success=201)
232
        return r.json['network']
233

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

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

    
262
    def create_floatingip(self, floating_network_id, floating_ip_address=''):
263
        return super(CycladesNetworkClient, self).create_floatingip(
264
            floating_network_id, floating_ip_address=floating_ip_address)
265

    
266
    def update_floatingip(self, floating_network_id, floating_ip_address=''):
267
        """To nullify something optional, use None"""
268
        return super(CycladesNetworkClient, self).update_floatingip(
269
            floating_network_id, floating_ip_address=floating_ip_address)