Revision 0baa1e3d snf-cyclades-app/synnefo/api/networks.py

b/snf-cyclades-app/synnefo/api/networks.py
1
# Copyright 2011-2013 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 1
from django.conf import settings
34 2
from django.conf.urls import patterns
35 3

  
36
from django.db.models import Q
37 4
from django.http import HttpResponse
38
from django.template.loader import render_to_string
39 5
from django.utils import simplejson as json
40

  
6
from django.db import transaction
7
from django.db.models import Q
8
from synnefo.db.pools import EmptyPool
9
from synnefo.db.utils import validate_mac
10
from django.conf import settings
41 11
from snf_django.lib import api
42
from snf_django.lib.api import faults, utils
12
from snf_django.lib.api import utils
13
from synnefo.logic import backend
14
from django.template.loader import render_to_string
43 15
from synnefo.api import util
44
from synnefo.api.servers import network_actions
45 16
from synnefo.db.models import Network
46
from synnefo.logic import networks
47

  
48 17

  
49 18
from logging import getLogger
19

  
20
from synnefo import quotas
21

  
50 22
log = getLogger(__name__)
51 23

  
52 24
urlpatterns = patterns(
53 25
    'synnefo.api.networks',
54
    (r'^(?:/|.json|.xml)?$', 'demux'),
55
    (r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
56
    (r'^/(\w+)(?:.json|.xml)?$', 'network_demux'),
57
    (r'^/(\w+)/action(?:.json|.xml)?$', 'demux_network_action'),
58
)
59

  
26
     (r'^(?:/|.json|.xml)?$', 'demux'),
27
     (r'^/(\w+)(?:/|.json|.xml)?$', 'network_demux'))
60 28

  
61 29
def demux(request):
62 30
    if request.method == 'GET':
31
        #return HttpResponse("in network get")
63 32
        return list_networks(request)
64 33
    elif request.method == 'POST':
65 34
        return create_network(request)
35
        #return HttpResponse("in network post")
66 36
    else:
67 37
        return api.api_method_not_allowed(request)
68 38

  
69 39

  
70
def network_demux(request, network_id):
40
def network_demux(request, offset):
41

  
71 42
    if request.method == 'GET':
72
        return get_network_details(request, network_id)
73
    elif request.method == 'PUT':
74
        return update_network_name(request, network_id)
43
        return get_network(request, offset)
44
        #return HttpResponse("in network det get")
75 45
    elif request.method == 'DELETE':
76
        return delete_network(request, network_id)
46
        return delete_network(request, offset)
47
        #return HttpResponse("in network det delete")
48
    elif request.method == 'PUT':
49
        return update_network(request, offset)
50
        #return HttpResponse("in network det put")
77 51
    else:
78 52
        return api.api_method_not_allowed(request)
79 53

  
80 54

  
81
def network_to_dict(network, user_id, detail=True):
82
    d = {'id': str(network.id), 'name': network.name}
83
    d['links'] = util.network_to_links(network.id)
84
    if detail:
85
        d['user_id'] = network.userid
86
        d['tenant_id'] = network.userid
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.flavor
93
        d['updated'] = utils.isoformat(network.updated)
94
        d['created'] = utils.isoformat(network.created)
95
        d['status'] = network.state
96
        d['public'] = network.public
97

  
98
        attachments = [nic.id
99
                       for nic in network.nics.filter(machine__userid=user_id)
100
                                              .filter(state="ACTIVE")
101
                                              .order_by('machine')]
102
        d['attachments'] = 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 55
@api.api_method(http_method='GET', user_required=True, logger=log)
115 56
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 57
    log.debug('list_networks detail=%s', detail)
124
    user_networks = Network.objects.filter(Q(userid=request.user_uniq) |
125
                                           Q(public=True))
58

  
59
    user_networks = Network.objects.filter(
60
        Q(userid=request.user_uniq) | Q(public=True))
61

  
126 62
    user_networks = utils.filter_modified_since(request, objects=user_networks)
127 63

  
128
    networks_dict = [network_to_dict(network, request.user_uniq, detail)
129
                     for network in user_networks.order_by('id')]
64
    networks = [network_to_dict(network, detail)
65
                for network in user_networks.order_by('id')]
130 66

  
131 67
    if request.serialization == 'xml':
132 68
        data = render_to_string('list_networks.xml', {
133
            'networks': networks_dict,
134
            'detail': detail})
69
            "networks": networks})
135 70
    else:
136
        data = json.dumps({'networks': networks_dict})
71
        data = json.dumps({'networks': networks})
137 72

  
138 73
    return HttpResponse(data, status=200)
139 74

  
140 75

  
141 76
@api.api_method(http_method='POST', user_required=True, logger=log)
77
@transaction.commit_manually
142 78
def create_network(request):
143
    # Normal Response Code: 202
144
    # Error Response Codes: computeFault (400, 500),
145
    #                       serviceUnavailable (503),
146
    #                       unauthorized (401),
147
    #                       badMediaType(415),
148
    #                       badRequest (400),
149
    #                       forbidden (403)
150
    #                       overLimit (413)
151

  
152
    req = utils.get_request_dict(request)
153
    log.info('create_network %s', req)
154
    user_id = request.user_uniq
155 79
    try:
156
        d = req['network']
157
        name = d['name']
158
    except KeyError:
159
        raise faults.BadRequest("Malformed request")
160

  
161
    # Get and validate flavor. Flavors are still exposed as 'type' in the
162
    # API.
163
    flavor = d.get("type", None)
164
    if flavor is None:
165
        raise faults.BadRequest("Missing request parameter 'type'")
166
    elif flavor not in Network.FLAVORS.keys():
167
        raise faults.BadRequest("Invalid network type '%s'" % flavor)
168
    elif flavor not in settings.API_ENABLED_NETWORK_FLAVORS:
169
        raise faults.Forbidden("Can not create network of type '%s'" %
170
                               flavor)
171

  
172
    public = d.get("public", False)
173
    if public:
174
        raise faults.Forbidden("Can not create a public network.")
175

  
176
    dhcp = d.get('dhcp', True)
177

  
178
    # Get and validate network parameters
179
    subnet = d.get('cidr', '192.168.1.0/24')
180
    subnet6 = d.get('cidr6', None)
181
    gateway = d.get('gateway', None)
182
    gateway6 = d.get('gateway6', None)
183

  
184
    network = networks.create(user_id=user_id, name=name, flavor=flavor,
185
                              subnet=subnet, gateway=gateway, subnet6=subnet6,
186
                              gateway6=gateway6, dhcp=dhcp, public=False)
187

  
188
    networkdict = network_to_dict(network, request.user_uniq)
189
    response = render_network(request, networkdict, status=202)
80
        user_id = request.user_uniq
81
        req = utils.get_request_dict(request)
82
        log.info('create_network %s', req)
83
        try:
84
            d = req['network']
85
        except KeyError:
86
            raise api.faults.BadRequest("Malformed request")
87

  
88
        try:
89
            flavor = d['type']
90
        except KeyError:
91
            raise faults.BadRequest("Missing request parameter 'type'")
92

  
93
        if flavor not in Network.FLAVORS.keys():
94
            raise api.faults.BadRequest("Invalid network type '%s'"
95
                                        % flavor)
96
        if flavor not in settings.API_ENABLED_NETWORK_FLAVORS:
97
            msg = "Can not create network of type '%s'"
98
            raise api.faults.Forbidden(msg % flavor)
99

  
100
        try:
101
            name = d['name']
102
        except KeyError:
103
            name = ""
104

  
105
        try:
106
            #mode, link, mac_prefix, tags = util.values_from_flavor(flavor)
107

  
108
            #validate_mac(mac_prefix + "0:00:00:00")
109
            network = Network.objects.create(
110
                name=name,
111
                userid=user_id,
112
                flavor=flavor,
113
                #mode=mode,
114
                #link=link,
115
                #mac_prefix=mac_prefix,
116
                #tags=tags,
117
                action='CREATE',
118
                state='ACTIVE')
119
        except EmptyPool:
120
            msg = "Failed to allocate resources for network of type: %s"
121
            log.error(msg, flavor)
122
            raise api.faults.ServiceUnavailable("Failed to allocate network\
123
                  resources")
124

  
125
        # Issue commission to Quotaholder and accept it since at the end of
126
        # this transaction the Network object will be created in the DB.
127
        # Note: the following call does a commit!
128
        #quotas.issue_and_accept_commission(network)
129
        # COME BACK....
130

  
131
    except:
132
        transaction.rollback()
133
        log.info("roll")
134
        raise
135
    else:
136
        transaction.commit()
137
        log.info("commit")
138
    networkdict = network_to_dict(network)
139
    response = render_network(request, networkdict, status=201)
190 140

  
191 141
    return response
192 142

  
193 143

  
194 144
@api.api_method(http_method='GET', user_required=True, logger=log)
195
def get_network_details(request, network_id):
196
    # Normal Response Codes: 200, 203
197
    # Error Response Codes: computeFault (400, 500),
198
    #                       serviceUnavailable (503),
199
    #                       unauthorized (401),
200
    #                       badRequest (400),
201
    #                       itemNotFound (404),
202
    #                       overLimit (413)
203

  
145
def get_network(request, network_id):
204 146
    log.debug('get_network_details %s', network_id)
205 147
    net = util.get_network(network_id, request.user_uniq)
206
    netdict = network_to_dict(net, request.user_uniq)
207
    return render_network(request, netdict)
208

  
209 148

  
210
@api.api_method(http_method='PUT', user_required=True, logger=log)
211
def update_network_name(request, network_id):
212
    # Normal Response Code: 204
213
    # Error Response Codes: computeFault (400, 500),
214
    #                       serviceUnavailable (503),
215
    #                       unauthorized (401),
216
    #                       badRequest (400),
217
    #                       forbidden (403)
218
    #                       badMediaType(415),
219
    #                       itemNotFound (404),
220
    #                       overLimit (413)
221

  
222
    req = utils.get_request_dict(request)
223
    log.info('update_network_name %s', network_id)
224

  
225
    try:
226
        name = req['network']['name']
227
    except (TypeError, KeyError):
228
        raise faults.BadRequest('Malformed request.')
229

  
230
    network = util.get_network(network_id, request.user_uniq)
231
    if network.public:
232
        raise faults.Forbidden('Can not rename the public network.')
233
    network = networks.rename(network, name)
234
    return HttpResponse(status=204)
149
    #needs discussion
150
    if net.deleted:
151
        raise api.faults.BadRequest("Network has been deleted.")
152
    else:
153
        netdict = network_to_dict(net)
154
        return render_network(request, netdict)
235 155

  
236 156

  
237 157
@api.api_method(http_method='DELETE', user_required=True, logger=log)
158
@transaction.commit_on_success
238 159
def delete_network(request, network_id):
239
    # Normal Response Code: 204
240
    # Error Response Codes: computeFault (400, 500),
241
    #                       serviceUnavailable (503),
242
    #                       unauthorized (401),
243
    #                       forbidden (403)
244
    #                       itemNotFound (404),
245
    #                       overLimit (413)
246

  
247 160
    log.info('delete_network %s', network_id)
248
    network = util.get_network(network_id, request.user_uniq, for_update=True)
249
    if network.public:
250
        raise faults.Forbidden('Can not delete the public network.')
251
    networks.delete(network)
161
    net = util.get_network(network_id, request.user_uniq, for_update=True)
162

  
163
    log.info(net.name)
164
    if net.public:
165
        raise api.faults.Forbidden('Can not delete the public network.')
166

  
167
    if net.deleted:
168
        raise api.faults.BadRequest("Network has been deleted.")
169

  
170
    if net.machines.all():  # Nics attached on network
171
        #raise api.faults.NetworkInUse('Machines are connected to network.')
172
        #edit to return with 409
173
        return HttpResponse("Network in use", status=409)
174

  
175
    #check if there are any floating ips reserved
176
    #if net.floating_ips.all():
177
    #    #raise api.faults.NetworkInUse('Machines are connected to network.')
178
    #    #edit to return with 409
179
    #    return HttpResponse("Network in use", status=409)
180

  
181
    net.action = 'DESTROY'
182
    '''
183
    skip the backend part...
184
    backend_networks = net.backend_networks.exclude(operstate="DELETED")
185
    for bnet in backend_networks:
186
        backend.delete_network(net, bnet.backend)
187
    if not backend_networks:
188
        backend.update_network_state(net)
189
    '''
190

  
191
    #delete all the subnets
192

  
193
    for s in net.subnets.all():
194
        s.deleted = True
195

  
196
    #the following has to leave when fix the backend thing
197
    net.deleted = True
198
    net.save()
199

  
252 200
    return HttpResponse(status=204)
253 201

  
254 202

  
255
def key_to_action(action):
256
    return action.upper()
203
@api.api_method(http_method='PUT', user_required=True, logger=log)
204
def update_network(request, network_id):
205
    '''
206
    You can update only name
207
    '''
208
    net = util.get_network(network_id, request.user_uniq, for_update=True)
209
    info = utils.get_request_dict(request)
210

  
211
    updatable = set(["name"])
212
    try:
213
        new_vals = info["network"]
214
    except KeyError:
215
        raise api.faults.BadRequest("Malformed request")
257 216

  
217
    for key, val in new_vals.iteritems():
218
        if key in updatable:
219
            setattr(net, key, val)
220
        else:
221
            raise api.faults.BadRequest("Wrong field update")
222
    net.save()
223
    netdict = network_to_dict(net)
224
    return render_network(request, netdict, 200)
258 225

  
259
@api.api_method(http_method='POST', user_required=True, logger=log)
260
def demux_network_action(request, network_id):
261
    req = utils.get_request_dict(request)
262
    log.debug('network_action %s %s', network_id, req)
263
    if len(req) != 1:
264
        raise faults.BadRequest('Malformed request.')
265 226

  
266
    net = util.get_network(network_id, request.user_uniq)
267
    if net.public:
268
        raise faults.Forbidden('Can not modify the public network.')
269
    if net.deleted:
270
        raise faults.BadRequest("Network has been deleted.")
271

  
272
    action = req.keys()[0]
273
    if key_to_action(action) not in [x[0] for x in Network.ACTIONS]:
274
        raise faults.BadRequest("Action %s not supported." % action)
275
    action_args = req[action]
276
    if not isinstance(action_args, dict):
277
        raise faults.BadRequest("Invalid argument.")
278
    return network_actions[action](request, net, action_args)
227
def network_to_dict(network, detail=True):
228
    d = {'id': str(network.id), 'name': network.name}
229
    d['links'] = util.network_to_links(network.id)
230
    if detail:
231
        d['user_id'] = network.userid
232
        d['tenant_id'] = network.userid
233
        d['type'] = network.flavor
234
        d['updated'] = utils.isoformat(network.updated)
235
        d['created'] = utils.isoformat(network.created)
236
        d['status'] = network.state
237
        d['public'] = network.public
238
        d['external_router'] = network.external_router
239
        d['external_router'] = network.external_router
240
        d['admin_state_up'] = True
241
        subnet_cidr = [s.id for s in network.subnets.all()]
242
        d['subnets'] = subnet_cidr
243
    return d
244

  
245

  
246
def render_network(request, networkdict, status=200):
247
    if request.serialization == 'xml':
248
        data = render_to_string('network.xml', {'network': networkdict})
249
    else:
250
        data = json.dumps({'network': networkdict})
251
    return HttpResponse(data, status=status)

Also available in: Unified diff