ask the PI to click save
[plstackapi.git] / planetstack / core / dashboard / views / tenant.py
1 from view_common import *
2 from core.models import *
3 import functools
4 from django.contrib.auth.models import BaseUserManager
5 from django.core import serializers
6 from django.core.mail import EmailMultiAlternatives
7 import json
8
9 BLESSED_DEPLOYMENTS = ["US-MaxPlanck", "US-GeorgiaTech", "US-Princeton", "US-Washington", "US-Stanford"]
10
11 class RequestAccessView(View):
12     def post(self, request, *args, **kwargs):
13         email = request.POST.get("email", "0")
14         firstname = request.POST.get("firstname", "0")
15         lastname = request.POST.get("lastname", "0")
16         site = request.POST.get("site","0")
17         # see if it already exists
18         user=User.objects.filter(email=BaseUserManager.normalize_email(email))
19         if (user):
20              user = user[0]
21              if user.is_active:
22                  # force a new email to be sent
23                  user.is_registering=True
24                  user.save()
25                  return HttpResponse(json.dumps({"error": "already_approved"}), content_type='application/javascript')
26              else:
27                  return HttpResponse(json.dumps({"error": "already_pending"}), content_type='application/javascript')
28
29         user=User.deleted_objects.filter(email=BaseUserManager.normalize_email(email))
30         if (user):
31             return HttpResponse(json.dumps({"error": "is_deleted"}), content_type='application/javascript')
32
33         user = User(
34             email=BaseUserManager.normalize_email(email),
35             firstname=firstname,
36             lastname=lastname,
37             is_active=False,
38             is_admin=False,
39             is_registering=True
40         )
41         user.save()
42         user.site=Site.objects.get(name=site)
43         user.save(update_fields=['site'])
44         sitePriv = SitePrivilege.objects.filter(site=user.site)
45         userId = user.id
46         userUrl = "http://"+request.get_host()+"/admin/core/user/"+str(userId)
47         for sp in sitePriv:
48                 subject, from_email, to = 'Authorize OpenCloud User Account', 'support@opencloud.us', str(sp.user)
49                 text_content = 'This is an important message.'
50                 html_content = """<p>Please authorize the following user on site """+site+""": <br><br>User: """+firstname+""" """+lastname+"""<br>Email: """+email+"""<br><br>
51 Check the checkbox next to Is Active property at <a href="""+userUrl+"""> this link</a> to authorize the user, and then click the Save button. If you do not recognize this individual, or otherwise do not want to approve this account, please ignore this email. If you do not approve this request in 48 hours, the account will automatically be deleted.</p>"""
52                 msg = EmailMultiAlternatives(subject,text_content, from_email, [to])
53                 msg.attach_alternative(html_content, "text/html")
54                 msg.send()
55         return HttpResponse(serializers.serialize("json",[user,]), content_type='application/javascript')
56
57 class TenantCreateSlice(View):
58     def post(self, request, *args, **kwargs):
59         if request.user.isReadOnlyUser():
60             return HttpResponseForbidden("User is in read-only mode")
61
62         sliceName = request.POST.get("sliceName", "0")
63         serviceClass = request.POST.get("serviceClass", "0")
64         imageName = request.POST.get("imageName", "0")
65         actionToDo = request.POST.get("actionToDo", "0")
66         networkPorts = request.POST.get("network","0")
67         mountDataSets = request.POST.get("mountDataSets","0")
68         privateVolume = request.POST.get("privateVolume","0")
69         userEmail = request.POST.get("userEmail","0")
70         if (actionToDo == "add"):
71            serviceClass = ServiceClass.objects.get(name=serviceClass)
72            site = request.user.site
73            image = Image.objects.get(name=imageName)
74            newSlice = Slice(name=sliceName,serviceClass=serviceClass,site=site,image_preference=image,mount_data_sets=mountDataSets)
75            newSlice.save()
76            privateTemplate="Private"
77            publicTemplate="Public shared IPv4"\r
78            privateNetworkName = sliceName+"-"+privateTemplate\r
79            publicNetworkName = sliceName+"-"+publicTemplate\r
80            slice=Slice.objects.get(name=sliceName)\r
81            addNetwork(privateNetworkName,privateTemplate,slice)\r
82            addNetwork(publicNetworkName,publicTemplate,slice)\r
83            addOrModifyPorts(networkPorts,sliceName)\r
84            if privateVolume=="true":\r
85                 privateVolForSlice(request.user,sliceName)
86            slicePrivs=SlicePrivilege(user=User.objects.get(email=userEmail),slice=Slice.objects.get(name=sliceName),role=SliceRole.objects.get(role="admin"))
87            slicePrivs.save()
88         return HttpResponse(json.dumps("Slice created"), content_type='application/javascript')
89
90 class TenantAddUser(View):
91     def post(self, request, *args, **kwargs):
92         if request.user.isReadOnlyUser():
93             return HttpResponseForbidden("User is in read-only mode")
94
95         sliceName = request.POST.get("sliceName", "0")
96         userEmail = request.POST.get("userEmail","0")
97         slicePrivs=SlicePrivilege(user=User.objects.get(email=userEmail),slice=Slice.objects.get(name=sliceName),role=SliceRole.objects.get(role="admin"))
98         slicePrivs.save()
99         return HttpResponse(json.dumps("Slice created"), content_type='application/javascript')
100
101 def privateVolForSlice(user,sliceName):
102         if not hasPrivateVolume(sliceName):\r
103            volumeName=createPrivateVolume(user,sliceName)\r
104            readWrite="true"\r
105            mountVolume(sliceName,volumeName,readWrite)
106
107 class TenantUpdateSlice(View):
108     def post(self, request, *args, **kwargs):\r
109         if request.user.isReadOnlyUser():\r
110             return HttpResponseForbidden("User is in read-only mode")\r
111 \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         networkPorts = request.POST.get("networkPorts","0")\r
117         dataSet = request.POST.get("dataSet","0")\r
118         privateVolume = request.POST.get("privateVolume","0")\r
119         slice = Slice.objects.all()\r
120         for entry in slice:\r
121                 serviceClass = ServiceClass.objects.get(name=serviceClass)\r
122                 if(entry.name==sliceName):\r
123                          if (actionToDo == "update"):\r
124                                 setattr(entry,'serviceClass',serviceClass)\r
125                                 setattr(entry,'image_preference',imageName)\r
126                                 setattr(entry,'mount_data_sets',dataSet)\r
127                                 entry.save()\r
128                                 break\r
129         addOrModifyPorts(networkPorts,sliceName)\r
130         if privateVolume=="true":\r
131                 privateVolForSlice(request.user,sliceName)\r
132         return HttpResponse(json.dumps("Slice updated"), content_type='application/javascript')\r
133 \r
134 def addNetwork(name,template,sliceName):\r
135         networkTemplate=NetworkTemplate.objects.get(name=template)\r
136         newNetwork = Network(name = name,\r
137                               template = networkTemplate,\r
138                               owner = sliceName)\r
139         newNetwork.save()\r
140         addNetworkSlice(newNetwork,sliceName)\r
141 \r
142 def addNetworkSlice(networkSlice,sliceName):\r
143         newNetworkSlice=NetworkSlice(network =networkSlice,\r
144                                      slice=sliceName)\r
145         newNetworkSlice.save()\r
146 \r
147 def addOrModifyPorts(networkPorts,sliceName):\r
148         networkList = Network.objects.all()\r
149         networkInfo = []\r
150         if networkPorts:\r
151            for networkEntry in networkList:\r
152                networkSlices = networkEntry.slices.all()\r
153                for slice in networkSlices:\r
154                    if slice.name==sliceName:\r
155                           if networkEntry.template.name=="Public shared IPv4":\r
156                              setattr(networkEntry,'ports',networkPorts)\r
157                              networkEntry.save()\r
158 \r
159 def getTenantSliceInfo(user, tableFormat = False):
160     tenantSliceDetails = {}
161     tenantSliceData = getTenantInfo(user)
162     tenantServiceClassData = getServiceClassInfo(user)
163     if (tableFormat):
164        tenantSliceDetails['userSliceInfo'] = userSliceTableFormatter(tenantSliceData)
165        tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
166     else:
167        tenantSliceDetails['userSliceInfo'] = tenantSliceData
168     tenantSliceDetails['sliceServiceClass']=userSliceTableFormatter(tenantServiceClassData)
169     tenantSliceDetails['image']=userSliceTableFormatter(getImageInfo(user))
170     tenantSliceDetails['deploymentSites']=userSliceTableFormatter(getDeploymentSites())
171     #tenantSliceDetails['sites'] = userSliceTableFormatter(getTenantSitesInfo())
172     tenantSliceDetails['mountDataSets'] = userSliceTableFormatter(getMountDataSets())
173     tenantSliceDetails['publicKey'] = getPublicKey(user)
174     tenantSliceDetails['availableSites']=userSliceTableFormatter(getAvailableSites())
175     tenantSliceDetails['role']=getUserRole(user)
176     tenantSliceDetails['siteUsers']=getSiteUsers(user)
177     return tenantSliceDetails
178
179 def getSiteUsers(user):
180         users = User.objects.filter(site=user.site)
181         siteUsers=[]
182         for entry in users:
183                 siteUsers.append(str(entry))
184         return siteUsers
185
186
187 def getUserRole(user):
188         sp=SitePrivilege.objects.filter(user=user)
189         for entry in sp:
190                 return str(entry.role)
191
192
193 def getTenantInfo(user):
194     slices =Slice.objects.all()
195     userSliceInfo = []
196     for entry in slices:
197        if (entry.site == user.site):
198            sliceName = Slice.objects.get(id=entry.id).name
199            slice = Slice.objects.get(name=Slice.objects.get(id=entry.id).name)
200            sliceServiceClass = entry.serviceClass.name
201            preferredImage =  entry.image_preference
202            #sliceDataSet = entry.mount_data_sets
203            sliceNetwork = {}
204            numSliver = 0
205            sliceImage=""
206            sliceSite = {}
207            sliceNode = {}
208            sliceInstance= {}
209            #createPrivateVolume(user,sliceName)
210            available_sites = getAvailableSites()
211            for sliver in slice.slivers.all():
212                 if sliver.node.site.name in available_sites:
213                     sliceSite[sliver.node.site.name] = sliceSite.get(sliver.node.site.name,0) + 1
214                     sliceImage = sliver.image.name
215                     sliceNode[str(sliver)] = sliver.node.name
216            numSliver = sum(sliceSite.values())
217            numSites = len(sliceSite)
218            userSliceInfo.append({'sliceName': sliceName,'sliceServiceClass': sliceServiceClass,'preferredImage':preferredImage,'numOfSites':numSites, 'sliceSite':sliceSite,'sliceImage':sliceImage,'numOfSlivers':numSliver,'instanceNodePair':sliceNode})
219     return userSliceInfo
220
221 def getTenantSitesInfo():
222         availableSites=getAvailableSites()
223         tenantSiteInfo=[]
224         for entry in Site.objects.all():
225             if entry.name in availableSites:
226                  tenantSiteInfo.append({'siteName':entry.name})
227         return tenantSiteInfo
228
229 def getPublicKey(user):
230         users=User.objects.all()\r
231         for key in users:\r
232                 if (str(key.email)==str(user)):\r
233                         sshKey = key.public_key\r
234         return sshKey
235
236 def getServiceClassInfo(user):
237     serviceClassList = ServiceClass.objects.all()
238     sliceInfo = []
239     for entry in serviceClassList:
240           sliceInfo.append({'serviceClass':entry.name})
241     return sliceInfo
242
243 def getImageInfo(user):
244     #imageList = Image.objects.all()
245     #imageInfo = []
246     #for imageEntry in imageList:
247           #imageInfo.append({'Image':imageEntry.name})
248     imageInfo = []
249     tempImageInfo = []
250     length = len(BLESSED_DEPLOYMENTS)
251     for deployment in Deployment.objects.all():
252         if deployment.name in BLESSED_DEPLOYMENTS:
253             for x in deployment.imagedeployments.all():
254                 tempImageInfo.append(x.image.name)
255     temp = {}
256     for i in set(tempImageInfo):
257         temp[i] = tempImageInfo.count(i)
258     for key in temp:
259         if temp[key]>1:
260                 imageInfo.append(key)
261     return imageInfo
262
263 def createPrivateVolume(user, sliceName):
264     caps = Volume.CAP_READ_DATA | Volume.CAP_WRITE_DATA | Volume.CAP_HOST_DATA
265     getattr(Volume.default_gateway_caps,"read data") | \
266            getattr(Volume.default_gateway_caps,"write data") | \
267            getattr(Volume.default_gateway_caps,"host files")
268     v = Volume(name="private_" + sliceName, owner_id=user, description="private volume for %s" % sliceName, blocksize=61440, private=True, archive=False, default_gateway_caps = caps)
269     v.save()
270     return v
271
272 SYNDICATE_REPLICATE_PORTNUM = 1025
273
274 def get_free_port():
275     inuse={}
276     inuse[SYNDICATE_REPLICATE_PORTNUM] = True
277     for vs in VolumeSlice.objects.all():
278         inuse[vs.peer_portnum]=True
279         inuse[vs.replicate_portnum]=True
280     for network in Network.objects.all():
281         if not network.ports:
282             continue
283         network_ports = [x.strip() for x in network.ports.split(",")]
284         for network_port in network_ports:
285             try:
286                 inuse[int(network_port)] = True
287             except:
288                 # in case someone has put a malformed port number in the list
289                 pass
290     for i in range(1025, 65535):
291         if not inuse.get(i,False):
292             return i
293     return False
294
295 def mountVolume(sliceName, volumeName, readWrite):
296     slice = Slice.objects.get(name=sliceName)
297     volume = Volume.objects.get(name=volumeName)
298     # choose some unused port numbers
299     flags = Volume.CAP_READ_DATA
300     if readWrite:
301         flags = flags | Volume.CAP_WRITE_DATA
302     vs = VolumeSlice(volume_id = volume, slice_id = slice, gateway_caps=flags, peer_portnum = get_free_port(), replicate_portnum = SYNDICATE_REPLICATE_PORTNUM)
303     vs.save()
304
305 def hasPrivateVolume(sliceName):
306      slice = Slice.objects.get(name=sliceName)
307      for vs in VolumeSlice.objects.filter(slice_id=slice):
308          if vs.volume_id.private:
309              return True
310      return False
311
312 def getMountDataSets():
313         dataSetInfo=[]\r
314         for volume in Volume.objects.all():\r
315             if not volume.private:\r
316                 dataSetInfo.append({'DataSet': volume.name})\r
317 \r
318         return dataSetInfo
319
320 def getDeploymentSites():
321     deploymentList = Deployment.objects.all()
322     deploymentInfo = []
323     for entry in deploymentList:
324         deploymentInfo.append({'DeploymentSite':entry.name})
325     return deploymentInfo
326
327 def getAvailableSites():
328     available_sites = []
329     for deployment in Deployment.objects.all():
330         if deployment.name in BLESSED_DEPLOYMENTS:
331             for x in deployment.sitedeployments.all():
332                 if x.site.nodes.all():
333                         available_sites.append(x.site.name)
334     return list(set(available_sites))
335
336 class TenantDeleteSliceView(View):
337         def post(self,request):\r
338                 if request.user.isReadOnlyUser():\r
339                     return HttpResponseForbidden("User is in read-only mode")\r
340                 sliceName = request.POST.get("sliceName",None)\r
341                 slice = Slice.objects.get(name=sliceName)\r
342                 #print slice, slice.id\r
343                 sliceToDel=Slice(name=sliceName, id=slice.id)\r
344                 sliceToDel.delete()
345                 return HttpResponse(json.dumps("Slice deleted"), content_type='application/javascript')
346
347 class TenantAddOrRemoveSliverView(View):
348     """ Add or remove slivers from a Slice
349
350         Arguments:
351             siteName - name of site. If not specified, PlanetStack will pick the
352                        best site.,
353             actionToDo - [add | rem]
354             count - number of slivers to add or remove
355             sliceName - name of slice
356             noAct - if set, no changes will be made to db, but result will still
357                     show which sites would have been modified.
358
359         Returns:
360             Dictionary of sites that were modified, and the count of nodes
361             that were added or removed at each site.
362     """
363     def post(self, request, *args, **kwargs):
364         siteName = request.POST.get("siteName", None)
365         actionToDo = request.POST.get("actionToDo", None)
366         count = int(request.POST.get("count","0"))
367         sliceName = request.POST.get("slice", None)
368         imageName = request.POST.get("image",None)
369         noAct = request.POST.get("noAct", False)
370
371         if not sliceName:
372             return HttpResponseServerError("No slice name given")
373
374         slice = Slice.objects.get(name=sliceName)
375         image = Image.objects.get(name=imageName)
376
377         if siteName:
378             siteList = [Site.objects.get(name=siteName)]
379         else:
380             siteList = None
381
382         if (actionToDo == "add"):
383             user_ip = request.GET.get("ip", get_ip(request))
384             if (siteList is None):
385                 siteList = tenant_pick_sites(user, user_ip, slice, count)
386
387             sitesChanged = slice_increase_slivers(request.user, user_ip, siteList, slice, image, count, noAct)
388         elif (actionToDo == "rem"):
389             sitesChanged = slice_decrease_slivers(request.user, siteList, slice, count, noAct)
390         else:
391             return HttpResponseServerError("Unknown actionToDo %s" % actionToDo)
392
393         return HttpResponse(json.dumps(sitesChanged), content_type='application/javascript')
394
395     def get(self, request, *args, **kwargs):
396         request.POST = request.GET
397         return self.post(request, *args, **kwargs)  # for testing REST in browser
398         #return HttpResponseServerError("GET is not supported")
399
400 class TenantPickSitesView(View):
401     """ primarily just for testing purposes """
402     def get(self, request, *args, **kwargs):
403         count = request.GET.get("count","0")
404         slice = request.GET.get("slice",None)
405         if slice:
406             slice = Slice.objects.get(name=slice)
407         ip = request.GET.get("ip", get_ip(request))
408         sites = tenant_pick_sites(request.user, user_ip=ip, count=0, slice=slice)
409         sites = [x.name for x in sites]
410         return HttpResponse(json.dumps(sites), content_type='application/javascript')
411
412 def siteSortKey(site, slice=None, count=None, lat=None, lon=None):
413     # try to pick a site we're already using
414     has_slivers_here=False
415     if slice:
416         for sliver in slice.slivers.all():
417             if sliver.node.site.name == site.name:
418                 has_slivers_here=True
419
420     # Haversine method
421     d = haversine(site.location.latitude, site.location.longitude, lat, lon)
422
423     return (-has_slivers_here, d)
424
425 def tenant_pick_sites(user, user_ip=None, slice=None, count=None):
426     """ Returns list of sites, sorted from most favorable to least favorable """
427     lat=None
428     lon=None
429     try:
430         client_geo = GeoIP().city(user_ip)
431         if client_geo:
432             lat=float(client_geo["latitude"])
433             lon=float(client_geo["longitude"])
434     except:
435         print "exception in geo code"
436         traceback.print_exc()
437
438     available_sites = getAvailableSites()
439     sites = Site.objects.all()
440     sites = [x for x in sites if x.name in available_sites]
441     sites = sorted(sites, key=functools.partial(siteSortKey, slice=slice, count=count, lat=lat, lon=lon))
442
443     return sites
444
445 class TenantViewData(View):
446     def get(self, request, **kwargs):
447         return HttpResponse(json.dumps(getTenantSliceInfo(request.user, True)), content_type='application/javascript')
448
449 class RequestAccountView(View):
450     def get(self, request, **kwargs):
451         return HttpResponse()