blob: 7adc4cc5e0e172d9fb6a62645a3dda1bb6cf0781 [file] [log] [blame]
Scott Bakere9ff7ce2015-04-14 17:19:16 -07001from django.db import models
Scott Bakere6780e92016-02-13 14:38:43 -08002from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool
Scott Bakere9ff7ce2015-04-14 17:19:16 -07003from core.models.plcorebase import StrippedCharField
4import os
Scott Baker192da222015-07-08 19:01:56 -07005from django.db import models, transaction
Scott Bakere9ff7ce2015-04-14 17:19:16 -07006from django.forms.models import model_to_dict
7from django.db.models import Q
8from operator import itemgetter, attrgetter, methodcaller
Scott Baker5b8eb412016-02-02 16:56:18 -08009from core.models import Tag
10from core.models.service import LeastLoadedNodeScheduler
Scott Bakerfa5cdd62015-04-20 09:16:17 -070011import traceback
Scott Baker7f8ef8f2015-04-20 14:24:29 -070012from xos.exceptions import *
Scott Bakere6780e92016-02-13 14:38:43 -080013from xos.config import Config
Scott Bakere9ff7ce2015-04-14 17:19:16 -070014
Scott Bakere9ff7ce2015-04-14 17:19:16 -070015class ConfigurationError(Exception):
16 pass
17
Scott Bakerd9133342015-07-06 14:38:02 -070018VOLT_KIND = "vOLT"
19VCPE_KIND = "vCPE"
20VBNG_KIND = "vBNG"
Scott Baker81de6402015-07-06 16:50:30 -070021CORD_SUBSCRIBER_KIND = "CordSubscriberRoot"
22
Scott Bakere6780e92016-02-13 14:38:43 -080023CORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
24
Scott Baker81de6402015-07-06 16:50:30 -070025# -------------------------------------------
26# CordSubscriberRoot
27# -------------------------------------------
28
29class CordSubscriberRoot(Subscriber):
Scott Bakere4364d12015-07-06 17:21:21 -070030 class Meta:
31 proxy = True
32
Scott Baker81de6402015-07-06 16:50:30 -070033 KIND = CORD_SUBSCRIBER_KIND
34
Scott Baker669212f2016-03-01 20:14:35 -080035 status_choices = (("enabled", "Enabled"),
36 ("suspended", "Suspended"),
37 ("delinquent", "Delinquent"),
38 ("copyrightviolation", "Copyright Violation"))
39
40 # 'simple_attributes' will be expanded into properties and setters that
41 # store the attribute using self.set_attribute / self.get_attribute.
42
43 simple_attributes = ( ("firewall_enable", False),
44 ("firewall_rules", "accept all anywhere anywhere"),
45 ("url_filter_enable", False),
46 ("url_filter_rules", "allow all"),
47 ("url_filter_level", "PG"),
48 ("cdn_enable", False),
49 ("users", []),
50 ("is_demo_user", False),
51
Matteoc1018542016-03-02 09:33:20 -080052 ("uplink_speed", 1000000000), # 1 gigabit, a reasonable default?
53 ("downlink_speed", 1000000000),
Scott Baker669212f2016-03-01 20:14:35 -080054 ("enable_uverse", True) )
55
56 default_attributes = {"status": "enabled"}
Scott Baker81de6402015-07-06 16:50:30 -070057
Scott Baker050f8b32015-07-07 12:15:03 -070058 sync_attributes = ("firewall_enable",
59 "firewall_rules",
60 "url_filter_enable",
61 "url_filter_rules",
Scott Bakere98eea82016-03-01 20:44:40 -080062 "cdn_enable",
63 "uplink_speed",
64 "downlink_speed",
65 "enable_uverse",
66 "status")
Scott Baker050f8b32015-07-07 12:15:03 -070067
Scott Baker81de6402015-07-06 16:50:30 -070068 def __init__(self, *args, **kwargs):
69 super(CordSubscriberRoot, self).__init__(*args, **kwargs)
70 self.cached_volt = None
Scott Baker050f8b32015-07-07 12:15:03 -070071 self._initial_url_filter_enable = self.url_filter_enable
Scott Baker81de6402015-07-06 16:50:30 -070072
73 @property
74 def volt(self):
75 volt = self.get_newest_subscribed_tenant(VOLTTenant)
76 if not volt:
77 return None
78
79 # always return the same object when possible
80 if (self.cached_volt) and (self.cached_volt.id == volt.id):
Scott Baker126ad472015-07-07 17:59:44 -070081 return self.cached_volt
Scott Baker81de6402015-07-06 16:50:30 -070082
Scott Bakere4364d12015-07-06 17:21:21 -070083 #volt.caller = self.creator
Scott Baker81de6402015-07-06 16:50:30 -070084 self.cached_volt = volt
85 return volt
86
87 @property
Scott Baker669212f2016-03-01 20:14:35 -080088 def status(self):
89 return self.get_attribute("status", self.default_attributes["status"])
Scott Baker81de6402015-07-06 16:50:30 -070090
Scott Baker669212f2016-03-01 20:14:35 -080091 @status.setter
92 def status(self, value):
93 if not value in [x[0] for x in self.status_choices]:
94 raise Exception("invalid status %s" % value)
95 self.set_attribute("status", value)
Scott Baker81de6402015-07-06 16:50:30 -070096
97 def find_user(self, uid):
98 uid = int(uid)
99 for user in self.users:
100 if user["id"] == uid:
101 return user
102 return None
103
104 def update_user(self, uid, **kwargs):
105 # kwargs may be "level" or "mac"
106 # Setting one of these to None will cause None to be stored in the db
107 uid = int(uid)
108 users = self.users
109 for user in users:
110 if user["id"] == uid:
111 for arg in kwargs.keys():
112 user[arg] = kwargs[arg]
113 self.users = users
114 return user
115 raise ValueError("User %d not found" % uid)
116
117 def create_user(self, **kwargs):
118 if "name" not in kwargs:
119 raise XOSMissingField("The name field is required")
120
121 for user in self.users:
122 if kwargs["name"] == user["name"]:
123 raise XOSDuplicateKey("User %s already exists" % kwargs["name"])
124
125 uids = [x["id"] for x in self.users]
126 if uids:
127 uid = max(uids)+1
128 else:
129 uid = 0
130 newuser = kwargs.copy()
131 newuser["id"] = uid
132
133 users = self.users
134 users.append(newuser)
135 self.users = users
136
137 return newuser
138
139 def delete_user(self, uid):
140 uid = int(uid)
141 users = self.users
142 for user in users:
143 if user["id"]==uid:
144 users.remove(user)
145 self.users = users
146 return
147
148 raise ValueError("Users %d not found" % uid)
149
150 @property
151 def services(self):
152 return {"cdn": self.cdn_enable,
153 "url_filter": self.url_filter_enable,
154 "firewall": self.firewall_enable}
155
156 @services.setter
157 def services(self, value):
158 pass
Scott Bakerd9133342015-07-06 14:38:02 -0700159
Scott Baker050f8b32015-07-07 12:15:03 -0700160 def save(self, *args, **kwargs):
Scott Bakerf3735762016-03-31 14:45:31 -0700161 self.validate_unique_service_specific_id(none_okay=True)
Scott Bakerfe38c2a2015-07-24 18:56:57 -0700162 if (not hasattr(self, 'caller') or not self.caller.is_admin):
163 if (self.has_field_changed("service_specific_id")):
164 raise XOSPermissionDenied("You do not have permission to change service_specific_id")
Scott Baker050f8b32015-07-07 12:15:03 -0700165 super(CordSubscriberRoot, self).save(*args, **kwargs)
166 if (self.volt) and (self.volt.vcpe): # and (self._initial_url_filter_enabled != self.url_filter_enable):
167 # 1) trigger manage_bbs_account to run
168 # 2) trigger vcpe observer to wake up
169 self.volt.vcpe.save()
170
Scott Baker669212f2016-03-01 20:14:35 -0800171CordSubscriberRoot.setup_simple_attributes()
Scott Baker126ad472015-07-07 17:59:44 -0700172
Scott Bakerd4b48c02015-04-15 20:59:15 -0700173# -------------------------------------------
174# VOLT
175# -------------------------------------------
176
177class VOLTService(Service):
Scott Bakerd9133342015-07-06 14:38:02 -0700178 KIND = VOLT_KIND
Scott Bakerd4b48c02015-04-15 20:59:15 -0700179
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700180 class Meta:
Scott Bakerd4b48c02015-04-15 20:59:15 -0700181 app_label = "cord"
182 verbose_name = "vOLT Service"
183 proxy = True
184
185class VOLTTenant(Tenant):
186 class Meta:
187 proxy = True
188
Scott Bakerd9133342015-07-06 14:38:02 -0700189 KIND = VOLT_KIND
Scott Bakerd4b48c02015-04-15 20:59:15 -0700190
Scott Bakerf1527cd2015-11-09 20:25:21 -0800191 default_attributes = {"vlan_id": None, "s_tag": None, "c_tag": None}
Scott Baker323eca92015-04-20 09:48:34 -0700192 def __init__(self, *args, **kwargs):
193 volt_services = VOLTService.get_service_objects().all()
194 if volt_services:
195 self._meta.get_field("provider_service").default = volt_services[0].id
196 super(VOLTTenant, self).__init__(*args, **kwargs)
Scott Bakerd9133342015-07-06 14:38:02 -0700197 self.cached_vcpe = None
Scott Baker868dabf2015-06-24 12:54:24 -0700198
Scott Bakerd4b48c02015-04-15 20:59:15 -0700199 @property
Scott Bakerf1527cd2015-11-09 20:25:21 -0800200 def s_tag(self):
201 return self.get_attribute("s_tag", self.default_attributes["s_tag"])
202
203 @s_tag.setter
204 def s_tag(self, value):
205 self.set_attribute("s_tag", value)
206
207 @property
208 def c_tag(self):
209 return self.get_attribute("c_tag", self.default_attributes["c_tag"])
210
211 @c_tag.setter
212 def c_tag(self, value):
213 self.set_attribute("c_tag", value)
214
215 # for now, vlan_id is a synonym for c_tag
216
217 @property
Scott Baker679f7022015-04-20 11:50:09 -0700218 def vlan_id(self):
Scott Bakerf1527cd2015-11-09 20:25:21 -0800219 return self.c_tag
Scott Baker679f7022015-04-20 11:50:09 -0700220
221 @vlan_id.setter
222 def vlan_id(self, value):
Scott Bakerf1527cd2015-11-09 20:25:21 -0800223 self.c_tag = value
Scott Baker679f7022015-04-20 11:50:09 -0700224
225 @property
Scott Bakerd4b48c02015-04-15 20:59:15 -0700226 def vcpe(self):
Scott Baker4c052b22016-02-11 11:08:42 -0800227 vcpe = self.get_newest_subscribed_tenant(VSGTenant)
Scott Bakerd9133342015-07-06 14:38:02 -0700228 if not vcpe:
229 return None
230
231 # always return the same object when possible
232 if (self.cached_vcpe) and (self.cached_vcpe.id == vcpe.id):
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700233 return self.cached_vcpe
Scott Bakerd9133342015-07-06 14:38:02 -0700234
Scott Bakerc633dc92015-05-05 17:49:46 -0700235 vcpe.caller = self.creator
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700236 self.cached_vcpe = vcpe
237 return vcpe
Scott Bakerd4b48c02015-04-15 20:59:15 -0700238
239 @vcpe.setter
240 def vcpe(self, value):
Scott Bakere4364d12015-07-06 17:21:21 -0700241 raise XOSConfigurationError("vOLT.vCPE cannot be set this way -- create a new vCPE object and set its subscriber_tenant instead")
242
243 @property
244 def subscriber(self):
245 if not self.subscriber_root:
246 return None
247 subs = CordSubscriberRoot.objects.filter(id=self.subscriber_root.id)
248 if not subs:
249 return None
250 return subs[0]
Scott Bakerd4b48c02015-04-15 20:59:15 -0700251
Scott Bakerc633dc92015-05-05 17:49:46 -0700252 @property
253 def creator(self):
254 if getattr(self, "cached_creator", None):
255 return self.cached_creator
256 creator_id=self.get_attribute("creator_id")
257 if not creator_id:
258 return None
259 users=User.objects.filter(id=creator_id)
260 if not users:
261 return None
262 user=users[0]
263 self.cached_creator = users[0]
264 return user
265
266 @creator.setter
267 def creator(self, value):
268 if value:
269 value = value.id
270 if (value != self.get_attribute("creator_id", None)):
271 self.cached_creator=None
272 self.set_attribute("creator_id", value)
273
Scott Bakerd4b48c02015-04-15 20:59:15 -0700274 def manage_vcpe(self):
275 # Each VOLT object owns exactly one VCPE object
276
277 if self.deleted:
278 return
279
280 if self.vcpe is None:
Scott Bakerf91e6152016-02-11 12:07:10 -0800281 vsgServices = VSGService.get_service_objects().all()
282 if not vsgServices:
283 raise XOSConfigurationError("No VSG Services available")
Scott Bakerd4b48c02015-04-15 20:59:15 -0700284
Scott Bakerf91e6152016-02-11 12:07:10 -0800285 vcpe = VSGTenant(provider_service = vsgServices[0],
Scott Bakerd4b48c02015-04-15 20:59:15 -0700286 subscriber_tenant = self)
Scott Bakerc633dc92015-05-05 17:49:46 -0700287 vcpe.caller = self.creator
Scott Bakerd4b48c02015-04-15 20:59:15 -0700288 vcpe.save()
289
Scott Baker050f8b32015-07-07 12:15:03 -0700290 def manage_subscriber(self):
291 if (self.subscriber_root is None):
292 # The vOLT is not connected to a Subscriber, so either find an
293 # existing subscriber with the same SSID, or autogenerate a new
294 # subscriber.
295 #
296 # TODO: This probably goes away when we rethink the ONOS-to-XOS
297 # vOLT API.
298
299 subs = CordSubscriberRoot.get_tenant_objects().filter(service_specific_id = self.service_specific_id)
300 if subs:
301 sub = subs[0]
302 else:
303 sub = CordSubscriberRoot(service_specific_id = self.service_specific_id,
304 name = "autogenerated-for-vOLT-%s" % self.id)
305 sub.save()
306 self.subscriber_root = sub
307 self.save()
308
Scott Bakerd4b48c02015-04-15 20:59:15 -0700309 def cleanup_vcpe(self):
310 if self.vcpe:
Scott Bakerd9133342015-07-06 14:38:02 -0700311 # print "XXX cleanup vcpe", self.vcpe
Scott Bakerd4b48c02015-04-15 20:59:15 -0700312 self.vcpe.delete()
Scott Bakerd4b48c02015-04-15 20:59:15 -0700313
Scott Baker868dabf2015-06-24 12:54:24 -0700314 def cleanup_orphans(self):
Scott Bakerd9133342015-07-06 14:38:02 -0700315 # ensure vOLT only has one vCPE
316 cur_vcpe = self.vcpe
Scott Baker4c052b22016-02-11 11:08:42 -0800317 for vcpe in list(self.get_subscribed_tenants(VSGTenant)):
Scott Bakerd9133342015-07-06 14:38:02 -0700318 if (not cur_vcpe) or (vcpe.id != cur_vcpe.id):
319 # print "XXX clean up orphaned vcpe", vcpe
320 vcpe.delete()
Scott Baker868dabf2015-06-24 12:54:24 -0700321
Scott Bakerd4b48c02015-04-15 20:59:15 -0700322 def save(self, *args, **kwargs):
Scott Baker6292f342016-03-30 09:39:59 -0700323 # VOLTTenant probably doesn't need a SSID anymore; that will be handled
324 # by CORDSubscriberRoot...
325 # self.validate_unique_service_specific_id()
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700326
Scott Baker050f8b32015-07-07 12:15:03 -0700327 if (self.subscriber_root is not None):
328 subs = self.subscriber_root.get_subscribed_tenants(VOLTTenant)
329 if (subs) and (self not in subs):
330 raise XOSDuplicateKey("Subscriber should only be linked to one vOLT")
331
Scott Bakerc633dc92015-05-05 17:49:46 -0700332 if not self.creator:
333 if not getattr(self, "caller", None):
334 # caller must be set when creating a vCPE since it creates a slice
335 raise XOSProgrammingError("VOLTTenant's self.caller was not set")
336 self.creator = self.caller
337 if not self.creator:
338 raise XOSProgrammingError("VOLTTenant's self.creator was not set")
339
Scott Bakerd4b48c02015-04-15 20:59:15 -0700340 super(VOLTTenant, self).save(*args, **kwargs)
Scott Baker192da222015-07-08 19:01:56 -0700341 model_policy_volt(self.pk)
342 #self.manage_vcpe()
343 #self.manage_subscriber()
344 #self.cleanup_orphans()
Scott Bakerd4b48c02015-04-15 20:59:15 -0700345
346 def delete(self, *args, **kwargs):
347 self.cleanup_vcpe()
348 super(VOLTTenant, self).delete(*args, **kwargs)
349
Scott Baker192da222015-07-08 19:01:56 -0700350def model_policy_volt(pk):
351 # TODO: this should be made in to a real model_policy
352 with transaction.atomic():
353 volt = VOLTTenant.objects.select_for_update().filter(pk=pk)
354 if not volt:
355 return
356 volt = volt[0]
357 volt.manage_vcpe()
358 volt.manage_subscriber()
359 volt.cleanup_orphans()
360
Scott Bakerd4b48c02015-04-15 20:59:15 -0700361# -------------------------------------------
362# VCPE
363# -------------------------------------------
364
Scott Bakerf91e6152016-02-11 12:07:10 -0800365class VSGService(Service):
Scott Bakerd9133342015-07-06 14:38:02 -0700366 KIND = VCPE_KIND
Scott Bakerd4b48c02015-04-15 20:59:15 -0700367
Scott Bakerbe2bc9a2016-03-10 20:12:15 -0800368 URL_FILTER_KIND_CHOICES = ( (None, "None"), ("safebrowsing", "Safe Browsing"), ("answerx", "AnswerX") )
Scott Baker9b870572016-03-10 19:23:10 -0800369
Scott Baker2b56cf42015-07-15 18:08:06 -0700370 simple_attributes = ( ("bbs_api_hostname", None),
371 ("bbs_api_port", None),
Scott Baker80157da2015-07-15 17:42:43 -0700372 ("bbs_server", None),
Scott Bakercf6eb992016-02-18 06:43:02 -0800373 ("backend_network_label", "hpc_client"),
374 ("wan_container_gateway_ip", ""),
375 ("wan_container_gateway_mac", ""),
Scott Bakerae6187c2016-02-23 14:58:49 -0800376 ("wan_container_netbits", "24"),
Scott Baker67074ab2016-03-04 11:29:02 -0800377 ("dns_servers", "8.8.8.8"),
Scott Baker9b870572016-03-10 19:23:10 -0800378 ("url_filter_kind", None),
Scott Baker67074ab2016-03-04 11:29:02 -0800379 ("node_label", None) )
Scott Baker62312972015-07-13 14:30:25 -0700380
381 def __init__(self, *args, **kwargs):
Scott Bakerf91e6152016-02-11 12:07:10 -0800382 super(VSGService, self).__init__(*args, **kwargs)
Scott Baker62312972015-07-13 14:30:25 -0700383
Scott Bakerd4b48c02015-04-15 20:59:15 -0700384 class Meta:
385 app_label = "cord"
Scott Baker2516ce32016-02-11 16:56:34 -0800386 verbose_name = "vSG Service"
Scott Bakerd4b48c02015-04-15 20:59:15 -0700387 proxy = True
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700388
Scott Baker4e6d60d2015-05-20 20:45:11 -0700389 def allocate_bbs_account(self):
Scott Baker4c052b22016-02-11 11:08:42 -0800390 vcpes = VSGTenant.get_tenant_objects().all()
Scott Baker4e6d60d2015-05-20 20:45:11 -0700391 bbs_accounts = [vcpe.bbs_account for vcpe in vcpes]
392
393 # There's a bit of a race here; some other user could be trying to
394 # allocate a bbs_account at the same time we are.
395
Scott Baker2f0b3462015-06-09 12:03:56 -0700396 for i in range(2,21):
Scott Baker4e6d60d2015-05-20 20:45:11 -0700397 account_name = "bbs%02d@onlab.us" % i
398 if (account_name not in bbs_accounts):
399 return account_name
400
401 raise XOSConfigurationError("We've run out of available broadbandshield accounts. Delete some vcpe and try again.")
402
Scott Baker80157da2015-07-15 17:42:43 -0700403 @property
404 def bbs_slice(self):
405 bbs_slice_id=self.get_attribute("bbs_slice_id")
406 if not bbs_slice_id:
407 return None
408 bbs_slices=Slice.objects.filter(id=bbs_slice_id)
409 if not bbs_slices:
410 return None
411 return bbs_slices[0]
412
413 @bbs_slice.setter
414 def bbs_slice(self, value):
415 if value:
416 value = value.id
417 self.set_attribute("bbs_slice_id", value)
418
Scott Bakerf91e6152016-02-11 12:07:10 -0800419VSGService.setup_simple_attributes()
Scott Baker62312972015-07-13 14:30:25 -0700420
Scott Baker5b8eb412016-02-02 16:56:18 -0800421#class STagBlock(PlCoreBase):
422# instance = models.ForeignKey(Instance, related_name="s_tags")
423# s_tag = models.CharField(null=false, blank=false, unique=true, max_length=10)
424# #c_tags = models.TextField(null=true, blank=true)
425#
426# def __unicode__(self): return u'%s' % (self.s_tag)
Scott Baker62312972015-07-13 14:30:25 -0700427
Scott Baker4c052b22016-02-11 11:08:42 -0800428class VSGTenant(TenantWithContainer):
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700429 class Meta:
430 proxy = True
431
Scott Bakerd9133342015-07-06 14:38:02 -0700432 KIND = VCPE_KIND
Scott Bakerd4b48c02015-04-15 20:59:15 -0700433
Scott Baker92a81d42015-09-09 10:57:16 -0700434 sync_attributes = ("nat_ip", "nat_mac",
435 "lan_ip", "lan_mac",
Scott Bakere6780e92016-02-13 14:38:43 -0800436 "wan_ip", "wan_mac",
437 "wan_container_ip", "wan_container_mac",
Scott Baker92a81d42015-09-09 10:57:16 -0700438 "private_ip", "private_mac",
439 "hpc_client_ip", "hpc_client_mac")
Scott Bakerc633dc92015-05-05 17:49:46 -0700440
Tony Mackd8515472015-08-19 11:58:18 -0400441 default_attributes = {"instance_id": None,
Scott Baker4b206972015-11-02 20:55:07 -0800442 "container_id": None,
Scott Baker4e6d60d2015-05-20 20:45:11 -0700443 "users": [],
Scott Baker4aa660e2015-06-09 12:22:29 -0700444 "bbs_account": None,
Scott Bakere6780e92016-02-13 14:38:43 -0800445 "last_ansible_hash": None,
446 "wan_container_ip": None}
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700447
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700448 def __init__(self, *args, **kwargs):
Scott Baker4c052b22016-02-11 11:08:42 -0800449 super(VSGTenant, self).__init__(*args, **kwargs)
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700450 self.cached_vbng=None
Scott Bakerc633dc92015-05-05 17:49:46 -0700451
452 @property
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700453 def vbng(self):
Scott Bakerd9133342015-07-06 14:38:02 -0700454 vbng = self.get_newest_subscribed_tenant(VBNGTenant)
455 if not vbng:
456 return None
457
458 # always return the same object when possible
459 if (self.cached_vbng) and (self.cached_vbng.id == vbng.id):
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700460 return self.cached_vbng
Scott Bakerd9133342015-07-06 14:38:02 -0700461
Scott Bakerc633dc92015-05-05 17:49:46 -0700462 vbng.caller = self.creator
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700463 self.cached_vbng = vbng
464 return vbng
465
466 @vbng.setter
467 def vbng(self, value):
Scott Bakerd9133342015-07-06 14:38:02 -0700468 raise XOSConfigurationError("vCPE.vBNG cannot be set this way -- create a new vBNG object and set it's subscriber_tenant instead")
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700469
Scott Bakere4364d12015-07-06 17:21:21 -0700470 @property
471 def volt(self):
472 if not self.subscriber_tenant:
473 return None
474 volts = VOLTTenant.objects.filter(id=self.subscriber_tenant.id)
475 if not volts:
476 return None
477 return volts[0]
478
Scott Baker81de6402015-07-06 16:50:30 -0700479 @property
480 def bbs_account(self):
481 return self.get_attribute("bbs_account", self.default_attributes["bbs_account"])
482
483 @bbs_account.setter
484 def bbs_account(self, value):
485 return self.set_attribute("bbs_account", value)
486
487 @property
488 def last_ansible_hash(self):
489 return self.get_attribute("last_ansible_hash", self.default_attributes["last_ansible_hash"])
490
491 @last_ansible_hash.setter
492 def last_ansible_hash(self, value):
493 return self.set_attribute("last_ansible_hash", value)
494
495 @property
496 def ssh_command(self):
Tony Mackd8515472015-08-19 11:58:18 -0400497 if self.instance:
498 return self.instance.get_ssh_command()
Scott Baker81de6402015-07-06 16:50:30 -0700499 else:
Tony Mackd8515472015-08-19 11:58:18 -0400500 return "no-instance"
Scott Baker81de6402015-07-06 16:50:30 -0700501
502 @ssh_command.setter
503 def ssh_command(self, value):
504 pass
505
Scott Baker5571c692015-05-20 08:19:25 -0700506 @property
507 def addresses(self):
Scott Baker4b206972015-11-02 20:55:07 -0800508 if self.instance:
509 ports = self.instance.ports.all()
510 elif self.container:
511 ports = self.container.ports.all()
512 else:
Scott Baker5571c692015-05-20 08:19:25 -0700513 return {}
514
515 addresses = {}
Scott Baker4b206972015-11-02 20:55:07 -0800516 for ns in ports:
Scott Baker5571c692015-05-20 08:19:25 -0700517 if "lan" in ns.network.name.lower():
Scott Baker92a81d42015-09-09 10:57:16 -0700518 addresses["lan"] = (ns.ip, ns.mac)
Scott Baker5571c692015-05-20 08:19:25 -0700519 elif "wan" in ns.network.name.lower():
Scott Baker92a81d42015-09-09 10:57:16 -0700520 addresses["wan"] = (ns.ip, ns.mac)
Scott Baker5571c692015-05-20 08:19:25 -0700521 elif "private" in ns.network.name.lower():
Scott Baker92a81d42015-09-09 10:57:16 -0700522 addresses["private"] = (ns.ip, ns.mac)
Scott Baker5571c692015-05-20 08:19:25 -0700523 elif "nat" in ns.network.name.lower():
Scott Baker92a81d42015-09-09 10:57:16 -0700524 addresses["nat"] = (ns.ip, ns.mac)
Scott Baker281b1af2015-06-04 10:26:44 -0700525 elif "hpc_client" in ns.network.name.lower():
Scott Baker92a81d42015-09-09 10:57:16 -0700526 addresses["hpc_client"] = (ns.ip, ns.mac)
Scott Baker5571c692015-05-20 08:19:25 -0700527 return addresses
528
Scott Bakere6780e92016-02-13 14:38:43 -0800529 # ------------------------------------------------------------------------
530 # The following IP addresses all come from the VM
531 # Note: They might not be useful for the VTN-vSG
532
Scott Baker432d1402015-05-20 08:25:29 -0700533 @property
534 def nat_ip(self):
Scott Baker92a81d42015-09-09 10:57:16 -0700535 return self.addresses.get("nat", (None,None) )[0]
536
537 @property
538 def nat_mac(self):
539 return self.addresses.get("nat", (None,None) )[1]
Scott Baker432d1402015-05-20 08:25:29 -0700540
541 @property
542 def lan_ip(self):
Scott Baker92a81d42015-09-09 10:57:16 -0700543 return self.addresses.get("lan", (None, None) )[0]
544
545 @property
546 def lan_mac(self):
547 return self.addresses.get("lan", (None, None) )[1]
Scott Baker432d1402015-05-20 08:25:29 -0700548
549 @property
550 def wan_ip(self):
Scott Baker92a81d42015-09-09 10:57:16 -0700551 return self.addresses.get("wan", (None, None) )[0]
Scott Baker432d1402015-05-20 08:25:29 -0700552
553 @property
Scott Baker4f972592015-06-05 12:08:34 -0700554 def wan_mac(self):
Andy Bavier4c7e76d2015-09-09 18:06:30 -0400555 return self.addresses.get("wan", (None, None) )[1]
Scott Baker4f972592015-06-05 12:08:34 -0700556
Scott Bakere6780e92016-02-13 14:38:43 -0800557 # end of VM IP address stubs
558 # ------------------------------------------------------------------------
559
560 @property
561 def wan_container_ip(self):
562 if CORD_USE_VTN:
563 # When using VTN, wan_container_ip is stored and maintained inside
564 # of the vSG object.
565 return self.get_attribute("wan_container_ip", self.default_attributes["wan_container_ip"])
566 else:
567 # When not using VTN, wan_container_ip is the same as wan_ip.
568 # XXX Is this broken for multiple-containers-per-VM?
569 return self.wan_ip
570
571 @wan_container_ip.setter
572 def wan_container_ip(self, value):
573 if CORD_USE_VTN:
574 self.set_attribute("wan_container_ip", value)
575 else:
576 raise Exception("wan_container_ip.setter called on non-VTN CORD")
577
Scott Baker6fd78342016-02-18 15:18:47 -0800578 def ip_to_mac(self, ip):
579 (a, b, c, d) = ip.split('.')
580 return "02:42:%02x:%02x:%02x:%02x" % (int(a), int(b), int(c), int(d))
581
Andy Bavier733733d2015-10-29 14:01:47 -0400582 # Generate the MAC for the container interface connected to WAN
583 @property
584 def wan_container_mac(self):
Scott Bakere6780e92016-02-13 14:38:43 -0800585 if not self.wan_container_ip:
586 return None
Scott Baker6fd78342016-02-18 15:18:47 -0800587 return self.ip_to_mac(self.wan_container_ip)
Andy Bavier733733d2015-10-29 14:01:47 -0400588
Scott Baker4f972592015-06-05 12:08:34 -0700589 @property
Scott Baker432d1402015-05-20 08:25:29 -0700590 def private_ip(self):
Scott Baker92a81d42015-09-09 10:57:16 -0700591 return self.addresses.get("private", (None, None) )[0]
592
593 @property
594 def private_mac(self):
595 return self.addresses.get("private", (None, None) )[1]
Scott Baker432d1402015-05-20 08:25:29 -0700596
Scott Baker281b1af2015-06-04 10:26:44 -0700597 @property
598 def hpc_client_ip(self):
Scott Baker92a81d42015-09-09 10:57:16 -0700599 return self.addresses.get("hpc_client", (None, None) )[0]
600
601 @property
602 def hpc_client_mac(self):
603 return self.addresses.get("hpc_client", (None, None) )[1]
Scott Baker281b1af2015-06-04 10:26:44 -0700604
Scott Bakerd517a102015-06-09 12:30:30 -0700605 @property
606 def is_synced(self):
607 return (self.enacted is not None) and (self.enacted >= self.updated)
608
609 @is_synced.setter
610 def is_synced(self, value):
611 pass
612
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700613 def manage_vbng(self):
614 # Each vCPE object owns exactly one vBNG object
615
616 if self.deleted:
617 return
618
619 if self.vbng is None:
620 vbngServices = VBNGService.get_service_objects().all()
621 if not vbngServices:
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700622 raise XOSConfigurationError("No VBNG Services available")
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700623
624 vbng = VBNGTenant(provider_service = vbngServices[0],
625 subscriber_tenant = self)
Scott Bakerc633dc92015-05-05 17:49:46 -0700626 vbng.caller = self.creator
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700627 vbng.save()
628
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700629 def cleanup_vbng(self):
630 if self.vbng:
Scott Bakerd9133342015-07-06 14:38:02 -0700631 # print "XXX cleanup vnbg", self.vbng
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700632 self.vbng.delete()
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700633
Scott Baker868dabf2015-06-24 12:54:24 -0700634 def cleanup_orphans(self):
Scott Bakerd9133342015-07-06 14:38:02 -0700635 # ensure vCPE only has one vBNG
636 cur_vbng = self.vbng
637 for vbng in list(self.get_subscribed_tenants(VBNGTenant)):
638 if (not cur_vbng) or (vbng.id != cur_vbng.id):
639 # print "XXX clean up orphaned vbng", vbng
640 vbng.delete()
Scott Baker868dabf2015-06-24 12:54:24 -0700641
Tony Mackd8515472015-08-19 11:58:18 -0400642 if self.orig_instance_id and (self.orig_instance_id != self.get_attribute("instance_id")):
643 instances=Instance.objects.filter(id=self.orig_instance_id)
644 if instances:
645 # print "XXX clean up orphaned instance", instances[0]
646 instances[0].delete()
Scott Baker868dabf2015-06-24 12:54:24 -0700647
Scott Baker5b8eb412016-02-02 16:56:18 -0800648 def get_slice(self):
649 if not self.provider_service.slices.count():
650 raise XOSConfigurationError("The service has no slices")
651 slice = self.provider_service.slices.all()[0]
652 return slice
653
Scott Baker67074ab2016-03-04 11:29:02 -0800654 def get_vsg_service(self):
655 return VSGService.get_service_objects().get(id=self.provider_service.id)
656
Scott Baker5b8eb412016-02-02 16:56:18 -0800657 def find_instance_for_s_tag(self, s_tag):
658 #s_tags = STagBlock.objects.find(s_s_tag)
659 #if s_tags:
660 # return s_tags[0].instance
661
662 tags = Tag.objects.filter(name="s_tag", value=s_tag)
663 if tags:
664 return tags[0].content_object
665
666 return None
667
668 def find_or_make_instance_for_s_tag(self, s_tag):
669 instance = self.find_instance_for_s_tag(self.volt.s_tag)
670 if instance:
671 return instance
672
673 flavors = Flavor.objects.filter(name="m1.small")
674 if not flavors:
675 raise XOSConfigurationError("No m1.small flavor")
676
677 slice = self.provider_service.slices.all()[0]
678
679 if slice.default_isolation == "container_vm":
680 (node, parent) = ContainerVmScheduler(slice).pick()
681 else:
Scott Baker67074ab2016-03-04 11:29:02 -0800682 (node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).pick()
Scott Baker5b8eb412016-02-02 16:56:18 -0800683
684 instance = Instance(slice = slice,
685 node = node,
686 image = self.image,
687 creator = self.creator,
688 deployment = node.site_deployment.deployment,
689 flavor = flavors[0],
690 isolation = slice.default_isolation,
691 parent = parent)
Scott Baker63c8b372016-02-12 16:17:04 -0800692
Scott Baker5b8eb412016-02-02 16:56:18 -0800693 self.save_instance(instance)
694
695 return instance
696
697 def manage_container(self):
698 from core.models import Instance, Flavor
699
700 if self.deleted:
701 return
702
703 # For container or container_vm isolation, use what TenantWithCotnainer
704 # provides us
705 slice = self.get_slice()
706 if slice.default_isolation in ["container_vm", "container"]:
Scott Baker4c052b22016-02-11 11:08:42 -0800707 super(VSGTenant,self).manage_container()
Scott Baker5b8eb412016-02-02 16:56:18 -0800708 return
709
710 if not self.volt:
711 raise XOSConfigurationError("This vCPE container has no volt")
712
Scott Bakercf6eb992016-02-18 06:43:02 -0800713 if self.instance:
714 # We're good.
715 return
716
Scott Baker5b8eb412016-02-02 16:56:18 -0800717 instance = self.find_or_make_instance_for_s_tag(self.volt.s_tag)
718 self.instance = instance
719 super(TenantWithContainer, self).save()
720
721 def cleanup_container(self):
722 if self.get_slice().default_isolation in ["container_vm", "container"]:
Scott Baker4c052b22016-02-11 11:08:42 -0800723 super(VSGTenant,self).cleanup_container()
Scott Baker5b8eb412016-02-02 16:56:18 -0800724
725 # To-do: cleanup unused instances
726 pass
727
Scott Baker4e6d60d2015-05-20 20:45:11 -0700728 def manage_bbs_account(self):
729 if self.deleted:
730 return
731
Scott Baker126ad472015-07-07 17:59:44 -0700732 if self.volt and self.volt.subscriber and self.volt.subscriber.url_filter_enable:
Scott Baker642126f2015-05-20 20:57:28 -0700733 if not self.bbs_account:
Scott Bakerf91e6152016-02-11 12:07:10 -0800734 # make sure we use the proxied VSGService object, not the generic Service object
735 vcpe_service = VSGService.objects.get(id=self.provider_service.id)
Scott Baker050f8b32015-07-07 12:15:03 -0700736 self.bbs_account = vcpe_service.allocate_bbs_account()
Scott Baker4c052b22016-02-11 11:08:42 -0800737 super(VSGTenant, self).save()
Scott Baker642126f2015-05-20 20:57:28 -0700738 else:
739 if self.bbs_account:
740 self.bbs_account = None
Scott Baker4c052b22016-02-11 11:08:42 -0800741 super(VSGTenant, self).save()
Scott Baker4e6d60d2015-05-20 20:45:11 -0700742
Scott Baker6fd78342016-02-18 15:18:47 -0800743 def get_wan_address_from_pool(self):
744 ap = AddressPool.objects.filter(name="public_addresses")
745 if not ap:
746 raise Exception("AddressPool 'public_addresses' does not exist. Please configure it.")
747 ap = ap[0]
748
749 addr = ap.get_address()
750 if not addr:
751 raise Exception("AddressPool 'public_addresses' has run out of addresses.")
752 return addr
753
754 def put_wan_address_to_pool(self, addr):
755 AddressPool.objects.filter(name="public_addresses")[0].put_address(addr)
756
Scott Bakere6780e92016-02-13 14:38:43 -0800757 def manage_wan_container_ip(self):
758 if CORD_USE_VTN:
759 if not self.wan_container_ip:
Scott Baker6fd78342016-02-18 15:18:47 -0800760 addr = self.get_wan_address_from_pool()
Scott Bakere6780e92016-02-13 14:38:43 -0800761
762 self.wan_container_ip = addr
Scott Bakercf6eb992016-02-18 06:43:02 -0800763 super(TenantWithContainer, self).save()
Scott Bakere6780e92016-02-13 14:38:43 -0800764
765 def cleanup_wan_container_ip(self):
766 if CORD_USE_VTN and self.wan_container_ip:
Scott Baker6fd78342016-02-18 15:18:47 -0800767 self.put_wan_address_to_pool(self.wan_container_ip)
Scott Bakere6780e92016-02-13 14:38:43 -0800768 self.wan_container_ip = None
769
Scott Baker839daa82015-11-16 22:53:49 -0800770 def find_or_make_port(self, instance, network, **kwargs):
771 port = Port.objects.filter(instance=instance, network=network)
772 if port:
773 port = port[0]
774 else:
775 port = Port(instance=instance, network=network, **kwargs)
776 port.save()
777 return port
778
Scott Baker63c8b372016-02-12 16:17:04 -0800779 def get_lan_network(self, instance):
Scott Baker63c8b372016-02-12 16:17:04 -0800780 slice = self.provider_service.slices.all()[0]
Scott Bakere6780e92016-02-13 14:38:43 -0800781 if CORD_USE_VTN:
782 # there should only be one network private network, and its template should not be the management template
783 lan_networks = [x for x in slice.networks.all() if x.template.visibility=="private" and (not "management" in x.template.name)]
784 if len(lan_networks)>1:
785 raise XOSProgrammingError("The vSG slice should only have one non-management private network")
786 else:
787 lan_networks = [x for x in slice.networks.all() if "lan" in x.name]
Scott Baker63c8b372016-02-12 16:17:04 -0800788 if not lan_networks:
789 raise XOSProgrammingError("No lan_network")
790 return lan_networks[0]
791
Scott Baker839daa82015-11-16 22:53:49 -0800792 def save_instance(self, instance):
793 with transaction.atomic():
Andy Bavier1aa49af2015-12-16 14:10:01 -0500794 instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
Scott Baker4c052b22016-02-11 11:08:42 -0800795 super(VSGTenant, self).save_instance(instance)
Scott Baker839daa82015-11-16 22:53:49 -0800796
797 if instance.isolation in ["container", "container_vm"]:
Scott Baker63c8b372016-02-12 16:17:04 -0800798 lan_network = self.get_lan_network(instance)
799 port = self.find_or_make_port(instance, lan_network, ip="192.168.0.1", port_id="unmanaged")
Scott Baker839daa82015-11-16 22:53:49 -0800800 port.set_parameter("c_tag", self.volt.c_tag)
801 port.set_parameter("s_tag", self.volt.s_tag)
802 port.set_parameter("device", "eth1")
Scott Bakere2920b22015-11-19 16:51:27 -0800803 port.set_parameter("bridge", "br-lan")
Scott Baker839daa82015-11-16 22:53:49 -0800804
805 wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
806 if not wan_networks:
807 raise XOSProgrammingError("No wan_network")
808 port = self.find_or_make_port(instance, wan_networks[0])
809 port.set_parameter("next_hop", value="10.0.1.253") # FIX ME
810 port.set_parameter("device", "eth0")
811
Scott Baker63c8b372016-02-12 16:17:04 -0800812 if instance.isolation in ["vm"]:
813 lan_network = self.get_lan_network(instance)
814 port = self.find_or_make_port(instance, lan_network)
815 port.set_parameter("c_tag", self.volt.c_tag)
816 port.set_parameter("s_tag", self.volt.s_tag)
817 port.set_parameter("neutron_port_name", "stag-%s" % self.volt.s_tag)
818 port.save()
819
Scott Baker5b8eb412016-02-02 16:56:18 -0800820 # tag the instance with the s-tag, so we can easily find the
821 # instance later
822 if self.volt and self.volt.s_tag:
823 tags = Tag.objects.filter(name="s_tag", value=self.volt.s_tag)
824 if not tags:
825 tag = Tag(service=self.provider_service, content_object=instance, name="s_tag", value=self.volt.s_tag)
826 tag.save()
827
Scott Baker6fd78342016-02-18 15:18:47 -0800828 # VTN-CORD needs a WAN address for the VM, so that the VM can
829 # be configured.
830 if CORD_USE_VTN:
831 tags = Tag.select_by_content_object(instance).filter(name="vm_wan_addr")
832 if not tags:
833 address = self.get_wan_address_from_pool()
834 tag = Tag(service=self.provider_service, content_object=instance, name="vm_wan_addr", value="%s,%s,%s" % ("public_addresses", address, self.ip_to_mac(address)))
835 tag.save()
836
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700837 def save(self, *args, **kwargs):
Scott Bakerc633dc92015-05-05 17:49:46 -0700838 if not self.creator:
839 if not getattr(self, "caller", None):
840 # caller must be set when creating a vCPE since it creates a slice
Scott Baker4c052b22016-02-11 11:08:42 -0800841 raise XOSProgrammingError("VSGTenant's self.caller was not set")
Scott Bakerc633dc92015-05-05 17:49:46 -0700842 self.creator = self.caller
843 if not self.creator:
Scott Baker4c052b22016-02-11 11:08:42 -0800844 raise XOSProgrammingError("VSGTenant's self.creator was not set")
Scott Bakerc633dc92015-05-05 17:49:46 -0700845
Scott Baker4c052b22016-02-11 11:08:42 -0800846 super(VSGTenant, self).save(*args, **kwargs)
Scott Baker192da222015-07-08 19:01:56 -0700847 model_policy_vcpe(self.pk)
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700848
849 def delete(self, *args, **kwargs):
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700850 self.cleanup_vbng()
Scott Bakerc1584b82015-09-09 16:36:06 -0700851 self.cleanup_container()
Scott Bakere6780e92016-02-13 14:38:43 -0800852 self.cleanup_wan_container_ip()
Scott Baker4c052b22016-02-11 11:08:42 -0800853 super(VSGTenant, self).delete(*args, **kwargs)
Scott Bakere9ff7ce2015-04-14 17:19:16 -0700854
Scott Baker192da222015-07-08 19:01:56 -0700855def model_policy_vcpe(pk):
856 # TODO: this should be made in to a real model_policy
857 with transaction.atomic():
Scott Baker4c052b22016-02-11 11:08:42 -0800858 vcpe = VSGTenant.objects.select_for_update().filter(pk=pk)
Scott Baker192da222015-07-08 19:01:56 -0700859 if not vcpe:
860 return
861 vcpe = vcpe[0]
Scott Bakere6780e92016-02-13 14:38:43 -0800862 vcpe.manage_wan_container_ip()
Scott Bakerc1584b82015-09-09 16:36:06 -0700863 vcpe.manage_container()
Scott Baker192da222015-07-08 19:01:56 -0700864 vcpe.manage_vbng()
865 vcpe.manage_bbs_account()
866 vcpe.cleanup_orphans()
867
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700868#----------------------------------------------------------------------------
869# vBNG
870#----------------------------------------------------------------------------
871
872class VBNGService(Service):
Scott Bakerd9133342015-07-06 14:38:02 -0700873 KIND = VBNG_KIND
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700874
Scott Baker6667ddc2015-10-20 22:12:51 -0700875 simple_attributes = ( ("vbng_url", ""), ) # "http://10.0.3.136:8181/onos/virtualbng/"
Scott Baker21d18932015-07-21 18:24:21 -0700876
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700877 class Meta:
878 app_label = "cord"
879 verbose_name = "vBNG Service"
880 proxy = True
881
Scott Baker21d18932015-07-21 18:24:21 -0700882VBNGService.setup_simple_attributes()
883
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700884class VBNGTenant(Tenant):
885 class Meta:
886 proxy = True
887
Scott Bakerd9133342015-07-06 14:38:02 -0700888 KIND = VBNG_KIND
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700889
Scott Baker61c8e8d2015-06-02 14:34:04 -0700890 default_attributes = {"routeable_subnet": "",
Scott Bakercf155f42015-06-08 19:09:53 -0700891 "mapped_ip": "",
892 "mapped_mac": "",
893 "mapped_hostname": ""}
Scott Bakerfa5cdd62015-04-20 09:16:17 -0700894
895 @property
896 def routeable_subnet(self):
897 return self.get_attribute("routeable_subnet", self.default_attributes["routeable_subnet"])
898
899 @routeable_subnet.setter
900 def routeable_subnet(self, value):
901 self.set_attribute("routeable_subnet", value)
Scott Baker61c8e8d2015-06-02 14:34:04 -0700902
903 @property
904 def mapped_ip(self):
905 return self.get_attribute("mapped_ip", self.default_attributes["mapped_ip"])
906
907 @mapped_ip.setter
908 def mapped_ip(self, value):
909 self.set_attribute("mapped_ip", value)
Scott Bakercf155f42015-06-08 19:09:53 -0700910
911 @property
912 def mapped_mac(self):
913 return self.get_attribute("mapped_mac", self.default_attributes["mapped_mac"])
914
915 @mapped_mac.setter
916 def mapped_mac(self, value):
917 self.set_attribute("mapped_mac", value)
918
919 @property
920 def mapped_hostname(self):
921 return self.get_attribute("mapped_hostname", self.default_attributes["mapped_hostname"])
922
923 @mapped_hostname.setter
924 def mapped_hostname(self, value):
925 self.set_attribute("mapped_hostname", value)