Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / ports.py @ 14402edc

History | View | Annotate | Download (10.5 kB)

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

    
34
#from django.conf import settings
35
import ipaddr
36
from django.conf.urls import patterns
37
from django.http import HttpResponse
38
from django.utils import simplejson as json
39
from django.db import transaction
40
from django.template.loader import render_to_string
41

    
42
from snf_django.lib import api
43
from snf_django.lib.api import faults
44

    
45
from synnefo.api import util
46
from synnefo.db.models import NetworkInterface
47
from synnefo.logic import servers, ips
48

    
49
from logging import getLogger
50

    
51
log = getLogger(__name__)
52

    
53
urlpatterns = patterns(
54
    'synnefo.api.ports',
55
    (r'^(?:/|.json|.xml)?$', 'demux'),
56
    (r'^/detail(?:.json|.xml)?$', 'list_ports', {'detail': True}),
57
    (r'^/([-\w]+)(?:/|.json|.xml)?$', 'port_demux'))
58

    
59

    
60
def demux(request):
61
    if request.method == 'GET':
62
        return list_ports(request)
63
    elif request.method == 'POST':
64
        return create_port(request)
65
    else:
66
        return api.api_method_not_allowed(request)
67

    
68

    
69
def port_demux(request, port_id):
70

    
71
    if request.method == 'GET':
72
        return get_port_details(request, port_id)
73
    elif request.method == 'DELETE':
74
        return delete_port(request, port_id)
75
    elif request.method == 'PUT':
76
        return update_port(request, port_id)
77
    else:
78
        return api.api_method_not_allowed(request)
79

    
80

    
81
@api.api_method(http_method='GET', user_required=True, logger=log)
82
def list_ports(request, detail=True):
83

    
84
    log.debug('list_ports detail=%s', detail)
85

    
86
    user_ports = NetworkInterface.objects.filter(userid=request.user_uniq)
87

    
88
    if detail:
89
        user_ports = user_ports.prefetch_related("ips")
90

    
91
    port_dicts = [port_to_dict(port, detail)
92
                  for port in user_ports.order_by('id')]
93

    
94
    if request.serialization == 'xml':
95
        data = render_to_string('list_ports.xml', {
96
            "ports": port_dicts})
97
    else:
98
        data = json.dumps({'ports': port_dicts})
99

    
100
    return HttpResponse(data, status=200)
101

    
102

    
103
@api.api_method(http_method='POST', user_required=True, logger=log)
104
@transaction.commit_on_success
105
def create_port(request):
106
    user_id = request.user_uniq
107
    req = api.utils.get_request_dict(request)
108
    log.info('create_port %s', req)
109

    
110
    port_dict = api.utils.get_attribute(req, "port", attr_type=dict)
111
    net_id = api.utils.get_attribute(port_dict, "network_id",
112
                                     attr_type=(basestring, int))
113

    
114
    device_id = api.utils.get_attribute(port_dict, "device_id", required=False,
115
                                        attr_type=(basestring, int))
116
    vm = None
117
    if device_id is not None:
118
        vm = util.get_vm(device_id, user_id, for_update=True, non_deleted=True,
119
                         non_suspended=True)
120

    
121
    # Check if the request contains a valid IPv4 address
122
    fixed_ips = api.utils.get_attribute(port_dict, "fixed_ips", required=False,
123
                                        attr_type=list)
124
    if fixed_ips is not None and len(fixed_ips) > 0:
125
        if len(fixed_ips) > 1:
126
            msg = "'fixed_ips' attribute must contain only one fixed IP."
127
            raise faults.BadRequest(msg)
128
        fixed_ip = fixed_ips[0]
129
        if not isinstance(fixed_ip, dict):
130
            raise faults.BadRequest("Invalid 'fixed_ips' field.")
131
        fixed_ip_address = fixed_ip.get("ip_address")
132
        if fixed_ip_address is not None:
133
            try:
134
                ip = ipaddr.IPAddress(fixed_ip_address)
135
                if ip.version == 6:
136
                    msg = "'ip_address' can be only an IPv4 address'"
137
                    raise faults.BadRequest(msg)
138
            except ValueError:
139
                msg = "%s is not a valid IPv4 Address" % fixed_ip_address
140
                raise faults.BadRequest(msg)
141
    else:
142
        fixed_ip_address = None
143

    
144
    network = util.get_network(net_id, user_id, non_deleted=True,
145
                               for_update=True)
146

    
147
    ipaddress = None
148
    if network.public:
149
        # Creating a port to a public network is only allowed if the user has
150
        # already a floating IP address in this network which is specified
151
        # as the fixed IP address of the port
152
        if fixed_ip_address is None:
153
            msg = ("'fixed_ips' attribute must contain a floating IP address"
154
                   " in order to connect to a public network.")
155
            raise faults.BadRequest(msg)
156
        ipaddress = util.get_floating_ip_by_address(user_id, fixed_ip_address,
157
                                                    for_update=True)
158
    elif fixed_ip_address:
159
        ipaddress = ips.allocate_ip(network, user_id,
160
                                    address=fixed_ip_address)
161

    
162
    name = api.utils.get_attribute(port_dict, "name", required=False,
163
                                   attr_type=basestring)
164
    if name is None:
165
        name = ""
166

    
167
    security_groups = api.utils.get_attribute(port_dict,
168
                                              "security_groups",
169
                                              required=False,
170
                                              attr_type=list)
171
    #validate security groups
172
    # like get security group from db
173
    sg_list = []
174
    if security_groups:
175
        for gid in security_groups:
176
            try:
177
                sg = util.get_security_group(int(gid))
178
            except (KeyError, ValueError):
179
                raise faults.BadRequest("Invalid 'security_groups' field.")
180
            sg_list.append(sg)
181

    
182
    new_port = servers.create_port(user_id, network, use_ipaddress=ipaddress,
183
                                   machine=vm, name=name)
184

    
185
    response = render_port(request, port_to_dict(new_port), status=201)
186

    
187
    return response
188

    
189

    
190
@api.api_method(http_method='GET', user_required=True, logger=log)
191
def get_port_details(request, port_id):
192
    log.debug('get_port_details %s', port_id)
193
    port = util.get_port(port_id, request.user_uniq)
194
    return render_port(request, port_to_dict(port))
195

    
196

    
197
@api.api_method(http_method='PUT', user_required=True, logger=log)
198
def update_port(request, port_id):
199
    '''
200
    You can update only name, security_groups
201
    '''
202
    port = util.get_port(port_id, request.user_uniq, for_update=True)
203
    req = api.utils.get_request_dict(request)
204

    
205
    port_info = api.utils.get_attribute(req, "port", required=True,
206
                                        attr_type=dict)
207
    name = api.utils.get_attribute(port_info, "name", required=False,
208
                                   attr_type=basestring)
209

    
210
    if name:
211
        port.name = name
212

    
213
    security_groups = api.utils.get_attribute(port_info, "security_groups",
214
                                              required=False, attr_type=list)
215

    
216
    if security_groups:
217
        sg_list = []
218
        #validate security groups
219
        for gid in security_groups:
220
            try:
221
                sg = util.get_security_group(int(gid))
222
            except (KeyError, ValueError):
223
                raise faults.BadRequest("Invalid 'security_groups' field.")
224
            sg_list.append(sg)
225

    
226
        #clear the old security groups
227
        port.security_groups.clear()
228

    
229
        #add the new groups
230
        port.security_groups.add(*sg_list)
231

    
232
    port.save()
233
    return render_port(request, port_to_dict(port), 200)
234

    
235

    
236
@api.api_method(http_method='DELETE', user_required=True, logger=log)
237
@transaction.commit_on_success
238
def delete_port(request, port_id):
239
    log.info('delete_port %s', port_id)
240
    user_id = request.user_uniq
241
    port = util.get_port(port_id, user_id, for_update=True)
242

    
243
    # Deleting port that is connected to a public network is allowed only if
244
    # the port has an associated floating IP address.
245
    if port.network.public and not port.ips.filter(floating_ip=True,
246
                                                   deleted=False).exists():
247
        raise faults.Forbidden("Cannot disconnect from public network.")
248

    
249
    servers.delete_port(port)
250
    return HttpResponse(status=204)
251

    
252
#util functions
253

    
254

    
255
def port_to_dict(port, detail=True):
256
    d = {'id': str(port.id), 'name': port.name}
257
    d['links'] = util.port_to_links(port.id)
258
    if detail:
259
        user_id = port.userid
260
        machine_id = port.machine_id
261
        d['user_id'] = user_id
262
        d['tenant_id'] = user_id
263
        d['device_id'] = str(machine_id) if machine_id else None
264
        # TODO: Change this based on the status of VM
265
        d['admin_state_up'] = True
266
        d['mac_address'] = port.mac
267
        d['status'] = port.state
268
        d['device_owner'] = port.device_owner
269
        d['network_id'] = str(port.network_id)
270
        d['updated'] = api.utils.isoformat(port.updated)
271
        d['created'] = api.utils.isoformat(port.created)
272
        d['fixed_ips'] = []
273
        for ip in port.ips.all():
274
            d['fixed_ips'].append({"ip_address": ip.address,
275
                                   "subnet": str(ip.subnet_id)})
276
        # Avoid extra queries until security groups are implemented!
277
        #sg_list = list(port.security_groups.values_list('id', flat=True))
278
        d['security_groups'] = []
279

    
280
    return d
281

    
282

    
283
def render_port(request, portdict, status=200):
284
    if request.serialization == 'xml':
285
        data = render_to_string('port.xml', {'port': portdict})
286
    else:
287
        data = json.dumps({'port': portdict})
288
    return HttpResponse(data, status=status)