6 from django.views.generic import TemplateView, View
8 from pprint import pprint
10 from syndicate.models import *
11 from core.models import *
12 from hpc.models import ContentProvider
13 from operator import attrgetter
14 from django import template
15 from django.views.decorators.csrf import csrf_exempt
16 from django.http import HttpResponse, HttpResponseServerError
17 from django.core import urlresolvers
18 from django.contrib.gis.geoip import GeoIP
19 from ipware.ip import get_ip
23 BLESSED_SITES = ["Stanford", "Washington", "Princeton", "GeorgiaTech", "MaxPlanck"]
25 if os.path.exists("/home/smbaker/projects/vicci/cdn/bigquery"):
26 sys.path.append("/home/smbaker/projects/vicci/cdn/bigquery")
28 sys.path.append("/opt/planetstack/hpc_wizard")
30 from planetstack_analytics import DoPlanetStackAnalytics, PlanetStackAnalytics, RED_LOAD, BLUE_LOAD
32 class DashboardWelcomeView(TemplateView):
33 template_name = 'admin/dashboard/welcome.html'
35 def get(self, request, *args, **kwargs):
36 context = self.get_context_data(**kwargs)
37 userDetails = getUserSliceInfo(request.user)
38 #context['site'] = userDetails['site']
40 context['userSliceInfo'] = userDetails['userSliceInfo']
41 context['cdnData'] = userDetails['cdnData']
42 context['cdnContentProviders'] = userDetails['cdnContentProviders']
43 return self.render_to_response(context=context)
45 class DashboardView(TemplateView):
46 head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
47 {% load admin_static %}
51 tail_template = r"{% endblock %}"
53 def get(self, request, name="hpc_historical", *args, **kwargs):
54 context = self.get_context_data(**kwargs)
56 head_template = self.head_template
57 tail_template = self.tail_template
60 # quick fix for tenant view
61 head_template = head_template + '<div id="tabs-5"></div>'
64 t = template.Template(head_template + open("/opt/planetstack/templates/admin/dashboard/%s.html" % name, "r").read() + self.tail_template)
66 userDetails = getUserSliceInfo(request.user)
67 #context['site'] = userDetails['site']
69 context['userSliceInfo'] = userDetails['userSliceInfo']
70 context['cdnData'] = userDetails['cdnData']
71 context['cdnContentProviders'] = userDetails['cdnContentProviders']
74 response_kwargs.setdefault('content_type', self.content_type)
75 return self.response_class(
\r
81 def getUserSliceInfo(user, tableFormat = False):
84 userSliceData = getSliceInfo(user)
86 # pprint("******* GET USER SLICE INFO")
87 userDetails['userSliceInfo'] = userSliceTableFormatter(userSliceData)
89 userDetails['userSliceInfo'] = userSliceData
90 userDetails['cdnData'] = getCDNOperatorData(wait=False)
91 userDetails['cdnContentProviders'] = getCDNContentProviderData()
94 class TenantCreateSlice(View):
95 def post(self, request, *args, **kwargs):
96 sliceName = request.POST.get("sliceName", "0")
97 serviceClass = request.POST.get("serviceClass", "0")
98 imageName = request.POST.get("imageName", "0")
99 actionToDo = request.POST.get("actionToDo", "0")
100 #network = request.POST.get("network","0")
101 mountDataSets = request.POST.get("mountDataSets","0")
102 if (actionToDo == "add"):
103 serviceClass = ServiceClass.objects.get(name=serviceClass)
104 site = request.user.site
105 image = Image.objects.get(name=imageName)
106 newSlice = Slice(name=sliceName,serviceClass=serviceClass,site=site,imagePreference=image,mountDataSets=mountDataSets)
108 return HttpResponse("Slice created")
110 class TenantUpdateSlice(View):
111 def post(self, request, *args, **kwargs):
\r
112 sliceName = request.POST.get("sliceName", "0")
\r
113 serviceClass = request.POST.get("serviceClass", "0")
\r
114 imageName = request.POST.get("imageName", "0")
\r
115 actionToDo = request.POST.get("actionToDo", "0")
\r
116 #network = request.POST.get("network","0")
\r
117 dataSet = request.POST.get("dataSet","0")
\r
118 slice = Slice.objects.all()
\r
119 for entry in slice:
\r
120 serviceClass = ServiceClass.objects.get(name=serviceClass)
\r
121 if(entry.name==sliceName):
\r
122 if (actionToDo == "update"):
\r
123 setattr(entry,'serviceClass',serviceClass)
\r
124 setattr(entry,'imagePreference',imageName)
\r
125 #setattr(entry,'network',network)
\r
126 setattr(entry,'mountDataSets',dataSet)
\r
129 return HttpResponse("Slice updated")
\r
131 def getTenantSliceInfo(user, tableFormat = False):
132 tenantSliceDetails = {}
133 tenantSliceData = getTenantInfo(user)
134 tenantServiceClassData = getServiceClassInfo(user)
136 tenantSliceDetails['userSliceInfo'] = userSliceTableFormatter(tenantSliceData)
137 tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
139 tenantSliceDetails['userSliceInfo'] = tenantSliceData
140 tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
141 tenantSliceDetails['image']=userSliceTableFormatter(getImageInfo(user))
142 #tenantSliceDetails['network']=userSliceTableFormatter(getNetworkInfo(user))
143 tenantSliceDetails['deploymentSites']=userSliceTableFormatter(getDeploymentSites())
144 tenantSliceDetails['sites'] = userSliceTableFormatter(getTenantSitesInfo())
145 tenantSliceDetails['mountDataSets'] = userSliceTableFormatter(getMountDataSets())
146 tenantSliceDetails['publicKey'] = getPublicKey(user)
147 return tenantSliceDetails
149 def getTenantInfo(user):
150 slices =Slice.objects.all()
153 sliceName = Slice.objects.get(id=entry.id).name
154 slice = Slice.objects.get(name=Slice.objects.get(id=entry.id).name)
155 sliceServiceClass = entry.serviceClass.name
156 preferredImage = entry.imagePreference
157 sliceDataSet = entry.mountDataSets
158 #sliceNetwork = entry.network
164 for sliver in slice.slivers.all():
165 if sliver.node.site.name in BLESSED_SITES:
166 sliceSite[sliver.node.site.name] = sliceSite.get(sliver.node.site.name,0) + 1
167 sliceImage = sliver.image.name
168 sliceNode[str(sliver)] = sliver.node.name
169 numSliver = sum(sliceSite.values())
170 numSites = len(sliceSite)
171 userSliceInfo.append({'sliceName': sliceName,'sliceServiceClass': sliceServiceClass,'preferredImage':preferredImage,'numOfSites':numSites, 'sliceSite':sliceSite,'sliceImage':sliceImage,'numOfSlivers':numSliver,'sliceDataSet':sliceDataSet,'instanceNodePair':sliceNode})
174 def getTenantSitesInfo():
176 for entry in Site.objects.all():
177 if entry.name in BLESSED_SITES:
178 tenantSiteInfo.append({'siteName':entry.name})
179 return tenantSiteInfo
181 def userSliceTableFormatter(data):
188 def getPublicKey(user):
189 users=User.objects.all()
\r
191 if (str(key.email)==str(user)):
\r
192 sshKey = key.public_key
\r
195 def getServiceClassInfo(user):
196 serviceClassList = ServiceClass.objects.all()
198 for entry in serviceClassList:
199 sliceInfo.append({'serviceClass':entry.name})
202 def getImageInfo(user):
203 imageList = Image.objects.all()
204 #imageList = ['Fedora 16 LXC rev 1.3','Hadoop','MPI']
206 for imageEntry in imageList:
207 imageInfo.append({'Image':imageEntry.name})
208 #imageInfo.append({'Image':imageEntry})
211 def createPrivateVolume(user, sliceName):
212 caps = Volume.CAP_READ_DATA | Volume.CAP_WRITE_DATA | Volume.CAP_HOST_DATA
213 getattr(Volume.default_gateway_caps,"read data") | \
214 getattr(Volume.default_gateway_caps,"write data") | \
215 getattr(Volume.default_gateway_caps,"host files")
216 v = Volume(name="private_" + sliceName, owner_id=user, description="private volume for %s" % sliceName, blocksize=61440, private=True, archive=False, default_gateway_caps = caps)
219 SYNDICATE_REPLICATE_PORTNUM = 1025
223 inuse[SYNDICATE_REPLICATE_PORTNUM] = True
224 for vs in VolumeSlice.objects.all():
225 inuse[vs.peer_portnum]=True
226 inuse[vs.replicate_portnum]=True
227 for network in Network.objects.all():
228 network_ports = [x.strip() for x in network.ports.split(",")]
229 for network_port in network_ports:
231 inuse[int(network_port)] = True
233 # in case someone has put a malformed port number in the list
235 for i in range(1025, 65535):
236 if not inuse.get(i,False):
240 def mountVolume(sliceName, volumeName, readWrite):
241 slice = Slice.objects.get(name=sliceName)
242 volume = Volume.objects.get(name=volumeName)
243 # choose some unused port numbers
244 flags = Volume.CAP_READ_DATA
246 flags = flags | Volume.CAP_WRITE_DATA
247 vs = VolumeSlice(volume_id = volume, slice_id = slice, gateway_caps=flags, peer_portnum = get_free_port(), replicate_portnum = SYNDICATE_REPLICATE_PORTNUM)
250 def hasPrivateVolume(sliceName):
251 slice = Slice.objects.get(name=sliceName)
252 for vs in VolumeSlice.objects.filter(slice_id=slice):
253 if vs.volume_id.private:
257 def getMountDataSets():
259 for volume in Volume.objects.all():
\r
260 if not volume.private:
\r
261 dataSetInfo.append({'DataSet': volume.name})
\r
265 def getNetworkInfo(user):
266 #networkList = Network.objects.all()
267 networkList = ['Private Only','Private and Publicly Routable']
269 for networkEntry in networkList:
270 #networkInfo.append({'Network':networkEntry.name})
271 networkInfo.append({'Network':networkEntry})
274 def getDeploymentSites():
275 deploymentList = Deployment.objects.all()
277 for entry in deploymentList:
278 deploymentInfo.append({'DeploymentSite':entry.name})
279 return deploymentInfo
281 def getSliceInfo(user):
282 sliceList = Slice.objects.all()
283 slicePrivs = SlicePrivilege.objects.filter(user=user)
285 for entry in slicePrivs:
287 slicename = Slice.objects.get(id=entry.slice.id).name
288 slice = Slice.objects.get(name=Slice.objects.get(id=entry.slice.id).name)
289 sliverList=Sliver.objects.all()
291 for sliver in slice.slivers.all():
292 #sites_used['deploymentSites'] = sliver.node.deployment.name
293 # sites_used[sliver.image.name] = sliver.image.name
294 sites_used[sliver.node.site.name] = sliver.numberCores
295 sliceid = Slice.objects.get(id=entry.slice.id).id
297 sliverList = Sliver.objects.filter(slice=entry.slice.id)
300 if x.node.site not in siteList:
301 siteList[x.node.site] = 1
302 slivercount = len(sliverList)
303 sitecount = len(siteList)
305 traceback.print_exc()
309 userSliceInfo.append({'slicename': slicename, 'sliceid':sliceid,
310 'sitesUsed':sites_used,
311 'role': SliceRole.objects.get(id=entry.role.id).role,
312 'slivercount': slivercount,
313 'sitecount':sitecount})
317 def getCDNContentProviderData():
319 for dm_cp in ContentProvider.objects.all():
320 cp = {"name": dm_cp.name,
321 "account": dm_cp.account}
326 def getCDNOperatorData(randomizeData = False, wait=True):
327 HPC_SLICE_NAME = "HyperCache"
329 bq = PlanetStackAnalytics()
331 rows = bq.get_cached_query_results(bq.compose_cached_query(), wait)
333 # wait=False on the first time the Dashboard is opened. This means we might
334 # not have any rows yet. The dashboard code polls every 30 seconds, so it
335 # will eventually pick them up.
338 rows = bq.postprocess_results(rows, filter={"event": "hpc_heartbeat"}, maxi=["cpu"], count=["hostname"], computed=["bytes_sent/elapsed"], groupBy=["Time","site"], maxDeltaTime=80)
340 # dictionaryize the statistics rows by site name
343 stats_rows[row["site"]] = row
347 slice = Slice.objects.filter(name=HPC_SLICE_NAME)
349 slice_slivers = list(slice[0].slivers.all())
354 for site in Site.objects.all():
355 # compute number of slivers allocated in the data model
356 allocated_slivers = 0
357 for sliver in slice_slivers:
358 if sliver.node.site == site:
359 allocated_slivers = allocated_slivers + 1
361 stats_row = stats_rows.get(site.name,{})
363 max_cpu = stats_row.get("max_avg_cpu", stats_row.get("max_cpu",0))
364 cpu=float(max_cpu)/100.0
365 hotness = max(0.0, ((cpu*RED_LOAD) - BLUE_LOAD)/(RED_LOAD-BLUE_LOAD))
367 # format it to what that CDN Operations View is expecting
368 new_row = {"lat": float(site.location.longitude),
369 "long": float(site.location.longitude),
370 "lat": float(site.location.latitude),
372 "numNodes": int(site.nodes.count()),
373 "activeHPCSlivers": int(stats_row.get("count_hostname", 0)), # measured number of slivers, from bigquery statistics
374 "numHPCSlivers": allocated_slivers, # allocated number of slivers, from data model
375 "siteUrl": str(site.site_url),
376 "bandwidth": stats_row.get("sum_computed_bytes_sent_div_elapsed",0),
378 "hot": float(hotness)}
379 new_rows[str(site.name)] = new_row
381 # get rid of sites with 0 slivers that overlap other sites with >0 slivers
382 for (k,v) in new_rows.items():
384 if v["numHPCSlivers"]==0:
385 for v2 in new_rows.values():
386 if (v!=v2) and (v2["numHPCSlivers"]>=0):
387 d = haversine(v["lat"],v["long"],v2["lat"],v2["long"])
395 def getPageSummary(request):
396 slice = request.GET.get('slice', None)
397 site = request.GET.get('site', None)
398 node = request.GET.get('node', None)
400 class SimulatorView(View):
401 def get(self, request, **kwargs):
402 sim = json.loads(file("/tmp/simulator.json","r").read())
403 text = "<html><head></head><body>"
404 text += "Iteration: %d<br>" % sim["iteration"]
405 text += "Elapsed since report %d<br><br>" % sim["elapsed_since_report"]
406 text += "<table border=1>"
407 text += "<tr><th>site</th><th>trend</th><th>weight</th><th>bytes_sent</th><th>hot</th></tr>"
408 for site in sim["site_load"].values():
410 text += "<td>%s</td><td>%0.2f</td><td>%0.2f</td><td>%d</td><td>%0.2f</td>" % \
411 (site["name"], site["trend"], site["weight"], site["bytes_sent"], site["load_frac"])
414 text += "</body></html>"
415 return HttpResponse(text)
417 class DashboardUserSiteView(View):
418 def get(self, request, **kwargs):
419 return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
421 class TenantViewData(View):
422 def get(self, request, **kwargs):
423 return HttpResponse(json.dumps(getTenantSliceInfo(request.user, True)), mimetype='application/javascript')
425 def haversine(site_lat, site_lon, lat, lon):
427 if lat and lon and site_lat and site_lon:
428 site_lat = float(site_lat)
429 site_lon = float(site_lon)
433 a = math.sin( math.radians((lat - site_lat)/2.0) )**2 + math.cos( math.radians(lat) )*math.cos( math.radians(site_lat) )*(math.sin( math.radians((lon - site_lon)/2.0 ) )**2)
434 c = 2 * math.atan2( math.sqrt(a), math.sqrt(1 - a) )
439 def siteSortKey(site, slice=None, count=None, lat=None, lon=None):
440 # try to pick a site we're already using
441 has_slivers_here=False
443 for sliver in slice.slivers.all():
444 if sliver.node.site.name == site.name:
445 has_slivers_here=True
448 d = haversine(site.location.latitude, site.location.longitude, lat, lon)
450 return (-has_slivers_here, d)
452 def tenant_pick_sites(user, user_ip=None, slice=None, count=None):
453 """ Returns list of sites, sorted from most favorable to least favorable """
457 client_geo = GeoIP().city(user_ip)
459 lat=float(client_geo["latitude"])
460 lon=float(client_geo["longitude"])
462 print "exception in geo code"
463 traceback.print_exc()
465 sites = Site.objects.all()
466 sites = [x for x in sites if x.name in BLESSED_SITES]
467 sites = sorted(sites, key=functools.partial(siteSortKey, slice=slice, count=count, lat=lat, lon=lon))
471 def slice_increase_slivers(user, user_ip, siteList, slice, count, noAct=False):
474 # let's compute how many slivers are in use in each node of each site
475 for site in siteList:
476 site.nodeList = list(site.nodes.all())
477 for node in site.nodeList:
479 for sliver in node.slivers.all():
480 if sliver.slice.id == slice.id:
481 node.sliverCount = node.sliverCount + 1
483 # Allocate slivers to nodes
484 # for now, assume we want to allocate all slivers from the same site
485 nodes = siteList[0].nodeList
487 # Sort the node list by number of slivers per node, then pick the
488 # node with the least number of slivers.
489 nodes = sorted(nodes, key=attrgetter("sliverCount"))
492 print "adding sliver at node", node.name, "of site", node.site.name
495 sliver = Sliver(name=node.name,
498 image = Image.objects.all()[0],
499 creator = User.objects.get(email=user),
500 deploymentNetwork=node.deployment,
504 node.sliverCount = node.sliverCount + 1
508 sitesChanged[node.site.name] = sitesChanged.get(node.site.name,0) + 1
512 def slice_decrease_slivers(user, siteList, slice, count, noAct=False):
516 siteNames = [site.name for site in siteList]
520 for sliver in slice.slivers.all():
521 if(not siteNames) or (sliver.node.site.name in siteNames):
\r
523 sliverList[sliver.name]=node.name
525 for key in sliverList:
527 sliver = Sliver.objects.filter(name=key)[0]
\r
529 print "deleting sliver",sliverList[key],"at node",sliver.node.name
\r
531 sitesChanged[sliver.node.site.name] = sitesChanged.get(sliver.node.site.name,0) - 1
\r
535 class TenantDeleteSliceView(View):
536 def post(self,request):
\r
537 sliceName = request.POST.get("sliceName",None)
\r
538 slice = Slice.objects.get(name=sliceName)
\r
539 #print slice, slice.id
\r
540 sliceToDel=Slice(name=sliceName, id=slice.id)
\r
542 return HttpResponse("Slice deleted")
544 class TenantAddOrRemoveSliverView(View):
545 """ Add or remove slivers from a Slice
548 siteName - name of site. If not specified, PlanetStack will pick the
550 actionToDo - [add | rem]
551 count - number of slivers to add or remove
552 sliceName - name of slice
553 noAct - if set, no changes will be made to db, but result will still
554 show which sites would have been modified.
557 Dictionary of sites that were modified, and the count of nodes
558 that were added or removed at each site.
560 def post(self, request, *args, **kwargs):
561 siteName = request.POST.get("siteName", None)
562 actionToDo = request.POST.get("actionToDo", None)
563 count = int(request.POST.get("count","0"))
564 sliceName = request.POST.get("slice", None)
565 noAct = request.POST.get("noAct", False)
568 return HttpResponseServerError("No slice name given")
570 slice = Slice.objects.get(name=sliceName)
573 siteList = [Site.objects.get(name=siteName)]
577 if (actionToDo == "add"):
578 user_ip = request.GET.get("ip", get_ip(request))
579 if (siteList is None):
580 siteList = tenant_pick_sites(user, user_ip, slice, count)
582 sitesChanged = slice_increase_slivers(request.user, user_ip, siteList, slice, count, noAct)
583 elif (actionToDo == "rem"):
584 sitesChanged = slice_decrease_slivers(request.user, siteList, slice, count, noAct)
586 return HttpResponseServerError("Unknown actionToDo %s" % actionToDo)
588 return HttpResponse(json.dumps(sitesChanged), mimetype='application/javascript')
590 def get(self, request, *args, **kwargs):
591 request.POST = request.GET
592 return self.post(request, *args, **kwargs) # for testing REST in browser
593 #return HttpResponseServerError("GET is not supported")
595 class TenantPickSitesView(View):
596 """ primarily just for testing purposes """
597 def get(self, request, *args, **kwargs):
598 count = request.GET.get("count","0")
599 slice = request.GET.get("slice",None)
601 slice = Slice.objects.get(name=slice)
602 ip = request.GET.get("ip", get_ip(request))
603 sites = tenant_pick_sites(request.user, user_ip=ip, count=0, slice=slice)
604 sites = [x.name for x in sites]
605 return HttpResponse(json.dumps(sites), mimetype='application/javascript')
607 class DashboardSummaryAjaxView(View):
608 def get(self, request, **kwargs):
612 return float(sum(x))/len(x)
614 sites = getCDNOperatorData().values()
616 sites = [site for site in sites if site["numHPCSlivers"]>0]
618 total_slivers = sum( [site["numHPCSlivers"] for site in sites] )
619 total_bandwidth = sum( [site["bandwidth"] for site in sites] )
620 average_cpu = int(avg( [site["load"] for site in sites] ))
622 result= {"total_slivers": total_slivers,
623 "total_bandwidth": total_bandwidth,
624 "average_cpu": average_cpu}
626 return HttpResponse(json.dumps(result), mimetype='application/javascript')
628 class DashboardAddOrRemoveSliverView(View):
629 # TODO: deprecate this view in favor of using TenantAddOrRemoveSliverView
631 def post(self, request, *args, **kwargs):
632 siteName = request.POST.get("site", None)
633 actionToDo = request.POST.get("actionToDo", "0")
635 siteList = [Site.objects.get(name=siteName)]
636 slice = Slice.objects.get(name="HyperCache")
638 if (actionToDo == "add"):
639 user_ip = request.GET.get("ip", get_ip(request))
640 slice_increase_slivers(request.user, user_ip, siteList, slice, 1)
641 elif (actionToDo == "rem"):
642 slice_decrease_slivers(request.user, siteList, slice, 1)
645 print 'Ask for site: ' + siteName + ' to ' + actionToDo + ' another HPC Sliver'
646 return HttpResponse('This is POST request ')
648 class DashboardAjaxView(View):
649 def get(self, request, **kwargs):
650 return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
652 class DashboardAnalyticsAjaxView(View):
653 def get(self, request, name="hello_world", **kwargs):
654 if (name == "hpcSummary"):
655 return HttpResponse(json.dumps(hpc_wizard.get_hpc_wizard().get_summary_for_view()), mimetype='application/javascript')
656 elif (name == "hpcUserSite"):
657 return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
658 elif (name == "hpcMap"):
659 return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
660 elif (name == "bigquery"):
661 (mimetype, data) = DoPlanetStackAnalytics(request)
662 return HttpResponse(data, mimetype=mimetype)
664 return HttpResponse(json.dumps("Unknown"), mimetype='application/javascript')