Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.6 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")
111
    net_id = api.utils.get_attribute(port_dict, "network_id")
112

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

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

    
138
    network = util.get_network(net_id, user_id, non_deleted=True,
139
                               for_update=True)
140

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

    
156
    name = api.utils.get_attribute(port_dict, "name", required=False)
157
    if name is None:
158
        name = ""
159

    
160
    security_groups = api.utils.get_attribute(port_dict,
161
                                              "security_groups",
162
                                              required=False)
163
    #validate security groups
164
    # like get security group from db
165
    sg_list = []
166
    if security_groups:
167
        for gid in security_groups:
168
            sg = util.get_security_group(int(gid))
169
            sg_list.append(sg)
170

    
171
    new_port = servers.create_port(user_id, network, use_ipaddress=ipaddress,
172
                                   machine=vm, name=name)
173

    
174
    response = render_port(request, port_to_dict(new_port), status=201)
175

    
176
    return response
177

    
178

    
179
@api.api_method(http_method='GET', user_required=True, logger=log)
180
def get_port_details(request, port_id):
181
    log.debug('get_port_details %s', port_id)
182
    port = util.get_port(port_id, request.user_uniq)
183
    return render_port(request, port_to_dict(port))
184

    
185

    
186
@api.api_method(http_method='PUT', user_required=True, logger=log)
187
def update_port(request, port_id):
188
    '''
189
    You can update only name, security_groups
190
    '''
191
    port = util.get_port(port_id, request.user_uniq, for_update=True)
192
    req = api.utils.get_request_dict(request)
193

    
194
    port_info = api.utils.get_attribute(req, "port", required=True)
195
    name = api.utils.get_attribute(port_info, "name", required=False)
196

    
197
    if name:
198
        port.name = name
199

    
200
    security_groups = api.utils.get_attribute(port_info, "security_groups",
201
                                              required=False)
202
    if security_groups:
203
        sg_list = []
204
        #validate security groups
205
        for gid in security_groups:
206
            sg = util.get_security_group(int(gid))
207
            sg_list.append(sg)
208

    
209
        #clear the old security groups
210
        port.security_groups.clear()
211

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

    
215
    port.save()
216
    return render_port(request, port_to_dict(port), 200)
217

    
218

    
219
@api.api_method(http_method='DELETE', user_required=True, logger=log)
220
@transaction.commit_on_success
221
def delete_port(request, port_id):
222
    log.info('delete_port %s', port_id)
223
    user_id = request.user_uniq
224
    port = util.get_port(port_id, user_id, for_update=True)
225

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

    
232
    servers.delete_port(port)
233
    return HttpResponse(status=204)
234

    
235
#util functions
236

    
237

    
238
def port_to_dict(port, detail=True):
239
    d = {'id': str(port.id), 'name': port.name}
240
    d['links'] = util.port_to_links(port.id)
241
    if detail:
242
        user_id = port.userid
243
        machine_id = port.machine_id
244
        d['user_id'] = user_id
245
        d['tenant_id'] = user_id
246
        d['device_id'] = str(machine_id) if machine_id else None
247
        # TODO: Change this based on the status of VM
248
        d['admin_state_up'] = True
249
        d['mac_address'] = port.mac
250
        d['status'] = port.state
251
        d['device_owner'] = port.device_owner
252
        d['network_id'] = str(port.network_id)
253
        d['updated'] = api.utils.isoformat(port.updated)
254
        d['created'] = api.utils.isoformat(port.created)
255
        d['fixed_ips'] = []
256
        for ip in port.ips.all():
257
            d['fixed_ips'].append({"ip_address": ip.address,
258
                                   "subnet": str(ip.subnet_id)})
259
        # Avoid extra queries until security groups are implemented!
260
        #sg_list = list(port.security_groups.values_list('id', flat=True))
261
        d['security_groups'] = []
262

    
263
    return d
264

    
265

    
266
def render_port(request, portdict, status=200):
267
    if request.serialization == 'xml':
268
        data = render_to_string('port.xml', {'port': portdict})
269
    else:
270
        data = json.dumps({'port': portdict})
271
    return HttpResponse(data, status=status)