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