Statistics
| Branch: | Tag: | Revision:

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

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.db.models import Network
50
from synnefo.db.pools import EmptyPool
51
from synnefo.logic import backend
52

    
53

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

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

    
63

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

    
72

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

    
83

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

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

    
104

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

    
112

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

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

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

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

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

    
144
    return HttpResponse(data, status=200)
145

    
146

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

    
159
    req = util.get_request_dict(request)
160
    log.info('create_network %s', req)
161

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

    
176
    if public:
177
        raise Forbidden('Can not create a public network.')
178

    
179
    if net_type not in ['PUBLIC_ROUTED', 'PRIVATE_MAC_FILTERED',
180
                        'PRIVATE_PHYSICAL_VLAN', 'CUSTOM_ROUTED',
181
                        'CUSTOM_BRIDGED']:
182
        raise BadRequest("Invalid network type: %s", net_type)
183
    if net_type not in settings.ENABLED_NETWORKS:
184
        raise Forbidden("Can not create %s network" % net_type)
185

    
186
    networks_user_limit = \
187
        settings.NETWORKS_USER_QUOTA.get(request.user_uniq,
188
                                         settings.MAX_NETWORKS_PER_USER)
189

    
190
    user_networks = len(Network.objects.filter(userid=request.user_uniq,
191
                                               deleted=False))
192

    
193
    if user_networks >= networks_user_limit:
194
        raise OverLimit('Network count limit exceeded for your account.')
195

    
196
    cidr_block = int(subnet.split('/')[1])
197
    if not util.validate_network_size(cidr_block):
198
        raise OverLimit("Unsupported network size.")
199

    
200
    try:
201
        link, mac_prefix = util.net_resources(net_type)
202
        if not link:
203
            raise Exception("Can not create network. No connectivity link.")
204

    
205
        network = Network.objects.create(
206
                name=name,
207
                userid=request.user_uniq,
208
                subnet=subnet,
209
                subnet6=subnet6,
210
                gateway=gateway,
211
                gateway6=gateway6,
212
                dhcp=dhcp,
213
                type=net_type,
214
                link=link,
215
                mac_prefix=mac_prefix,
216
                action='CREATE',
217
                state='PENDING')
218
    except EmptyPool:
219
        log.error("Failed to allocate resources for network of type: %s",
220
                  net_type)
221
        raise ServiceUnavailable("Failed to allocate resources for network")
222

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

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

    
229
    networkdict = network_to_dict(network, request.user_uniq)
230
    return render_network(request, networkdict, status=202)
231

    
232

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

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

    
248

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

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

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

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

    
278

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

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

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

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

    
301

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

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

    
308

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

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

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