Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12.2 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.utils import validate_mac
50
from synnefo.db.pools import EmptyPool
51
from synnefo.logic import backend
52

    
53

    
54
from logging import getLogger
55
log = getLogger(__name__)
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 api.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 api.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
    d['links'] = util.network_to_links(network.id)
89
    if detail:
90
        d['user_id'] = network.userid
91
        d['tenant_id'] = network.userid
92
        d['cidr'] = network.subnet
93
        d['cidr6'] = network.subnet6
94
        d['gateway'] = network.gateway
95
        d['gateway6'] = network.gateway6
96
        d['dhcp'] = network.dhcp
97
        d['type'] = network.flavor
98
        d['updated'] = utils.isoformat(network.updated)
99
        d['created'] = utils.isoformat(network.created)
100
        d['status'] = network.state
101
        d['public'] = network.public
102

    
103
        attachments = [util.construct_nic_id(nic)
104
                       for nic in network.nics.filter(machine__userid=user_id)
105
                                              .filter(state="ACTIVE")
106
                                              .order_by('machine')]
107
        d['attachments'] = attachments
108
    return d
109

    
110

    
111
def render_network(request, networkdict, status=200):
112
    if request.serialization == 'xml':
113
        data = render_to_string('network.xml', {'network': networkdict})
114
    else:
115
        data = json.dumps({'network': networkdict})
116
    return HttpResponse(data, status=status)
117

    
118

    
119
@api.api_method(http_method='GET', user_required=True, logger=log)
120
def list_networks(request, detail=False):
121
    # Normal Response Codes: 200, 203
122
    # Error Response Codes: computeFault (400, 500),
123
    #                       serviceUnavailable (503),
124
    #                       unauthorized (401),
125
    #                       badRequest (400),
126
    #                       overLimit (413)
127

    
128
    log.debug('list_networks detail=%s', detail)
129
    since = utils.isoparse(request.GET.get('changes-since'))
130
    user_networks = Network.objects.filter(Q(userid=request.user_uniq) |
131
                                           Q(public=True))
132

    
133
    if since:
134
        user_networks = user_networks.filter(updated__gte=since)
135
        if not user_networks:
136
            return HttpResponse(status=304)
137
    else:
138
        user_networks = user_networks.filter(deleted=False)
139

    
140
    networks = [network_to_dict(network, request.user_uniq, detail)
141
                for network in user_networks.order_by('id')]
142

    
143
    if request.serialization == 'xml':
144
        data = render_to_string('list_networks.xml', {
145
            'networks': networks,
146
            'detail': detail})
147
    else:
148
        data = json.dumps({'networks': networks})
149

    
150
    return HttpResponse(data, status=200)
151

    
152

    
153
@api.api_method(http_method='POST', user_required=True, logger=log)
154
@transaction.commit_manually
155
def create_network(request):
156
    # Normal Response Code: 202
157
    # Error Response Codes: computeFault (400, 500),
158
    #                       serviceUnavailable (503),
159
    #                       unauthorized (401),
160
    #                       badMediaType(415),
161
    #                       badRequest (400),
162
    #                       forbidden (403)
163
    #                       overLimit (413)
164

    
165
    try:
166
        req = utils.get_request_dict(request)
167
        log.info('create_network %s', req)
168

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

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

    
187
        public = d.get("public", False)
188
        if public:
189
            raise faults.Forbidden("Can not create a public network.")
190

    
191
        dhcp = d.get('dhcp', True)
192

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

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

    
225
        # Issue commission to Quotaholder and accept it since at the end of
226
        # this transaction the Network object will be created in the DB.
227
        # Note: the following call does a commit!
228
        quotas.issue_and_accept_commission(network)
229
    except:
230
        transaction.rollback()
231
        raise
232
    else:
233
        transaction.commit()
234

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

    
238
    return response
239

    
240

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

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

    
256

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

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

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

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

    
286

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

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

    
303
    if net.deleted:
304
        raise faults.BadRequest("Network has been deleted.")
305

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

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

    
312
    backend_networks = net.backend_networks.exclude(operstate="DELETED")
313
    for bnet in backend_networks:
314
        backend.delete_network(net, bnet.backend)
315
    if not backend_networks:
316
        backend.update_network_state(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.')