Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / networks.py @ 4500650c

History | View | Annotate | Download (11.5 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
                                              .order_by('machine')]
103
        d['attachments'] = {'values': attachments}
104
    return d
105

    
106

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

    
114

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

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

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

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

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

    
146
    return HttpResponse(data, status=200)
147

    
148

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

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

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

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

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

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

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

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

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

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

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

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

    
237
    return response
238

    
239

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

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

    
255

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

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

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

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

    
285

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

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

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

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

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

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

    
314

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

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

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