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