Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.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=False):
83

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

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

    
88
    port_dicts = [port_to_dict(port, detail)
89
                  for port in user_ports.order_by('id')]
90

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

    
97
    return HttpResponse(data, status=200)
98

    
99

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

    
107
    port_dict = api.utils.get_attribute(req, "port")
108
    net_id = api.utils.get_attribute(port_dict, "network_id")
109

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

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

    
135
    network = util.get_network(net_id, user_id, non_deleted=True,
136
                               for_update=True)
137

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

    
153
    name = api.utils.get_attribute(port_dict, "name", required=False)
154
    if name is None:
155
        name = ""
156

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

    
168
    new_port = servers.create_port(user_id, network, use_ipaddress=ipaddress,
169
                                   machine=vm)
170

    
171
    response = render_port(request, port_to_dict(new_port), status=201)
172

    
173
    return response
174

    
175

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

    
182

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

    
191
    port_info = api.utils.get_attribute(req, "port", required=True)
192
    name = api.utils.get_attribute(port_info, "name", required=False)
193

    
194
    if name:
195
        port.name = name
196

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

    
206
        #clear the old security groups
207
        port.security_groups.clear()
208

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

    
212
    port.save()
213
    return render_port(request, port_to_dict(port), 200)
214

    
215

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

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

    
229
    servers.delete_port(port)
230
    return HttpResponse(status=204)
231

    
232
#util functions
233

    
234

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

    
259
    return d
260

    
261

    
262
def render_port(request, portdict, status=200):
263
    if request.serialization == 'xml':
264
        data = render_to_string('port.xml', {'port': portdict})
265
    else:
266
        data = json.dumps({'port': portdict})
267
    return HttpResponse(data, status=status)