Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12.1 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

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

    
43
from snf_django.lib import api
44
from snf_django.lib.api import faults, utils
45
from synnefo.api import util
46
from synnefo.api.actions import network_actions
47
from synnefo import quotas
48
from synnefo.db.models import Network
49
from synnefo.db.pools import EmptyPool
50
from synnefo.logic import backend
51

    
52

    
53
from logging import getLogger
54
log = getLogger(__name__)
55

    
56
urlpatterns = patterns(
57
    '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 api.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 api.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'] = utils.isoformat(network.updated)
95
        d['created'] = utils.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
                                              .filter(state="ACTIVE")
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
@api.api_method(http_method='GET', user_required=True, logger=log)
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 = utils.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
@api.api_method(http_method='POST', user_required=True, logger=log)
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 = utils.get_request_dict(request)
164
        log.info('create_network %s', req)
165

    
166
        user_id = request.user_uniq
167
        try:
168
            d = req['network']
169
            name = d['name']
170
        except KeyError:
171
            raise faults.BadRequest("Malformed request")
172

    
173
        # Get and validate flavor. Flavors are still exposed as 'type' in the
174
        # API.
175
        flavor = d.get("type", None)
176
        if flavor is None:
177
            raise faults.BadRequest("Missing request parameter 'type'")
178
        elif flavor not in Network.FLAVORS.keys():
179
            raise faults.BadRequest("Invalid network type '%s'" % flavor)
180
        elif flavor not in settings.API_ENABLED_NETWORK_FLAVORS:
181
            raise faults.Forbidden("Can not create network of type '%s'" % flavor)
182

    
183
        public = d.get("public", False)
184
        if public:
185
            raise faults.Forbidden("Can not create a public network.")
186

    
187
        dhcp = d.get('dhcp', True)
188

    
189
        # Get and validate network parameters
190
        subnet = d.get('cidr', '192.168.1.0/24')
191
        subnet6 = d.get('cidr6', None)
192
        gateway = d.get('gateway', None)
193
        gateway6 = d.get('gateway6', None)
194
        # Check that user provided a valid subnet
195
        util.validate_network_params(subnet, gateway, subnet6, gateway6)
196

    
197
        # Issue commission
198
        serial = quotas.issue_network_commission(user_id)
199
        serials.append(serial)
200
        # Make the commission accepted, since in the end of this
201
        # transaction the Network will have been created in the DB.
202
        serial.accepted = True
203
        serial.save()
204

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

    
228
        # Create BackendNetwork entries for each Backend
229
        network.create_backend_network()
230
    except:
231
        transaction.rollback()
232
        raise
233
    else:
234
        transaction.commit()
235

    
236
    # Create the network in the actual backends
237
    backend.create_network(network)
238

    
239
    networkdict = network_to_dict(network, request.user_uniq)
240
    response = render_network(request, networkdict, status=202)
241

    
242
    return response
243

    
244

    
245
@api.api_method(http_method='GET', user_required=True, logger=log)
246
def get_network_details(request, network_id):
247
    # Normal Response Codes: 200, 203
248
    # Error Response Codes: computeFault (400, 500),
249
    #                       serviceUnavailable (503),
250
    #                       unauthorized (401),
251
    #                       badRequest (400),
252
    #                       itemNotFound (404),
253
    #                       overLimit (413)
254

    
255
    log.debug('get_network_details %s', network_id)
256
    net = util.get_network(network_id, request.user_uniq)
257
    netdict = network_to_dict(net, request.user_uniq)
258
    return render_network(request, netdict)
259

    
260

    
261
@api.api_method(http_method='PUT', user_required=True, logger=log)
262
def update_network_name(request, network_id):
263
    # Normal Response Code: 204
264
    # Error Response Codes: computeFault (400, 500),
265
    #                       serviceUnavailable (503),
266
    #                       unauthorized (401),
267
    #                       badRequest (400),
268
    #                       forbidden (403)
269
    #                       badMediaType(415),
270
    #                       itemNotFound (404),
271
    #                       overLimit (413)
272

    
273
    req = utils.get_request_dict(request)
274
    log.info('update_network_name %s', network_id)
275

    
276
    try:
277
        name = req['network']['name']
278
    except (TypeError, KeyError):
279
        raise faults.BadRequest('Malformed request.')
280

    
281
    net = util.get_network(network_id, request.user_uniq)
282
    if net.public:
283
        raise faults.Forbidden('Can not rename the public network.')
284
    if net.deleted:
285
        raise faults.BadRequest("Network has been deleted.")
286
    net.name = name
287
    net.save()
288
    return HttpResponse(status=204)
289

    
290

    
291
@api.api_method(http_method='DELETE', user_required=True, logger=log)
292
@transaction.commit_on_success
293
def delete_network(request, network_id):
294
    # Normal Response Code: 204
295
    # Error Response Codes: computeFault (400, 500),
296
    #                       serviceUnavailable (503),
297
    #                       unauthorized (401),
298
    #                       forbidden (403)
299
    #                       itemNotFound (404),
300
    #                       overLimit (413)
301

    
302
    log.info('delete_network %s', network_id)
303
    net = util.get_network(network_id, request.user_uniq, for_update=True)
304
    if net.public:
305
        raise faults.Forbidden('Can not delete the public network.')
306

    
307
    if net.deleted:
308
        raise faults.BadRequest("Network has been deleted.")
309

    
310
    if net.machines.all():  # Nics attached on network
311
        raise faults.NetworkInUse('Machines are connected to network.')
312

    
313
    net.action = 'DESTROY'
314
    net.save()
315

    
316
    backend.delete_network(net)
317
    return HttpResponse(status=204)
318

    
319

    
320
@api.api_method(http_method='POST', user_required=True, logger=log)
321
def network_action(request, network_id):
322
    req = utils.get_request_dict(request)
323
    log.debug('network_action %s %s', network_id, req)
324
    if len(req) != 1:
325
        raise faults.BadRequest('Malformed request.')
326

    
327
    net = util.get_network(network_id, request.user_uniq)
328
    if net.public:
329
        raise faults.Forbidden('Can not modify the public network.')
330
    if net.deleted:
331
        raise faults.BadRequest("Network has been deleted.")
332

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