Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.3 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_on_success
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
    req = util.get_request_dict(request)
162
    log.info('create_network %s', req)
163

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

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

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

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

    
187
    cidr_block = int(subnet.split('/')[1])
188
    if not util.validate_network_size(cidr_block):
189
        raise OverLimit("Unsupported network size.")
190

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

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

    
222
    # Create BackendNetwork entries for each Backend
223
    network.create_backend_network()
224

    
225
    # Create the network in the actual backends
226
    backend.create_network(network)
227

    
228
    networkdict = network_to_dict(network, request.user_uniq)
229
    response = render_network(request, networkdict, status=202)
230

    
231
    return response
232

    
233

    
234
@util.api_method('GET')
235
def get_network_details(request, network_id):
236
    # Normal Response Codes: 200, 203
237
    # Error Response Codes: computeFault (400, 500),
238
    #                       serviceUnavailable (503),
239
    #                       unauthorized (401),
240
    #                       badRequest (400),
241
    #                       itemNotFound (404),
242
    #                       overLimit (413)
243

    
244
    log.debug('get_network_details %s', network_id)
245
    net = util.get_network(network_id, request.user_uniq)
246
    netdict = network_to_dict(net, request.user_uniq)
247
    return render_network(request, netdict)
248

    
249

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

    
262
    req = util.get_request_dict(request)
263
    log.info('update_network_name %s', network_id)
264

    
265
    try:
266
        name = req['network']['name']
267
    except (TypeError, KeyError):
268
        raise BadRequest('Malformed request.')
269

    
270
    net = util.get_network(network_id, request.user_uniq)
271
    if net.public:
272
        raise Forbidden('Can not rename the public network.')
273
    if net.deleted:
274
        raise Network.DeletedError
275
    net.name = name
276
    net.save()
277
    return HttpResponse(status=204)
278

    
279

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

    
291
    log.info('delete_network %s', network_id)
292
    net = util.get_network(network_id, request.user_uniq, for_update=True)
293
    if net.public:
294
        raise Forbidden('Can not delete the public network.')
295

    
296
    if net.deleted:
297
        raise Network.DeletedError
298

    
299
    if net.machines.all():  # Nics attached on network
300
        raise NetworkInUse('Machines are connected to network.')
301

    
302

    
303
    net.action = 'DESTROY'
304
    net.save()
305

    
306
    backend.delete_network(net)
307
    return HttpResponse(status=204)
308

    
309

    
310
@util.api_method('POST')
311
def network_action(request, network_id):
312
    req = util.get_request_dict(request)
313
    log.debug('network_action %s %s', network_id, req)
314
    if len(req) != 1:
315
        raise BadRequest('Malformed request.')
316

    
317
    net = util.get_network(network_id, request.user_uniq)
318
    if net.public:
319
        raise Forbidden('Can not modify the public network.')
320
    if net.deleted:
321
        raise Network.DeletedError
322

    
323
    try:
324
        key = req.keys()[0]
325
        val = req[key]
326
        assert isinstance(val, dict)
327
        return network_actions[key](request, net, req[key])
328
    except KeyError:
329
        raise BadRequest('Unknown action.')
330
    except AssertionError:
331
        raise BadRequest('Invalid argument.')