Statistics
| Branch: | Tag: | Revision:

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

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('synnefo.api.networks',
58
    (r'^(?:/|.json|.xml)?$', 'demux'),
59
    (r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
60
    (r'^/(\w+)(?:.json|.xml)?$', 'network_demux'),
61
    (r'^/(\w+)/action(?:.json|.xml)?$', 'network_action'),
62
)
63

    
64

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

    
73

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

    
84

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

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

    
105

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

    
113

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

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

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

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

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

    
145
    return HttpResponse(data, status=200)
146

    
147

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

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

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

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

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

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

    
188
        cidr_block = int(subnet.split('/')[1])
189
        if not util.validate_network_size(cidr_block):
190
            raise OverLimit("Unsupported network size.")
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
    else:
228
        transaction.commit()
229

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

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

    
236
    return response
237

    
238

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

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

    
254

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

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

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

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

    
284

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

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

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

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

    
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.')