Revision d6b24130
b/snf-cyclades-app/synnefo/neutron/models.py | ||
---|---|---|
1 |
# Copyright 2011-2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or without |
|
4 |
# modification, are permitted provided that the following conditions |
|
5 |
# are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above copyright |
|
8 |
# notice, this list of conditions and the following disclaimer. |
|
9 |
# |
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright |
|
11 |
# notice, this list of conditions and the following disclaimer in the |
|
12 |
# documentation and/or other materials provided with the distribution. |
|
13 |
# |
|
14 |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
15 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
16 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
17 |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
18 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
19 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
20 |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
21 |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
22 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
23 |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
24 |
# SUCH DAMAGE. |
|
25 |
# |
|
26 |
# The views and conclusions contained in the software and documentation are |
|
27 |
# those of the authors and should not be interpreted as representing official |
|
28 |
# policies, either expressed or implied, of GRNET S.A. |
|
29 |
|
|
30 |
import datetime |
|
31 |
|
|
32 |
from copy import deepcopy |
|
33 |
from django.conf import settings |
|
34 |
from django.db import models |
|
35 |
from django.db import IntegrityError |
|
36 |
|
|
37 |
from contextlib import contextmanager |
|
38 |
from hashlib import sha1 |
|
39 |
from snf_django.lib.api import faults |
|
40 |
from django.conf import settings as snf_settings |
|
41 |
|
|
42 |
from synnefo.db.managers import ForUpdateManager, ProtectedDeleteManager |
|
43 |
from synnefo.db import pools |
|
44 |
|
|
45 |
from synnefo.db.models import VirtualMachine, QuotaHolderSerial |
|
46 |
|
|
47 |
from synnefo.logic.rapi_pool import (get_rapi_client, |
|
48 |
put_rapi_client) |
|
49 |
|
|
50 |
import logging |
|
51 |
log = logging.getLogger(__name__) |
|
52 |
|
|
53 |
|
|
54 |
|
|
55 |
class Network(models.Model): |
|
56 |
OPER_STATES = ( |
|
57 |
('PENDING', 'Pending'), # Unused because of lazy networks |
|
58 |
('ACTIVE', 'Active'), |
|
59 |
('DELETED', 'Deleted'), |
|
60 |
('ERROR', 'Error') |
|
61 |
) |
|
62 |
|
|
63 |
ACTIONS = ( |
|
64 |
('CREATE', 'Create Network'), |
|
65 |
('DESTROY', 'Destroy Network'), |
|
66 |
('ADD', 'Add server to Network'), |
|
67 |
('REMOVE', 'Remove server from Network'), |
|
68 |
) |
|
69 |
|
|
70 |
RSAPI_STATE_FROM_OPER_STATE = { |
|
71 |
'PENDING': 'PENDING', |
|
72 |
'ACTIVE': 'ACTIVE', |
|
73 |
'DELETED': 'DELETED', |
|
74 |
'ERROR': 'ERROR' |
|
75 |
} |
|
76 |
|
|
77 |
FLAVORS = { |
|
78 |
'CUSTOM': { |
|
79 |
'mode': 'bridged', |
|
80 |
'link': settings.DEFAULT_BRIDGE, |
|
81 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX, |
|
82 |
'tags': None, |
|
83 |
'desc': "Basic flavor used for a bridged network", |
|
84 |
}, |
|
85 |
'IP_LESS_ROUTED': { |
|
86 |
'mode': 'routed', |
|
87 |
'link': settings.DEFAULT_ROUTING_TABLE, |
|
88 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX, |
|
89 |
'tags': 'ip-less-routed', |
|
90 |
'desc': "Flavor used for an IP-less routed network using" |
|
91 |
" Proxy ARP", |
|
92 |
}, |
|
93 |
'MAC_FILTERED': { |
|
94 |
'mode': 'bridged', |
|
95 |
'link': settings.DEFAULT_MAC_FILTERED_BRIDGE, |
|
96 |
'mac_prefix': 'pool', |
|
97 |
'tags': 'private-filtered', |
|
98 |
'desc': "Flavor used for bridged networks that offer isolation" |
|
99 |
" via filtering packets based on their src " |
|
100 |
" MAC (ebtables)", |
|
101 |
}, |
|
102 |
'PHYSICAL_VLAN': { |
|
103 |
'mode': 'bridged', |
|
104 |
'link': 'pool', |
|
105 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX, |
|
106 |
'tags': 'physical-vlan', |
|
107 |
'desc': "Flavor used for bridged network that offer isolation" |
|
108 |
" via dedicated physical vlan", |
|
109 |
}, |
|
110 |
} |
|
111 |
|
|
112 |
name = models.CharField('Network Name', max_length=128) |
|
113 |
userid = models.CharField('User ID of the owner', max_length=128, |
|
114 |
null=True, db_index=True) |
|
115 |
flavor = models.CharField('Flavor', max_length=32, null=False) |
|
116 |
mode = models.CharField('Network Mode', max_length=16, null=True) |
|
117 |
link = models.CharField('Network Link', max_length=32, null=True) |
|
118 |
mac_prefix = models.CharField('MAC Prefix', max_length=32, null=False) |
|
119 |
tags = models.CharField('Network Tags', max_length=128, null=True) |
|
120 |
public = models.BooleanField(default=False, db_index=True) |
|
121 |
created = models.DateTimeField(auto_now_add=True) |
|
122 |
updated = models.DateTimeField(auto_now=True) |
|
123 |
deleted = models.BooleanField('Deleted', default=False, db_index=True) |
|
124 |
state = models.CharField(choices=OPER_STATES, max_length=32, |
|
125 |
default='PENDING') |
|
126 |
machines = models.ManyToManyField(VirtualMachine, |
|
127 |
through='NetworkInterface', related_name='neutron_machines') |
|
128 |
action = models.CharField(choices=ACTIONS, max_length=32, null=True, |
|
129 |
default=None) |
|
130 |
drained = models.BooleanField("Drained", default=False, null=False) # this is the opposite of admin_state_up |
|
131 |
floating_ip_pool = models.BooleanField('Floating IP Pool', null=False, |
|
132 |
default=False) |
|
133 |
#serial = models.ForeignKey(QuotaHolderSerial, related_name='network', |
|
134 |
# null=True) |
|
135 |
|
|
136 |
objects = ForUpdateManager() |
|
137 |
|
|
138 |
def __unicode__(self): |
|
139 |
return "<Network: %s>" % str(self.id) |
|
140 |
|
|
141 |
@property |
|
142 |
def backend_id(self): |
|
143 |
"""Return the backend id by prepending backend-prefix.""" |
|
144 |
if not self.id: |
|
145 |
raise Network.InvalidBackendIdError("self.id is None") |
|
146 |
return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id)) |
|
147 |
|
|
148 |
@property |
|
149 |
def backend_tag(self): |
|
150 |
"""Return the network tag to be used in backend |
|
151 |
|
|
152 |
""" |
|
153 |
if self.tags: |
|
154 |
return self.tags.split(',') |
|
155 |
else: |
|
156 |
return [] |
|
157 |
|
|
158 |
def create_backend_network(self, backend=None): |
|
159 |
"""Create corresponding BackendNetwork entries.""" |
|
160 |
|
|
161 |
backends = [backend] if backend else\ |
|
162 |
Backend.objects.filter(offline=False) |
|
163 |
for backend in backends: |
|
164 |
backend_exists =\ |
|
165 |
BackendNetwork.objects.filter(backend=backend, network=self)\ |
|
166 |
.exists() |
|
167 |
if not backend_exists: |
|
168 |
BackendNetwork.objects.create(backend=backend, network=self) |
|
169 |
|
|
170 |
def get_pool(self, with_lock=True): |
|
171 |
if not self.pool_id: |
|
172 |
self.pool = IPPoolTable.objects.create(available_map='', |
|
173 |
reserved_map='', |
|
174 |
size=0) |
|
175 |
self.save() |
|
176 |
objects = IPPoolTable.objects |
|
177 |
if with_lock: |
|
178 |
objects = objects.select_for_update() |
|
179 |
return objects.get(id=self.pool_id).pool |
|
180 |
|
|
181 |
def reserve_address(self, address): |
|
182 |
pool = self.get_pool() |
|
183 |
pool.reserve(address) |
|
184 |
pool.save() |
|
185 |
|
|
186 |
def release_address(self, address): |
|
187 |
pool = self.get_pool() |
|
188 |
pool.put(address) |
|
189 |
pool.save() |
|
190 |
|
|
191 |
class InvalidBackendIdError(Exception): |
|
192 |
def __init__(self, value): |
|
193 |
self.value = value |
|
194 |
|
|
195 |
def __str__(self): |
|
196 |
return repr(self.value) |
|
197 |
|
|
198 |
class InvalidBackendMsgError(Exception): |
|
199 |
def __init__(self, opcode, status): |
|
200 |
self.opcode = opcode |
|
201 |
self.status = status |
|
202 |
|
|
203 |
def __str__(self): |
|
204 |
return repr('<opcode: %s, status: %s>' |
|
205 |
% (self.opcode, self.status)) |
|
206 |
|
|
207 |
class InvalidActionError(Exception): |
|
208 |
def __init__(self, action): |
|
209 |
self._action = action |
|
210 |
|
|
211 |
def __str__(self): |
|
212 |
return repr(str(self._action)) |
|
213 |
|
|
214 |
class Subnet(models.Model): |
|
215 |
|
|
216 |
name = models.CharField('Network Name', max_length=128) |
|
217 |
subnet_id = models.CharField('ID of the subnet', max_length=128, |
|
218 |
null=True, db_index=True) |
|
219 |
network_id = models.ForeignKey('Network') |
|
220 |
# subnet will be null for IPv6 only networks |
|
221 |
subnet = models.CharField('Subnet', max_length=32, null=True) |
|
222 |
# subnet6 will be null for IPv4 only networks |
|
223 |
subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True) |
|
224 |
gateway = models.CharField('Gateway', max_length=32, null=True) |
|
225 |
gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True) |
|
226 |
dhcp = models.BooleanField('DHCP', default=True) |
|
227 |
#pool = models.OneToOneField('IPPoolTable', related_name='network', |
|
228 |
# default=lambda: IPPoolTable.objects.create( |
|
229 |
# available_map='', |
|
230 |
# reserved_map='', |
|
231 |
# size=0), |
|
232 |
# null=True) |
|
233 |
|
|
234 |
class NetworkInterface(models.Model): |
|
235 |
STATES = ( |
|
236 |
("ACTIVE", "Active"), |
|
237 |
("BUILDING", "Building"), |
|
238 |
) |
|
239 |
|
|
240 |
name = models.CharField('Network Name', max_length=128) |
|
241 |
machine = models.ForeignKey(VirtualMachine, related_name='neutron_nics') |
|
242 |
network = models.ForeignKey(Network, related_name='neutron_nics') |
|
243 |
created = models.DateTimeField(auto_now_add=True) |
|
244 |
updated = models.DateTimeField(auto_now=True) |
|
245 |
index = models.IntegerField(null=True) |
|
246 |
mac = models.CharField(max_length=32, null=True, unique=True) |
|
247 |
ipv4 = models.CharField(max_length=15, null=True) |
|
248 |
ipv6 = models.CharField(max_length=100, null=True) |
|
249 |
#firewall_profile = models.CharField(choices=FIREWALL_PROFILES, |
|
250 |
# max_length=30, null=True) |
|
251 |
dirty = models.BooleanField(default=False) |
|
252 |
state = models.CharField(max_length=32, null=False, default="ACTIVE", |
|
253 |
choices=STATES) |
|
254 |
admin_state_up = models.BooleanField(default=False, db_index=True) |
|
255 |
|
|
256 |
def __unicode__(self): |
|
257 |
return "<%s:vm:%s network:%s ipv4:%s ipv6:%s>" % \ |
|
258 |
(self.index, self.machine_id, self.network_id, self.ipv4, |
|
259 |
self.ipv6) |
|
260 |
|
|
261 |
@property |
|
262 |
def is_floating_ip(self): |
|
263 |
network = self.network |
|
264 |
if self.ipv4 and network.floating_ip_pool: |
|
265 |
return network.floating_ips.filter(machine=self.machine, |
|
266 |
ipv4=self.ipv4, |
|
267 |
deleted=False).exists() |
b/snf-cyclades-app/synnefo/neutron/network_views.py | ||
---|---|---|
1 |
from django.http import HttpResponse |
|
2 |
from django.utils import simplejson as json |
|
3 |
from django.db import transaction |
|
4 |
from django.db.models import Q |
|
5 |
from synnefo.db.pools import EmptyPool |
|
6 |
from synnefo.db.utils import validate_mac |
|
7 |
from django.conf import settings |
|
8 |
from snf_django.lib import api |
|
9 |
from snf_django.lib.api import utils |
|
10 |
from synnefo.logic import backend |
|
11 |
from django.template.loader import render_to_string |
|
12 |
from synnefo.api import util |
|
13 |
from models import Network |
|
14 |
|
|
15 |
from logging import getLogger |
|
16 |
|
|
17 |
from synnefo import quotas |
|
18 |
|
|
19 |
|
|
20 |
log = getLogger(__name__) |
|
21 |
|
|
22 |
def demux(request): |
|
23 |
if request.method == 'GET': |
|
24 |
#return HttpResponse("in network get") |
|
25 |
return list_networks(request) |
|
26 |
elif request.method == 'POST': |
|
27 |
#return create_network(request) |
|
28 |
return HttpResponse("in network post") |
|
29 |
else: |
|
30 |
return api.api_method_not_allowed(request) |
|
31 |
|
|
32 |
def network_demux(request,offset): |
|
33 |
|
|
34 |
if request.method == 'GET': |
|
35 |
return get_network(request,offset) |
|
36 |
#return HttpResponse("in network det get") |
|
37 |
elif request.method == 'DELETE': |
|
38 |
return delete_network(request,offset) |
|
39 |
#return HttpResponse("in network det delete") |
|
40 |
elif request.method == 'PUT': |
|
41 |
#return update_network(request,offset) |
|
42 |
return HttpResponse("in network det put") |
|
43 |
else: |
|
44 |
return api.api_method_not_allowed(request) |
|
45 |
|
|
46 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
|
47 |
def list_networks(request): |
|
48 |
log.debug('list_networks') |
|
49 |
|
|
50 |
user_networks = Network.objects.filter(Q(userid=request.user_uniq) | |
|
51 |
Q(public=True)) |
|
52 |
|
|
53 |
user_networks = user_networks.filter(deleted=False) |
|
54 |
|
|
55 |
networks = [network_to_dict(network) |
|
56 |
for network in user_networks.order_by('id')] |
|
57 |
|
|
58 |
if request.serialization == 'xml': |
|
59 |
data = render_to_string('list_networks.xml', { |
|
60 |
'networks': networks }) |
|
61 |
else: |
|
62 |
data = json.dumps({'networks': networks}) |
|
63 |
|
|
64 |
return HttpResponse(data, status=200) |
|
65 |
|
|
66 |
@api.api_method(http_method='POST', user_required=True, logger=log) |
|
67 |
@transaction.commit_manually |
|
68 |
def create_network (request): |
|
69 |
''' |
|
70 |
This operation does not require a rest body. If specified, the body might |
|
71 |
include one or more of the following attributes: |
|
72 |
|
|
73 |
name: a string specifying a symbolic name for the network, which is not |
|
74 |
required to be unique; |
|
75 |
|
|
76 |
admin_state_up: a bool value specifying the administrative status of the network; |
|
77 |
|
|
78 |
2 more attirbutes for administrative users (not supported) |
|
79 |
''' |
|
80 |
|
|
81 |
try: |
|
82 |
user_id = request.user_uniq |
|
83 |
if request.raw_post_data: |
|
84 |
req = utils.get_request_dict(request) |
|
85 |
log.info('create_network %s', req) |
|
86 |
try: |
|
87 |
d = req['network'] |
|
88 |
name = d['name'] |
|
89 |
except KeyError: |
|
90 |
raise api.faults.BadRequest("Malformed request") |
|
91 |
|
|
92 |
flavor = d.get("type", None) |
|
93 |
if flavor is None: |
|
94 |
#raise faults.BadRequest("Missing request parameter 'type'") |
|
95 |
# set default flavor |
|
96 |
#FIX ME!!! |
|
97 |
flavor="MAC_FILTERED" |
|
98 |
log.info("not found flavor") |
|
99 |
elif flavor not in Network.FLAVORS.keys(): |
|
100 |
raise api.faults.BadRequest("Invalid network type '%s'" % flavor) |
|
101 |
log.info("found flavor 1") |
|
102 |
elif flavor not in settings.API_ENABLED_NETWORK_FLAVORS: |
|
103 |
log.info("found flavor2") |
|
104 |
raise api.faults.Forbidden("Can not create network of type '%s'" %flavor) |
|
105 |
# Get and validate flavor. Flavors are still exposed as 'type' in the |
|
106 |
# API. |
|
107 |
else: |
|
108 |
name = "" |
|
109 |
flavor = "MAC_FILTERED" # this is the default FIX ME |
|
110 |
|
|
111 |
try: |
|
112 |
mode, link, mac_prefix, tags = util.values_from_flavor(flavor) |
|
113 |
validate_mac(mac_prefix + "0:00:00:00") |
|
114 |
network = Network.objects.create( |
|
115 |
name=name, |
|
116 |
userid=user_id, |
|
117 |
flavor=flavor, |
|
118 |
mode=mode, |
|
119 |
link=link, |
|
120 |
mac_prefix=mac_prefix, |
|
121 |
tags=tags, |
|
122 |
action='CREATE', |
|
123 |
state='ACTIVE') |
|
124 |
except EmptyPool: |
|
125 |
log.error("Failed to allocate resources for network of type: %s", |
|
126 |
flavor) |
|
127 |
raise api.faults.ServiceUnavailable("Failed to allocate network" |
|
128 |
" resources") |
|
129 |
|
|
130 |
# Issue commission to Quotaholder and accept it since at the end of |
|
131 |
# this transaction the Network object will be created in the DB. |
|
132 |
# Note: the following call does a commit! |
|
133 |
#quotas.issue_and_accept_commission(network) |
|
134 |
# COME BACK.... |
|
135 |
|
|
136 |
except: |
|
137 |
transaction.rollback() |
|
138 |
log.info("roll") |
|
139 |
raise |
|
140 |
else: |
|
141 |
transaction.commit() |
|
142 |
log.info("commit") |
|
143 |
networkdict = network_to_dict(network) |
|
144 |
response = render_network(request, networkdict, status=201) |
|
145 |
|
|
146 |
return response |
|
147 |
|
|
148 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
|
149 |
def get_network(request,network_id): |
|
150 |
log.debug('get_network_details %s', network_id) |
|
151 |
net = get_network_fromdb(network_id, request.user_uniq) |
|
152 |
|
|
153 |
#needs discussion |
|
154 |
if net.deleted: |
|
155 |
raise api.faults.BadRequest("Network has been deleted.") |
|
156 |
else: |
|
157 |
netdict = network_to_dict(net) |
|
158 |
return render_network(request, netdict) |
|
159 |
|
|
160 |
@api.api_method(http_method='DELETE', user_required=True, logger=log) |
|
161 |
@transaction.commit_on_success |
|
162 |
def delete_network(request,network_id): |
|
163 |
log.info('delete_network %s', network_id) |
|
164 |
net = get_network_fromdb(network_id, request.user_uniq, for_update=True) |
|
165 |
|
|
166 |
log.info(net.name) |
|
167 |
if net.public: |
|
168 |
raise api.faults.Forbidden('Can not delete the public network.') |
|
169 |
|
|
170 |
if net.deleted: |
|
171 |
raise api.faults.BadRequest("Network has been deleted.") |
|
172 |
|
|
173 |
if net.machines.all(): # Nics attached on network |
|
174 |
raise api.faults.NetworkInUse('Machines are connected to network.') |
|
175 |
|
|
176 |
net.action = 'DESTROY' |
|
177 |
net.save() |
|
178 |
''' |
|
179 |
skip the backend part... |
|
180 |
backend_networks = net.backend_networks.exclude(operstate="DELETED") |
|
181 |
for bnet in backend_networks: |
|
182 |
backend.delete_network(net, bnet.backend) |
|
183 |
if not backend_networks: |
|
184 |
backend.update_network_state(net) |
|
185 |
''' |
|
186 |
|
|
187 |
#the following has to leave when fix the backend thing |
|
188 |
net.deleted = True |
|
189 |
net.save() |
|
190 |
|
|
191 |
return HttpResponse(status=204) |
|
192 |
|
|
193 |
|
|
194 |
@api.api_method(http_method='PUT', user_required=True, logger=log) |
|
195 |
def update_network(request,network_id): |
|
196 |
''' |
|
197 |
You can update only name and admin_state_up |
|
198 |
''' |
|
199 |
net = get_network_fromdb(network_id, request.user_uniq, for_update=True) |
|
200 |
info = utils.get_request_dict(request) |
|
201 |
|
|
202 |
updatable = set(["name","admin_state_up"]) |
|
203 |
try: |
|
204 |
new_vals = info["network"] |
|
205 |
except Keyerror: |
|
206 |
raise api.faults.BadRequest("Malformed request") |
|
207 |
|
|
208 |
for key,val in new_vals.iteritems(): |
|
209 |
if key in updatable: |
|
210 |
setattr(net,key,val) |
|
211 |
else: |
|
212 |
raise api.faults.BadRequest("Wrong field update") |
|
213 |
net.save() |
|
214 |
#net_dic = network_to_dict(net) |
|
215 |
#data = json.dumps({"network":net_dic}) |
|
216 |
netdict = network_to_dict(net) |
|
217 |
return render_network(request, netdict, 200) |
|
218 |
|
|
219 |
def network_to_dict(network): |
|
220 |
d = {'id': str(network.id), 'name': network.name} |
|
221 |
d['links'] = util.network_to_links(network.id) |
|
222 |
d['user_id'] = network.userid |
|
223 |
d['tenant_id'] = network.userid |
|
224 |
d['type'] = network.flavor |
|
225 |
d['updated'] = utils.isoformat(network.updated) |
|
226 |
d['created'] = utils.isoformat(network.created) |
|
227 |
d['status'] = network.state |
|
228 |
d['public'] = network.public |
|
229 |
d['admin_state_up'] = "true" |
|
230 |
subnet_cidr = [s.subnet_id for s in network.subnet_set.all()] |
|
231 |
d['subnets'] = subnet_cidr |
|
232 |
return d |
|
233 |
|
|
234 |
|
|
235 |
|
|
236 |
|
|
237 |
|
|
238 |
def render_network(request, networkdict, status=200): |
|
239 |
if request.serialization == 'xml': |
|
240 |
data = render_to_string('network.xml', {'network': networkdict}) |
|
241 |
else: |
|
242 |
data = json.dumps({'network': networkdict}) |
|
243 |
return HttpResponse(data, status=status) |
|
244 |
|
|
245 |
|
|
246 |
|
|
247 |
|
|
248 |
def get_network_fromdb(network_id, user_id, for_update=False): |
|
249 |
""" |
|
250 |
Return a Network instance or raise ItemNotFound. |
|
251 |
This is the same as util.get_network |
|
252 |
""" |
|
253 |
try: |
|
254 |
network_id = int(network_id) |
|
255 |
objects = Network.objects |
|
256 |
if for_update: |
|
257 |
objects = objects.select_for_update() |
|
258 |
return objects.get(Q(userid=user_id) | Q(public=True), id=network_id) |
|
259 |
except (ValueError, Network.DoesNotExist): |
|
260 |
raise api.faults.ItemNotFound('Network not found.') |
b/snf-cyclades-app/synnefo/neutron/port_views.py | ||
---|---|---|
1 |
from django.http import HttpResponse |
|
2 |
from django.utils import simplejson as json |
|
3 |
from django.db import transaction |
|
4 |
from django.db.models import Q |
|
5 |
from synnefo.db.pools import EmptyPool |
|
6 |
from synnefo.db.utils import validate_mac |
|
7 |
from django.conf import settings |
|
8 |
from snf_django.lib import api |
|
9 |
from snf_django.lib.api import utils |
|
10 |
from synnefo.logic import backend |
|
11 |
from django.template.loader import render_to_string |
|
12 |
from synnefo.api import util |
|
13 |
from models import NetworkInterface |
|
14 |
|
|
15 |
from logging import getLogger |
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
log = getLogger(__name__) |
|
20 |
|
|
21 |
def demux(request): |
|
22 |
if request.method == 'GET': |
|
23 |
return HttpResponse("list ports") |
|
24 |
#return list_ports(request) |
|
25 |
elif request.method == 'POST': |
|
26 |
#return create_port(request) |
|
27 |
return HttpResponse("create port") |
|
28 |
else: |
|
29 |
return api.api_method_not_allowed(request) |
|
30 |
|
|
31 |
def port_demux(request,offset): |
|
32 |
|
|
33 |
if request.method == 'GET': |
|
34 |
return HttpResponse("get single port") |
|
35 |
#return get_port(request,offset) |
|
36 |
elif request.method == 'DELETE': |
|
37 |
return HttpResponse("delete port") |
|
38 |
#return delete_port(request,offset) |
|
39 |
elif request.method == 'PUT': |
|
40 |
return HttpResponse("put port") |
|
41 |
#return update_port(request,offset) |
|
42 |
else: |
|
43 |
return api.api_method_not_allowed(request) |
|
44 |
""" |
|
45 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
|
46 |
def list_ports(request): |
|
47 |
log.debug('list_networks') |
|
48 |
|
|
49 |
user_networks = Network.objects.filter(Q(userid=request.user_uniq) | |
|
50 |
Q(public=True)) |
|
51 |
|
|
52 |
user_networks = user_networks.filter(deleted=False) |
|
53 |
|
|
54 |
networks = [network_to_dict(network) |
|
55 |
for network in user_networks.order_by('id')] |
|
56 |
|
|
57 |
if request.serialization == 'xml': |
|
58 |
data = render_to_string('list_networks.xml', { |
|
59 |
'networks': networks }) |
|
60 |
else: |
|
61 |
data = json.dumps({'networks': networks}) |
|
62 |
|
|
63 |
return HttpResponse(data, status=200) |
|
64 |
""" |
|
65 |
@api.api_method(http_method='POST', user_required=True, logger=log) |
|
66 |
@transaction.commit_manually |
|
67 |
def create_port (request): |
|
68 |
''' |
|
69 |
''' |
|
70 |
|
|
71 |
try: |
|
72 |
user_id = request.user_uniq |
|
73 |
req = utils.get_request_dict(request) |
|
74 |
log.info('create_network %s', req) |
|
75 |
try: |
|
76 |
d = req['port'] |
|
77 |
name = d['name'] |
|
78 |
except KeyError: |
|
79 |
raise api.faults.BadRequest("Malformed request") |
|
80 |
|
|
81 |
flavor = d.get("type", None) |
|
82 |
if flavor is None: |
|
83 |
#raise faults.BadRequest("Missing request parameter 'type'") |
|
84 |
# set default flavor |
|
85 |
#FIX ME!!! |
|
86 |
flavor="MAC_FILTERED" |
|
87 |
log.info("not found flavor") |
|
88 |
elif flavor not in Network.FLAVORS.keys(): |
|
89 |
raise api.faults.BadRequest("Invalid network type '%s'" % flavor) |
|
90 |
log.info("found flavor 1") |
|
91 |
elif flavor not in settings.API_ENABLED_NETWORK_FLAVORS: |
|
92 |
log.info("found flavor2") |
|
93 |
raise api.faults.Forbidden("Can not create network of type '%s'" %flavor) |
|
94 |
# Get and validate flavor. Flavors are still exposed as 'type' in the |
|
95 |
# API. |
|
96 |
|
|
97 |
try: |
|
98 |
mode, link, mac_prefix, tags = util.values_from_flavor(flavor) |
|
99 |
validate_mac(mac_prefix + "0:00:00:00") |
|
100 |
network = Network.objects.create( |
|
101 |
name=name, |
|
102 |
userid=user_id, |
|
103 |
flavor=flavor, |
|
104 |
mode=mode, |
|
105 |
link=link, |
|
106 |
mac_prefix=mac_prefix, |
|
107 |
tags=tags, |
|
108 |
action='CREATE', |
|
109 |
state='ACTIVE') |
|
110 |
except EmptyPool: |
|
111 |
log.error("Failed to allocate resources for network of type: %s", |
|
112 |
flavor) |
|
113 |
raise api.faults.ServiceUnavailable("Failed to allocate network" |
|
114 |
" resources") |
|
115 |
|
|
116 |
# Issue commission to Quotaholder and accept it since at the end of |
|
117 |
# this transaction the Network object will be created in the DB. |
|
118 |
# Note: the following call does a commit! |
|
119 |
#quotas.issue_and_accept_commission(network) |
|
120 |
# COME BACK.... |
|
121 |
|
|
122 |
except: |
|
123 |
transaction.rollback() |
|
124 |
log.info("roll") |
|
125 |
raise |
|
126 |
else: |
|
127 |
transaction.commit() |
|
128 |
log.info("commit") |
|
129 |
networkdict = network_to_dict(network) |
|
130 |
response = render_network(request, networkdict, status=201) |
|
131 |
|
|
132 |
return response |
|
133 |
|
b/snf-cyclades-app/synnefo/neutron/subnet_views.py | ||
---|---|---|
1 |
from django.http import HttpResponse |
|
2 |
from django.utils import simplejson as json |
|
3 |
|
|
4 |
from snf_django.lib import api |
|
5 |
from snf_django.lib.api import utils |
|
6 |
|
|
7 |
from models import Network |
|
8 |
|
|
9 |
from logging import getLogger |
|
10 |
|
|
11 |
log = getLogger(__name__) |
|
12 |
|
|
13 |
def demux(request): |
|
14 |
if request.method == 'GET': |
|
15 |
return list_networks(request) |
|
16 |
#return HttpResponse("in subnet get") |
|
17 |
elif request.method == 'POST': |
|
18 |
#return create_network(request) |
|
19 |
return HttpResponse("in subnet POST") |
|
20 |
else: |
|
21 |
return api.api_method_not_allowed(request) |
|
22 |
|
|
23 |
def subnet_demux(request,offset): |
|
24 |
if request.method == 'GET': |
|
25 |
#return get_network(request,offset) |
|
26 |
return HttpResponse("in subnet det get") |
|
27 |
elif request.method == 'DELETE': |
|
28 |
#return delete_network(request,offset) |
|
29 |
return HttpResponse("in subnet det delete") |
|
30 |
elif request.method == 'PUT': |
|
31 |
#return update_network(request,offset) |
|
32 |
return HttpResponse("in subnet det put") |
|
33 |
else: |
|
34 |
return api.api_method_not_allowed(request) |
|
35 |
|
|
36 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
|
37 |
def list_networks(request): |
|
38 |
user_networks = Network.objects.all() |
|
39 |
networks_dict = [network_to_dict(net) |
|
40 |
for net in user_networks.order_by('name')] |
|
41 |
data = json.dumps({'networks': networks_dict}) |
|
42 |
return HttpResponse(data, status=200) |
|
43 |
|
|
44 |
@api.api_method(http_method='POST', user_required=True, logger=log) |
|
45 |
def create_network(request): |
|
46 |
dic = utils.get_request_dict(request) |
|
47 |
try: |
|
48 |
net = dic["network"] |
|
49 |
name = net["name"] |
|
50 |
user_id = request.user_uniq |
|
51 |
except KeyError: |
|
52 |
raise api.faults.BadRequest("Wrong data") |
|
53 |
# get more info from the JSON |
|
54 |
new_net = Network(name=name,user_id=user_id) |
|
55 |
new_net.save() |
|
56 |
net_dic = network_to_dict(new_net) |
|
57 |
data = json.dumps({'network':net_dic}) |
|
58 |
return HttpResponse("hello world") |
|
59 |
|
|
60 |
|
|
61 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
|
62 |
def get_network(request,offset): |
|
63 |
try: |
|
64 |
net = Network.objects.get(id=offset) |
|
65 |
except Network.DoesNotExist: |
|
66 |
raise api.faults.ItemNotFound("Network not found") |
|
67 |
|
|
68 |
net_dic = network_to_dict(net) |
|
69 |
data = json.dumps({'network': net_dic}) |
|
70 |
return HttpResponse(data,status=200) |
|
71 |
|
|
72 |
@api.api_method(http_method='DELETE', user_required=True, logger=log) |
|
73 |
def delete_network(request,offset): |
|
74 |
try: |
|
75 |
net = Network.objects.get(id=offset) |
|
76 |
except Network.DoesNotExist: |
|
77 |
raise api.faults.ItemNotFound("Network not found") |
|
78 |
|
|
79 |
net.delete() |
|
80 |
return HttpResponse(status=204) |
|
81 |
|
|
82 |
@api.api_method(http_method='PUT', user_required=True, logger=log) |
|
83 |
def update_network(request,offset): |
|
84 |
try: |
|
85 |
net = Network.objects.get(id=offset) |
|
86 |
except Network.DoesNotExist: |
|
87 |
raise api.faults.ItemNotFound("Network not found") |
|
88 |
info = utils.get_request_dict(request) |
|
89 |
updatable = set(["name","driver"]) |
|
90 |
try: |
|
91 |
new_vals = info["network"] |
|
92 |
except Keyerror: |
|
93 |
raise api.faults.BadRequest() |
|
94 |
|
|
95 |
for key,val in new_vals.iteritems(): |
|
96 |
if key in updatable: |
|
97 |
setattr(net,key,val) |
|
98 |
else: |
|
99 |
raise api.faults.BadRequest() |
|
100 |
net.save() |
|
101 |
net_dic = network_to_dict(net) |
|
102 |
data = json.dumps({"network":net_dic}) |
|
103 |
return HttpResponse(data) |
|
104 |
|
|
105 |
def network_to_dict(net): |
|
106 |
d = dict(name=net.name,driver=net.driver,driver_ops=net.driver_ops,id=net.id,user_id=net.user_id) |
|
107 |
return d |
|
108 |
|
|
109 |
|
b/snf-cyclades-app/synnefo/neutron/tests/__init__.py | ||
---|---|---|
1 |
from synnefo.neutron.tests.api import * |
b/snf-cyclades-app/synnefo/neutron/tests/api.py | ||
---|---|---|
1 |
from snf_django.utils.testing import BaseAPITest |
|
2 |
from django.utils import simplejson as json |
|
3 |
from synnefo.cyclades_settings import cyclades_services |
|
4 |
from synnefo.lib.services import get_service_path |
|
5 |
from synnefo.lib import join_urls |
|
6 |
|
|
7 |
|
|
8 |
NEUTRON_URL = get_service_path(cyclades_services, "neutron", "v2.0") |
|
9 |
NETWORKS_URL = join_urls(NEUTRON_URL, "networks/") |
|
10 |
|
|
11 |
|
|
12 |
class NetworkTest(BaseAPITest): |
|
13 |
def test_list_networks(self): |
|
14 |
response = self.get(NETWORKS_URL) |
|
15 |
self.assertSuccess(response) |
|
16 |
networks = json.loads(response.content) |
|
17 |
self.assertEqual(networks, {'networks': []}) |
|
18 |
|
|
19 |
def test_create_network(self): |
|
20 |
request = {} |
|
21 |
response = self.post(NETWORKS_URL, json.dumps(request), "json") |
|
22 |
code = response.status_code |
|
23 |
self.assertEqual(code, 501) |
|
24 |
|
|
25 |
def test_get_unfound_network(self): |
|
26 |
url = join_urls(NETWORKS_URL,"123") |
|
27 |
response = self.get(url) |
|
28 |
self.assertItemNotFound(response) |
|
29 |
|
|
30 |
def test_delete_unfound_network(self): |
|
31 |
url = join_urls(NETWORKS_URL,"123") |
|
32 |
response = self.delete(url) |
|
33 |
self.assertItemNotFound(response) |
|
34 |
|
|
35 |
|
|
36 |
def marios(self): |
|
37 |
print "hello world" |
b/snf-cyclades-app/synnefo/neutron/urls.py | ||
---|---|---|
1 |
# Copyright 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.urls.defaults import patterns, include |
|
35 |
from django.http import HttpResponseNotAllowed |
|
36 |
from snf_django.lib.api import api_endpoint_not_found |
|
37 |
|
|
38 |
from synnefo.neutron import views |
|
39 |
|
|
40 |
|
|
41 |
|
|
42 |
|
|
43 |
neutron_v2_patterns = patterns('', |
|
44 |
(r'^networks(?:/|.json|.xml)?$', 'synnefo.neutron.network_views.demux'), |
|
45 |
(r'^networks/(\w+)(?:/|.json|.xml)?$', 'synnefo.neutron.network_views.network_demux'), |
|
46 |
(r'^subnets(?:/|.json|.xml)?$', 'synnefo.neutron.subnet_views.demux'), |
|
47 |
(r'^subnets/(\w+)(?:/|.json|.xml)?$', 'synnefo.neutron.subnet_views.subnet_demux'), |
|
48 |
(r'^ports(?:/|.json|.xml)?$', 'synnefo.neutron.port_views.demux'), |
|
49 |
(r'^ports/(\w+)(?:/|.json|.xml)?$', 'synnefo.neutron.port_views.port_demux') |
|
50 |
) |
|
51 |
|
|
52 |
urlpatterns = patterns( |
|
53 |
'', |
|
54 |
(r'^v2.0/', include(neutron_v2_patterns)), |
|
55 |
(r'^.*', api_endpoint_not_found), |
|
56 |
) |
b/snf-cyclades-app/synnefo/neutron/views.py | ||
---|---|---|
1 |
# Copyright 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 |
import json |
|
35 |
|
|
36 |
from logging import getLogger |
|
37 |
#from django.conf import settings |
|
38 |
from django.http import HttpResponse |
|
39 |
|
|
40 |
from snf_django.lib import api |
|
41 |
from snf_django.lib.api import faults |
|
42 |
|
|
43 |
log = getLogger('synnefo.neutron') |
|
44 |
|
|
45 |
|
|
46 |
@api.api_method(http_method="GET", user_required=True, logger=log) |
|
47 |
def list_networks(request, detail=False): |
|
48 |
networks = [] |
|
49 |
data = json.dumps(networks) |
|
50 |
return HttpResponse(data) |
|
51 |
|
|
52 |
|
|
53 |
@api.api_method(http_method="POST", user_required=True, logger=log) |
|
54 |
def add_network(request): |
|
55 |
raise faults.NotImplemented(message="Adding a network is not supported.") |
Also available in: Unified diff