blob: a64250f97f80f49248ff084c1047685fcd2ac1a3 [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 Baker866c5b32014-08-29 11:34:00 -070029 user = User(
30 email=BaseUserManager.normalize_email(email),
31 firstname=firstname,
32 lastname=lastname,
Scott Bakerbb2fb7e2015-01-27 23:07:51 -080033 is_active=False,
Scott Baker28e2e3a2015-01-28 16:03:40 -080034 is_admin=False,
35 is_registering=True
Scott Baker866c5b32014-08-29 11:34:00 -070036 )
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>
47Check 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')
Scott Bakerc7325a42014-05-30 16:06:46 -070052
53class 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")
Scott Baker866c5b32014-08-29 11:34:00 -070065 userEmail = request.POST.get("userEmail","0")
Scott Bakerc7325a42014-05-30 16:06:46 -070066 if (actionToDo == "add"):
67 serviceClass = ServiceClass.objects.get(name=serviceClass)
68 site = request.user.site
69 image = Image.objects.get(name=imageName)
Scott Bakeree9736d2015-01-03 16:26:38 -080070 newSlice = Slice(name=sliceName,serviceClass=serviceClass,site=site,image_preference=image,mount_data_sets=mountDataSets)
Scott Bakerc7325a42014-05-30 16:06:46 -070071 newSlice.save()
72 privateTemplate="Private"
73 publicTemplate="Public shared IPv4"
74 privateNetworkName = sliceName+"-"+privateTemplate
75 publicNetworkName = sliceName+"-"+publicTemplate
76 slice=Slice.objects.get(name=sliceName)
77 addNetwork(privateNetworkName,privateTemplate,slice)
78 addNetwork(publicNetworkName,publicTemplate,slice)
79 addOrModifyPorts(networkPorts,sliceName)
80 if privateVolume=="true":
81 privateVolForSlice(request.user,sliceName)
Scott Baker866c5b32014-08-29 11:34:00 -070082 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
86class 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()
Scott Baker823b7212014-06-16 10:25:39 -070095 return HttpResponse(json.dumps("Slice created"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -070096
97def privateVolForSlice(user,sliceName):
98 if not hasPrivateVolume(sliceName):
99 volumeName=createPrivateVolume(user,sliceName)
100 readWrite="true"
101 mountVolume(sliceName,volumeName,readWrite)
102
103class TenantUpdateSlice(View):
104 def post(self, request, *args, **kwargs):
105 if request.user.isReadOnlyUser():
106 return HttpResponseForbidden("User is in read-only mode")
107
108 sliceName = request.POST.get("sliceName", "0")
109 serviceClass = request.POST.get("serviceClass", "0")
110 imageName = request.POST.get("imageName", "0")
111 actionToDo = request.POST.get("actionToDo", "0")
112 networkPorts = request.POST.get("networkPorts","0")
113 dataSet = request.POST.get("dataSet","0")
114 privateVolume = request.POST.get("privateVolume","0")
115 slice = Slice.objects.all()
116 for entry in slice:
117 serviceClass = ServiceClass.objects.get(name=serviceClass)
118 if(entry.name==sliceName):
119 if (actionToDo == "update"):
120 setattr(entry,'serviceClass',serviceClass)
Scott Bakeree9736d2015-01-03 16:26:38 -0800121 setattr(entry,'image_preference',imageName)
122 setattr(entry,'mount_data_sets',dataSet)
Scott Bakerc7325a42014-05-30 16:06:46 -0700123 entry.save()
124 break
125 addOrModifyPorts(networkPorts,sliceName)
126 if privateVolume=="true":
127 privateVolForSlice(request.user,sliceName)
Scott Baker823b7212014-06-16 10:25:39 -0700128 return HttpResponse(json.dumps("Slice updated"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700129
130def addNetwork(name,template,sliceName):
131 networkTemplate=NetworkTemplate.objects.get(name=template)
132 newNetwork = Network(name = name,
133 template = networkTemplate,
134 owner = sliceName)
135 newNetwork.save()
136 addNetworkSlice(newNetwork,sliceName)
137
138def addNetworkSlice(networkSlice,sliceName):
139 newNetworkSlice=NetworkSlice(network =networkSlice,
140 slice=sliceName)
141 newNetworkSlice.save()
142
143def addOrModifyPorts(networkPorts,sliceName):
144 networkList = Network.objects.all()
145 networkInfo = []
146 if networkPorts:
147 for networkEntry in networkList:
148 networkSlices = networkEntry.slices.all()
149 for slice in networkSlices:
150 if slice.name==sliceName:
151 if networkEntry.template.name=="Public shared IPv4":
152 setattr(networkEntry,'ports',networkPorts)
153 networkEntry.save()
154
155def 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())
Scott Baker866c5b32014-08-29 11:34:00 -0700167 #tenantSliceDetails['sites'] = userSliceTableFormatter(getTenantSitesInfo())
Scott Bakerc7325a42014-05-30 16:06:46 -0700168 tenantSliceDetails['mountDataSets'] = userSliceTableFormatter(getMountDataSets())
169 tenantSliceDetails['publicKey'] = getPublicKey(user)
Scott Baker866c5b32014-08-29 11:34:00 -0700170 tenantSliceDetails['availableSites']=userSliceTableFormatter(getAvailableSites())
171 tenantSliceDetails['role']=getUserRole(user)
172 tenantSliceDetails['siteUsers']=getSiteUsers(user)
Scott Bakerc7325a42014-05-30 16:06:46 -0700173 return tenantSliceDetails
174
Scott Baker866c5b32014-08-29 11:34:00 -0700175def 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
183def getUserRole(user):
184 sp=SitePrivilege.objects.filter(user=user)
185 for entry in sp:
186 return str(entry.role)
187
188
Scott Bakerc7325a42014-05-30 16:06:46 -0700189def getTenantInfo(user):
190 slices =Slice.objects.all()
191 userSliceInfo = []
192 for entry in slices:
Scott Baker866c5b32014-08-29 11:34:00 -0700193 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
Scott Bakeree9736d2015-01-03 16:26:38 -0800197 preferredImage = entry.image_preference
198 #sliceDataSet = entry.mount_data_sets
Scott Baker866c5b32014-08-29 11:34:00 -0700199 sliceNetwork = {}
200 numSliver = 0
201 sliceImage=""
202 sliceSite = {}
203 sliceNode = {}
204 sliceInstance= {}
205 #createPrivateVolume(user,sliceName)
Scott Baker01ef6492014-08-29 12:19:09 -0700206 available_sites = getAvailableSites()
Scott Baker866c5b32014-08-29 11:34:00 -0700207 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})
Scott Bakerc7325a42014-05-30 16:06:46 -0700215 return userSliceInfo
216
217def getTenantSitesInfo():
Scott Baker866c5b32014-08-29 11:34:00 -0700218 availableSites=getAvailableSites()
Scott Bakerc7325a42014-05-30 16:06:46 -0700219 tenantSiteInfo=[]
220 for entry in Site.objects.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700221 if entry.name in availableSites:
Scott Bakerc7325a42014-05-30 16:06:46 -0700222 tenantSiteInfo.append({'siteName':entry.name})
223 return tenantSiteInfo
224
225def getPublicKey(user):
226 users=User.objects.all()
227 for key in users:
228 if (str(key.email)==str(user)):
229 sshKey = key.public_key
230 return sshKey
231
232def getServiceClassInfo(user):
233 serviceClassList = ServiceClass.objects.all()
234 sliceInfo = []
235 for entry in serviceClassList:
236 sliceInfo.append({'serviceClass':entry.name})
237 return sliceInfo
238
239def getImageInfo(user):
Scott Baker866c5b32014-08-29 11:34:00 -0700240 #imageList = Image.objects.all()
241 #imageInfo = []
242 #for imageEntry in imageList:
243 #imageInfo.append({'Image':imageEntry.name})
Scott Bakerc7325a42014-05-30 16:06:46 -0700244 imageInfo = []
Scott Baker866c5b32014-08-29 11:34:00 -0700245 tempImageInfo = []
246 length = len(BLESSED_DEPLOYMENTS)
247 for deployment in Deployment.objects.all():
248 if deployment.name in BLESSED_DEPLOYMENTS:
Sapan Bhatia400f5b52014-11-20 15:08:18 -0500249 for x in deployment.imagedeployments.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700250 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)
Scott Bakerc7325a42014-05-30 16:06:46 -0700257 return imageInfo
258
259def 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
268SYNDICATE_REPLICATE_PORTNUM = 1025
269
270def 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
291def 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
301def 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
308def getMountDataSets():
309 dataSetInfo=[]
310 for volume in Volume.objects.all():
311 if not volume.private:
312 dataSetInfo.append({'DataSet': volume.name})
313
314 return dataSetInfo
315
316def getDeploymentSites():
317 deploymentList = Deployment.objects.all()
318 deploymentInfo = []
319 for entry in deploymentList:
320 deploymentInfo.append({'DeploymentSite':entry.name})
321 return deploymentInfo
322
Scott Baker866c5b32014-08-29 11:34:00 -0700323def getAvailableSites():
324 available_sites = []
325 for deployment in Deployment.objects.all():
326 if deployment.name in BLESSED_DEPLOYMENTS:
Sapan Bhatia400f5b52014-11-20 15:08:18 -0500327 for x in deployment.sitedeployments.all():
Scott Baker866c5b32014-08-29 11:34:00 -0700328 if x.site.nodes.all():
329 available_sites.append(x.site.name)
330 return list(set(available_sites))
331
Scott Bakerc7325a42014-05-30 16:06:46 -0700332class TenantDeleteSliceView(View):
333 def post(self,request):
334 if request.user.isReadOnlyUser():
335 return HttpResponseForbidden("User is in read-only mode")
336 sliceName = request.POST.get("sliceName",None)
337 slice = Slice.objects.get(name=sliceName)
338 #print slice, slice.id
339 sliceToDel=Slice(name=sliceName, id=slice.id)
340 sliceToDel.delete()
Scott Baker823b7212014-06-16 10:25:39 -0700341 return HttpResponse(json.dumps("Slice deleted"), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700342
343class 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)
Scott Baker866c5b32014-08-29 11:34:00 -0700364 imageName = request.POST.get("image",None)
Scott Bakerc7325a42014-05-30 16:06:46 -0700365 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)
Scott Baker866c5b32014-08-29 11:34:00 -0700371 image = Image.objects.get(name=imageName)
Scott Bakerc7325a42014-05-30 16:06:46 -0700372
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
Scott Baker866c5b32014-08-29 11:34:00 -0700383 sitesChanged = slice_increase_slivers(request.user, user_ip, siteList, slice, image, count, noAct)
Scott Bakerc7325a42014-05-30 16:06:46 -0700384 elif (actionToDo == "rem"):
385 sitesChanged = slice_decrease_slivers(request.user, siteList, slice, count, noAct)
386 else:
387 return HttpResponseServerError("Unknown actionToDo %s" % actionToDo)
388
Scott Baker823b7212014-06-16 10:25:39 -0700389 return HttpResponse(json.dumps(sitesChanged), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700390
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
396class 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]
Scott Baker823b7212014-06-16 10:25:39 -0700406 return HttpResponse(json.dumps(sites), content_type='application/javascript')
Scott Bakerc7325a42014-05-30 16:06:46 -0700407
408def 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
421def 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
Scott Baker01ef6492014-08-29 12:19:09 -0700434 available_sites = getAvailableSites()
Scott Bakerc7325a42014-05-30 16:06:46 -0700435 sites = Site.objects.all()
Scott Baker866c5b32014-08-29 11:34:00 -0700436 sites = [x for x in sites if x.name in available_sites]
Scott Bakerc7325a42014-05-30 16:06:46 -0700437 sites = sorted(sites, key=functools.partial(siteSortKey, slice=slice, count=count, lat=lat, lon=lon))
438
439 return sites
440
441class TenantViewData(View):
442 def get(self, request, **kwargs):
Scott Baker823b7212014-06-16 10:25:39 -0700443 return HttpResponse(json.dumps(getTenantSliceInfo(request.user, True)), content_type='application/javascript')
Scott Baker866c5b32014-08-29 11:34:00 -0700444
445class RequestAccountView(View):
446 def get(self, request, **kwargs):
447 return HttpResponse()