blob: 8d71d12fbe0c56d5c73da4bb6b69cf365997c415 [file] [log] [blame]
Scott Baker8e6647a2016-06-20 17:16:20 -07001from django.db import models
2from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool, User
3from core.models.plcorebase import StrippedCharField
4import os
5from django.db import models, transaction
6from django.forms.models import model_to_dict
7from django.db.models import Q
8from operator import itemgetter, attrgetter, methodcaller
9from core.models import Tag
10from core.models.service import LeastLoadedNodeScheduler
11from services.vrouter.models import VRouterService, VRouterTenant
12import traceback
13from xos.exceptions import *
14from xos.config import Config
15
16class ConfigurationError(Exception):
17 pass
18
19VOLT_KIND = "vOLT"
20CORD_SUBSCRIBER_KIND = "CordSubscriberRoot"
21
22CORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
23
24# -------------------------------------------
25# CordSubscriberRoot
26# -------------------------------------------
27
28class CordSubscriberRoot(Subscriber):
29 class Meta:
30 proxy = True
31
32 KIND = CORD_SUBSCRIBER_KIND
33
34 status_choices = (("enabled", "Enabled"),
35 ("suspended", "Suspended"),
36 ("delinquent", "Delinquent"),
37 ("copyrightviolation", "Copyright Violation"))
38
39 # 'simple_attributes' will be expanded into properties and setters that
40 # store the attribute using self.set_attribute / self.get_attribute.
41
42 simple_attributes = ( ("firewall_enable", False),
43 ("firewall_rules", "accept all anywhere anywhere"),
44 ("url_filter_enable", False),
45 ("url_filter_rules", "allow all"),
46 ("url_filter_level", "PG"),
47 ("cdn_enable", False),
48 ("devices", []),
49 ("is_demo_user", False),
50
51 ("uplink_speed", 1000000000), # 1 gigabit, a reasonable default?
52 ("downlink_speed", 1000000000),
53 ("enable_uverse", True) )
54
55 default_attributes = {"status": "enabled"}
56
57 sync_attributes = ("firewall_enable",
58 "firewall_rules",
59 "url_filter_enable",
60 "url_filter_rules",
61 "cdn_enable",
62 "uplink_speed",
63 "downlink_speed",
64 "enable_uverse",
65 "status")
66
67 def __init__(self, *args, **kwargs):
68 super(CordSubscriberRoot, self).__init__(*args, **kwargs)
69 self.cached_volt = None
70 self._initial_url_filter_enable = self.url_filter_enable
71
72 @property
73 def volt(self):
74 volt = self.get_newest_subscribed_tenant(VOLTTenant)
75 if not volt:
76 return None
77
78 # always return the same object when possible
79 if (self.cached_volt) and (self.cached_volt.id == volt.id):
80 return self.cached_volt
81
82 #volt.caller = self.creator
83 self.cached_volt = volt
84 return volt
85
86 @property
87 def status(self):
88 return self.get_attribute("status", self.default_attributes["status"])
89
90 @status.setter
91 def status(self, value):
92 if not value in [x[0] for x in self.status_choices]:
93 raise Exception("invalid status %s" % value)
94 self.set_attribute("status", value)
95
96 def find_device(self, mac):
97 for device in self.devices:
98 if device["mac"] == mac:
99 return device
100 return None
101
102 def update_device(self, mac, **kwargs):
103 # kwargs may be "level" or "mac"
104 # Setting one of these to None will cause None to be stored in the db
105 devices = self.devices
106 for device in devices:
107 if device["mac"] == mac:
108 for arg in kwargs.keys():
109 device[arg] = kwargs[arg]
110 self.devices = devices
111 return device
112 raise ValueError("Device with mac %s not found" % mac)
113
114 def create_device(self, **kwargs):
115 if "mac" not in kwargs:
116 raise XOSMissingField("The mac field is required")
117
118 if self.find_device(kwargs['mac']):
119 raise XOSDuplicateKey("Device with mac %s already exists" % kwargs["mac"])
120
121 device = kwargs.copy()
122
123 devices = self.devices
124 devices.append(device)
125 self.devices = devices
126
127 return device
128
129 def delete_device(self, mac):
130 devices = self.devices
131 for device in devices:
132 if device["mac"]==mac:
133 devices.remove(device)
134 self.devices = devices
135 return
136
137 raise ValueError("Device with mac %s not found" % mac)
138
139 #--------------------------------------------------------------------------
140 # Deprecated -- devices used to be called users
141
142 def find_user(self, uid):
143 return self.find_device(uid)
144
145 def update_user(self, uid, **kwargs):
146 return self.update_device(uid, **kwargs)
147
148 def create_user(self, **kwargs):
149 return self.create_device(**kwargs)
150
151 def delete_user(self, uid):
152 return self.delete_user(uid)
153
154 # ------------------------------------------------------------------------
155
156 @property
157 def services(self):
158 return {"cdn": self.cdn_enable,
159 "url_filter": self.url_filter_enable,
160 "firewall": self.firewall_enable}
161
162 @services.setter
163 def services(self, value):
164 pass
165
166 def save(self, *args, **kwargs):
167 self.validate_unique_service_specific_id(none_okay=True)
168 if (not hasattr(self, 'caller') or not self.caller.is_admin):
169 if (self.has_field_changed("service_specific_id")):
170 raise XOSPermissionDenied("You do not have permission to change service_specific_id")
171 super(CordSubscriberRoot, self).save(*args, **kwargs)
172 if (self.volt) and (self.volt.vcpe): # and (self._initial_url_filter_enabled != self.url_filter_enable):
173 # 1) trigger manage_bbs_account to run
174 # 2) trigger vcpe observer to wake up
175 self.volt.vcpe.save()
176
177CordSubscriberRoot.setup_simple_attributes()
178
179# -------------------------------------------
180# VOLT
181# -------------------------------------------
182
183class VOLTService(Service):
184 KIND = VOLT_KIND
185
186 class Meta:
187 app_label = "volt"
188 verbose_name = "vOLT Service"
189
190class VOLTTenant(Tenant):
191 KIND = VOLT_KIND
192
193 class Meta:
194 app_label = "volt"
195 verbose_name = "vOLT Tenant"
196
197 s_tag = models.IntegerField(null=True, blank=True, help_text="s-tag")
198 c_tag = models.IntegerField(null=True, blank=True, help_text="c-tag")
199
200 # at some point, this should probably end up part of Tenant.
201 creator = models.ForeignKey(User, related_name='created_volts', blank=True, null=True)
202
203 def __init__(self, *args, **kwargs):
204 volt_services = VOLTService.get_service_objects().all()
205 if volt_services:
206 self._meta.get_field("provider_service").default = volt_services[0].id
207 super(VOLTTenant, self).__init__(*args, **kwargs)
208 self.cached_vcpe = None
209
210 @property
211 def vcpe(self):
212 from services.vsg.models import VSGTenant
213 vcpe = self.get_newest_subscribed_tenant(VSGTenant)
214 if not vcpe:
215 return None
216
217 # always return the same object when possible
218 if (self.cached_vcpe) and (self.cached_vcpe.id == vcpe.id):
219 return self.cached_vcpe
220
221 vcpe.caller = self.creator
222 self.cached_vcpe = vcpe
223 return vcpe
224
225 @vcpe.setter
226 def vcpe(self, value):
227 raise XOSConfigurationError("vOLT.vCPE cannot be set this way -- create a new vCPE object and set its subscriber_tenant instead")
228
229 @property
230 def subscriber(self):
231 if not self.subscriber_root:
232 return None
233 subs = CordSubscriberRoot.objects.filter(id=self.subscriber_root.id)
234 if not subs:
235 return None
236 return subs[0]
237
238 def manage_vcpe(self):
239 # Each VOLT object owns exactly one VCPE object
240
241 if self.deleted:
242 return
243
Scott Bakerf6094192017-01-19 16:16:34 -0800244 # Check to see if the wrong s-tag is set. This can only happen if the
245 # user changed the s-tag after the VoltTenant object was created.
246 if self.vcpe and self.vcpe.instance:
247 s_tags = Tag.select_by_content_object(self.vcpe.instance).filter(name="s_tag")
248 if s_tags and (s_tags[0].value != str(self.s_tag)):
249 self.vcpe.delete()
250
Scott Baker8e6647a2016-06-20 17:16:20 -0700251 if self.vcpe is None:
252 from services.vsg.models import VSGService, VSGTenant
253 vsgServices = VSGService.get_service_objects().all()
254 if not vsgServices:
255 raise XOSConfigurationError("No VSG Services available")
256
257 vcpe = VSGTenant(provider_service = vsgServices[0],
258 subscriber_tenant = self)
259 vcpe.caller = self.creator
260 vcpe.save()
261
262 def manage_subscriber(self):
263 if (self.subscriber_root is None):
264 # The vOLT is not connected to a Subscriber, so either find an
265 # existing subscriber with the same SSID, or autogenerate a new
266 # subscriber.
267 #
268 # TODO: This probably goes away when we rethink the ONOS-to-XOS
269 # vOLT API.
270
271 subs = CordSubscriberRoot.get_tenant_objects().filter(service_specific_id = self.service_specific_id)
272 if subs:
273 sub = subs[0]
274 else:
275 sub = CordSubscriberRoot(service_specific_id = self.service_specific_id,
276 name = "autogenerated-for-vOLT-%s" % self.id)
277 sub.save()
278 self.subscriber_root = sub
279 self.save()
280
281 def cleanup_vcpe(self):
282 if self.vcpe:
283 # print "XXX cleanup vcpe", self.vcpe
284 self.vcpe.delete()
285
286 def cleanup_orphans(self):
287 from services.vsg.models import VSGTenant
288 # ensure vOLT only has one vCPE
289 cur_vcpe = self.vcpe
290 for vcpe in list(self.get_subscribed_tenants(VSGTenant)):
291 if (not cur_vcpe) or (vcpe.id != cur_vcpe.id):
292 # print "XXX clean up orphaned vcpe", vcpe
293 vcpe.delete()
294
295 def save(self, *args, **kwargs):
296 # VOLTTenant probably doesn't need a SSID anymore; that will be handled
297 # by CORDSubscriberRoot...
298 # self.validate_unique_service_specific_id()
299
300 if (self.subscriber_root is not None):
301 subs = self.subscriber_root.get_subscribed_tenants(VOLTTenant)
302 if (subs) and (self not in subs):
303 raise XOSDuplicateKey("Subscriber should only be linked to one vOLT")
304
305 if not self.creator:
306 if not getattr(self, "caller", None):
307 # caller must be set when creating a vCPE since it creates a slice
308 raise XOSProgrammingError("VOLTTenant's self.caller was not set")
309 self.creator = self.caller
310 if not self.creator:
311 raise XOSProgrammingError("VOLTTenant's self.creator was not set")
312
313 super(VOLTTenant, self).save(*args, **kwargs)
314 model_policy_volt(self.pk)
315
316 def delete(self, *args, **kwargs):
317 self.cleanup_vcpe()
318 super(VOLTTenant, self).delete(*args, **kwargs)
319
320def model_policy_volt(pk):
321 # TODO: this should be made in to a real model_policy
322 with transaction.atomic():
323 volt = VOLTTenant.objects.select_for_update().filter(pk=pk)
324 if not volt:
325 return
326 volt = volt[0]
327 volt.manage_vcpe()
328 volt.manage_subscriber()
329 volt.cleanup_orphans()
330
331class VOLTDevice(PlCoreBase):
332 class Meta:
333 app_label = "volt"
334
335 name = models.CharField(max_length=254, help_text="name of device", null=False, blank=False)
336 volt_service = models.ForeignKey(VOLTService, related_name='volt_devices')
337 openflow_id = models.CharField(max_length=254, help_text="OpenFlow ID", null=True, blank=True)
338 driver = models.CharField(max_length=254, help_text="driver", null=True, blank=True)
339 access_agent = models.ForeignKey("AccessAgent", related_name='volt_devices', blank=True, null=True)
340
341 def __unicode__(self): return u'%s' % (self.name)
342
343class AccessDevice(PlCoreBase):
344 class Meta:
345 app_label = "volt"
346
347 volt_device = models.ForeignKey(VOLTDevice, related_name='access_devices')
348 uplink = models.IntegerField(null=True, blank=True)
349 vlan = models.IntegerField(null=True, blank=True)
350
351 def __unicode__(self): return u'%s-%d:%d' % (self.volt_device.name,self.uplink,self.vlan)
352
353class AccessAgent(PlCoreBase):
354 class Meta:
355 app_label = "volt"
356
357 name = models.CharField(max_length=254, help_text="name of agent", null=False, blank=False)
358 volt_service = models.ForeignKey(VOLTService, related_name='access_agents')
359 mac = models.CharField(max_length=32, help_text="MAC Address or Access Agent", null=True, blank=True)
360
361 def __unicode__(self): return u'%s' % (self.name)
362
363class AgentPortMapping(PlCoreBase):
364 class Meta:
365 app_label = "volt"
366
367 access_agent = models.ForeignKey(AccessAgent, related_name='port_mappings')
368 mac = models.CharField(max_length=32, help_text="MAC Address", null=True, blank=True)
369 port = models.CharField(max_length=32, help_text="Openflow port ID", null=True, blank=True)
370
371 def __unicode__(self): return u'%s-%s-%s' % (self.access_agent.name, self.port, self.mac)
372
373
374