root / snf-cyclades-app / synnefo / neutron / models.py @ 0dae1b9f
History | View | Annotate | Download (10.3 kB)
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 |
class Network(models.Model): |
55 |
OPER_STATES = ( |
56 |
('PENDING', 'Pending'), # Unused because of lazy networks |
57 |
('ACTIVE', 'Active'), |
58 |
('DELETED', 'Deleted'), |
59 |
('ERROR', 'Error') |
60 |
) |
61 |
|
62 |
ACTIONS = ( |
63 |
('CREATE', 'Create Network'), |
64 |
('DESTROY', 'Destroy Network'), |
65 |
('ADD', 'Add server to Network'), |
66 |
('REMOVE', 'Remove server from Network'), |
67 |
) |
68 |
|
69 |
RSAPI_STATE_FROM_OPER_STATE = { |
70 |
'PENDING': 'PENDING', |
71 |
'ACTIVE': 'ACTIVE', |
72 |
'DELETED': 'DELETED', |
73 |
'ERROR': 'ERROR' |
74 |
} |
75 |
|
76 |
FLAVORS = { |
77 |
'CUSTOM': {
|
78 |
'mode': 'bridged', |
79 |
'link': settings.DEFAULT_BRIDGE,
|
80 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX,
|
81 |
'tags': None, |
82 |
'desc': "Basic flavor used for a bridged network", |
83 |
}, |
84 |
'IP_LESS_ROUTED': {
|
85 |
'mode': 'routed', |
86 |
'link': settings.DEFAULT_ROUTING_TABLE,
|
87 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX,
|
88 |
'tags': 'ip-less-routed', |
89 |
'desc': "Flavor used for an IP-less routed network using" |
90 |
" Proxy ARP",
|
91 |
}, |
92 |
'MAC_FILTERED': {
|
93 |
'mode': 'bridged', |
94 |
'link': settings.DEFAULT_MAC_FILTERED_BRIDGE,
|
95 |
'mac_prefix': 'pool', |
96 |
'tags': 'private-filtered', |
97 |
'desc': "Flavor used for bridged networks that offer isolation" |
98 |
" via filtering packets based on their src "
|
99 |
" MAC (ebtables)",
|
100 |
}, |
101 |
'PHYSICAL_VLAN': {
|
102 |
'mode': 'bridged', |
103 |
'link': 'pool', |
104 |
'mac_prefix': settings.DEFAULT_MAC_PREFIX,
|
105 |
'tags': 'physical-vlan', |
106 |
'desc': "Flavor used for bridged network that offer isolation" |
107 |
" via dedicated physical vlan",
|
108 |
}, |
109 |
} |
110 |
|
111 |
name = models.CharField('Network Name', max_length=128) |
112 |
userid = models.CharField('User ID of the owner', max_length=128, |
113 |
null=True, db_index=True) |
114 |
flavor = models.CharField('Flavor', max_length=32, null=False) |
115 |
mode = models.CharField('Network Mode', max_length=16, null=True) |
116 |
link = models.CharField('Network Link', max_length=32, null=True) |
117 |
mac_prefix = models.CharField('MAC Prefix', max_length=32, null=False) |
118 |
tags = models.CharField('Network Tags', max_length=128, null=True) |
119 |
public = models.BooleanField(default=False, db_index=True) |
120 |
created = models.DateTimeField(auto_now_add=True)
|
121 |
updated = models.DateTimeField(auto_now=True)
|
122 |
deleted = models.BooleanField('Deleted', default=False, db_index=True) |
123 |
state = models.CharField(choices=OPER_STATES, max_length=32,
|
124 |
default='PENDING')
|
125 |
machines = models.ManyToManyField(VirtualMachine, |
126 |
through='NetworkInterface',
|
127 |
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) |
131 |
# this is the opposite of admin_state_up
|
132 |
floating_ip_pool = models.BooleanField('Floating IP Pool', null=False, |
133 |
default=False)
|
134 |
#serial = models.ForeignKey(QuotaHolderSerial, related_name='network',
|
135 |
# null=True)
|
136 |
|
137 |
objects = ForUpdateManager() |
138 |
|
139 |
def __unicode__(self): |
140 |
return "<Network: %s>" % str(self.id) |
141 |
|
142 |
@property
|
143 |
def backend_id(self): |
144 |
"""Return the backend id by prepending backend-prefix."""
|
145 |
if not self.id: |
146 |
raise Network.InvalidBackendIdError("self.id is None") |
147 |
return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id)) |
148 |
|
149 |
@property
|
150 |
def backend_tag(self): |
151 |
"""Return the network tag to be used in backend
|
152 |
|
153 |
"""
|
154 |
if self.tags: |
155 |
return self.tags.split(',') |
156 |
else:
|
157 |
return []
|
158 |
|
159 |
def create_backend_network(self, backend=None): |
160 |
"""Create corresponding BackendNetwork entries."""
|
161 |
|
162 |
backends = [backend] if backend else\ |
163 |
Backend.objects.filter(offline=False)
|
164 |
for backend in backends: |
165 |
backend_exists =\ |
166 |
BackendNetwork.objects.filter(backend=backend, network=self)\
|
167 |
.exists() |
168 |
if not backend_exists: |
169 |
BackendNetwork.objects.create(backend=backend, network=self)
|
170 |
|
171 |
def get_pool(self, with_lock=True): |
172 |
if not self.pool_id: |
173 |
self.pool = IPPoolTable.objects.create(available_map='', |
174 |
reserved_map='',
|
175 |
size=0)
|
176 |
self.save()
|
177 |
objects = IPPoolTable.objects |
178 |
if with_lock:
|
179 |
objects = objects.select_for_update() |
180 |
return objects.get(id=self.pool_id).pool |
181 |
|
182 |
def reserve_address(self, address): |
183 |
pool = self.get_pool()
|
184 |
pool.reserve(address) |
185 |
pool.save() |
186 |
|
187 |
def release_address(self, address): |
188 |
pool = self.get_pool()
|
189 |
pool.put(address) |
190 |
pool.save() |
191 |
|
192 |
class InvalidBackendIdError(Exception): |
193 |
def __init__(self, value): |
194 |
self.value = value
|
195 |
|
196 |
def __str__(self): |
197 |
return repr(self.value) |
198 |
|
199 |
class InvalidBackendMsgError(Exception): |
200 |
def __init__(self, opcode, status): |
201 |
self.opcode = opcode
|
202 |
self.status = status
|
203 |
|
204 |
def __str__(self): |
205 |
return repr('<opcode: %s, status: %s>' |
206 |
% (self.opcode, self.status)) |
207 |
|
208 |
class InvalidActionError(Exception): |
209 |
def __init__(self, action): |
210 |
self._action = action
|
211 |
|
212 |
def __str__(self): |
213 |
return repr(str(self._action)) |
214 |
|
215 |
|
216 |
class Subnet(models.Model): |
217 |
SUBNET_NAME_LENGTH = 128
|
218 |
|
219 |
network = models.ForeignKey('Network')
|
220 |
name = models.CharField('Network Name', max_length=SUBNET_NAME_LENGTH,
|
221 |
null=True)
|
222 |
ipversion = models.IntegerField('IP Version', default=4) |
223 |
cidr = models.CharField('Subnet', max_length=32, null=True) |
224 |
gateway = models.CharField('Gateway', max_length=32, null=True) |
225 |
dhcp = models.BooleanField('DHCP', default=True) |
226 |
|
227 |
# Synnefo related fields
|
228 |
# subnet6 will be null for IPv4 only networks
|
229 |
#pool = models.OneToOneField('IPPoolTable', related_name='network',
|
230 |
# default=lambda: IPPoolTable.objects.create(
|
231 |
# available_map='',
|
232 |
# reserved_map='',
|
233 |
# size=0),
|
234 |
# null=True)
|
235 |
|
236 |
def __unicode__(self): |
237 |
return "<Subnet %s>" % str(self.id) |
238 |
|
239 |
|
240 |
class NetworkInterface(models.Model): |
241 |
STATES = ( |
242 |
("ACTIVE", "Active"), |
243 |
("BUILDING", "Building"), |
244 |
) |
245 |
|
246 |
name = models.CharField('Network Name', max_length=128) |
247 |
machine = models.ForeignKey(VirtualMachine, related_name='neutron_nics')
|
248 |
network = models.ForeignKey(Network, related_name='neutron_nics')
|
249 |
created = models.DateTimeField(auto_now_add=True)
|
250 |
updated = models.DateTimeField(auto_now=True)
|
251 |
index = models.IntegerField(null=True)
|
252 |
mac = models.CharField(max_length=32, null=True, unique=True) |
253 |
ipv4 = models.CharField(max_length=15, null=True) |
254 |
ipv6 = models.CharField(max_length=100, null=True) |
255 |
#firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
|
256 |
# max_length=30, null=True)
|
257 |
dirty = models.BooleanField(default=False)
|
258 |
state = models.CharField(max_length=32, null=False, default="ACTIVE", |
259 |
choices=STATES) |
260 |
admin_state_up = models.BooleanField(default=False, db_index=True) |
261 |
|
262 |
def __unicode__(self): |
263 |
return "<%s:vm:%s network:%s ipv4:%s ipv6:%s>" % \ |
264 |
(self.index, self.machine_id, self.network_id, self.ipv4, |
265 |
self.ipv6)
|
266 |
|
267 |
@property
|
268 |
def is_floating_ip(self): |
269 |
network = self.network
|
270 |
if self.ipv4 and network.floating_ip_pool: |
271 |
return network.floating_ips.filter(machine=self.machine, |
272 |
ipv4=self.ipv4,
|
273 |
deleted=False).exists()
|