blob: a0190912cb074b7ebe8cdf6b22f09a8877a3a7e4 [file] [log] [blame]
Scott Baker58a9c7a2013-07-29 15:43:07 -07001import os
2import socket
Scott Bakerbf7bc4a2015-01-06 15:11:29 -08003import sys
Scott Baker58a9c7a2013-07-29 15:43:07 -07004from django.db import models
Scott Baker0cefa532015-11-09 16:17:11 -08005from core.models import PlCoreBase, Site, Slice, Instance, Controller
Tony Mackf3bbe472014-11-30 15:33:35 -05006from core.models import ControllerLinkManager,ControllerLinkDeletionManager
Scott Baker58a9c7a2013-07-29 15:43:07 -07007from django.contrib.contenttypes.models import ContentType
8from django.contrib.contenttypes import generic
Scott Baker198fda12014-10-17 16:22:20 -07009from django.core.exceptions import ValidationError
Scott Baker63efe792015-10-07 22:50:21 -070010from django.db.models import Q
Scott Baker58a9c7a2013-07-29 15:43:07 -070011
12# If true, then IP addresses will be allocated by the model. If false, then
13# we will assume the observer handles it.
Scott Baker026bfe72013-07-29 16:03:50 -070014NO_OBSERVER=False
Scott Baker58a9c7a2013-07-29 15:43:07 -070015
Scott Baker198fda12014-10-17 16:22:20 -070016def ParseNatList(ports):
17 """ Support a list of ports in the format "protocol:port, protocol:port, ..."
18 examples:
19 tcp 123
20 tcp 123:133
21 tcp 123, tcp 124, tcp 125, udp 201, udp 202
22
23 User can put either a "/" or a " " between protocol and ports
24 Port ranges can be specified with "-" or ":"
25 """
26 nats = []
27 if ports:
28 parts = ports.split(",")
29 for part in parts:
30 part = part.strip()
31 if "/" in part:
32 (protocol, ports) = part.split("/",1)
33 elif " " in part:
34 (protocol, ports) = part.split(None,1)
35 else:
36 raise TypeError('malformed port specifier %s, format example: "tcp 123, tcp 201:206, udp 333"' % part)
37
38 protocol = protocol.strip()
39 ports = ports.strip()
40
41 if not (protocol in ["udp", "tcp"]):
42 raise ValueError('unknown protocol %s' % protocol)
43
44 if "-" in ports:
45 (first, last) = ports.split("-")
46 first = int(first.strip())
47 last = int(last.strip())
48 portStr = "%d:%d" % (first, last)
49 elif ":" in ports:
50 (first, last) = ports.split(":")
51 first = int(first.strip())
52 last = int(last.strip())
53 portStr = "%d:%d" % (first, last)
54 else:
55 portStr = "%d" % int(ports)
56
57 nats.append( {"l4_protocol": protocol, "l4_port": portStr} )
58
59 return nats
60
61def ValidateNatList(ports):
62 try:
63 ParseNatList(ports)
64 except Exception,e:
65 raise ValidationError(str(e))
66
Scott Baker7db8f7c2015-11-16 15:07:43 -080067class ParameterMixin(object):
68 # helper classes for dealing with NetworkParameter
69
70 def get_parameters(self):
71 parameter_dict = {}
72
73 instance_type = ContentType.objects.get_for_model(self)
74 for param in NetworkParameter.objects.filter(content_type__pk=instance_type.id, object_id=self.id):
75 parameter_dict[param.parameter.name] = param.value
76
77 return parameter_dict
78
79 def set_parameter(self, name, value):
80 instance_type = ContentType.objects.get_for_model(self)
81 existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
82 if existing_params:
83 p=existing_params[0]
84 p.value = value
85 p.save()
86 else:
87 pt = NetworkParameterType.objects.get(name=name)
88 p = NetworkParameter(parameter=pt, content_type=instance_type, object_id=self.id, value=value)
89 p.save()
90
91 def unset_parameter(self, name):
92 instance_type = ContentType.objects.get_for_model(self)
93 existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
94 for p in existing_params:
95 p.delete()
96
97
98class NetworkTemplate(PlCoreBase, ParameterMixin):
Scott Baker58a9c7a2013-07-29 15:43:07 -070099 VISIBILITY_CHOICES = (('public', 'public'), ('private', 'private'))
Scott Baker87e5e092013-08-07 18:58:10 -0700100 TRANSLATION_CHOICES = (('none', 'none'), ('NAT', 'NAT'))
Scott Bakerf2e0cfc2014-11-17 16:03:49 -0800101 TOPOLOGY_CHOICES = (('bigswitch', 'BigSwitch'), ('physical', 'Physical'), ('custom', 'Custom'))
102 CONTROLLER_CHOICES = ((None, 'None'), ('onos', 'ONOS'), ('custom', 'Custom'))
Scott Baker9d5bf3b2015-12-09 15:44:55 -0800103 ACCESS_CHOICES = ((None, 'None'), ('indirect', 'Indirect'), ('direct', 'Direct'))
Scott Baker58a9c7a2013-07-29 15:43:07 -0700104
105 name = models.CharField(max_length=32)
106 description = models.CharField(max_length=1024, blank=True, null=True)
Scott Baker81fa17f2015-01-03 12:03:38 -0800107 guaranteed_bandwidth = models.IntegerField(default=0)
Scott Baker58a9c7a2013-07-29 15:43:07 -0700108 visibility = models.CharField(max_length=30, choices=VISIBILITY_CHOICES, default="private")
Scott Baker87e5e092013-08-07 18:58:10 -0700109 translation = models.CharField(max_length=30, choices=TRANSLATION_CHOICES, default="none")
Scott Baker9d5bf3b2015-12-09 15:44:55 -0800110 access = models.CharField(max_length=30, null=True, blank=True, choices=ACCESS_CHOICES, help_text="Advertise this network as a means for other slices to contact this slice")
Scott Baker81fa17f2015-01-03 12:03:38 -0800111 shared_network_name = models.CharField(max_length=30, blank=True, null=True)
112 shared_network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
Scott Baker55aa2652015-12-09 16:31:26 -0800113 topology_kind = models.CharField(null=False, blank=False, max_length=30, choices=TOPOLOGY_CHOICES, default="bigswitch")
Scott Baker81fa17f2015-01-03 12:03:38 -0800114 controller_kind = models.CharField(null=True, blank=True, max_length=30, choices=CONTROLLER_CHOICES, default=None)
Scott Baker58a9c7a2013-07-29 15:43:07 -0700115
Scott Bakera3134fe2014-12-23 10:56:06 -0800116 def __init__(self, *args, **kwargs):
117 super(NetworkTemplate, self).__init__(*args, **kwargs)
118
119 # somehow these got set wrong inside of the live database. Remove this
120 # code after all is well...
Scott Baker81fa17f2015-01-03 12:03:38 -0800121 if (self.topology_kind=="BigSwitch"):
Scott Bakerbf7bc4a2015-01-06 15:11:29 -0800122 print >> sys.stderr, "XXX warning: topology_kind invalid case"
Scott Baker81fa17f2015-01-03 12:03:38 -0800123 self.topology_kind="bigswitch"
124 elif (self.topology_kind=="Physical"):
Scott Bakerbf7bc4a2015-01-06 15:11:29 -0800125 print >> sys.stderr, "XXX warning: topology_kind invalid case"
Scott Baker81fa17f2015-01-03 12:03:38 -0800126 self.topology_kind="physical"
127 elif (self.topology_kind=="Custom"):
Scott Bakerbf7bc4a2015-01-06 15:11:29 -0800128 print >> sys.stderr, "XXX warning: topology_kind invalid case"
Scott Baker81fa17f2015-01-03 12:03:38 -0800129 self.toplogy_kind="custom"
Scott Bakera3134fe2014-12-23 10:56:06 -0800130
Scott Baker55aa2652015-12-09 16:31:26 -0800131 def save(self, *args, **kwargs):
132 self.enforce_choices(self.access, self.ACCESS_CHOICES)
133 super(NetworkTemplate, self).save(*args, **kwargs)
134
Scott Baker58a9c7a2013-07-29 15:43:07 -0700135 def __unicode__(self): return u'%s' % (self.name)
136
Scott Baker7db8f7c2015-11-16 15:07:43 -0800137class Network(PlCoreBase, ParameterMixin):
Scott Baker58a9c7a2013-07-29 15:43:07 -0700138 name = models.CharField(max_length=32)
139 template = models.ForeignKey(NetworkTemplate)
140 subnet = models.CharField(max_length=32, blank=True)
Scott Baker198fda12014-10-17 16:22:20 -0700141 ports = models.CharField(max_length=1024, blank=True, null=True, validators=[ValidateNatList])
Scott Baker58a9c7a2013-07-29 15:43:07 -0700142 labels = models.CharField(max_length=1024, blank=True, null=True)
Siobhan Tullyce652d02013-10-08 21:52:35 -0400143 owner = models.ForeignKey(Slice, related_name="ownedNetworks", help_text="Slice that owns control of this Network")
Scott Baker58a9c7a2013-07-29 15:43:07 -0700144
Scott Baker0451fb62015-01-03 12:29:29 -0800145 guaranteed_bandwidth = models.IntegerField(default=0)
146 permit_all_slices = models.BooleanField(default=False)
147 permitted_slices = models.ManyToManyField(Slice, blank=True, related_name="availableNetworks")
Scott Baker87191e72013-08-06 08:55:07 -0700148 slices = models.ManyToManyField(Slice, blank=True, related_name="networks", through="NetworkSlice")
Scott Baker93683232015-09-14 11:41:05 -0700149 instances = models.ManyToManyField(Instance, blank=True, related_name="networks", through="Port")
Scott Baker58a9c7a2013-07-29 15:43:07 -0700150
Scott Baker0451fb62015-01-03 12:29:29 -0800151 topology_parameters = models.TextField(null=True, blank=True)
152 controller_url = models.CharField(null=True, blank=True, max_length=1024)
153 controller_parameters = models.TextField(null=True, blank=True)
Scott Bakerf2e0cfc2014-11-17 16:03:49 -0800154
Scott Baker87191e72013-08-06 08:55:07 -0700155 # for observer/manager
156 network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
157 router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
158 subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
159
Scott Bakera4c11bd2015-08-21 16:40:53 -0700160 autoconnect = models.BooleanField(default=True, help_text="This network can be autoconnected to the slice that owns it")
161
Scott Baker58a9c7a2013-07-29 15:43:07 -0700162 def __unicode__(self): return u'%s' % (self.name)
163
164 def save(self, *args, **kwds):
165 if (not self.subnet) and (NO_OBSERVER):
166 from util.network_subnet_allocator import find_unused_subnet
167 self.subnet = find_unused_subnet(existing_subnets=[x.subnet for x in Network.objects.all()])
168 super(Network, self).save(*args, **kwds)
169
Tony Mack5b061472014-02-04 07:57:10 -0500170 def can_update(self, user):
Tony Mack5ff90fc2015-02-08 21:38:41 -0500171 return user.can_update_slice(self.owner)
Tony Mack5b061472014-02-04 07:57:10 -0500172
Scott Baker5bbaa232014-08-14 17:23:15 -0700173 @property
174 def nat_list(self):
Scott Baker198fda12014-10-17 16:22:20 -0700175 return ParseNatList(self.ports)
Scott Baker5bbaa232014-08-14 17:23:15 -0700176
Tony Mack5b061472014-02-04 07:57:10 -0500177 @staticmethod
178 def select_by_user(user):
179 if user.is_admin:
180 qs = Network.objects.all()
181 else:
Tony Mack5efa1332014-04-02 15:45:48 -0400182 slices = Slice.select_by_user(user)
183 #slice_ids = [s.id for s in Slice.select_by_user(user)]
184 qs = Network.objects.filter(owner__in=slices)
Tony Mack5b061472014-02-04 07:57:10 -0500185 return qs
186
Scott Baker7db8f7c2015-11-16 15:07:43 -0800187 def get_parameters(self):
188 # returns parameters from the template, updated by self.
189 p={}
190 if self.template:
191 p = self.template.get_parameters()
192 p.update(ParameterMixin.get_parameters(self))
193 return p
194
Tony Macka7dbd422015-01-05 22:48:11 -0500195class ControllerNetwork(PlCoreBase):
Tony Mackf3bbe472014-11-30 15:33:35 -0500196 objects = ControllerLinkManager()
197 deleted_objects = ControllerLinkDeletionManager()
Sapan Bhatia6df56512014-09-22 14:52:59 -0400198
Tony Mackf3bbe472014-11-30 15:33:35 -0500199 # Stores the openstack ids at various controllers
200 network = models.ForeignKey(Network, related_name='controllernetworks')
201 controller = models.ForeignKey(Controller, related_name='controllernetworks')
Tony Mack457c84c2014-04-08 16:37:56 -0400202 net_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
Tony Macke9b08692014-04-07 19:38:28 -0400203 router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
Scott Baker95d81c72014-08-12 18:29:27 -0700204 subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
205 subnet = models.CharField(max_length=32, blank=True)
Scott Bakerda513652015-10-26 15:12:13 -0700206
Tony Mack549f7b12015-04-11 12:17:59 -0400207 class Meta:
208 unique_together = ('network', 'controller')
Scott Baker7db8f7c2015-11-16 15:07:43 -0800209
Tony Macke9b08692014-04-07 19:38:28 -0400210 @staticmethod
211 def select_by_user(user):
212 if user.is_admin:
Scott Baker0da79c52015-02-23 17:22:33 -0800213 qs = ControllerNetwork.objects.all()
Tony Macke9b08692014-04-07 19:38:28 -0400214 else:
215 slices = Slice.select_by_user(user)
216 networks = Network.objects.filter(owner__in=slices)
Scott Baker0da79c52015-02-23 17:22:33 -0800217 qs = ControllerNetwork.objects.filter(network__in=networks)
Scott Baker95d81c72014-08-12 18:29:27 -0700218 return qs
Tony Macke9b08692014-04-07 19:38:28 -0400219
Scott Baker87191e72013-08-06 08:55:07 -0700220class NetworkSlice(PlCoreBase):
221 # This object exists solely so we can implement the permission check when
222 # adding slices to networks. It adds no additional fields to the relation.
223
Sapan Bhatia13d2db92014-11-11 21:47:45 -0500224 network = models.ForeignKey(Network,related_name='networkslices')
225 slice = models.ForeignKey(Slice,related_name='networkslices')
Scott Baker87191e72013-08-06 08:55:07 -0700226
Tony Mack549f7b12015-04-11 12:17:59 -0400227 class Meta:
228 unique_together = ('network', 'slice')
Tony Macka4736f52015-03-09 17:13:14 -0400229
Scott Baker87191e72013-08-06 08:55:07 -0700230 def save(self, *args, **kwds):
231 slice = self.slice
Scott Bakerf1b474b2015-04-13 17:23:28 -0700232 if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
Tony Mack3de59e32015-08-19 11:58:18 -0400233 # to add a instance to the network, then one of the following must be true:
234 # 1) instance's slice is in network's permittedSlices list,
235 # 2) instance's slice is network's owner, or
Scott Baker87191e72013-08-06 08:55:07 -0700236 # 3) network's permitAllSlices is true
237 raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
238
239 super(NetworkSlice, self).save(*args, **kwds)
240
241 def __unicode__(self): return u'%s-%s' % (self.network.name, self.slice.name)
242
Tony Mack5b061472014-02-04 07:57:10 -0500243 def can_update(self, user):
Tony Mack5ff90fc2015-02-08 21:38:41 -0500244 return user.can_update_slice(self.slice)
Tony Mack5b061472014-02-04 07:57:10 -0500245
Tony Mack5b061472014-02-04 07:57:10 -0500246 @staticmethod
247 def select_by_user(user):
248 if user.is_admin:
249 qs = NetworkSlice.objects.all()
250 else:
251 slice_ids = [s.id for s in Slice.select_by_user(user)]
Scott Baker63efe792015-10-07 22:50:21 -0700252 network_ids = [network.id for network in Network.select_by_user(user)]
253 qs = NetworkSlice.objects.filter(Q(slice__in=slice_ids) | Q(network__in=network_ids))
Tony Mack5b061472014-02-04 07:57:10 -0500254 return qs
255
Scott Baker7db8f7c2015-11-16 15:07:43 -0800256class Port(PlCoreBase, ParameterMixin):
Scott Baker93683232015-09-14 11:41:05 -0700257 network = models.ForeignKey(Network,related_name='links')
258 instance = models.ForeignKey(Instance, null=True, blank=True, related_name='ports')
Tony Mack3de59e32015-08-19 11:58:18 -0400259 ip = models.GenericIPAddressField(help_text="Instance ip address", blank=True, null=True)
Scott Baker4c3ea372015-11-16 20:15:41 -0800260 port_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron port id")
Scott Baker0672e982015-09-08 18:22:15 -0700261 mac = models.CharField(null=True, blank=True, max_length=256, help_text="MAC address associated with this port")
Scott Baker58a9c7a2013-07-29 15:43:07 -0700262
Tony Mack549f7b12015-04-11 12:17:59 -0400263 class Meta:
Tony Mack3de59e32015-08-19 11:58:18 -0400264 unique_together = ('network', 'instance')
Tony Macka4736f52015-03-09 17:13:14 -0400265
Scott Baker58a9c7a2013-07-29 15:43:07 -0700266 def save(self, *args, **kwds):
Tony Mack3de59e32015-08-19 11:58:18 -0400267 if self.instance:
268 slice = self.instance.slice
Scott Bakerdeb5f7b2015-08-18 17:04:01 -0700269 if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
Tony Mack3de59e32015-08-19 11:58:18 -0400270 # to add a instance to the network, then one of the following must be true:
271 # 1) instance's slice is in network's permittedSlices list,
272 # 2) instance's slice is network's owner, or
Scott Bakerdeb5f7b2015-08-18 17:04:01 -0700273 # 3) network's permitAllSlices is true
274 raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
275
Scott Baker93683232015-09-14 11:41:05 -0700276 super(Port, self).save(*args, **kwds)
Scott Baker58a9c7a2013-07-29 15:43:07 -0700277
Scott Bakerdeb5f7b2015-08-18 17:04:01 -0700278 def __unicode__(self):
Tony Mack3de59e32015-08-19 11:58:18 -0400279 if self.instance:
280 return u'%s-%s' % (self.network.name, self.instance.instance_name)
Scott Bakerdeb5f7b2015-08-18 17:04:01 -0700281 else:
Scott Bakerac4516e2015-08-25 23:24:36 -0700282 return u'%s-unboundport-%s' % (self.network.name, self.id)
Scott Baker58a9c7a2013-07-29 15:43:07 -0700283
Tony Mack5b061472014-02-04 07:57:10 -0500284 def can_update(self, user):
Tony Mack3de59e32015-08-19 11:58:18 -0400285 if self.instance:
286 return user.can_update_slice(self.instance.slice)
Scott Bakerdeb5f7b2015-08-18 17:04:01 -0700287 if self.network:
288 return user.can_update_slice(self.network.owner)
289 return False
Tony Mack5b061472014-02-04 07:57:10 -0500290
Tony Mack5b061472014-02-04 07:57:10 -0500291 @staticmethod
292 def select_by_user(user):
293 if user.is_admin:
Scott Baker93683232015-09-14 11:41:05 -0700294 qs = Port.objects.all()
Tony Mack5b061472014-02-04 07:57:10 -0500295 else:
Scott Baker63efe792015-10-07 22:50:21 -0700296 instances = Instance.select_by_user(user)
297 instance_ids = [instance.id for instance in instances]
298 networks = Network.select_by_user(user)
299 network_ids = [network.id for network in networks]
300 qs = Port.objects.filter(Q(instance__in=instance_ids) | Q(network__in=network_ids))
Tony Mack5b061472014-02-04 07:57:10 -0500301 return qs
302
Scott Baker7db8f7c2015-11-16 15:07:43 -0800303 def get_parameters(self):
304 # returns parameters from the network, updated by self.
305 p={}
306 if self.network:
307 p = self.network.get_parameters()
308 p.update(ParameterMixin.get_parameters(self))
309 return p
310
Scott Baker58a9c7a2013-07-29 15:43:07 -0700311class Router(PlCoreBase):
312 name = models.CharField(max_length=32)
313 owner = models.ForeignKey(Slice, related_name="routers")
314 permittedNetworks = models.ManyToManyField(Network, blank=True, related_name="availableRouters")
315 networks = models.ManyToManyField(Network, blank=True, related_name="routers")
316
317 def __unicode__(self): return u'%s' % (self.name)
318
Tony Mack5ff90fc2015-02-08 21:38:41 -0500319 def can_update(self, user):
320 return user.can_update_slice(self.owner)
321
Scott Baker58a9c7a2013-07-29 15:43:07 -0700322class NetworkParameterType(PlCoreBase):
323 name = models.SlugField(help_text="The name of this parameter", max_length=128)
324 description = models.CharField(max_length=1024)
325
326 def __unicode__(self): return u'%s' % (self.name)
327
328class NetworkParameter(PlCoreBase):
Sapan Bhatia13d2db92014-11-11 21:47:45 -0500329 parameter = models.ForeignKey(NetworkParameterType, related_name="networkparameters", help_text="The type of the parameter")
Scott Baker58a9c7a2013-07-29 15:43:07 -0700330 value = models.CharField(help_text="The value of this parameter", max_length=1024)
331
332 # The required fields to do a ObjectType lookup, and object_id assignment
333 content_type = models.ForeignKey(ContentType)
334 object_id = models.PositiveIntegerField()
335 content_object = generic.GenericForeignKey('content_type', 'object_id')
336
337 def __unicode__(self):
338 return self.parameter.name
339
340