blob: 5a450335d031784bc6660eae75f80911fed5840e [file] [log] [blame]
Scott Bakerc7325a42014-05-30 16:06:46 -07001from view_common import *
Scott Baker866c5b32014-08-29 11:34:00 -07002from core.models import *
Scott Bakerc7325a42014-05-30 16:06:46 -07003import functools
Scott Baker866c5b32014-08-29 11:34:00 -07004from django.contrib.auth.models import BaseUserManager
5from django.core import serializers
6from django.core.mail import EmailMultiAlternatives
Scott Baker28e2e3a2015-01-28 16:03:40 -08007import json
Scott Bakerc7325a42014-05-30 16:06:46 -07008
Scott Baker866c5b32014-08-29 11:34:00 -07009BLESSED_DEPLOYMENTS = ["US-MaxPlanck", "US-GeorgiaTech", "US-Princeton", "US-Washington", "US-Stanford"]
10
11class 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")
Scott Baker28e2e3a2015-01-28 16:03:40 -080017 # 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
Scott Baker150ee862015-01-28 16:11:35 -080029 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
Scott Baker866c5b32014-08-29 11:34:00 -070033 user = User(
34 email=BaseUserManager.normalize_email(email),
35 firstname=firstname,
36 lastname=lastname,
Scott Bakerbb2fb7e2015-01-27 23:07:51 -080037 is_active=False,
Scott Baker28e2e3a2015-01-28 16:03:40 -080038 is_admin=False,
39 is_registering=True
Scott Baker866c5b32014-08-29 11:34:00 -070040 )
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>
Scott Baker5ced6ab2015-01-28 16:38:05 -080051Check 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>"""
Scott Baker866c5b32014-08-29 11:34:00 -070052 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')
Scott Bakerc7325a42014-05-30 16:06:46 -070056
57class 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")
Scott Baker866c5b32014-08-29 11:34:00 -070069 userEmail = request.POST.get("userEmail","0")
Scott Bakerc7325a42014-05-30 16:06:46 -070070 if (actionToDo == "add"):
71 serviceClass = ServiceClass.objects.get(name=serviceClass)
72 site = request.user.site
73 image = Image.objects.get(name=imageName)
Scott Bakeree9736d2015-01-03 16:26:38 -080074 newSlice = Slice(name=sliceName,serviceClass=serviceClass,site=site,image_preference=image,mount_data_sets=mountDataSets)
Scott Bakerc7325a42014-05-30 16:06:46 -070075 newSlice.save()
76 privateTemplate="Private"
77 publicTemplate="Public shared IPv4"
78 privateNetworkName = sliceName+"-"+privateTemplate
79 publicNetworkName = sliceName+"-"+publicTemplate
80 slice=Slice.objects.get(name=sliceName)
81 addNetwork(privateNetworkName,privateTemplate,slice)
82 addNetwork(publicNetworkName,publicTemplate,slice)
83 addOrModifyPorts(networkPorts,sliceName)
84 if privateVolume=="true":
85 privateVolForSlice(request.user,sliceName)
Scott Baker866c5b32014-08-29 11:34:00 -070086 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
90class 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()
Scott Baker823b7212014-06-16 10:25:39 -070099 return HttpResponse(json.dumps("Slice created"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700100
101def privateVolForSlice(user,sliceName):
102 if not hasPrivateVolume(sliceName):
103 volumeName=createPrivateVolume(user,sliceName)
104 readWrite="true"
105 mountVolume(sliceName,volumeName,readWrite)
106
107class TenantUpdateSlice(View):
108 def post(self, request, *args, **kwargs):
109 if request.user.isReadOnlyUser():
110 return HttpResponseForbidden("User is in read-only mode")
111
112 sliceName = request.POST.get("sliceName", "0")
113 serviceClass = request.POST.get("serviceClass", "0")
114 imageName = request.POST.get("imageName", "0")
115 actionToDo = request.POST.get("actionToDo", "0")
116 networkPorts = request.POST.get("networkPorts","0")
117 dataSet = request.POST.get("dataSet","0")
118 privateVolume = request.POST.get("privateVolume","0")
119 slice = Slice.objects.all()
120 for entry in slice:
121 serviceClass = ServiceClass.objects.get(name=serviceClass)
122 if(entry.name==sliceName):
123 if (actionToDo == "update"):
124 setattr(entry,'serviceClass',serviceClass)
Scott Bakeree9736d2015-01-03 16:26:38 -0800125 setattr(entry,'image_preference',imageName)
126 setattr(entry,'mount_data_sets',dataSet)
Scott Bakerc7325a42014-05-30 16:06:46 -0700127 entry.save()
128 break
129 addOrModifyPorts(networkPorts,sliceName)
130 if privateVolume=="true":
131 privateVolForSlice(request.user,sliceName)
Scott Baker823b7212014-06-16 10:25:39 -0700132 return HttpResponse(json.dumps("Slice updated"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700133
134def addNetwork(name,template,sliceName):
135 networkTemplate=NetworkTemplate.objects.get(name=template)
136 newNetwork = Network(name = name,
137 template = networkTemplate,
138 owner = sliceName)
139 newNetwork.save()
140 addNetworkSlice(newNetwork,sliceName)
141
142def addNetworkSlice(networkSlice,sliceName):
143 newNetworkSlice=NetworkSlice(network =networkSlice,
144 slice=sliceName)
145 newNetworkSlice.save()
146
147def addOrModifyPorts(networkPorts,sliceName):
148 networkList = Network.objects.all()
149 networkInfo = []
150 if networkPorts:
151 for networkEntry in networkList:
152 networkSlices = networkEntry.slices.all()
153 for slice in networkSlices:
154 if slice.name==sliceName:
155 if networkEntry.template.name=="Public shared IPv4":
156 setattr(networkEntry,'ports',networkPorts)
157 networkEntry.save()
158
159def 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())
Scott Baker866c5b32014-08-29 11:34:00 -0700171 #tenantSliceDetails['sites'] = userSliceTableFormatter(getTenantSitesInfo())
Scott Bakerc7325a42014-05-30 16:06:46 -0700172 tenantSliceDetails['mountDataSets'] = userSliceTableFormatter(getMountDataSets())
173 tenantSliceDetails['publicKey'] = getPublicKey(user)
Scott Baker866c5b32014-08-29 11:34:00 -0700174 tenantSliceDetails['availableSites']=userSliceTableFormatter(getAvailableSites())
175 tenantSliceDetails['role']=getUserRole(user)
176 tenantSliceDetails['siteUsers']=getSiteUsers(user)
Scott Bakerc7325a42014-05-30 16:06:46 -0700177 return tenantSliceDetails
178
Scott Baker866c5b32014-08-29 11:34:00 -0700179def 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
187def getUserRole(user):
188 sp=SitePrivilege.objects.filter(user=user)
189 for entry in sp:
190 return str(entry.role)
191
192
Scott Bakerc7325a42014-05-30 16:06:46 -0700193def getTenantInfo(user):
194 slices =Slice.objects.all()
195 userSliceInfo = []
196 for entry in slices:
Scott Baker866c5b32014-08-29 11:34:00 -0700197 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
Scott Bakeree9736d2015-01-03 16:26:38 -0800201 preferredImage = entry.image_preference
202 #sliceDataSet = entry.mount_data_sets
Scott Baker866c5b32014-08-29 11:34:00 -0700203 sliceNetwork = {}
204 numSliver = 0
205 sliceImage=""
206 sliceSite = {}
207 sliceNode = {}
208 sliceInstance= {}
209 #createPrivateVolume(user,sliceName)
Scott Baker01ef6492014-08-29 12:19:09 -0700210 available_sites = getAvailableSites()
Scott Baker866c5b32014-08-29 11:34:00 -0700211 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})
Scott Bakerc7325a42014-05-30 16:06:46 -0700219 return userSliceInfo
220
221def getTenantSitesInfo():
Scott Baker866c5b32014-08-29 11:34:00 -0700222 availableSites=getAvailableSites()
Scott Bakerc7325a42014-05-30 16:06:46 -0700223 tenantSiteInfo=[]
224 for entry in Site.objects.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700225 if entry.name in availableSites:
Scott Bakerc7325a42014-05-30 16:06:46 -0700226 tenantSiteInfo.append({'siteName':entry.name})
227 return tenantSiteInfo
228
229def getPublicKey(user):
230 users=User.objects.all()
231 for key in users:
232 if (str(key.email)==str(user)):
233 sshKey = key.public_key
234 return sshKey
235
236def getServiceClassInfo(user):
237 serviceClassList = ServiceClass.objects.all()
238 sliceInfo = []
239 for entry in serviceClassList:
240 sliceInfo.append({'serviceClass':entry.name})
241 return sliceInfo
242
243def getImageInfo(user):
Scott Baker866c5b32014-08-29 11:34:00 -0700244 #imageList = Image.objects.all()
245 #imageInfo = []
246 #for imageEntry in imageList:
247 #imageInfo.append({'Image':imageEntry.name})
Scott Bakerc7325a42014-05-30 16:06:46 -0700248 imageInfo = []
Scott Baker866c5b32014-08-29 11:34:00 -0700249 tempImageInfo = []
250 length = len(BLESSED_DEPLOYMENTS)
251 for deployment in Deployment.objects.all():
252 if deployment.name in BLESSED_DEPLOYMENTS:
Sapan Bhatia400f5b52014-11-20 15:08:18 -0500253 for x in deployment.imagedeployments.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700254 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)
Scott Bakerc7325a42014-05-30 16:06:46 -0700261 return imageInfo
262
263def 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
272SYNDICATE_REPLICATE_PORTNUM = 1025
273
274def 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
295def 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
305def 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
312def getMountDataSets():
313 dataSetInfo=[]
314 for volume in Volume.objects.all():
315 if not volume.private:
316 dataSetInfo.append({'DataSet': volume.name})
317
318 return dataSetInfo
319
320def getDeploymentSites():
321 deploymentList = Deployment.objects.all()
322 deploymentInfo = []
323 for entry in deploymentList:
324 deploymentInfo.append({'DeploymentSite':entry.name})
325 return deploymentInfo
326
Scott Baker866c5b32014-08-29 11:34:00 -0700327def getAvailableSites():
328 available_sites = []
329 for deployment in Deployment.objects.all():
330 if deployment.name in BLESSED_DEPLOYMENTS:
Sapan Bhatia400f5b52014-11-20 15:08:18 -0500331 for x in deployment.sitedeployments.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700332 if x.site.nodes.all():
333 available_sites.append(x.site.name)
334 return list(set(available_sites))
335
Scott Bakerc7325a42014-05-30 16:06:46 -0700336class TenantDeleteSliceView(View):
337 def post(self,request):
338 if request.user.isReadOnlyUser():
339 return HttpResponseForbidden("User is in read-only mode")
340 sliceName = request.POST.get("sliceName",None)
341 slice = Slice.objects.get(name=sliceName)
342 #print slice, slice.id
343 sliceToDel=Slice(name=sliceName, id=slice.id)
344 sliceToDel.delete()
Scott Baker823b7212014-06-16 10:25:39 -0700345 return HttpResponse(json.dumps("Slice deleted"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700346
347class 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)
Scott Baker866c5b32014-08-29 11:34:00 -0700368 imageName = request.POST.get("image",None)
Scott Bakerc7325a42014-05-30 16:06:46 -0700369 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)
Scott Baker866c5b32014-08-29 11:34:00 -0700375 image = Image.objects.get(name=imageName)
Scott Bakerc7325a42014-05-30 16:06:46 -0700376
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
Scott Baker866c5b32014-08-29 11:34:00 -0700387 sitesChanged = slice_increase_slivers(request.user, user_ip, siteList, slice, image, count, noAct)
Scott Bakerc7325a42014-05-30 16:06:46 -0700388 elif (actionToDo == "rem"):
389 sitesChanged = slice_decrease_slivers(request.user, siteList, slice, count, noAct)
390 else:
391 return HttpResponseServerError("Unknown actionToDo %s" % actionToDo)
392
Scott Baker823b7212014-06-16 10:25:39 -0700393 return HttpResponse(json.dumps(sitesChanged), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700394
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
400class 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]
Scott Baker823b7212014-06-16 10:25:39 -0700410 return HttpResponse(json.dumps(sites), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700411
412def 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
425def 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
Scott Baker01ef6492014-08-29 12:19:09 -0700438 available_sites = getAvailableSites()
Scott Bakerc7325a42014-05-30 16:06:46 -0700439 sites = Site.objects.all()
Scott Baker866c5b32014-08-29 11:34:00 -0700440 sites = [x for x in sites if x.name in available_sites]
Scott Bakerc7325a42014-05-30 16:06:46 -0700441 sites = sorted(sites, key=functools.partial(siteSortKey, slice=slice, count=count, lat=lat, lon=lon))
442
443 return sites
444
445class TenantViewData(View):
446 def get(self, request, **kwargs):
Scott Baker823b7212014-06-16 10:25:39 -0700447 return HttpResponse(json.dumps(getTenantSliceInfo(request.user, True)), content_type='application/javascript')
Scott Baker866c5b32014-08-29 11:34:00 -0700448
449class RequestAccountView(View):
450 def get(self, request, **kwargs):
451 return HttpResponse()