Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12 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
try:
37
    from django.conf.urls import patterns
38
except ImportError:  # Django==1.2
39
    from django.conf.urls.defaults import patterns
40

    
41
from django.db import transaction
42
from django.db.models import Q
43
from django.http import HttpResponse
44
from django.template.loader import render_to_string
45
from django.utils import simplejson as json
46

    
47
from snf_django.lib import api
48
from snf_django.lib.api import faults, utils
49
from synnefo.api import util
50
from synnefo.api.actions import network_actions
51
from synnefo import quotas
52
from synnefo.db.models import Network
53
from synnefo.db.utils import validate_mac
54
from synnefo.db.pools import EmptyPool
55
from synnefo.logic import backend
56

    
57

    
58
from logging import getLogger
59
log = getLogger(__name__)
60

    
61
urlpatterns = patterns(
62
    'synnefo.api.networks',
63
    (r'^(?:/|.json|.xml)?$', 'demux'),
64
    (r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
65
    (r'^/(\w+)(?:.json|.xml)?$', 'network_demux'),
66
    (r'^/(\w+)/action(?:.json|.xml)?$', 'network_action'),
67
)
68

    
69

    
70
def demux(request):
71
    if request.method == 'GET':
72
        return list_networks(request)
73
    elif request.method == 'POST':
74
        return create_network(request)
75
    else:
76
        return api.api_method_not_allowed(request)
77

    
78

    
79
def network_demux(request, network_id):
80
    if request.method == 'GET':
81
        return get_network_details(request, network_id)
82
    elif request.method == 'PUT':
83
        return update_network_name(request, network_id)
84
    elif request.method == 'DELETE':
85
        return delete_network(request, network_id)
86
    else:
87
        return api.api_method_not_allowed(request)
88

    
89

    
90
def network_to_dict(network, user_id, detail=True):
91
    d = {'id': str(network.id), 'name': network.name}
92
    d['links'] = util.network_to_links(network.id)
93
    if detail:
94
        d['user_id'] = network.userid
95
        d['tenant_id'] = network.userid
96
        d['cidr'] = network.subnet
97
        d['cidr6'] = network.subnet6
98
        d['gateway'] = network.gateway
99
        d['gateway6'] = network.gateway6
100
        d['dhcp'] = network.dhcp
101
        d['type'] = network.flavor
102
        d['updated'] = utils.isoformat(network.updated)
103
        d['created'] = utils.isoformat(network.created)
104
        d['status'] = network.state
105
        d['public'] = network.public
106

    
107
        attachments = [util.construct_nic_id(nic)
108
                       for nic in network.nics.filter(machine__userid=user_id)
109
                                              .filter(state="ACTIVE")
110
                                              .order_by('machine')]
111
        d['attachments'] = attachments
112
    return d
113

    
114

    
115
def render_network(request, networkdict, status=200):
116
    if request.serialization == 'xml':
117
        data = render_to_string('network.xml', {'network': networkdict})
118
    else:
119
        data = json.dumps({'network': networkdict})
120
    return HttpResponse(data, status=status)
121

    
122

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

    
132
    log.debug('list_networks detail=%s', detail)
133
    since = utils.isoparse(request.GET.get('changes-since'))
134
    user_networks = Network.objects.filter(Q(userid=request.user_uniq) |
135
                                           Q(public=True))
136

    
137
    if since:
138
        user_networks = user_networks.filter(updated__gte=since)
139
        if not user_networks:
140
            return HttpResponse(status=304)
141
    else:
142
        user_networks = user_networks.filter(deleted=False)
143

    
144
    networks = [network_to_dict(network, request.user_uniq, detail)
145
                for network in user_networks.order_by('id')]
146

    
147
    if request.serialization == 'xml':
148
        data = render_to_string('list_networks.xml', {
149
            'networks': networks,
150
            'detail': detail})
151
    else:
152
        data = json.dumps({'networks': networks})
153

    
154
    return HttpResponse(data, status=200)
155

    
156

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

    
169
    req = utils.get_request_dict(request)
170
    log.info('create_network %s', req)
171

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

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

    
190
    public = d.get("public", False)
191
    if public:
192
        raise faults.Forbidden("Can not create a public network.")
193

    
194
    dhcp = d.get('dhcp', True)
195

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

    
204
    try:
205
        mode, link, mac_prefix, tags = util.values_from_flavor(flavor)
206
        validate_mac(mac_prefix + "0:00:00:00")
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='ACTIVE')
222
    except EmptyPool:
223
        log.error("Failed to allocate resources for network of type: %s",
224
                  flavor)
225
        raise faults.ServiceUnavailable("Failed to allocate network resources")
226

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

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

    
235
    return response
236

    
237

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

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

    
253

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

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

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

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

    
283

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

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

    
300
    if net.deleted:
301
        raise faults.BadRequest("Network has been deleted.")
302

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

    
306
    net.action = 'DESTROY'
307
    net.save()
308

    
309
    backend_networks = net.backend_networks.exclude(operstate="DELETED")
310
    for bnet in backend_networks:
311
        backend.delete_network(net, bnet.backend)
312
    if not backend_networks:
313
        backend.update_network_state(net)
314
    return HttpResponse(status=204)
315

    
316

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

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

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