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