Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / networks.py @ 939d71dd

History | View | Annotate | Download (11.6 kB)

1
# Copyright 2011-2012 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 logging import getLogger
35

    
36
from django.conf.urls.defaults import patterns
37
from django.conf import settings
38
from django.db.models import Q
39
from django.db import transaction
40
from django.http import HttpResponse
41
from django.template.loader import render_to_string
42
from django.utils import simplejson as json
43

    
44
from synnefo.api import util
45
from synnefo.api.actions import network_actions
46
from synnefo.api.common import method_not_allowed
47
from synnefo.api.faults import (ServiceUnavailable, BadRequest, Forbidden,
48
                                NetworkInUse, OverLimit)
49
from synnefo import quotas
50
from synnefo.db.models import Network
51
from synnefo.db.pools import EmptyPool
52
from synnefo.logic import backend
53

    
54

    
55
log = getLogger('synnefo.api')
56

    
57
urlpatterns = patterns(
58
    'synnefo.api.networks',
59
    (r'^(?:/|.json|.xml)?$', 'demux'),
60
    (r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
61
    (r'^/(\w+)(?:.json|.xml)?$', 'network_demux'),
62
    (r'^/(\w+)/action(?:.json|.xml)?$', 'network_action'),
63
)
64

    
65

    
66
def demux(request):
67
    if request.method == 'GET':
68
        return list_networks(request)
69
    elif request.method == 'POST':
70
        return create_network(request)
71
    else:
72
        return method_not_allowed(request)
73

    
74

    
75
def network_demux(request, network_id):
76
    if request.method == 'GET':
77
        return get_network_details(request, network_id)
78
    elif request.method == 'PUT':
79
        return update_network_name(request, network_id)
80
    elif request.method == 'DELETE':
81
        return delete_network(request, network_id)
82
    else:
83
        return method_not_allowed(request)
84

    
85

    
86
def network_to_dict(network, user_id, detail=True):
87
    d = {'id': str(network.id), 'name': network.name}
88
    if detail:
89
        d['cidr'] = network.subnet
90
        d['cidr6'] = network.subnet6
91
        d['gateway'] = network.gateway
92
        d['gateway6'] = network.gateway6
93
        d['dhcp'] = network.dhcp
94
        d['type'] = network.flavor
95
        d['updated'] = util.isoformat(network.updated)
96
        d['created'] = util.isoformat(network.created)
97
        d['status'] = network.state
98
        d['public'] = network.public
99

    
100
        attachments = [util.construct_nic_id(nic)
101
                       for nic in network.nics.filter(machine__userid=user_id)
102
                                              .filter(state="ACTIVE")
103
                                              .order_by('machine')]
104
        d['attachments'] = {'values': attachments}
105
    return d
106

    
107

    
108
def render_network(request, networkdict, status=200):
109
    if request.serialization == 'xml':
110
        data = render_to_string('network.xml', {'network': networkdict})
111
    else:
112
        data = json.dumps({'network': networkdict})
113
    return HttpResponse(data, status=status)
114

    
115

    
116
@util.api_method('GET')
117
def list_networks(request, detail=False):
118
    # Normal Response Codes: 200, 203
119
    # Error Response Codes: computeFault (400, 500),
120
    #                       serviceUnavailable (503),
121
    #                       unauthorized (401),
122
    #                       badRequest (400),
123
    #                       overLimit (413)
124

    
125
    log.debug('list_networks detail=%s', detail)
126
    since = util.isoparse(request.GET.get('changes-since'))
127
    user_networks = Network.objects.filter(Q(userid=request.user_uniq) |
128
                                           Q(public=True))
129

    
130
    if since:
131
        user_networks = user_networks.filter(updated__gte=since)
132
        if not user_networks:
133
            return HttpResponse(status=304)
134
    else:
135
        user_networks = user_networks.filter(deleted=False)
136

    
137
    networks = [network_to_dict(network, request.user_uniq, detail)
138
                for network in user_networks.order_by('id')]
139

    
140
    if request.serialization == 'xml':
141
        data = render_to_string('list_networks.xml', {
142
            'networks': networks,
143
            'detail': detail})
144
    else:
145
        data = json.dumps({'networks': {'values': networks}})
146

    
147
    return HttpResponse(data, status=200)
148

    
149

    
150
@util.api_method('POST')
151
@quotas.uses_commission
152
@transaction.commit_manually
153
def create_network(serials, request):
154
    # Normal Response Code: 202
155
    # Error Response Codes: computeFault (400, 500),
156
    #                       serviceUnavailable (503),
157
    #                       unauthorized (401),
158
    #                       badMediaType(415),
159
    #                       badRequest (400),
160
    #                       forbidden (403)
161
    #                       overLimit (413)
162

    
163
    try:
164
        req = util.get_request_dict(request)
165
        log.info('create_network %s', req)
166

    
167
        try:
168
            d = req['network']
169
            name = d['name']
170
            # TODO: Fix this temp values:
171
            subnet = d.get('cidr', '192.168.1.0/24')
172
            subnet6 = d.get('cidr6', None)
173
            gateway = d.get('gateway', None)
174
            gateway6 = d.get('gateway6', None)
175
            flavor = d.get('type', 'MAC_FILTERED')
176
            public = d.get('public', False)
177
            dhcp = d.get('dhcp', True)
178
        except (KeyError, ValueError):
179
            raise BadRequest('Malformed request.')
180

    
181
        if public:
182
            raise Forbidden('Can not create a public network.')
183

    
184
        if flavor not in Network.FLAVORS.keys():
185
            raise BadRequest("Invalid network flavors %s" % flavor)
186

    
187
        if flavor not in settings.API_ENABLED_NETWORK_FLAVORS:
188
            raise Forbidden("Can not create %s network" % flavor)
189

    
190
        # Check that user provided a valid subnet
191
        util.validate_network_params(subnet, gateway, subnet6, gateway6)
192

    
193
        user_id = request.user_uniq
194
        serial = quotas.issue_network_commission(user_id)
195
        serials.append(serial)
196
        # Make the commission accepted, since in the end of this
197
        # transaction the Network will have been created in the DB.
198
        serial.accepted = True
199
        serial.save()
200

    
201
        try:
202
            mode, link, mac_prefix, tags = util.values_from_flavor(flavor)
203
            network = Network.objects.create(
204
                name=name,
205
                userid=user_id,
206
                subnet=subnet,
207
                subnet6=subnet6,
208
                gateway=gateway,
209
                gateway6=gateway6,
210
                dhcp=dhcp,
211
                flavor=flavor,
212
                mode=mode,
213
                link=link,
214
                mac_prefix=mac_prefix,
215
                tags=tags,
216
                action='CREATE',
217
                state='PENDING',
218
                serial=serial)
219
        except EmptyPool:
220
            log.error("Failed to allocate resources for network of type: %s",
221
                      flavor)
222
            raise ServiceUnavailable("Failed to allocate network resources")
223

    
224
        # Create BackendNetwork entries for each Backend
225
        network.create_backend_network()
226
    except:
227
        transaction.rollback()
228
        raise
229
    else:
230
        transaction.commit()
231

    
232
    # Create the network in the actual backends
233
    backend.create_network(network)
234

    
235
    networkdict = network_to_dict(network, request.user_uniq)
236
    response = render_network(request, networkdict, status=202)
237

    
238
    return response
239

    
240

    
241
@util.api_method('GET')
242
def get_network_details(request, network_id):
243
    # Normal Response Codes: 200, 203
244
    # Error Response Codes: computeFault (400, 500),
245
    #                       serviceUnavailable (503),
246
    #                       unauthorized (401),
247
    #                       badRequest (400),
248
    #                       itemNotFound (404),
249
    #                       overLimit (413)
250

    
251
    log.debug('get_network_details %s', network_id)
252
    net = util.get_network(network_id, request.user_uniq)
253
    netdict = network_to_dict(net, request.user_uniq)
254
    return render_network(request, netdict)
255

    
256

    
257
@util.api_method('PUT')
258
def update_network_name(request, network_id):
259
    # Normal Response Code: 204
260
    # Error Response Codes: computeFault (400, 500),
261
    #                       serviceUnavailable (503),
262
    #                       unauthorized (401),
263
    #                       badRequest (400),
264
    #                       forbidden (403)
265
    #                       badMediaType(415),
266
    #                       itemNotFound (404),
267
    #                       overLimit (413)
268

    
269
    req = util.get_request_dict(request)
270
    log.info('update_network_name %s', network_id)
271

    
272
    try:
273
        name = req['network']['name']
274
    except (TypeError, KeyError):
275
        raise BadRequest('Malformed request.')
276

    
277
    net = util.get_network(network_id, request.user_uniq)
278
    if net.public:
279
        raise Forbidden('Can not rename the public network.')
280
    if net.deleted:
281
        raise Network.DeletedError
282
    net.name = name
283
    net.save()
284
    return HttpResponse(status=204)
285

    
286

    
287
@util.api_method('DELETE')
288
@transaction.commit_on_success
289
def delete_network(request, network_id):
290
    # Normal Response Code: 204
291
    # Error Response Codes: computeFault (400, 500),
292
    #                       serviceUnavailable (503),
293
    #                       unauthorized (401),
294
    #                       forbidden (403)
295
    #                       itemNotFound (404),
296
    #                       overLimit (413)
297

    
298
    log.info('delete_network %s', network_id)
299
    net = util.get_network(network_id, request.user_uniq, for_update=True)
300
    if net.public:
301
        raise Forbidden('Can not delete the public network.')
302

    
303
    if net.deleted:
304
        raise Network.DeletedError
305

    
306
    if net.machines.all():  # Nics attached on network
307
        raise NetworkInUse('Machines are connected to network.')
308

    
309
    net.action = 'DESTROY'
310
    net.save()
311

    
312
    backend.delete_network(net)
313
    return HttpResponse(status=204)
314

    
315

    
316
@util.api_method('POST')
317
def network_action(request, network_id):
318
    req = util.get_request_dict(request)
319
    log.debug('network_action %s %s', network_id, req)
320
    if len(req) != 1:
321
        raise BadRequest('Malformed request.')
322

    
323
    net = util.get_network(network_id, request.user_uniq)
324
    if net.public:
325
        raise Forbidden('Can not modify the public network.')
326
    if net.deleted:
327
        raise Network.DeletedError
328

    
329
    try:
330
        key = req.keys()[0]
331
        val = req[key]
332
        assert isinstance(val, dict)
333
        return network_actions[key](request, net, req[key])
334
    except KeyError:
335
        raise BadRequest('Unknown action.')
336
    except AssertionError:
337
        raise BadRequest('Invalid argument.')