diff options
Diffstat (limited to 'src/api/views.py')
-rw-r--r-- | src/api/views.py | 756 |
1 files changed, 0 insertions, 756 deletions
diff --git a/src/api/views.py b/src/api/views.py deleted file mode 100644 index d5966ed..0000000 --- a/src/api/views.py +++ /dev/null @@ -1,756 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import json -import math -import traceback -import sys -from datetime import timedelta - -from django.contrib.auth.decorators import login_required -from django.shortcuts import redirect, get_object_or_404 -from django.utils.decorators import method_decorator -from django.utils import timezone -from django.views import View -from django.http import HttpResponseNotFound -from django.http.response import JsonResponse, HttpResponse -from rest_framework import viewsets -from rest_framework.authtoken.models import Token -from django.views.decorators.csrf import csrf_exempt -from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q - -from api.serializers.booking_serializer import BookingSerializer -from api.serializers.old_serializers import UserSerializer -from api.forms import DowntimeForm -from account.models import UserProfile, Lab -from booking.models import Booking -from booking.quick_deployer import create_from_API -from api.models import LabManagerTracker, get_task, Job, AutomationAPIManager, APILog, GeneratedCloudConfig -from notifier.manager import NotificationHandler -from analytics.models import ActiveVPNUser -from resource_inventory.models import ( - Image, - Opsys, - CloudInitFile, - ResourceQuery, - ResourceTemplate, -) - -import yaml -import uuid -from deepmerge import Merger - -""" -API views. - -All functions return a Json blob -Most functions that deal with info from a specific lab (tasks, host info) -requires the Lab auth token. - for example, curl -H auth-token:mylabsauthtoken url - -Most functions let you GET or POST to the same endpoint, and -the correct thing will happen -""" - - -class BookingViewSet(viewsets.ModelViewSet): - queryset = Booking.objects.all() - serializer_class = BookingSerializer - filter_fields = ('resource', 'id') - - -class UserViewSet(viewsets.ModelViewSet): - queryset = UserProfile.objects.all() - serializer_class = UserSerializer - - -@method_decorator(login_required, name='dispatch') -class GenerateTokenView(View): - def get(self, request, *args, **kwargs): - user = self.request.user - token, created = Token.objects.get_or_create(user=user) - if not created: - token.delete() - Token.objects.create(user=user) - return redirect('account:settings') - - -def lab_inventory(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return JsonResponse(lab_manager.get_inventory(), safe=False) - - -@csrf_exempt -def lab_host(request, lab_name="", host_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "GET": - return JsonResponse(lab_manager.get_host(host_id), safe=False) - if request.method == "POST": - return JsonResponse(lab_manager.update_host(host_id, request.POST), safe=False) - -# API extension for Cobbler integration - - -def all_images(request, lab_name=""): - a = [] - for i in Image.objects.all(): - a.append(i.serialize()) - return JsonResponse(a, safe=False) - - -def all_opsyss(request, lab_name=""): - a = [] - for opsys in Opsys.objects.all(): - a.append(opsys.serialize()) - - return JsonResponse(a, safe=False) - - -@csrf_exempt -def single_image(request, lab_name="", image_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - img = lab_manager.get_image(image_id).first() - - if request.method == "GET": - if not img: - return HttpResponse(status=404) - return JsonResponse(img.serialize(), safe=False) - - if request.method == "POST": - # get POST data - data = json.loads(request.body.decode('utf-8')) - if img: - img.update(data) - else: - # append lab name and the ID from the URL - data['from_lab_id'] = lab_name - data['lab_id'] = image_id - - # create and save a new Image object - img = Image.new_from_data(data) - - img.save() - - # indicate success in response - return HttpResponse(status=200) - return HttpResponse(status=405) - - -@csrf_exempt -def single_opsys(request, lab_name="", opsys_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - opsys = lab_manager.get_opsys(opsys_id).first() - - if request.method == "GET": - if not opsys: - return HttpResponse(status=404) - return JsonResponse(opsys.serialize(), safe=False) - - if request.method == "POST": - data = json.loads(request.body.decode('utf-8')) - if opsys: - opsys.update(data) - else: - # only name, available, and obsolete are needed to create an Opsys - # other fields are derived from the URL parameters - data['from_lab_id'] = lab_name - data['lab_id'] = opsys_id - opsys = Opsys.new_from_data(data) - - opsys.save() - return HttpResponse(status=200) - return HttpResponse(status=405) - -# end API extension - - -def get_pdf(request, lab_name="", booking_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_pdf(booking_id), content_type="text/plain") - - -def get_idf(request, lab_name="", booking_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_idf(booking_id), content_type="text/plain") - - -def lab_status(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "POST": - return JsonResponse(lab_manager.set_status(request.POST), safe=False) - return JsonResponse(lab_manager.get_status(), safe=False) - - -def lab_users(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_users(), content_type="text/plain") - - -def lab_user(request, lab_name="", user_id=-1): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_user(user_id), content_type="text/plain") - - -@csrf_exempt -def update_host_bmc(request, lab_name="", host_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "POST": - # update / create RemoteInfo for host - return JsonResponse( - lab_manager.update_host_remote_info(request.POST, host_id), - safe=False - ) - - -def lab_profile(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return JsonResponse(lab_manager.get_profile(), safe=False) - - -@csrf_exempt -def specific_task(request, lab_name="", job_id="", task_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - LabManagerTracker.get(lab_name, lab_token) # Authorize caller, but we dont need the result - - if request.method == "POST": - task = get_task(task_id) - if 'status' in request.POST: - task.status = request.POST.get('status') - if 'message' in request.POST: - task.message = request.POST.get('message') - if 'lab_token' in request.POST: - task.lab_token = request.POST.get('lab_token') - task.save() - NotificationHandler.task_updated(task) - d = {} - d['task'] = task.config.get_delta() - m = {} - m['status'] = task.status - m['job'] = str(task.job) - m['message'] = task.message - d['meta'] = m - return JsonResponse(d, safe=False) - elif request.method == "GET": - return JsonResponse(get_task(task_id).config.get_delta()) - - -@csrf_exempt -def specific_job(request, lab_name="", job_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "POST": - return JsonResponse(lab_manager.update_job(job_id, request.POST), safe=False) - return JsonResponse(lab_manager.get_job(job_id), safe=False) - - -@csrf_exempt -def resource_ci_userdata(request, lab_name="", job_id="", resource_id="", file_id=0): - # lab_token = request.META.get('HTTP_AUTH_TOKEN') - # lab_manager = LabManagerTracker.get(lab_name, lab_token) - - # job = lab_manager.get_job(job_id) - Job.objects.get(id=job_id) # verify a valid job was given, even if we don't use it - - cifile = None - try: - cifile = CloudInitFile.objects.get(id=file_id) - except ObjectDoesNotExist: - return HttpResponseNotFound("Could not find a matching resource by id " + str(resource_id)) - - text = cifile.text - - prepended_text = "#cloud-config\n" - # mstrat = CloudInitFile.merge_strategy() - # prepended_text = prepended_text + yaml.dump({"merge_strategy": mstrat}) + "\n" - # print("in cloudinitfile create") - text = prepended_text + text - cloud_dict = { - "datasource": { - "None": { - "metadata": { - "instance-id": str(uuid.uuid4()) - }, - "userdata_raw": text, - }, - }, - "datasource_list": ["None"], - } - - return HttpResponse(yaml.dump(cloud_dict, width=float("inf")), status=200) - - -@csrf_exempt -def resource_ci_metadata(request, lab_name="", job_id="", resource_id="", file_id=0): - return HttpResponse("#cloud-config", status=200) - - -@csrf_exempt -def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id=""): - # files = [{"id": file.file_id, "priority": file.priority} for file in CloudInitFile.objects.filter(job__id=job_id, resource_id=resource_id).order_by("priority").all()] - resource = ResourceQuery.get(labid=resource_id, lab=Lab.objects.get(name=lab_name)) - files = resource.config.cloud_init_files - files = [{"id": file.id, "priority": file.priority} for file in files.order_by("priority").all()] - - d = {} - - merge_failures = [] - - merger = Merger( - [ - (list, ["append"]), - (dict, ["merge"]), - ], - ["override"], # fallback - ["override"], # if types conflict (shouldn't happen in CI, but handle case) - ) - - for f in resource.config.cloud_init_files.order_by("priority").all(): - try: - other_dict = yaml.safe_load(f.text) - if not (type(d) is dict): - raise Exception("CI file was valid yaml but was not a dict") - - merger.merge(d, other_dict) - except Exception as e: - # if fail to merge, then just skip - print("Failed to merge file in, as it had invalid content:", f.id) - print("File text was:") - print(f.text) - merge_failures.append({f.id: str(e)}) - - if len(merge_failures) > 0: - d['merge_failures'] = merge_failures - - file = CloudInitFile.create(text=yaml.dump(d, width=float("inf")), priority=0) - - return HttpResponse(json.dumps([{"id": file.id, "priority": file.priority}]), status=200) - - -def new_jobs(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return JsonResponse(lab_manager.get_new_jobs(), safe=False) - - -def current_jobs(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return JsonResponse(lab_manager.get_current_jobs(), safe=False) - - -@csrf_exempt -def analytics_job(request, lab_name=""): - """ returns all jobs with type booking""" - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "GET": - return JsonResponse(lab_manager.get_analytics_job(), safe=False) - if request.method == "POST": - users = json.loads(request.body.decode('utf-8'))['active_users'] - try: - ActiveVPNUser.create(lab_name, users) - except ObjectDoesNotExist: - return JsonResponse('Lab does not exist!', safe=False) - return HttpResponse(status=200) - return HttpResponse(status=405) - - -def lab_downtime(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - if request.method == "GET": - return JsonResponse(lab_manager.get_downtime_json()) - if request.method == "POST": - return post_lab_downtime(request, lab_manager) - if request.method == "DELETE": - return delete_lab_downtime(lab_manager) - return HttpResponse(status=405) - - -def post_lab_downtime(request, lab_manager): - current_downtime = lab_manager.get_downtime() - if current_downtime.exists(): - return JsonResponse({"error": "Lab is already in downtime"}, status=422) - form = DowntimeForm(request.POST) - if form.is_valid(): - return JsonResponse(lab_manager.create_downtime(form)) - else: - return JsonResponse(form.errors.get_json_data(), status=400) - - -def delete_lab_downtime(lab_manager): - current_downtime = lab_manager.get_downtime() - if current_downtime.exists(): - dt = current_downtime.first() - dt.end = timezone.now() - dt.save() - return JsonResponse(lab_manager.get_downtime_json(), safe=False) - else: - return JsonResponse({"error": "Lab is not in downtime"}, status=422) - - -def done_jobs(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return JsonResponse(lab_manager.get_done_jobs(), safe=False) - - -def auth_and_log(request, endpoint): - """ - Function to authenticate an API user and log info - in the API log model. This is to keep record of - all calls to the dashboard - """ - user_token = request.META.get('HTTP_AUTH_TOKEN') - response = None - - if user_token is None: - return HttpResponse('Unauthorized', status=401) - - try: - token = Token.objects.get(key=user_token) - except Token.DoesNotExist: - token = None - # Added logic to detect malformed token - if len(str(user_token)) != 40: - response = HttpResponse('Malformed Token', status=401) - else: - response = HttpResponse('Unauthorized', status=401) - - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - if x_forwarded_for: - ip = x_forwarded_for.split(',')[0] - else: - ip = request.META.get('REMOTE_ADDR') - - body = None - - if request.method in ['POST', 'PUT']: - try: - body = json.loads(request.body.decode('utf-8')), - except Exception: - response = HttpResponse('Invalid Request Body', status=400) - - APILog.objects.create( - user=token.user, - call_time=timezone.now(), - method=request.method, - endpoint=endpoint, - body=body, - ip_addr=ip - ) - - if response: - return response - else: - return token - - -""" -Booking API Views -""" - - -def user_bookings(request): - token = auth_and_log(request, 'booking') - - if isinstance(token, HttpResponse): - return token - - bookings = Booking.objects.filter(owner=token.user, end__gte=timezone.now()) - output = [AutomationAPIManager.serialize_booking(booking) - for booking in bookings] - return JsonResponse(output, safe=False) - - -@csrf_exempt -def specific_booking(request, booking_id=""): - token = auth_and_log(request, 'booking/{}'.format(booking_id)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - if request.method == "GET": - sbooking = AutomationAPIManager.serialize_booking(booking) - return JsonResponse(sbooking, safe=False) - - if request.method == "DELETE": - - if booking.end < timezone.now(): - return HttpResponse("Booking already over", status=400) - - booking.end = timezone.now() - booking.save() - return HttpResponse("Booking successfully cancelled") - - -@csrf_exempt -def extend_booking(request, booking_id="", days=""): - token = auth_and_log(request, 'booking/{}/extendBooking/{}'.format(booking_id, days)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - - if booking.end < timezone.now(): - return HttpResponse("This booking is already over, cannot extend") - - if days > 30: - return HttpResponse("Cannot extend a booking longer than 30 days") - - if booking.ext_count == 0: - return HttpResponse("Booking has already been extended 2 times, cannot extend again") - - booking.end += timedelta(days=days) - booking.ext_count -= 1 - booking.save() - - return HttpResponse("Booking successfully extended") - - -@csrf_exempt -def make_booking(request): - token = auth_and_log(request, 'booking/makeBooking') - - if isinstance(token, HttpResponse): - return token - - try: - booking = create_from_API(request.body, token.user) - - except Exception: - finalTrace = '' - exc_type, exc_value, exc_traceback = sys.exc_info() - for i in traceback.format_exception(exc_type, exc_value, exc_traceback): - finalTrace += '<br>' + i.strip() - return HttpResponse(finalTrace, status=400) - - sbooking = AutomationAPIManager.serialize_booking(booking) - return JsonResponse(sbooking, safe=False) - - -""" -Resource Inventory API Views -""" - - -def available_templates(request): - token = auth_and_log(request, 'resource_inventory/availableTemplates') - - if isinstance(token, HttpResponse): - return token - - # get available templates - # mirrors MultipleSelectFilter Widget - avt = [] - for lab in Lab.objects.all(): - for template in ResourceTemplate.objects.filter(Q(owner=token.user) | Q(public=True), lab=lab, temporary=False): - available_resources = lab.get_available_resources() - required_resources = template.get_required_resources() - least_available = 100 - - for resource, count_required in required_resources.items(): - try: - curr_count = math.floor(available_resources[str(resource)] / count_required) - if curr_count < least_available: - least_available = curr_count - except KeyError: - least_available = 0 - - if least_available > 0: - avt.append((template, least_available)) - - savt = [AutomationAPIManager.serialize_template(temp) - for temp in avt] - - return JsonResponse(savt, safe=False) - - -def images_for_template(request, template_id=""): - _ = auth_and_log(request, 'resource_inventory/{}/images'.format(template_id)) - - template = get_object_or_404(ResourceTemplate, pk=template_id) - images = [AutomationAPIManager.serialize_image(config.image) - for config in template.getConfigs()] - return JsonResponse(images, safe=False) - - -""" -User API Views -""" - - -def all_users(request): - token = auth_and_log(request, 'users') - - if token is None: - return HttpResponse('Unauthorized', status=401) - - users = [AutomationAPIManager.serialize_userprofile(up) - for up in UserProfile.objects.filter(public_user=True)] - - return JsonResponse(users, safe=False) - - -def create_ci_file(request): - token = auth_and_log(request, 'booking/makeCloudConfig') - - if isinstance(token, HttpResponse): - return token - - try: - cconf = request.body - d = yaml.load(cconf) - if not (type(d) is dict): - raise Exception() - - cconf = CloudInitFile.create(text=cconf, priority=CloudInitFile.objects.count()) - - return JsonResponse({"id": cconf.id}) - except Exception: - return JsonResponse({"error": "Provided config file was not valid yaml or was not a dict at the top level"}) - - -""" -Lab API Views -""" - - -def list_labs(request): - lab_list = [] - for lab in Lab.objects.all(): - lab_info = { - 'name': lab.name, - 'username': lab.lab_user.username, - 'status': lab.status, - 'project': lab.project, - 'description': lab.description, - 'location': lab.location, - 'info': lab.lab_info_link, - 'email': lab.contact_email, - 'phone': lab.contact_phone - } - lab_list.append(lab_info) - - return JsonResponse(lab_list, safe=False) - - -""" -Booking Details API Views -""" - - -def booking_details(request, booking_id=""): - token = auth_and_log(request, 'booking/{}/details'.format(booking_id)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - - # overview - overview = { - 'username': GeneratedCloudConfig._normalize_username(None, str(token.user)), - 'purpose': booking.purpose, - 'project': booking.project, - 'start_time': booking.start, - 'end_time': booking.end, - 'pod_definitions': booking.resource.template, - 'lab': booking.lab - } - - # deployment progress - task_list = [] - for task in booking.job.get_tasklist(): - task_info = { - 'name': str(task), - 'status': 'DONE', - 'lab_response': 'No response provided (yet)' - } - if task.status < 100: - task_info['status'] = 'PENDING' - elif task.status < 200: - task_info['status'] = 'IN PROGRESS' - - if task.message: - if task.type_str == "Access Task" and request.user.id != task.config.user.id: - task_info['lab_response'] = '--secret--' - else: - task_info['lab_response'] = str(task.message) - task_list.append(task_info) - - # pods - pod_list = [] - for host in booking.resource.get_resources(): - pod_info = { - 'hostname': host.config.name, - 'machine': host.name, - 'role': '', - 'is_headnode': host.config.is_head_node, - 'image': host.config.image, - 'ram': {'amount': str(host.profile.ramprofile.first().amount) + 'G', 'channels': host.profile.ramprofile.first().channels}, - 'cpu': {'arch': host.profile.cpuprofile.first().architecture, 'cores': host.profile.cpuprofile.first().cores, 'sockets': host.profile.cpuprofile.first().cpus}, - 'disk': {'size': str(host.profile.storageprofile.first().size) + 'GiB', 'type': host.profile.storageprofile.first().media_type, 'mount_point': host.profile.storageprofile.first().name}, - 'interfaces': [], - } - try: - pod_info['role'] = host.template.opnfvRole - except Exception: - pass - for intprof in host.profile.interfaceprofile.all(): - int_info = { - 'name': intprof.name, - 'speed': intprof.speed - } - pod_info['interfaces'].append(int_info) - pod_list.append(pod_info) - - # diagnostic info - diagnostic_info = { - 'job_id': booking.job.id, - 'ci_files': '', - 'pods': [] - } - for host in booking.resource.get_resources(): - pod = { - 'host': host.name, - 'configs': [], - - } - for ci_file in host.config.cloud_init_files.all(): - ci_info = { - 'id': ci_file.id, - 'text': ci_file.text - } - pod['configs'].append(ci_info) - diagnostic_info['pods'].append(pod) - - details = { - 'overview': overview, - 'deployment_progress': task_list, - 'pods': pod_list, - 'diagnostic_info': diagnostic_info, - 'pdf': booking.pdf - } - return JsonResponse(str(details), safe=False) |