Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.5 kB)

1 5db2001a Marios Kogias
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2 5db2001a Marios Kogias
#
3 5db2001a Marios Kogias
# Redistribution and use in source and binary forms, with or
4 5db2001a Marios Kogias
# without modification, are permitted provided that the following
5 5db2001a Marios Kogias
# conditions are met:
6 5db2001a Marios Kogias
#
7 5db2001a Marios Kogias
#   1. Redistributions of source code must retain the above
8 5db2001a Marios Kogias
#      copyright notice, this list of conditions and the following
9 5db2001a Marios Kogias
#      disclaimer.
10 5db2001a Marios Kogias
#
11 5db2001a Marios Kogias
#   2. Redistributions in binary form must reproduce the above
12 5db2001a Marios Kogias
#      copyright notice, this list of conditions and the following
13 5db2001a Marios Kogias
#      disclaimer in the documentation and/or other materials
14 5db2001a Marios Kogias
#      provided with the distribution.
15 5db2001a Marios Kogias
#
16 5db2001a Marios Kogias
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5db2001a Marios Kogias
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5db2001a Marios Kogias
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5db2001a Marios Kogias
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5db2001a Marios Kogias
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5db2001a Marios Kogias
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5db2001a Marios Kogias
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5db2001a Marios Kogias
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5db2001a Marios Kogias
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5db2001a Marios Kogias
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5db2001a Marios Kogias
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5db2001a Marios Kogias
# POSSIBILITY OF SUCH DAMAGE.
28 5db2001a Marios Kogias
#
29 5db2001a Marios Kogias
# The views and conclusions contained in the software and
30 5db2001a Marios Kogias
# documentation are those of the authors and should not be
31 5db2001a Marios Kogias
# interpreted as representing official policies, either expressed
32 5db2001a Marios Kogias
# or implied, of GRNET S.A.
33 5db2001a Marios Kogias
34 86961519 Christos Stavrakakis
#from django.conf import settings
35 69c8d65d Christos Stavrakakis
import ipaddr
36 593851e0 Buildbot
from django.conf.urls import patterns
37 593851e0 Buildbot
from django.http import HttpResponse
38 593851e0 Buildbot
from django.utils import simplejson as json
39 593851e0 Buildbot
from django.db import transaction
40 593851e0 Buildbot
from django.template.loader import render_to_string
41 5db2001a Marios Kogias
42 5db2001a Marios Kogias
from snf_django.lib import api
43 69c8d65d Christos Stavrakakis
from snf_django.lib.api import faults
44 5db2001a Marios Kogias
45 593851e0 Buildbot
from synnefo.api import util
46 86961519 Christos Stavrakakis
from synnefo.db.models import NetworkInterface
47 0292883e Christos Stavrakakis
from synnefo.logic import servers, ips
48 593851e0 Buildbot
49 593851e0 Buildbot
from logging import getLogger
50 593851e0 Buildbot
51 593851e0 Buildbot
log = getLogger(__name__)
52 593851e0 Buildbot
53 593851e0 Buildbot
urlpatterns = patterns(
54 593851e0 Buildbot
    'synnefo.api.ports',
55 593851e0 Buildbot
    (r'^(?:/|.json|.xml)?$', 'demux'),
56 342acb45 Kostas Papadimitriou
    (r'^/detail(?:.json|.xml)?$', 'list_ports', {'detail': True}),
57 593851e0 Buildbot
    (r'^/([-\w]+)(?:/|.json|.xml)?$', 'port_demux'))
58 593851e0 Buildbot
59 5db2001a Marios Kogias
60 593851e0 Buildbot
def demux(request):
61 593851e0 Buildbot
    if request.method == 'GET':
62 593851e0 Buildbot
        return list_ports(request)
63 593851e0 Buildbot
    elif request.method == 'POST':
64 593851e0 Buildbot
        return create_port(request)
65 593851e0 Buildbot
    else:
66 593851e0 Buildbot
        return api.api_method_not_allowed(request)
67 593851e0 Buildbot
68 593851e0 Buildbot
69 5db2001a Marios Kogias
def port_demux(request, port_id):
70 593851e0 Buildbot
71 593851e0 Buildbot
    if request.method == 'GET':
72 5db2001a Marios Kogias
        return get_port_details(request, port_id)
73 593851e0 Buildbot
    elif request.method == 'DELETE':
74 5db2001a Marios Kogias
        return delete_port(request, port_id)
75 593851e0 Buildbot
    elif request.method == 'PUT':
76 5db2001a Marios Kogias
        return update_port(request, port_id)
77 593851e0 Buildbot
    else:
78 593851e0 Buildbot
        return api.api_method_not_allowed(request)
79 593851e0 Buildbot
80 593851e0 Buildbot
81 593851e0 Buildbot
@api.api_method(http_method='GET', user_required=True, logger=log)
82 bf58eed3 Dionysis Grigoropoulos
def list_ports(request, detail=True):
83 593851e0 Buildbot
84 593851e0 Buildbot
    log.debug('list_ports detail=%s', detail)
85 593851e0 Buildbot
86 3549cb2f Dionysis Grigoropoulos
    user_ports = NetworkInterface.objects.filter(userid=request.user_uniq)
87 593851e0 Buildbot
88 883c1f94 Christos Stavrakakis
    if detail:
89 883c1f94 Christos Stavrakakis
        user_ports = user_ports.prefetch_related("ips")
90 883c1f94 Christos Stavrakakis
91 5db2001a Marios Kogias
    port_dicts = [port_to_dict(port, detail)
92 9f6760ee Christos Stavrakakis
                  for port in user_ports.order_by('id')]
93 593851e0 Buildbot
94 593851e0 Buildbot
    if request.serialization == 'xml':
95 5db2001a Marios Kogias
        data = render_to_string('list_ports.xml', {
96 5db2001a Marios Kogias
            "ports": port_dicts})
97 593851e0 Buildbot
    else:
98 5db2001a Marios Kogias
        data = json.dumps({'ports': port_dicts})
99 593851e0 Buildbot
100 593851e0 Buildbot
    return HttpResponse(data, status=200)
101 593851e0 Buildbot
102 593851e0 Buildbot
103 593851e0 Buildbot
@api.api_method(http_method='POST', user_required=True, logger=log)
104 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
105 593851e0 Buildbot
def create_port(request):
106 593851e0 Buildbot
    user_id = request.user_uniq
107 5db2001a Marios Kogias
    req = api.utils.get_request_dict(request)
108 593851e0 Buildbot
    log.info('create_port %s', req)
109 593851e0 Buildbot
110 14402edc Christos Stavrakakis
    port_dict = api.utils.get_attribute(req, "port", attr_type=dict)
111 14402edc Christos Stavrakakis
    net_id = api.utils.get_attribute(port_dict, "network_id",
112 14402edc Christos Stavrakakis
                                     attr_type=(basestring, int))
113 f16aa9e6 Marios Kogias
114 14402edc Christos Stavrakakis
    device_id = api.utils.get_attribute(port_dict, "device_id", required=False,
115 14402edc Christos Stavrakakis
                                        attr_type=(basestring, int))
116 e1fab40f Christos Stavrakakis
    vm = None
117 e1fab40f Christos Stavrakakis
    if device_id is not None:
118 e1fab40f Christos Stavrakakis
        vm = util.get_vm(device_id, user_id, for_update=True, non_deleted=True,
119 e1fab40f Christos Stavrakakis
                         non_suspended=True)
120 f16aa9e6 Marios Kogias
121 69c8d65d Christos Stavrakakis
    # Check if the request contains a valid IPv4 address
122 14402edc Christos Stavrakakis
    fixed_ips = api.utils.get_attribute(port_dict, "fixed_ips", required=False,
123 14402edc Christos Stavrakakis
                                        attr_type=list)
124 69c8d65d Christos Stavrakakis
    if fixed_ips is not None and len(fixed_ips) > 0:
125 69c8d65d Christos Stavrakakis
        if len(fixed_ips) > 1:
126 69c8d65d Christos Stavrakakis
            msg = "'fixed_ips' attribute must contain only one fixed IP."
127 69c8d65d Christos Stavrakakis
            raise faults.BadRequest(msg)
128 14402edc Christos Stavrakakis
        fixed_ip = fixed_ips[0]
129 14402edc Christos Stavrakakis
        if not isinstance(fixed_ip, dict):
130 14402edc Christos Stavrakakis
            raise faults.BadRequest("Invalid 'fixed_ips' field.")
131 14402edc Christos Stavrakakis
        fixed_ip_address = fixed_ip.get("ip_address")
132 69c8d65d Christos Stavrakakis
        if fixed_ip_address is not None:
133 69c8d65d Christos Stavrakakis
            try:
134 69c8d65d Christos Stavrakakis
                ip = ipaddr.IPAddress(fixed_ip_address)
135 69c8d65d Christos Stavrakakis
                if ip.version == 6:
136 69c8d65d Christos Stavrakakis
                    msg = "'ip_address' can be only an IPv4 address'"
137 69c8d65d Christos Stavrakakis
                    raise faults.BadRequest(msg)
138 69c8d65d Christos Stavrakakis
            except ValueError:
139 69c8d65d Christos Stavrakakis
                msg = "%s is not a valid IPv4 Address" % fixed_ip_address
140 69c8d65d Christos Stavrakakis
                raise faults.BadRequest(msg)
141 69c8d65d Christos Stavrakakis
    else:
142 69c8d65d Christos Stavrakakis
        fixed_ip_address = None
143 69c8d65d Christos Stavrakakis
144 e1fab40f Christos Stavrakakis
    network = util.get_network(net_id, user_id, non_deleted=True,
145 e1fab40f Christos Stavrakakis
                               for_update=True)
146 e1fab40f Christos Stavrakakis
147 9c74cc19 Marios Kogias
    ipaddress = None
148 f16aa9e6 Marios Kogias
    if network.public:
149 69c8d65d Christos Stavrakakis
        # Creating a port to a public network is only allowed if the user has
150 69c8d65d Christos Stavrakakis
        # already a floating IP address in this network which is specified
151 69c8d65d Christos Stavrakakis
        # as the fixed IP address of the port
152 69c8d65d Christos Stavrakakis
        if fixed_ip_address is None:
153 69c8d65d Christos Stavrakakis
            msg = ("'fixed_ips' attribute must contain a floating IP address"
154 69c8d65d Christos Stavrakakis
                   " in order to connect to a public network.")
155 69c8d65d Christos Stavrakakis
            raise faults.BadRequest(msg)
156 69c8d65d Christos Stavrakakis
        ipaddress = util.get_floating_ip_by_address(user_id, fixed_ip_address,
157 b968e8e6 Christos Stavrakakis
                                                    for_update=True)
158 69c8d65d Christos Stavrakakis
    elif fixed_ip_address:
159 0292883e Christos Stavrakakis
        ipaddress = ips.allocate_ip(network, user_id,
160 0292883e Christos Stavrakakis
                                    address=fixed_ip_address)
161 f16aa9e6 Marios Kogias
162 14402edc Christos Stavrakakis
    name = api.utils.get_attribute(port_dict, "name", required=False,
163 14402edc Christos Stavrakakis
                                   attr_type=basestring)
164 f16aa9e6 Marios Kogias
    if name is None:
165 f16aa9e6 Marios Kogias
        name = ""
166 f16aa9e6 Marios Kogias
167 f16aa9e6 Marios Kogias
    security_groups = api.utils.get_attribute(port_dict,
168 f16aa9e6 Marios Kogias
                                              "security_groups",
169 14402edc Christos Stavrakakis
                                              required=False,
170 14402edc Christos Stavrakakis
                                              attr_type=list)
171 f16aa9e6 Marios Kogias
    #validate security groups
172 f16aa9e6 Marios Kogias
    # like get security group from db
173 a1713485 Marios Kogias
    sg_list = []
174 f16aa9e6 Marios Kogias
    if security_groups:
175 f16aa9e6 Marios Kogias
        for gid in security_groups:
176 14402edc Christos Stavrakakis
            try:
177 14402edc Christos Stavrakakis
                sg = util.get_security_group(int(gid))
178 14402edc Christos Stavrakakis
            except (KeyError, ValueError):
179 14402edc Christos Stavrakakis
                raise faults.BadRequest("Invalid 'security_groups' field.")
180 a1713485 Marios Kogias
            sg_list.append(sg)
181 f16aa9e6 Marios Kogias
182 fae6e5f0 Christos Stavrakakis
    new_port = servers.create_port(user_id, network, use_ipaddress=ipaddress,
183 e0b9a688 Dionysis Grigoropoulos
                                   machine=vm, name=name)
184 593851e0 Buildbot
185 5db2001a Marios Kogias
    response = render_port(request, port_to_dict(new_port), status=201)
186 593851e0 Buildbot
187 593851e0 Buildbot
    return response
188 593851e0 Buildbot
189 593851e0 Buildbot
190 5db2001a Marios Kogias
@api.api_method(http_method='GET', user_required=True, logger=log)
191 5db2001a Marios Kogias
def get_port_details(request, port_id):
192 5db2001a Marios Kogias
    log.debug('get_port_details %s', port_id)
193 5db2001a Marios Kogias
    port = util.get_port(port_id, request.user_uniq)
194 5db2001a Marios Kogias
    return render_port(request, port_to_dict(port))
195 5db2001a Marios Kogias
196 5db2001a Marios Kogias
197 5db2001a Marios Kogias
@api.api_method(http_method='PUT', user_required=True, logger=log)
198 5db2001a Marios Kogias
def update_port(request, port_id):
199 5db2001a Marios Kogias
    '''
200 5db2001a Marios Kogias
    You can update only name, security_groups
201 5db2001a Marios Kogias
    '''
202 5db2001a Marios Kogias
    port = util.get_port(port_id, request.user_uniq, for_update=True)
203 5db2001a Marios Kogias
    req = api.utils.get_request_dict(request)
204 5db2001a Marios Kogias
205 14402edc Christos Stavrakakis
    port_info = api.utils.get_attribute(req, "port", required=True,
206 14402edc Christos Stavrakakis
                                        attr_type=dict)
207 14402edc Christos Stavrakakis
    name = api.utils.get_attribute(port_info, "name", required=False,
208 14402edc Christos Stavrakakis
                                   attr_type=basestring)
209 5db2001a Marios Kogias
210 5db2001a Marios Kogias
    if name:
211 5db2001a Marios Kogias
        port.name = name
212 5db2001a Marios Kogias
213 5db2001a Marios Kogias
    security_groups = api.utils.get_attribute(port_info, "security_groups",
214 14402edc Christos Stavrakakis
                                              required=False, attr_type=list)
215 14402edc Christos Stavrakakis
216 5db2001a Marios Kogias
    if security_groups:
217 5db2001a Marios Kogias
        sg_list = []
218 5db2001a Marios Kogias
        #validate security groups
219 5db2001a Marios Kogias
        for gid in security_groups:
220 14402edc Christos Stavrakakis
            try:
221 14402edc Christos Stavrakakis
                sg = util.get_security_group(int(gid))
222 14402edc Christos Stavrakakis
            except (KeyError, ValueError):
223 14402edc Christos Stavrakakis
                raise faults.BadRequest("Invalid 'security_groups' field.")
224 a1713485 Marios Kogias
            sg_list.append(sg)
225 5db2001a Marios Kogias
226 5db2001a Marios Kogias
        #clear the old security groups
227 5db2001a Marios Kogias
        port.security_groups.clear()
228 5db2001a Marios Kogias
229 5db2001a Marios Kogias
        #add the new groups
230 5db2001a Marios Kogias
        port.security_groups.add(*sg_list)
231 5db2001a Marios Kogias
232 5db2001a Marios Kogias
    port.save()
233 5db2001a Marios Kogias
    return render_port(request, port_to_dict(port), 200)
234 5db2001a Marios Kogias
235 5db2001a Marios Kogias
236 5db2001a Marios Kogias
@api.api_method(http_method='DELETE', user_required=True, logger=log)
237 5db2001a Marios Kogias
@transaction.commit_on_success
238 5db2001a Marios Kogias
def delete_port(request, port_id):
239 5db2001a Marios Kogias
    log.info('delete_port %s', port_id)
240 86961519 Christos Stavrakakis
    user_id = request.user_uniq
241 86961519 Christos Stavrakakis
    port = util.get_port(port_id, user_id, for_update=True)
242 6e73f499 Christos Stavrakakis
243 6e73f499 Christos Stavrakakis
    # Deleting port that is connected to a public network is allowed only if
244 6e73f499 Christos Stavrakakis
    # the port has an associated floating IP address.
245 6e73f499 Christos Stavrakakis
    if port.network.public and not port.ips.filter(floating_ip=True,
246 6e73f499 Christos Stavrakakis
                                                   deleted=False).exists():
247 6e73f499 Christos Stavrakakis
        raise faults.Forbidden("Cannot disconnect from public network.")
248 6e73f499 Christos Stavrakakis
249 fae6e5f0 Christos Stavrakakis
    servers.delete_port(port)
250 5db2001a Marios Kogias
    return HttpResponse(status=204)
251 5db2001a Marios Kogias
252 593851e0 Buildbot
#util functions
253 593851e0 Buildbot
254 593851e0 Buildbot
255 593851e0 Buildbot
def port_to_dict(port, detail=True):
256 593851e0 Buildbot
    d = {'id': str(port.id), 'name': port.name}
257 24ea227b Dionysis Grigoropoulos
    d['links'] = util.port_to_links(port.id)
258 593851e0 Buildbot
    if detail:
259 fae6e5f0 Christos Stavrakakis
        user_id = port.userid
260 fae6e5f0 Christos Stavrakakis
        machine_id = port.machine_id
261 86961519 Christos Stavrakakis
        d['user_id'] = user_id
262 86961519 Christos Stavrakakis
        d['tenant_id'] = user_id
263 fae6e5f0 Christos Stavrakakis
        d['device_id'] = str(machine_id) if machine_id else None
264 86961519 Christos Stavrakakis
        # TODO: Change this based on the status of VM
265 593851e0 Buildbot
        d['admin_state_up'] = True
266 593851e0 Buildbot
        d['mac_address'] = port.mac
267 593851e0 Buildbot
        d['status'] = port.state
268 593851e0 Buildbot
        d['device_owner'] = port.device_owner
269 883c1f94 Christos Stavrakakis
        d['network_id'] = str(port.network_id)
270 5db2001a Marios Kogias
        d['updated'] = api.utils.isoformat(port.updated)
271 5db2001a Marios Kogias
        d['created'] = api.utils.isoformat(port.created)
272 593851e0 Buildbot
        d['fixed_ips'] = []
273 593851e0 Buildbot
        for ip in port.ips.all():
274 593851e0 Buildbot
            d['fixed_ips'].append({"ip_address": ip.address,
275 883c1f94 Christos Stavrakakis
                                   "subnet": str(ip.subnet_id)})
276 883c1f94 Christos Stavrakakis
        # Avoid extra queries until security groups are implemented!
277 883c1f94 Christos Stavrakakis
        #sg_list = list(port.security_groups.values_list('id', flat=True))
278 883c1f94 Christos Stavrakakis
        d['security_groups'] = []
279 5db2001a Marios Kogias
280 593851e0 Buildbot
    return d
281 593851e0 Buildbot
282 593851e0 Buildbot
283 593851e0 Buildbot
def render_port(request, portdict, status=200):
284 593851e0 Buildbot
    if request.serialization == 'xml':
285 5db2001a Marios Kogias
        data = render_to_string('port.xml', {'port': portdict})
286 593851e0 Buildbot
    else:
287 593851e0 Buildbot
        data = json.dumps({'port': portdict})
288 593851e0 Buildbot
    return HttpResponse(data, status=status)