ports.py 10.5 KB
Newer Older
1
# Copyright 2011-2014 GRNET S.A. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

34
#from django.conf import settings
35
import ipaddr
Marios Kogias's avatar
Marios Kogias committed
36
37
38
39
40
from django.conf.urls import patterns
from django.http import HttpResponse
from django.utils import simplejson as json
from django.db import transaction
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

Marios Kogias's avatar
Marios Kogias committed
45
from synnefo.api import util
46
from synnefo.db.models import NetworkInterface
Christos Stavrakakis's avatar
Christos Stavrakakis committed
47
from synnefo.logic import servers, ips
Marios Kogias's avatar
Marios Kogias committed
48
49
50
51
52
53
54
55

from logging import getLogger

log = getLogger(__name__)

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

59

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


69
def port_demux(request, port_id):
Marios Kogias's avatar
Marios Kogias committed
70
71

    if request.method == 'GET':
72
        return get_port_details(request, port_id)
Marios Kogias's avatar
Marios Kogias committed
73
    elif request.method == 'DELETE':
74
        return delete_port(request, port_id)
Marios Kogias's avatar
Marios Kogias committed
75
    elif request.method == 'PUT':
76
        return update_port(request, port_id)
Marios Kogias's avatar
Marios Kogias committed
77
78
79
80
81
    else:
        return api.api_method_not_allowed(request)


@api.api_method(http_method='GET', user_required=True, logger=log)
82
def list_ports(request, detail=True):
Marios Kogias's avatar
Marios Kogias committed
83
84
85

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

86
    user_ports = NetworkInterface.objects.filter(userid=request.user_uniq)
Marios Kogias's avatar
Marios Kogias committed
87

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

91
    port_dicts = [port_to_dict(port, detail)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
92
                  for port in user_ports.order_by('id')]
Marios Kogias's avatar
Marios Kogias committed
93
94

    if request.serialization == 'xml':
95
96
        data = render_to_string('list_ports.xml', {
            "ports": port_dicts})
Marios Kogias's avatar
Marios Kogias committed
97
    else:
98
        data = json.dumps({'ports': port_dicts})
Marios Kogias's avatar
Marios Kogias committed
99
100
101
102
103

    return HttpResponse(data, status=200)


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

110
111
112
    port_dict = api.utils.get_attribute(req, "port", attr_type=dict)
    net_id = api.utils.get_attribute(port_dict, "network_id",
                                     attr_type=(basestring, int))
Marios Kogias's avatar
Marios Kogias committed
113

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

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

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

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

162
163
    name = api.utils.get_attribute(port_dict, "name", required=False,
                                   attr_type=basestring)
Marios Kogias's avatar
Marios Kogias committed
164
165
166
167
168
    if name is None:
        name = ""

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

182
    new_port = servers.create_port(user_id, network, use_ipaddress=ipaddress,
183
                                   machine=vm, name=name)
Marios Kogias's avatar
Marios Kogias committed
184

185
    response = render_port(request, port_to_dict(new_port), status=201)
Marios Kogias's avatar
Marios Kogias committed
186
187
188
189

    return response


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


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

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

    if name:
        port.name = name

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

216
217
218
219
    if security_groups:
        sg_list = []
        #validate security groups
        for gid in security_groups:
220
221
222
223
            try:
                sg = util.get_security_group(int(gid))
            except (KeyError, ValueError):
                raise faults.BadRequest("Invalid 'security_groups' field.")
224
            sg_list.append(sg)
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

        #clear the old security groups
        port.security_groups.clear()

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

    port.save()
    return render_port(request, port_to_dict(port), 200)


@api.api_method(http_method='DELETE', user_required=True, logger=log)
@transaction.commit_on_success
def delete_port(request, port_id):
    log.info('delete_port %s', port_id)
240
241
    user_id = request.user_uniq
    port = util.get_port(port_id, user_id, for_update=True)
242
243
244
245
246
247
248

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

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

Marios Kogias's avatar
Marios Kogias committed
252
253
254
255
256
#util functions


def port_to_dict(port, detail=True):
    d = {'id': str(port.id), 'name': port.name}
257
    d['links'] = util.port_to_links(port.id)
Marios Kogias's avatar
Marios Kogias committed
258
    if detail:
259
260
        user_id = port.userid
        machine_id = port.machine_id
261
262
        d['user_id'] = user_id
        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
Marios Kogias's avatar
Marios Kogias committed
265
266
267
268
        d['admin_state_up'] = True
        d['mac_address'] = port.mac
        d['status'] = port.state
        d['device_owner'] = port.device_owner
269
        d['network_id'] = str(port.network_id)
270
271
        d['updated'] = api.utils.isoformat(port.updated)
        d['created'] = api.utils.isoformat(port.created)
Marios Kogias's avatar
Marios Kogias committed
272
273
274
        d['fixed_ips'] = []
        for ip in port.ips.all():
            d['fixed_ips'].append({"ip_address": ip.address,
275
276
277
278
                                   "subnet": str(ip.subnet_id)})
        # Avoid extra queries until security groups are implemented!
        #sg_list = list(port.security_groups.values_list('id', flat=True))
        d['security_groups'] = []
279

Marios Kogias's avatar
Marios Kogias committed
280
281
282
283
284
    return d


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