root / snf-cyclades-app / synnefo / neutron / models.py @ d6b24130
History | View | Annotate | Download (10.4 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 |
|
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()
|