From: Gergely Csatari Date: Thu, 26 Oct 2023 07:33:28 +0000 (+0300) Subject: Removing project content and adding a note X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=2ec0d7b9f5c1354977b821c6b06c24a3ffa13142;p=laas.git Removing project content and adding a note that the development continues in GitHub Change-Id: I25c58a679dbf92b2367d826429b7cda936bf9f0e Signed-off-by: Gergely Csatari --- diff --git a/INFO.yaml b/INFO.yaml deleted file mode 100644 index c34551c..0000000 --- a/INFO.yaml +++ /dev/null @@ -1,95 +0,0 @@ ---- -project: 'Lab as a Service' -project_creation_date: '2019.06.25' -project_category: '' -lifecycle_state: 'Incubation' -project_lead: &opnfv_laas_ptl - name: 'Sawyer Bergeron' - email: 'sawyerbergeron@gmail.com' - id: 'sbergeron' - company: 'None' - timezone: 'Unknown/Unknown' -primary_contact: *opnfv_laas_ptl -issue_tracking: - type: 'jira' - url: 'https://jira.opnfv.org/projects/laas' - key: 'LAAS' -mailing_list: - type: 'mailman2' - url: 'opnfv-tech-discuss@lists.opnfv.org' - tag: '[laas]' -realtime_discussion: - type: 'irc' - server: 'freenode.net' - channel: '#opnfv-laas' -meetings: - - type: 'gotomeeting+irc' - agenda: 'https://wiki.opnfv.org/display/' - url: 'https://global.gotomeeting.com/join/819733085' - server: 'freenode.net' - channel: '#opnfv-meeting' - repeats: 'never' - time: 'none' -repositories: - - 'laas' -committers: - - <<: *opnfv_laas_ptl - - name: 'Trevor Bramwell' - email: 'tbramwell@linuxfoundation.org' - company: 'linuxfoundation' - id: 'bramwelt' - timezone: 'Unknown/Unknown' - - name: 'Nikos Karandreas' - email: 'nick@intracom-telecom.com' - company: 'intracom-telecom' - id: 'nikoskarandreas' - timezone: 'Unknown/Unknown' - - name: 'Panagiotis Karalis' - email: 'panos.pkaralis@gmail.com' - company: 'gmail' - id: 'pkaralis' - timezone: 'Unknown/Unknown' - - name: 'Jeremy Plsek' - email: 'jeremyplsek@gmail.com' - company: 'gmail' - id: 'jplsek' - timezone: 'Unknown/Unknown' - - name: 'Eric Ball' - email: 'eball@linuxfoundation.org' - company: 'linuxfoundation' - id: 'eball' - timezone: 'Unknown/Unknown' - - name: 'Lincoln Lavoie' - email: 'lylavoie@iol.unh.edu' - company: 'iol.unh' - id: 'lylavoie' - timezone: 'Unknown/Unknown' - - name: 'Mark Beierl' - email: 'mbeierl@vmware.com' - company: 'vmware' - id: 'mbeierl' - timezone: 'Unknown/Unknown' - - name: 'latha P' - email: 'sreelatha.paramatmuni@gmail.com' - company: 'gmail' - id: 'latha2019' - timezone: 'Unknown/Unknown' - - name: 'Justin Choquette' - email: 'jchoquette@iol.unh.edu' - company: 'UNH' - id: 'jchoquetteiol' - - name: 'Raven Hodgdon' - email: 'jhodgdon@iol.unh.edu' - company: 'UNH' - id: 'jhodgdon' - - name: 'Nicholas Schoenfeld' - email: 'nschoenfeld@iol.unh.edu' - company: 'UNH' - id: 'nschoenfeld' -tsc: - # yamllint disable rule:line-length - approval: 'http://meetbot.opnfv.org/meetings/opnfv-meeting/2019/opnfv-meeting.2019-06-25-12.59.txt' - changes: - - type: 'removal/promotion' - name: '' - link: '' diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e9e3e91..0000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2019 Open Platform for NFV Project, Inc. and its contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index eccb215..0000000 --- a/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -.PHONY: build dev-up dev-start dev-stop up start stop data shell-nginx shell-web shell-db log-nginx log-web log-ps log-rmq log-worker - -build: - docker-compose -f docker-compose.yml -f docker-compose.override-dev.yml build - -dev-up: - docker-compose -f docker-compose.yml -f docker-compose.override-dev.yml up - -dev-start: - docker-compose -f docker-compose.yml -f docker-compose.override-dev.yml start - -dev-stop: - docker-compose -f docker-compose.yml -f docker-compose.override-dev.yml stop - -up: - docker-compose up -d - -start: - docker-compose start - -stop: - docker-compose stop - -data: - docker volume create --name=laas-data - -shell-nginx: - docker exec -ti ng01 bash - -shell-web: - docker exec -ti dg01 bash - -shell-db: - docker exec -ti ps01 bash - -log-nginx: - docker-compose logs nginx - -log-web: - docker-compose logs web - -log-ps: - docker-compose logs postgres - -log-rmq: - docker-compose logs rabbitmq - -log-worker: - docker-compose logs worker diff --git a/config.env.sample b/config.env.sample deleted file mode 100644 index baa5b8c..0000000 --- a/config.env.sample +++ /dev/null @@ -1,74 +0,0 @@ -DASHBOARD_URL=http://127.0.0.1:8000 - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG=True -# TEST should be True if you want to run some tests in your local dev environment -TEST=False - -# These configure the postgres container and -# tell django how to access the database -# You shouldn't really need to change these, unless -# You want a specific user / pass on the DB -# The POSTGRES_ vars and DB_ vars should be kept in sync, eg -# POSTGRES_DB == DB_NAME -# POSTGRES_USER == DB_USER -# POSTGRES_PASSWORD == DB_PASS -POSTGRES_DB=sample_name -POSTGRES_USER=sample_user -POSTGRES_PASSWORD=sample_pass -DB_NAME=sample_name -DB_USER=sample_user -DB_PASS=sample_pass -DB_SERVICE=postgres -DB_PORT=5432 - -# tells the dashboard to expect host forwarding from proxy (if using LFID, needs to be True) -EXPECT_HOST_FORWARDING=False - -# string indicating what authorization to deploy with (LFID) -AUTH_SETTING=LFID - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY=http://www.miniwebtool.com/django-secret-key-generator/ - -OAUTH_CONSUMER_KEY=sample_key -OAUTH_CONSUMER_SECRET=sample_secret - -# LFID -OIDC_CLIENT_ID=sample_id -OIDC_CLIENT_SECRET=sample_secret - -OIDC_AUTHORIZATION_ENDPOINT=https://linuxfoundation-test.auth0.com/authorize -OIDC_TOKEN_ENDPOINT=https://linuxfoundation-test.auth0.com/oauth/token -OIDC_USER_ENDPOINT=https://linuxfoundation-test.auth0.com/userinfo -OIDC_AUTHENTICATION_CALLBACK_URL=https://laas.anuket.iol.org/oidc/callback - -CLAIMS_ENDPOINT=https://sso.linuxfoundation.org/claims/ - -OIDC_RP_SIGN_ALGO=RS256 -OIDC_OP_JWKS_ENDPOINT=https://sso.linuxfoundation.org/.well-known/jwks.json - -# Jira -JIRA_URL=https://jira.opnfv.org -JIRA_USER_NAME= -JIRA_USER_PASSWORD= - -# Rabbitmq -RABBITMQ_DEFAULT_USER=opnfv -RABBITMQ_DEFAULT_PASS=opnfvopnfv - -# Jenkins Build Server -JENKINS_URL=https://build.opnfv.org/ci - -# Email Settings -EMAIL_HOST= -EMAIL_PORT= -EMAIL_HOST_USER= -EMAIL_HOST_PASSWORD= -DEFAULT_FROM_EMAIL=webmaster@localhost - -BOOKING_EXPIRE_TIME=30 -BOOKING_MAXIMUM_NUMBER=10 - -TEMPLATE_OVERRIDE_DIR=laas - diff --git a/config/nginx/pharos_dashboard.conf b/config/nginx/pharos_dashboard.conf deleted file mode 100644 index 6f32979..0000000 --- a/config/nginx/pharos_dashboard.conf +++ /dev/null @@ -1,24 +0,0 @@ -upstream web { - ip_hash; - server web:8000; -} - -# portal -server { - listen 80; - server_name localhost; - charset utf-8; - - location /static { - alias /static; - } - - location /media { - alias /media; - } - - location / { - proxy_set_header Host $host; - proxy_pass http://web/; - } -} diff --git a/config/rabbitmq/rabbitmq.conf b/config/rabbitmq/rabbitmq.conf deleted file mode 100644 index 39c222c..0000000 --- a/config/rabbitmq/rabbitmq.conf +++ /dev/null @@ -1,2 +0,0 @@ -default_user=opnfv -default_pass=opnfvopnfv diff --git a/data/UNH_IOL/HPE_Proliant/hpe13.yaml b/data/UNH_IOL/HPE_Proliant/hpe13.yaml deleted file mode 100644 index fd40aff..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe13.yaml +++ /dev/null @@ -1,53 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G - - - name: loop0 - size: 100G - - - name: loop1 - size: 2G - - - name: loop2 - size: 792M - - - name: loop3 - size: 825M -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:1d:54:00' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:c3:70' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:c3:71' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:c3:72' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:ae:d0' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:ae:d1' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe14.yaml b/data/UNH_IOL/HPE_Proliant/hpe14.yaml deleted file mode 100644 index 612f4d0..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe14.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:1d:4d:60' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:b2:00' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:b2:01' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:b2:02' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:b1:d8' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:b1:d9' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe15.yaml b/data/UNH_IOL/HPE_Proliant/hpe15.yaml deleted file mode 100644 index c6799ac..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe15.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:22:c5:c0' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:ae:68' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:ae:69' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:ae:6a' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:ad:90' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:ad:91' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe16.yaml b/data/UNH_IOL/HPE_Proliant/hpe16.yaml deleted file mode 100644 index 3148532..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe16.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eth0 - mac: '48:df:37:22:c5:30' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: eth2 - mac: '3c:fd:fe:b2:b1:60' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: eth3 - mac: '3c:fd:fe:b2:b1:61' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: eth4 - mac: '3c:fd:fe:b2:b1:62' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: eth6 - mac: '3c:fd:fe:b2:b3:b8' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: eth7 - mac: '3c:fd:fe:b2:b3:b9' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe17.yaml b/data/UNH_IOL/HPE_Proliant/hpe17.yaml deleted file mode 100644 index 4b13ae2..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe17.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:1d:48:e0' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:ad:80' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:ad:81' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:ad:82' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:b2:d0' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:b2:d1' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe18.yaml b/data/UNH_IOL/HPE_Proliant/hpe18.yaml deleted file mode 100644 index aa411f1..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe18.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:19:a9:b0' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:ae:b8' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:ae:b9' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:ae:ba' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:af:50' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:af:51' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe19.yaml b/data/UNH_IOL/HPE_Proliant/hpe19.yaml deleted file mode 100644 index 40e538c..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe19.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:22:b5:90' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:ad:20' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:ad:21' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:ad:22' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:b3:10' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:b3:11' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/HPE_Proliant/hpe20.yaml b/data/UNH_IOL/HPE_Proliant/hpe20.yaml deleted file mode 100644 index 6c83e79..0000000 --- a/data/UNH_IOL/HPE_Proliant/hpe20.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -disk: - - - name: sda - size: 894.2G -cpu: - arch: x86_64 - cpus: 2 - cores: 88 -memory: 503G -interface: - - - name: eno49 - mac: '48:df:37:1d:49:a0' - busaddr: '0000:04:00.0' - speed: 10000 - - - name: ens1f0 - mac: '3c:fd:fe:b2:b1:c8' - busaddr: '0000:05:00.0' - speed: 10000 - - - name: ens1f1 - mac: '3c:fd:fe:b2:b1:c9' - busaddr: '0000:05:00.1' - speed: 10000 - - - name: ens1f2 - mac: '3c:fd:fe:b2:b1:ca' - busaddr: '0000:05:00.2' - speed: 10000 - - - name: ens4f0 - mac: '3c:fd:fe:b2:ae:a0' - busaddr: '0000:88:00.0' - speed: 10000 - - - name: ens4f1 - mac: '3c:fd:fe:b2:ae:a1' - busaddr: '0000:88:00.1' - speed: 10000 diff --git a/data/UNH_IOL/hostlist.json b/data/UNH_IOL/hostlist.json deleted file mode 100644 index cd03859..0000000 --- a/data/UNH_IOL/hostlist.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "profiles": { - "HPE_Proliant": [ - "hpe13", - "hpe14", - "hpe15", - "hpe16", - "hpe17", - "hpe18", - "hpe19", - "hpe20" - ] - } -} diff --git a/docker-compose.override-dev.yml b/docker-compose.override-dev.yml deleted file mode 100644 index 4d42569..0000000 --- a/docker-compose.override-dev.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -############################################################################## -# Copyright (c) 2018 Trevor Bramwell 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 -############################################################################## -version: '3' -services: - nginx: - command: echo "Nginx is disabled in dev mode." - restart: "no" - - web: - image: opnfv/laas-dashboard:dev - build: - context: . - dockerfile: web/Dockerfile - command: > - sh -c "cd static && npm install && cd .. && - ./manage.py runserver 0:8000" - volumes: - - ./src:/laas_dashboard - ports: - - "8000:8000" - - worker: - image: opnfv/laas-celery:dev - build: - context: . - dockerfile: worker/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f0de7b2..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,70 +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 -############################################################################## -version: '3' -services: - nginx: - restart: always - image: nginx:latest - container_name: ng01 - ports: - - "80:80" - volumes: - - ./config/nginx:/etc/nginx/conf.d - - laas-static:/static:ro - - laas-media:/media - depends_on: - - web - - web: - image: opnfv/laas-dashboard:latest - restart: always - container_name: dg01 - depends_on: - - postgres - links: - - postgres - env_file: config.env - volumes: - - laas-static:/static - - laas-media:/media - expose: - - "8000" - - postgres: - restart: always - image: postgres:10 - container_name: ps01 - env_file: config.env - volumes: - - laas-data:/var/lib/postgresql/data - - rabbitmq: - restart: always - image: rabbitmq - container_name: rm01 - #env_file: config.env - volumes: - - ./config/rabbitmq:/etc/rabbitmq - ports: - - "5672:5672" - - worker: - image: opnfv/laas-celery:latest - restart: always - env_file: config.env - links: - - postgres - - rabbitmq -volumes: - laas-media: - laas-static: - laas-data: - external: true diff --git a/laas_api_documentation.yaml b/laas_api_documentation.yaml deleted file mode 100644 index d8f6186..0000000 --- a/laas_api_documentation.yaml +++ /dev/null @@ -1,424 +0,0 @@ -swagger: '2.0' -info: - description: |- - Details for all endpoints for LaaS automation API. This serves to allow users - to create bookings outside of the web UI hosted at labs.lfnetworking.org. - All included setup is referencing the development server hosted while in - beta testing for the API. - version: 1.0.0 - title: LaaS Automation API - termsOfService: 'http://labs.lfnetworking.org' - contact: - email: opnfv@iol.unh.edu - license: - name: MIT License -host: 10.10.30.55 -basePath: /api -tags: - - name: Bookings - description: View and edit existing bookings - - name: Resource Inventory - description: Examine and manage resources in a lab - - name: Users - description: All actions for referencing -schemes: - - http -security: - - AutomationAPI: [] -paths: - /booking: - get: - tags: - - Bookings - summary: Get all bookings belonging to user - description: Get all bookings belonging to the user authenticated by API key. - operationId: retrieveBookings - produces: - - application/json - responses: - '200': - description: successful operation - schema: - type: array - items: - $ref: '#/definitions/Booking' - '401': - description: Unauthorized API key - /booking/makeBooking: - put: - tags: - - Bookings - summary: Make booking by specifying information - description: Exposes same functionality as quick booking form from dashboard - operationId: makeBooking - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: booking - description: the booking to create - schema: - $ref: '#/definitions/MakeBookingTemplate' - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/Booking' - '400': - description: Error in booking info - '401': - description: Unauthorized API key - '/booking/{bookingID}': - get: - tags: - - Bookings - summary: See all info for specific booking - description: '' - operationId: specificBooking - parameters: - - in: path - name: bookingID - required: true - type: integer - produces: - - application/json - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/Booking' - '404': - description: Booking does not exist - '401': - description: Unauthorized API key - delete: - tags: - - Bookings - summary: Cancel booking - description: '' - operationId: cancelBooking - parameters: - - in: path - name: bookingID - required: true - type: integer - produces: - - application/json - responses: - '200': - description: successfully canceled booking - '404': - description: Booking does not exist - '400': - description: Cannnot cancel booking - '401': - description: Unauthorized API key - '/booking/{bookingID}/details': - get: - tags: - - Bookings - summary: Get booking details - description: '' - operationID: bookingDetails - parameters: - - in: path - name: bookingID - required: true - type: integer - produces: - - application/json - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/Booking' - '404': - description: Booking does not exist - '401': - description: Unauthorized API key - '/booking/{bookingID}/extendBooking/{days}': - post: - tags: - - Bookings - summary: Extend end date of booking - description: '' - operationId: extendBooking - parameters: - - in: path - name: bookingID - required: true - type: integer - - in: path - name: days - required: true - type: integer - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/Booking' - '404': - description: Booking to extend does not exist - '400': - description: Cannot extend Booking - '401': - description: Unauthorized API key - '/resource_inventory/{templateLabID}/images': - get: - tags: - - Resource Inventory - summary: See valid images for a resource template - description: '' - operationId: viewImages - parameters: - - in: path - name: templateLabID - required: true - type: integer - produces: - - application/json - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/Image' - '404': - description: Resource Template does not exist - '401': - description: Unauthorized API key - /resource_inventory/availableTemplates: - get: - tags: - - Resource Inventory - summary: All Resource Templates currently available - description: '' - operationId: listTemplates - produces: - - application/json - responses: - '200': - description: successful operation - schema: - $ref: '#/definitions/ResourceTemplate' - '401': - description: Unauthorized API key - /users: - get: - tags: - - Users - summary: See all public users that can be added to a booking - description: '' - operationId: getUsers - produces: - - application/json - responses: - '200': - description: successful operation - schema: - type: array - items: - $ref: '#/definitions/UserProfile' - '401': - description: Unauthorized API key - /labs: - get: - tags: - - Lab - summary: List all labs and some of their info - description: '' - operationId: listLabs - produces: - - application/json - responses: - '200': - description: successful operation - schema: - type: array - items: - $ref: '#/definitions/Lab' - '401': - description: Unauthorized API Key - /labs/{labID}/users: - get: - tags: - - Lab - summary: Get all users that are visible to a lab for operational purposes - description: '' - operationId: labUsers - consumes: - - application/json - produces: - - application/json - parameters: - - in: path - name: labID - required: true - type: string - responses: - '200': - description: successful - schema: array - items: - $ref: '#/definitions/UserProfile' - '400': - description: invalid lab id -securityDefinitions: - AutomationAPI: - type: apiKey - in: header - name: auth-token -definitions: - Lab: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - MakeBookingTemplate: - type: object - required: - - templateID - - purpose - - project - - collaborators - - hostname - - length - - imageLabID - properties: - templateID: - type: integer - purpose: - type: string - project: - type: string - collaborators: - type: array - items: - type: string - description: username of the referred user - hostname: - type: string - length: - type: integer - description: length of the booking in days (max 21, min 1) - imageLabID: - type: integer - Booking: - type: object - required: - - id - - owner - - collaborators - - start - - end - - lab - - purpose - - project - - resourceBundle - properties: - id: - type: integer - format: int64 - owner: - type: string - collaborators: - type: array - items: - $ref: '#/definitions/UserProfile' - start: - type: string - format: date-time - end: - type: string - format: date-time - lab: - $ref: '#/definitions/Lab' - purpose: - type: string - resourceBundle: - $ref: '#/definitions/ResourceBundle' - project: - type: string - Image: - type: object - required: - - labID - - resources - properties: - labID: - type: integer - format: int64 - name: - type: string - ResourceBundle: - type: object - required: - - id - - resources - properties: - id: - type: integer - format: int64 - resources: - type: array - items: - $ref: '#/definitions/Server' - ResourceProfile: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - UserProfile: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - ResourceTemplate: - type: object - required: - - id - - name - - resourceProfiles - properties: - id: - type: integer - format: int64 - name: - type: string - resourceProfiles: - type: array - items: - $ref: '#/definitions/ResourceProfile' - Server: - type: object - required: - - id - - labid - - profile - properties: - id: - type: integer - format: int64 - profile: - $ref: '#/definitions/ResourceProfile' - labid: - type: string diff --git a/open-api-spec.yaml b/open-api-spec.yaml deleted file mode 100644 index 2e8dfd6..0000000 --- a/open-api-spec.yaml +++ /dev/null @@ -1,523 +0,0 @@ ---- -swagger: "2.0" -info: - description: This is the Lab as a Service API - version: 2.0.1 - title: LaaS API - contact: - email: nfvlab@iol.unh.edu - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html -host: virtserver.swaggerhub.com -basePath: /IOL-OPNFV-LaaS/Labs/1.0.0 -tags: -- name: admin - description: Secured Admin-only calls -- name: developers - description: Operations available to regular developers -schemes: -- https -paths: - /api/labs/{lab-name}/jobs/new: - get: - summary: list of new, unstarted jobs for the lab - description: | - List of jobs for to start. These jobs all must have a status of `new`, - meaning they are unstarted. - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - responses: - 200: - description: search results matching criteria - schema: - type: array - items: - $ref: '#/definitions/Job' - /api/labs/{lab-name}/jobs/current: - get: - summary: list of unfinished jobs - description: | - List of jobs for that are still in progress. A job is in progress if - it has been started but has not finished. - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - responses: - 200: - description: search results matching criteria - schema: - type: array - items: - $ref: '#/definitions/Job' - /api/labs/{lab-name}/jobs/done: - get: - summary: list of done jobs - description: | - List of jobs for that were started and are no longer in progress. - A job can be marked 'done' with a succesful or error status. - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - responses: - 200: - description: search results matching criteria - schema: - type: array - items: - $ref: '#/definitions/Job' - /api/labs/{lab-name}/jobs/{job_id}/{task_id}>: - post: - summary: update job information - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - - name: job_id - in: path - required: true - type: integer - - name: task_id - in: path - required: true - type: string - - in: body - name: payload - description: payload, schema based on job type - required: true - schema: - $ref: '#/definitions/JobUpdate' - responses: - 200: - description: success - /api/labs/{lab-name}/inventory: - get: - summary: lab inventory - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - responses: - 200: - description: lab inventory - schema: - $ref: '#/definitions/Inventory' - post: - summary: updates lab inventory - parameters: - - name: lab-name - in: path - required: true - type: string - - in: body - name: inventory - required: true - schema: - $ref: '#/definitions/Inventory' - responses: - 200: - description: success - /api/labs/{lab-name}/profile: - get: - summary: lab profile - produces: - - application/json - parameters: - - name: lab-name - in: path - required: true - type: string - responses: - 200: - description: lab profile - schema: - $ref: '#/definitions/Profile' - post: - summary: updates lab profile - parameters: - - name: lab-name - in: path - required: true - type: string - - in: body - name: profile - required: true - schema: - $ref: '#/definitions/Profile' - responses: - 200: - description: success -definitions: - Host_Interface: - properties: - mac: - type: string - example: 00:11:22:33:44:55 - description: mac address - busaddr: - type: string - example: 0000:02:00.1 - description: bus address reported by `ethtool -i ` - switchport: - $ref: '#/definitions/Switchport' - Generic_Interface: - properties: - speed: - type: string - example: 10G - description: speed in M or G - name: - type: string - example: eno3 - description: interface name - Generic_Disk: - properties: - size: - type: string - example: 500G - description: size in M, G, or T - type: - type: string - example: SSD - description: must be SSD or HDD - name: - type: string - example: sda - description: name of root block device - CPU: - properties: - cores: - type: integer - format: int32 - example: 64 - description: how many CPU cores the host has (across all physical cpus) - minimum: 1 - arch: - type: string - example: x86_64 - description: must be x86_64 or aarch64 - cpus: - type: integer - example: 2 - description: Number of different physical CPU chips - minimum: 1 - Image: - properties: - name: - type: string - description: - type: string - lab_id: - type: string - description: identifier provided by lab - dashboard_id: - type: string - description: identifier provided by dashboard - Inventory_Host: - properties: - interfaces: - type: array - items: - $ref: '#/definitions/Host_Interface' - hostname: - type: string - example: hpe3.opnfv.iol.unh.edu - description: globally unique fqdn - host_type: - type: string - description: name of host type this host belongs to - Inventory_Network: - properties: - cidr: - type: string - example: 174.0.5.0/24 - description: subnet description - gateway: - type: string - example: 174.0.5.1 - description: ip of gateway - vlan: - type: integer - example: 100 - description: vlan tag of this network - Inventory: - properties: - hosts: - type: array - description: all hosts - items: - $ref: '#/definitions/Inventory_Host' - networks: - type: array - description: all networks - items: - $ref: '#/definitions/Inventory_Network' - images: - type: array - description: available images - items: - $ref: '#/definitions/Image' - host_types: - type: array - description: all host types hosted by a lab - items: - $ref: '#/definitions/Host_Type' - Host_Type: - properties: - cpu: - $ref: '#/definitions/CPU' - disks: - type: array - items: - $ref: '#/definitions/Generic_Disk' - description: - type: string - description: human readable description of host type - interface: - type: array - items: - $ref: '#/definitions/Generic_Interface' - ram: - $ref: '#/definitions/Ram' - name: - type: string - description: lab-unique name - Ram: - properties: - amount: - type: integer - example: 16 - description: amount of ram in Gibibytes (GiB) - Switchport: - properties: - switch_name: - type: string - example: Cisco-9 - description: name of switch owning this switchport - port_name: - type: string - example: Ethernet1/34 - description: name of port on switch - invariant_config: - type: array - description: list of vlans that may not be modified on this port - items: - $ref: '#/definitions/Vlan' - current_config: - type: array - description: list of current vlan configuration - items: - $ref: '#/definitions/Vlan' - Vlan: - properties: - vlan_id: - type: integer - example: 100 - description: vlan id - minimum: 1 - maximum: 4098 - tagged: - type: boolean - example: true - description: whether this vlan is tagged or untagged - Job: - properties: - id: - type: integer - description: globally unique job identifier - payload: - $ref: '#/definitions/JobPayload' - JobPayload: - properties: - hardware: - $ref: '#/definitions/HardwareTask' - software: - $ref: '#/definitions/SoftwareTask' - network: - $ref: '#/definitions/NetworkTask' - access: - $ref: '#/definitions/AccessTask' - snapshot: - $ref: '#/definitions/SnapshotTask' - HardwareTask: - properties: - taskId: - $ref: '#/definitions/HardwareConfig' - SoftwareTask: - properties: - taskId: - $ref: '#/definitions/SoftwarePayload' - NetworkTask: - properties: - taskId: - $ref: '#/definitions/NetworkPayload' - AccessTask: - properties: - taskId: - $ref: '#/definitions/AccessPayload' - SnapshotTask: - properties: - taskId: - $ref: '#/definitions/SnapshotPayload' - SnapshotPayload: - properties: - host: - type: string - example: hpe3 - description: how the lab identifies the host - image: - type: string - example: "4" - description: lab id of existing image, if updating an existing image. if this key does not exist, the lab must create a new image - dashboard_id: - type: string - description: how the dashboard identifies this image / snapshot - AccessPayload: - properties: - revoke: - type: boolean - description: whether to revoke key during completion of job - user: - type: string - description: PK/ID of user access is being given to - access_type: - type: string - example: ssh - description: type of access key to be generated. Options include "vpn and ssh" - hosts: - type: array - description: hosts to grant access to if applicable - items: - type: string - description: id of host - lab_token: - type: string - description: identifier provided by lab to this task - HardwareConfig: - properties: - id: - type: string - description: ID of host - image: - type: integer - example: 42 - description: lab provided ID of the request image - power: - type: string - example: on - description: desired power state, either on or off - hostname: - type: string - example: my_new_machine - description: user-defined hostname - ipmi_create: - type: boolean - description: whether or not to create an ipmi account - lab_token: - type: string - description: identifier provided by lab to this task - SoftwarePayload: - properties: - opnfv: - $ref: '#/definitions/OpnfvConfiguration' - lab_token: - type: string - description: identifier provided by lab to this task - OpnfvHost: - properties: - hostname: - type: string - example: Jumphost - description: maps hostname to OPNFV role - OpnfvConfiguration: - properties: - installer: - type: string - description: Installer user wants - scenario: - type: string - description: scenario of OPNFV to deploy - pdf: - type: string - example: LaaS.com/api/my_job/pdf - description: URL to find the Pod Descriptor File contents - idf: - type: string - example: LaaS.com/api/my_job/idf - description: URL to find the Installer Descriptor File contents - roles: - type: array - description: role the host will play in OPNFV - items: - $ref: '#/definitions/OpnfvHost' - NetworkPayload: - properties: - hostId: - $ref: '#/definitions/NetworkConfig' - lab_token: - type: string - description: identifier provided by lab to this task - NetworkConfig: - properties: - interface_name: - type: array - description: list of vlans on this interface - items: - $ref: '#/definitions/Vlan' - JobUpdate: - properties: - status: - type: integer - description: status type, see status enum - message: - type: string - description: message from lab for user - lab_token: - type: string - description: identifier provided by lab to this task - Profile: - properties: - name: - type: string - description: proper expanded lab name - contact: - $ref: '#/definitions/Contact' - description: - type: string - host_count: - type: array - items: - $ref: '#/definitions/Host_Number' - Host_Number: - properties: - type: - type: string - count: - type: integer - Contact: - properties: - phone: - type: string - description: phone number at which a lab can be reached - email: - type: string - description: email at which a lab can be reached diff --git a/readme.txt b/readme.txt index 40a39fc..6d02dcb 100644 --- a/readme.txt +++ b/readme.txt @@ -1,49 +1,2 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -The dashboard is deployed using docker-compose. - -Application / database files are saved in the 'laas-data' container -which needs to be pre-built before bringing up the dashboard. - -Deployment: - -- clone the repository -- complete the config.env.sample file and save it as config.env -- install docker, docker-compose -- run 'make data' -- run 'make up' to run the dashboard (or 'make dev-up' for development) -- get the rsa.pem and rsa.pub keys from your jira admin and place them in src/account - -Production will be running on port 80 by default. -Development will be running on port 8000 by default. - -Updating: - -- run 'docker-compose pull' -- run 'docker-compose up -d' -- make stop -- git pull -- make build -- make start - -If there is migrations that need user input (like renaming a field), they need to be run manually! - -Logs / Shell access: - -- there is some shortcuts in the makefile - -Development: - -- Install dependencies listed in 'Deployment' -- run 'make build' -- run 'make dev-up' - - NOTE: DEBUG must be set to True in config.env when running development builds +This project was moved to GitHub and the active development is happening in +https://github.com/anuket-project/laas \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c922851..0000000 --- a/requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -celery==5.1.2 -cryptography==3.4.7 -Django==2.2 -django-bootstrap4==0.0.8 -django-filter==2.0.0 -djangorestframework==3.8.2 -gunicorn==20.1.0 -oauth2==1.9.0.post1 -oauthlib==3.1.1 -pika==1.2.0 -psycopg2==2.8.6 -PyJWT==2.1.0 -requests==2.26.0 -django-fernet-fields==0.6 -pyyaml==6.0 -pytz==2018.5 -mozilla-django-oidc==1.2.3 -deepmerge==0.3 diff --git a/src/account/__init__.py b/src/account/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/account/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/account/admin.py b/src/account/admin.py deleted file mode 100644 index b4c142c..0000000 --- a/src/account/admin.py +++ /dev/null @@ -1,19 +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 -############################################################################## - - -from django.contrib import admin - -from account.models import UserProfile, Lab, VlanManager, PublicNetwork - -admin.site.register(UserProfile) -admin.site.register(Lab) -admin.site.register(VlanManager) -admin.site.register(PublicNetwork) diff --git a/src/account/apps.py b/src/account/apps.py deleted file mode 100644 index 9814648..0000000 --- a/src/account/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'account' diff --git a/src/account/forms.py b/src/account/forms.py deleted file mode 100644 index 28cb27d..0000000 --- a/src/account/forms.py +++ /dev/null @@ -1,29 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 django.forms as forms -import pytz as pytz -from django.utils.translation import gettext_lazy as _ - -from account.models import UserProfile - - -class AccountSettingsForm(forms.ModelForm): - class Meta: - model = UserProfile - fields = ['company', 'email_addr', 'public_user', 'ssh_public_key', 'pgp_public_key', 'timezone'] - labels = { - 'email_addr': _('Email Address'), - 'ssh_public_key': _('SSH Public Key'), - 'pgp_public_key': _('PGP Public Key'), - 'public_user': _('Public User') - } - - timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC') diff --git a/src/account/middleware.py b/src/account/middleware.py deleted file mode 100644 index 6a46dfe..0000000 --- a/src/account/middleware.py +++ /dev/null @@ -1,35 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.utils import timezone -from django.utils.deprecation import MiddlewareMixin - -from account.models import UserProfile - - -class TimezoneMiddleware(MiddlewareMixin): - """ - Manage user's Timezone preference. - - Activate the timezone from request.user.userprofile if user is authenticated, - deactivate the timezone otherwise and use default (UTC) - """ - - def process_request(self, request): - if request.user.is_authenticated: - try: - tz = request.user.userprofile.timezone - timezone.activate(tz) - except UserProfile.DoesNotExist: - UserProfile.objects.create(user=request.user) - tz = request.user.userprofile.timezone - timezone.activate(tz) - else: - timezone.deactivate() diff --git a/src/account/migrations/0001_initial.py b/src/account/migrations/0001_initial.py deleted file mode 100644 index c8b5bdc..0000000 --- a/src/account/migrations/0001_initial.py +++ /dev/null @@ -1,65 +0,0 @@ -# Generated by Django 2.1 on 2018-09-14 14:48 - -import account.models -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Lab', - fields=[ - ('name', models.CharField(max_length=200, primary_key=True, serialize=False, unique=True)), - ('contact_email', models.EmailField(blank=True, max_length=200, null=True)), - ('contact_phone', models.CharField(blank=True, max_length=20, null=True)), - ('status', models.IntegerField(default=0)), - ('location', models.TextField(default='unknown')), - ('api_token', models.CharField(max_length=50)), - ('lab_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='UserProfile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timezone', models.CharField(default='UTC', max_length=100)), - ('ssh_public_key', models.FileField(blank=True, null=True, upload_to=account.models.upload_to)), - ('pgp_public_key', models.FileField(blank=True, null=True, upload_to=account.models.upload_to)), - ('email_addr', models.CharField(default='email@mail.com', max_length=300)), - ('company', models.CharField(max_length=200)), - ('oauth_token', models.CharField(max_length=1024)), - ('oauth_secret', models.CharField(max_length=1024)), - ('jira_url', models.CharField(default='', max_length=100)), - ('full_name', models.CharField(default='', max_length=100)), - ('booking_privledge', models.BooleanField(default=False)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'db_table': 'user_profile', - }, - ), - migrations.CreateModel( - name='VlanManager', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vlans', models.TextField()), - ('block_size', models.IntegerField()), - ('allow_overlapping', models.BooleanField()), - ('reserved_vlans', models.TextField()), - ], - ), - migrations.AddField( - model_name='lab', - name='vlan_manager', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='account.VlanManager'), - ), - ] diff --git a/src/account/migrations/0002_lab_description.py b/src/account/migrations/0002_lab_description.py deleted file mode 100644 index 445501a..0000000 --- a/src/account/migrations/0002_lab_description.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1 on 2018-09-14 20:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='lab', - name='description', - field=models.CharField(default='Lab description default', max_length=240), - preserve_default=False, - ), - ] diff --git a/src/account/migrations/0003_publicnetwork.py b/src/account/migrations/0003_publicnetwork.py deleted file mode 100644 index 71e5caa..0000000 --- a/src/account/migrations/0003_publicnetwork.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1 on 2018-09-26 14:41 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0002_lab_description'), - ] - - operations = [ - migrations.CreateModel( - name='PublicNetwork', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vlan', models.IntegerField()), - ('in_use', models.BooleanField(default=False)), - ('cidr', models.CharField(default='0.0.0.0/0', max_length=50)), - ('gateway', models.CharField(default='0.0.0.0', max_length=50)), - ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), - ], - ), - ] diff --git a/src/account/migrations/0004_downtime.py b/src/account/migrations/0004_downtime.py deleted file mode 100644 index fc700d1..0000000 --- a/src/account/migrations/0004_downtime.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2 on 2019-08-13 16:45 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0003_publicnetwork'), - ] - - operations = [ - migrations.CreateModel( - name='Downtime', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('start', models.DateTimeField()), - ('end', models.DateTimeField()), - ('description', models.TextField(default='This lab will be down for maintenance')), - ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), - ], - ), - ] diff --git a/src/account/migrations/0005_auto_20200723_2100.py b/src/account/migrations/0005_auto_20200723_2100.py deleted file mode 100644 index d995f80..0000000 --- a/src/account/migrations/0005_auto_20200723_2100.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2020-07-23 21:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0004_downtime'), - ] - - operations = [ - migrations.AddField( - model_name='lab', - name='lab_info_link', - field=models.URLField(null=True), - ), - migrations.AddField( - model_name='lab', - name='project', - field=models.CharField(default='LaaS', max_length=100), - ), - ] diff --git a/src/account/migrations/0006_auto_20201109_1947.py b/src/account/migrations/0006_auto_20201109_1947.py deleted file mode 100644 index d08c426..0000000 --- a/src/account/migrations/0006_auto_20201109_1947.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 19:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0005_auto_20200723_2100'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='full_name', - field=models.CharField(blank=True, default='', max_length=100, null=True), - ), - migrations.AlterField( - model_name='userprofile', - name='jira_url', - field=models.CharField(blank=True, default='', max_length=100, null=True), - ), - ] diff --git a/src/account/migrations/0007_userprofile_pulic_user.py b/src/account/migrations/0007_userprofile_pulic_user.py deleted file mode 100644 index 6a229e6..0000000 --- a/src/account/migrations/0007_userprofile_pulic_user.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-03-24 21:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0006_auto_20201109_1947'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='pulic_user', - field=models.BooleanField(default=False), - ), - ] diff --git a/src/account/migrations/0008_auto_20210324_2106.py b/src/account/migrations/0008_auto_20210324_2106.py deleted file mode 100644 index 9ff513d..0000000 --- a/src/account/migrations/0008_auto_20210324_2106.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-03-24 21:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0007_userprofile_pulic_user'), - ] - - operations = [ - migrations.RenameField( - model_name='userprofile', - old_name='pulic_user', - new_name='public_user', - ), - ] diff --git a/src/account/migrations/0009_auto_20210324_2107.py b/src/account/migrations/0009_auto_20210324_2107.py deleted file mode 100644 index baa7382..0000000 --- a/src/account/migrations/0009_auto_20210324_2107.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-03-24 21:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0008_auto_20210324_2106'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='public_user', - field=models.BooleanField(default=False), - ), - ] diff --git a/src/account/migrations/__init__.py b/src/account/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/account/models.py b/src/account/models.py deleted file mode 100644 index 32229b1..0000000 --- a/src/account/models.py +++ /dev/null @@ -1,297 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.contrib.auth.models import User -from django.db import models -from django.apps import apps -import json -import random - -from collections import Counter - -from dashboard.exceptions import ResourceAvailabilityException - - -class LabStatus(object): - """ - A Poor man's enum for the status of a lab. - - If everything is working fine at a lab, it is UP. - If it is down temporarily e.g. for maintenance, it is TEMP_DOWN - If its broken, its DOWN - """ - - UP = 0 - TEMP_DOWN = 100 - DOWN = 200 - - -def upload_to(object, filename): - return object.user.username + '/' + filename - - -class UserProfile(models.Model): - """Extend the Django User model.""" - - user = models.OneToOneField(User, on_delete=models.CASCADE) - timezone = models.CharField(max_length=100, blank=False, default='UTC') - ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) - pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) - email_addr = models.CharField(max_length=300, blank=False, default='email@mail.com') - company = models.CharField(max_length=200, blank=False) - - oauth_token = models.CharField(max_length=1024, blank=False) - oauth_secret = models.CharField(max_length=1024, blank=False) - - jira_url = models.CharField(max_length=100, null=True, blank=True, default='') - - full_name = models.CharField(max_length=100, null=True, blank=True, default='') - booking_privledge = models.BooleanField(default=False) - - public_user = models.BooleanField(default=False) - - class Meta: - db_table = 'user_profile' - - def __str__(self): - return self.user.username - - -class VlanManager(models.Model): - """ - Keeps track of the vlans for a lab. - - Vlans are represented as indexes into a 4096 element list. - This list is serialized to JSON for storing in the DB. - """ - - # list of length 4096 containing either 0 (not available) or 1 (available) - vlans = models.TextField() - # list of length 4096 containing either 0 (not reserved) or 1 (reserved) - reserved_vlans = models.TextField() - - block_size = models.IntegerField() - - # True if the lab allows two different users to have the same private vlans - # if they use QinQ or a vxlan overlay, for example - allow_overlapping = models.BooleanField() - - def get_vlans(self, count=1, within=None): - """ - Return the IDs of available vlans as a list[int], but does not reserve them. - - Will throw index exception if not enough vlans are available. - Always returns a list of ints - - If `within` is not none, will filter against that as a set, requiring that any vlans returned are within that set - """ - allocated = [] - vlans = json.loads(self.vlans) - reserved = json.loads(self.reserved_vlans) - - for i in range(0, len(vlans) - 1): - if len(allocated) >= count: - break - - if vlans[i] == 0 and self.allow_overlapping is False: - continue - - if reserved[i] == 1: - continue - - # vlan is available and not reserved, so safe to add - if within is not None: - if i in within: - allocated.append(i) - else: - allocated.append(i) - continue - - if len(allocated) != count: - raise ResourceAvailabilityException("There were not enough available private vlans for the allocation. Please contact the administrators.") - - return allocated - - def get_public_vlan(self, within=None): - """Return reference to an available public network without reserving it.""" - r = PublicNetwork.objects.filter(lab=self.lab_set.first(), in_use=False) - if within is not None: - r = r.filter(vlan__in=within) - - if r.count() < 1: - raise ResourceAvailabilityException("There were not enough available public vlans for the allocation. Please contact the administrators.") - - return r.first() - - def reserve_public_vlan(self, vlan): - """Reserves the Public Network that has the given vlan.""" - net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan, in_use=False) - net.in_use = True - net.save() - - def release_public_vlan(self, vlan): - """Un-reserves a public network with the given vlan.""" - net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan, in_use=True) - net.in_use = False - net.save() - - def public_vlan_is_available(self, vlan): - """ - Whether the public vlan is available. - - returns true if the network with the given vlan is free to use, - False otherwise - """ - net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan) - return not net.in_use - - def is_available(self, vlans): - """ - If the vlans are available. - - 'vlans' is either a single vlan id integer or a list of integers - will return true (available) or false - """ - if self.allow_overlapping: - return True - - reserved = json.loads(self.reserved_vlans) - vlan_master_list = json.loads(self.vlans) - try: - iter(vlans) - except Exception: - vlans = [vlans] - - for vlan in vlans: - if not vlan_master_list[vlan] or reserved[vlan]: - return False - return True - - def release_vlans(self, vlans): - """ - Make the vlans available for another booking. - - 'vlans' is either a single vlan id integer or a list of integers - will make the vlans available - doesnt return a value - """ - my_vlans = json.loads(self.vlans) - - try: - iter(vlans) - except Exception: - vlans = [vlans] - - for vlan in vlans: - my_vlans[vlan] = 1 - self.vlans = json.dumps(my_vlans) - self.save() - - def reserve_vlans(self, vlans): - """ - Reserves all given vlans or throws a ValueError. - - vlans can be an integer or a list of integers. - """ - my_vlans = json.loads(self.vlans) - - reserved = json.loads(self.reserved_vlans) - - try: - iter(vlans) - except Exception: - vlans = [vlans] - - vlans = set(vlans) - - for vlan in vlans: - if my_vlans[vlan] == 0 or reserved[vlan] == 1: - raise ValueError("vlan " + str(vlan) + " is not available") - - my_vlans[vlan] = 0 - self.vlans = json.dumps(my_vlans) - self.save() - - -class Lab(models.Model): - """ - Model representing a Hosting Lab. - - Anybody that wants to host resources for LaaS needs to have a Lab model - We associate hardware with Labs so we know what is available and where. - """ - - lab_user = models.OneToOneField(User, on_delete=models.CASCADE) - name = models.CharField(max_length=200, primary_key=True, unique=True, null=False, blank=False) - contact_email = models.EmailField(max_length=200, null=True, blank=True) - contact_phone = models.CharField(max_length=20, null=True, blank=True) - status = models.IntegerField(default=LabStatus.UP) - vlan_manager = models.ForeignKey(VlanManager, on_delete=models.CASCADE, null=True) - location = models.TextField(default="unknown") - # This token must apear in API requests from this lab - api_token = models.CharField(max_length=50) - description = models.CharField(max_length=240) - lab_info_link = models.URLField(null=True) - project = models.CharField(default='LaaS', max_length=100) - - @staticmethod - def make_api_token(): - """Generate random 45 character string for API token.""" - alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - key = "" - for i in range(45): - key += random.choice(alphabet) - return key - - def get_available_resources(self): - # Cannot import model normally due to ciruclar import - Server = apps.get_model('resource_inventory', 'Server') # TODO: Find way to import ResourceQuery - resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, working=True, booked=False)] - return dict(Counter(resources)) - - def __str__(self): - return self.name - - -class PublicNetwork(models.Model): - """L2/L3 network that can reach the internet.""" - - vlan = models.IntegerField() - lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - in_use = models.BooleanField(default=False) - cidr = models.CharField(max_length=50, default="0.0.0.0/0") - gateway = models.CharField(max_length=50, default="0.0.0.0") - - -class Downtime(models.Model): - """ - A Downtime event. - - Labs can create Downtime objects so the dashboard can - alert users that the lab is down, etc - """ - - start = models.DateTimeField() - end = models.DateTimeField() - lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - description = models.TextField(default="This lab will be down for maintenance") - - def save(self, *args, **kwargs): - if self.start >= self.end: - raise ValueError('Start date is after end date') - - # check for overlapping downtimes - overlap_start = Downtime.objects.filter(lab=self.lab, start__gt=self.start, start__lt=self.end).exists() - overlap_end = Downtime.objects.filter(lab=self.lab, end__lt=self.end, end__gt=self.start).exists() - - if overlap_start or overlap_end: - raise ValueError('Overlapping Downtime') - - return super(Downtime, self).save(*args, **kwargs) diff --git a/src/account/tests/__init__.py b/src/account/tests/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/account/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/account/tests/test_general.py b/src/account/tests/test_general.py deleted file mode 100644 index 4020d89..0000000 --- a/src/account/tests/test_general.py +++ /dev/null @@ -1,60 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.contrib.auth.models import User -from django.test import Client -from django.test import TestCase -from django.urls import reverse -from django.utils import timezone - -from account.models import UserProfile - - -class AccountMiddlewareTestCase(TestCase): - def setUp(self): - self.client = Client() - self.user1 = User.objects.create(username='user1') - self.user1.set_password('user1') - self.user1profile = UserProfile.objects.create(user=self.user1) - self.user1.save() - - def test_timezone_middleware(self): - """ - Verify timezone is being set by Middleware. - - The timezone should be UTC for anonymous users, - for authenticated users it should be set to user.userprofile.timezone - """ - # default - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - url = reverse('account:settings') - # anonymous request - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - # authenticated user with UTC timezone (userprofile default) - self.client.login(username='user1', password='user1') - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - # authenticated user with custom timezone (userprofile default) - self.user1profile.timezone = 'Etc/Greenwich' - self.user1profile.save() - self.client.get(url) - self.assertEqual(timezone.get_current_timezone_name(), 'GMT') - - # if there is no profile for a user, it should be created - user2 = User.objects.create(username='user2') - user2.set_password('user2') - user2.save() - self.client.login(username='user2', password='user2') - self.client.get(url) - self.assertTrue(user2.userprofile) diff --git a/src/account/urls.py b/src/account/urls.py deleted file mode 100644 index 6d4ef2f..0000000 --- a/src/account/urls.py +++ /dev/null @@ -1,59 +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 -############################################################################## - - -""" -laas_dashboard URL Configuration. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from django.urls import path - -from account.views import ( - AccountSettingsView, - OIDCLoginView, - LogoutView, - UserListView, - account_resource_view, - account_booking_view, - account_images_view, - account_detail_view, - template_delete_view, - booking_cancel_view, - image_delete_view, -) - -app_name = 'account' - -urlpatterns = [ - url(r'^settings/', AccountSettingsView.as_view(), name='settings'), - url(r'^login/$', OIDCLoginView.as_view(), name='login'), - url(r'^logout/$', LogoutView.as_view(), name='logout'), - url(r'^users/$', UserListView.as_view(), name='users'), - url(r'^my/resources/$', account_resource_view, name='my-resources'), - path('my/resources/delete/', template_delete_view), - url(r'^my/bookings/$', account_booking_view, name='my-bookings'), - path('my/bookings/cancel/', booking_cancel_view), - url(r'^my/images/$', account_images_view, name='my-images'), - path('my/images/delete/', image_delete_view), - url(r'^my/$', account_detail_view, name='my-account'), -] diff --git a/src/account/views.py b/src/account/views.py deleted file mode 100644 index 8976ff9..0000000 --- a/src/account/views.py +++ /dev/null @@ -1,226 +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 os - -from django.utils import timezone -from django.contrib import messages -from django.contrib.auth import logout -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.models import User -from django.urls import reverse -from django.http import HttpResponse -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.generic import RedirectView, TemplateView, UpdateView -from django.shortcuts import render -from rest_framework.authtoken.models import Token -from mozilla_django_oidc.auth import OIDCAuthenticationBackend - - -from account.forms import AccountSettingsForm -from account.models import UserProfile -from booking.models import Booking -from resource_inventory.models import ResourceTemplate, Image - - -@method_decorator(login_required, name='dispatch') -class AccountSettingsView(UpdateView): - model = UserProfile - form_class = AccountSettingsForm - template_name_suffix = '_update_form' - - def get_success_url(self): - messages.add_message(self.request, messages.INFO, - 'Settings saved') - return '/' - - def get_object(self, queryset=None): - return self.request.user.userprofile - - def get_context_data(self, **kwargs): - token, created = Token.objects.get_or_create(user=self.request.user) - context = super(AccountSettingsView, self).get_context_data(**kwargs) - context.update({'title': "Settings", 'token': token}) - return context - - -class MyOIDCAB(OIDCAuthenticationBackend): - def filter_users_by_claims(self, claims): - """ - Checks to see if user exists and create user if not - - Linux foundation does not allow users to change their - username, so chose to match users based on their username. - If this changes we will need to match users based on some - other criterea. - """ - username = claims.get(os.environ.get('CLAIMS_ENDPOINT') + 'username') - - if not username: - return HttpResponse('No username provided, contact support.') - - try: - # For literally no (good) reason user needs to be a queryset - user = User.objects.filter(username=username) - return user - except User.DoesNotExist: - return self.UserModel.objects.none() - - def create_user(self, claims): - """ This creates a user and user profile""" - user = super(MyOIDCAB, self).create_user(claims) - user.username = claims.get(os.environ['CLAIMS_ENDPOINT'] + 'username') - user.save() - - up = UserProfile() - up.user = user - up.email_addr = claims.get('email') - up.save() - return user - - def update_user(self, user, claims): - """ If their account has different email, change the email """ - up = UserProfile.objects.get(user=user) - up.email_addr = claims.get('email') - up.save() - return user - - -class OIDCLoginView(RedirectView): - def get_redirect_url(self, *args, **kwargs): - return reverse('oidc_authentication_init') - - -class LogoutView(LoginRequiredMixin, RedirectView): - def get_redirect_url(self, *args, **kwargs): - logout(self.request) - return '/' - - -@method_decorator(login_required, name='dispatch') -class UserListView(TemplateView): - template_name = "account/user_list.html" - - def get_context_data(self, **kwargs): - users = UserProfile.objects.filter(public_user=True).select_related('user') - context = super(UserListView, self).get_context_data(**kwargs) - context.update({'title': "Dashboard Users", 'users': users}) - return context - - -def account_detail_view(request): - template = "account/details.html" - return render(request, template) - - -def account_resource_view(request): - """ - Display a user's resources. - - gathers a users genericResoureBundles and - turns them into displayable objects - """ - if not request.user.is_authenticated: - return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - template = "account/resource_list.html" - - active_bundles = [book.resource for book in Booking.objects.filter( - owner=request.user, end__gte=timezone.now(), resource__template__temporary=False)] - active_resources = [bundle.template.id for bundle in active_bundles] - resource_list = list(ResourceTemplate.objects.filter(owner=request.user, temporary=False)) - - context = { - "resources": resource_list, - "active_resources": active_resources, - "title": "My Resources" - } - return render(request, template, context=context) - - -def account_booking_view(request): - if not request.user.is_authenticated: - return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - template = "account/booking_list.html" - bookings = list(Booking.objects.filter(owner=request.user, end__gt=timezone.now()).order_by("-start")) - my_old_bookings = Booking.objects.filter(owner=request.user, end__lt=timezone.now()).order_by("-start") - collab_old_bookings = request.user.collaborators.filter(end__lt=timezone.now()).order_by("-start") - expired_bookings = list(my_old_bookings.union(collab_old_bookings)) - collab_bookings = list(request.user.collaborators.filter(end__gt=timezone.now()).order_by("-start")) - context = { - "title": "My Bookings", - "bookings": bookings, - "collab_bookings": collab_bookings, - "expired_bookings": expired_bookings - } - return render(request, template, context=context) - - -def account_images_view(request): - if not request.user.is_authenticated: - return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - template = "account/image_list.html" - my_images = Image.objects.filter(owner=request.user) - public_images = Image.objects.filter(public=True) - used_images = {} - for image in my_images: - if image.in_use(): - used_images[image.id] = "true" - context = { - "title": "Images", - "images": my_images, - "public_images": public_images, - "used_images": used_images - } - return render(request, template, context=context) - - -def template_delete_view(request, resource_id=None): - if not request.user.is_authenticated: - return HttpResponse(status=403) - template = get_object_or_404(ResourceTemplate, pk=resource_id) - if not request.user.id == template.owner.id: - return HttpResponse(status=403) - if Booking.objects.filter(resource__template=template, end__gt=timezone.now()).exists(): - return HttpResponse(status=403) - template.public = False - template.temporary = True - template.save() - return HttpResponse(status=200) - - -def booking_cancel_view(request, booking_id=None): - if not request.user.is_authenticated: - return HttpResponse('no') # 403? - booking = get_object_or_404(Booking, pk=booking_id) - if not request.user.id == booking.owner.id: - return HttpResponse('no') # 403? - - if booking.end < timezone.now(): # booking already over - return HttpResponse('') - - booking.end = timezone.now() - booking.save() - return HttpResponse('') - - -def image_delete_view(request, image_id=None): - if not request.user.is_authenticated: - return HttpResponse('no') # 403? - image = get_object_or_404(Image, pk=image_id) - if image.public or image.owner.id != request.user.id: - return HttpResponse('no') # 403? - # check if used in booking - if image.in_use(): - return HttpResponse('no') # 403? - image.delete() - return HttpResponse('') diff --git a/src/analytics/__init__.py b/src/analytics/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/analytics/admin.py b/src/analytics/admin.py deleted file mode 100644 index 63f139f..0000000 --- a/src/analytics/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sean Smith 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 -############################################################################## - -from django.contrib import admin -from analytics.models import ActiveVPNUser - -admin.site.register(ActiveVPNUser) diff --git a/src/analytics/apps.py b/src/analytics/apps.py deleted file mode 100644 index fe1b11f..0000000 --- a/src/analytics/apps.py +++ /dev/null @@ -1,14 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sean Smith 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 -############################################################################## - -from django.apps import AppConfig - - -class AnalyticsConfig(AppConfig): - name = 'analytics' diff --git a/src/analytics/migrations/0001_initial.py b/src/analytics/migrations/0001_initial.py deleted file mode 100644 index 05a7ec8..0000000 --- a/src/analytics/migrations/0001_initial.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2 on 2020-08-10 20:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ActiveVPNUsers', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time_stamp', models.DateTimeField(auto_now_add=True)), - ('active_users', models.IntegerField()), - ], - ), - ] diff --git a/src/analytics/migrations/0002_auto_20201109_2149.py b/src/analytics/migrations/0002_auto_20201109_2149.py deleted file mode 100644 index a845ff8..0000000 --- a/src/analytics/migrations/0002_auto_20201109_2149.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 21:49 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0006_auto_20201109_1947'), - ('analytics', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='ActiveVPNUser', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time_stamp', models.DateTimeField(auto_now_add=True)), - ('active_users', models.IntegerField()), - ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), - ], - ), - migrations.DeleteModel( - name='ActiveVPNUsers', - ), - ] diff --git a/src/analytics/migrations/__init__.py b/src/analytics/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/analytics/models.py b/src/analytics/models.py deleted file mode 100644 index 10baa0c..0000000 --- a/src/analytics/models.py +++ /dev/null @@ -1,30 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sean Smith 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 -############################################################################## - -from django.db import models -from account.models import Lab - - -class ActiveVPNUser(models.Model): - """ Keeps track of how many VPN Users are connected to Lab """ - time_stamp = models.DateTimeField(auto_now_add=True) - lab = models.ForeignKey(Lab, on_delete=models.CASCADE, null=False) - active_users = models.IntegerField() - - @classmethod - def create(cls, lab_name, active_users): - """ - This creates an Active VPN Users entry from - from lab_name as a string - """ - - lab = Lab.objects.get(name=lab_name) - avu = cls(lab=lab, active_users=active_users) - avu.save() - return avu diff --git a/src/analytics/tests.py b/src/analytics/tests.py deleted file mode 100644 index d234f48..0000000 --- a/src/analytics/tests.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sean Smith 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 -############################################################################## - -# from django.test import TestCase diff --git a/src/analytics/views.py b/src/analytics/views.py deleted file mode 100644 index 160bc59..0000000 --- a/src/analytics/views.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sean Smith 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 -############################################################################## - -# from django.shortcuts import render diff --git a/src/api/__init__.py b/src/api/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/api/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/api/admin.py b/src/api/admin.py deleted file mode 100644 index 1e243a0..0000000 --- a/src/api/admin.py +++ /dev/null @@ -1,43 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.apps import AppConfig -from django.contrib import admin - -from api.models import ( - Job, - OpnfvApiConfig, - HardwareConfig, - NetworkConfig, - SoftwareConfig, - AccessConfig, - AccessRelation, - SoftwareRelation, - HostHardwareRelation, - HostNetworkRelation, - APILog -) - - -class ApiConfig(AppConfig): - name = 'apiJobs' - - -admin.site.register(Job) -admin.site.register(OpnfvApiConfig) -admin.site.register(HardwareConfig) -admin.site.register(NetworkConfig) -admin.site.register(SoftwareConfig) -admin.site.register(AccessConfig) -admin.site.register(AccessRelation) -admin.site.register(SoftwareRelation) -admin.site.register(HostHardwareRelation) -admin.site.register(HostNetworkRelation) -admin.site.register(APILog) diff --git a/src/api/forms.py b/src/api/forms.py deleted file mode 100644 index 1b74a9b..0000000 --- a/src/api/forms.py +++ /dev/null @@ -1,16 +0,0 @@ -############################################################################## -# Copyright (c) 2019 Parker Berberian 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 django.forms as forms - - -class DowntimeForm(forms.Form): - start = forms.DateTimeField() - end = forms.DateTimeField() - description = forms.CharField(max_length=1000, required=False) diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py deleted file mode 100644 index abe6f5e..0000000 --- a/src/api/migrations/0001_initial.py +++ /dev/null @@ -1,185 +0,0 @@ -############################################################################## -# 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 -############################################################################## -# Generated by Django 2.1 on 2018-09-14 14:48 - -import api.models -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('booking', '__first__'), - ('resource_inventory', '__first__'), - ] - - operations = [ - migrations.CreateModel( - name='AccessRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='HostHardwareRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Host')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='HostNetworkRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Host')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Job', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('delta', models.TextField()), - ('complete', models.BooleanField(default=False)), - ('booking', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')), - ], - ), - migrations.CreateModel( - name='OpnfvApiConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('installer', models.CharField(max_length=100)), - ('scenario', models.CharField(max_length=100)), - ('delta', models.TextField()), - ('roles', models.ManyToManyField(to='resource_inventory.Host')), - ], - ), - migrations.CreateModel( - name='SoftwareRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='TaskConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.CreateModel( - name='AccessConfig', - fields=[ - ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')), - ('access_type', models.CharField(max_length=50)), - ('revoke', models.BooleanField(default=False)), - ('context', models.TextField(default='')), - ('delta', models.TextField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - bases=('api.taskconfig',), - ), - migrations.CreateModel( - name='HardwareConfig', - fields=[ - ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')), - ('image', models.CharField(default='defimage', max_length=100)), - ('power', models.CharField(default='off', max_length=100)), - ('hostname', models.CharField(default='hostname', max_length=100)), - ('ipmi_create', models.BooleanField(default=False)), - ('delta', models.TextField()), - ], - bases=('api.taskconfig',), - ), - migrations.CreateModel( - name='NetworkConfig', - fields=[ - ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')), - ('delta', models.TextField()), - ('interfaces', models.ManyToManyField(to='resource_inventory.Interface')), - ], - bases=('api.taskconfig',), - ), - migrations.CreateModel( - name='SoftwareConfig', - fields=[ - ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')), - ('opnfv', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.OpnfvApiConfig')), - ], - bases=('api.taskconfig',), - ), - migrations.AddField( - model_name='hostnetworkrelation', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'), - ), - migrations.AddField( - model_name='hosthardwarerelation', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'), - ), - migrations.AddField( - model_name='accessrelation', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'), - ), - migrations.AddField( - model_name='softwarerelation', - name='config', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.SoftwareConfig'), - ), - migrations.AddField( - model_name='hostnetworkrelation', - name='config', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.NetworkConfig'), - ), - migrations.AddField( - model_name='hosthardwarerelation', - name='config', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.HardwareConfig'), - ), - migrations.AddField( - model_name='accessrelation', - name='config', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.AccessConfig'), - ), - ] diff --git a/src/api/migrations/0002_remove_job_delta.py b/src/api/migrations/0002_remove_job_delta.py deleted file mode 100644 index 157a40f..0000000 --- a/src/api/migrations/0002_remove_job_delta.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1 on 2018-10-17 15:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='job', - name='delta', - ), - ] diff --git a/src/api/migrations/0003_auto_20190102_1956.py b/src/api/migrations/0003_auto_20190102_1956.py deleted file mode 100644 index 2ea5d70..0000000 --- a/src/api/migrations/0003_auto_20190102_1956.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1 on 2019-01-02 19:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0002_remove_job_delta'), - ] - - operations = [ - migrations.AlterField( - model_name='accessconfig', - name='delta', - field=models.TextField(default='{}'), - ), - ] diff --git a/src/api/migrations/0004_snapshotconfig_snapshotrelation.py b/src/api/migrations/0004_snapshotconfig_snapshotrelation.py deleted file mode 100644 index 62bc7af..0000000 --- a/src/api/migrations/0004_snapshotconfig_snapshotrelation.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 2.1 on 2019-01-17 15:54 - -import api.models -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0004_auto_20181017_1532'), - ('api', '0003_auto_20190102_1956'), - ] - - operations = [ - migrations.CreateModel( - name='SnapshotConfig', - fields=[ - ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')), - ('image', models.IntegerField(null=True)), - ('dashboard_id', models.IntegerField()), - ('host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Host')), - ], - bases=('api.taskconfig',), - ), - migrations.CreateModel( - name='SnapshotRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ('config', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.SnapshotConfig')), - ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')), - ('snapshot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Image')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/api/migrations/0005_snapshotconfig_delta.py b/src/api/migrations/0005_snapshotconfig_delta.py deleted file mode 100644 index 559af90..0000000 --- a/src/api/migrations/0005_snapshotconfig_delta.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1 on 2019-01-17 16:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0004_snapshotconfig_snapshotrelation'), - ] - - operations = [ - migrations.AddField( - model_name='snapshotconfig', - name='delta', - field=models.TextField(default='{}'), - ), - ] diff --git a/src/api/migrations/0006_auto_20190313_1729.py b/src/api/migrations/0006_auto_20190313_1729.py deleted file mode 100644 index ec148bd..0000000 --- a/src/api/migrations/0006_auto_20190313_1729.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1 on 2019-03-13 17:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0005_snapshotconfig_delta'), - ] - - operations = [ - migrations.AlterField( - model_name='opnfvapiconfig', - name='installer', - field=models.CharField(max_length=200), - ), - migrations.AlterField( - model_name='opnfvapiconfig', - name='scenario', - field=models.CharField(max_length=300), - ), - ] diff --git a/src/api/migrations/0007_auto_20190417_1511.py b/src/api/migrations/0007_auto_20190417_1511.py deleted file mode 100644 index e7d2c59..0000000 --- a/src/api/migrations/0007_auto_20190417_1511.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1 on 2019-04-17 15:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0006_auto_20190313_1729'), - ] - - operations = [ - migrations.AddField( - model_name='opnfvapiconfig', - name='idf', - field=models.CharField(default='', max_length=100), - preserve_default=False, - ), - migrations.AddField( - model_name='opnfvapiconfig', - name='pdf', - field=models.CharField(default='', max_length=100), - preserve_default=False, - ), - ] diff --git a/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py b/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py deleted file mode 100644 index 46f3631..0000000 --- a/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1 on 2019-05-01 18:53 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0010_auto_20190430_1405'), - ('api', '0006_auto_20190313_1729'), - ] - - operations = [ - migrations.AddField( - model_name='opnfvapiconfig', - name='opnfv_config', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'), - ), - ] diff --git a/src/api/migrations/0008_auto_20190419_1414.py b/src/api/migrations/0008_auto_20190419_1414.py deleted file mode 100644 index 03c3865..0000000 --- a/src/api/migrations/0008_auto_20190419_1414.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1 on 2019-04-19 14:14 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0009_auto_20190315_1757'), - ('api', '0007_auto_20190417_1511'), - ] - - operations = [ - migrations.CreateModel( - name='BridgeConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('interfaces', models.ManyToManyField(to='resource_inventory.Interface')), - ('opnfv_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.OPNFVConfig')), - ], - ), - migrations.AddField( - model_name='opnfvapiconfig', - name='bridge_config', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.BridgeConfig'), - ), - ] diff --git a/src/api/migrations/0009_merge_20190508_1317.py b/src/api/migrations/0009_merge_20190508_1317.py deleted file mode 100644 index 1a34380..0000000 --- a/src/api/migrations/0009_merge_20190508_1317.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.1 on 2019-05-08 13:17 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0008_auto_20190419_1414'), - ('api', '0007_opnfvapiconfig_opnfv_config'), - ] - - operations = [ - ] diff --git a/src/api/migrations/0010_auto_20191219_2004.py b/src/api/migrations/0010_auto_20191219_2004.py deleted file mode 100644 index ec48584..0000000 --- a/src/api/migrations/0010_auto_20191219_2004.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2019-12-19 20:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0009_merge_20190508_1317'), - ] - - operations = [ - migrations.AddField( - model_name='taskconfig', - name='delta_keys_list', - field=models.CharField(default='[]', max_length=200), - ), - migrations.AddField( - model_name='taskconfig', - name='state', - field=models.IntegerField(default=200), - ), - ] diff --git a/src/api/migrations/0011_auto_20200218_1536.py b/src/api/migrations/0011_auto_20200218_1536.py deleted file mode 100644 index 0fd7029..0000000 --- a/src/api/migrations/0011_auto_20200218_1536.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0010_auto_20191219_2004'), - # ('resource_inventory', '0013_auto_20200218_1536') - ] - - operations = [ - migrations.AddField( - model_name='hosthardwarerelation', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - migrations.AddField( - model_name='hostnetworkrelation', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - migrations.AddField( - model_name='snapshotconfig', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - ] diff --git a/src/api/migrations/0012_manual_20200218_1536.py b/src/api/migrations/0012_manual_20200218_1536.py deleted file mode 100644 index 55befbd..0000000 --- a/src/api/migrations/0012_manual_20200218_1536.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations - - -def set_resource_id(apps, schema_editor): - for cls in ["HostHardwareRelation", "HostNetworkRelation", "SnapshotConfig"]: - model = apps.get_model('api', cls) - for m in model.objects.all(): - m.resource_id = m.host.labid - m.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0011_auto_20200218_1536'), - ] - - operations = [ - migrations.RunPython(set_resource_id), - ] diff --git a/src/api/migrations/0013_manual_20200218_1536.py b/src/api/migrations/0013_manual_20200218_1536.py deleted file mode 100644 index 3ac427e..0000000 --- a/src/api/migrations/0013_manual_20200218_1536.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0012_manual_20200218_1536'), - ] - - operations = [ - migrations.RemoveField( - model_name='hosthardwarerelation', - name='host', - ), - migrations.RemoveField( - model_name='hostnetworkrelation', - name='host', - ), - migrations.RemoveField( - model_name='snapshotconfig', - name='host', - ), - migrations.RemoveField( - model_name='opnfvapiconfig', - name='roles', - ), - ] diff --git a/src/api/migrations/0014_manual_20200220.py b/src/api/migrations/0014_manual_20200220.py deleted file mode 100644 index 2e2cd58..0000000 --- a/src/api/migrations/0014_manual_20200220.py +++ /dev/null @@ -1,18 +0,0 @@ - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0013_manual_20200218_1536'), - ('resource_inventory', '0013_auto_20200218_1536') - ] - - operations = [ - migrations.AddField( - model_name='opnfvapiconfig', - name='roles', - field=models.ManyToManyField(to='resource_inventory.ResourceOPNFVConfig'), - ), - ] diff --git a/src/api/migrations/0015_auto_20201109_1947.py b/src/api/migrations/0015_auto_20201109_1947.py deleted file mode 100644 index bbd51e5..0000000 --- a/src/api/migrations/0015_auto_20201109_1947.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 19:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0014_manual_20200220'), - ] - - operations = [ - migrations.AlterField( - model_name='taskconfig', - name='state', - field=models.IntegerField(default=0), - ), - ] diff --git a/src/api/migrations/0016_auto_20201109_2149.py b/src/api/migrations/0016_auto_20201109_2149.py deleted file mode 100644 index a430659..0000000 --- a/src/api/migrations/0016_auto_20201109_2149.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 21:49 - -import api.models -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0015_auto_20201109_1947'), - ] - - operations = [ - migrations.CreateModel( - name='ActiveUsersConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.AddField( - model_name='job', - name='job_type', - field=models.CharField(choices=[('BOOK', 'Booking'), ('DATA', 'Analytics')], default='BOOK', max_length=4), - ), - migrations.CreateModel( - name='ActiveUsersRelation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.IntegerField(default=0)), - ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)), - ('lab_token', models.CharField(default='null', max_length=50)), - ('message', models.TextField(default='')), - ('config', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.ActiveUsersConfig')), - ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/api/migrations/0017_apilog.py b/src/api/migrations/0017_apilog.py deleted file mode 100644 index d209aef..0000000 --- a/src/api/migrations/0017_apilog.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2 on 2021-03-19 20:45 - -from django.conf import settings -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('api', '0016_auto_20201109_2149'), - ] - - operations = [ - migrations.CreateModel( - name='APILog', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('call_time', models.DateTimeField(auto_now=True)), - ('endpoint', models.CharField(max_length=300)), - ('body', django.contrib.postgres.fields.jsonb.JSONField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/src/api/migrations/0017_auto_20210630_1629.py b/src/api/migrations/0017_auto_20210630_1629.py deleted file mode 100644 index 643ff5f..0000000 --- a/src/api/migrations/0017_auto_20210630_1629.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-06-30 16:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0016_auto_20201109_2149'), - ] - - operations = [ - migrations.AlterField( - model_name='snapshotconfig', - name='image', - field=models.CharField(max_length=200, null=True), - ), - ] diff --git a/src/api/migrations/0018_apilog_ip_addr.py b/src/api/migrations/0018_apilog_ip_addr.py deleted file mode 100644 index 4b7ce39..0000000 --- a/src/api/migrations/0018_apilog_ip_addr.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 18:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0017_apilog'), - ] - - operations = [ - migrations.AddField( - model_name='apilog', - name='ip_addr', - field=models.GenericIPAddressField(null=True), - ), - ] diff --git a/src/api/migrations/0018_cloudinitfile.py b/src/api/migrations/0018_cloudinitfile.py deleted file mode 100644 index 4e41b39..0000000 --- a/src/api/migrations/0018_cloudinitfile.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.2 on 2021-07-01 20:45 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0019_auto_20210701_1947'), - ('booking', '0008_auto_20201109_1947'), - ('api', '0017_auto_20210630_1629'), - ] - - operations = [ - migrations.CreateModel( - name='CloudInitFile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('resource_id', models.CharField(max_length=200)), - ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')), - ('rconfig', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceConfiguration')), - ], - ), - ] diff --git a/src/api/migrations/0019_auto_20210322_1823.py b/src/api/migrations/0019_auto_20210322_1823.py deleted file mode 100644 index b3c4cdf..0000000 --- a/src/api/migrations/0019_auto_20210322_1823.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 18:23 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0018_apilog_ip_addr'), - ] - - operations = [ - migrations.AlterField( - model_name='apilog', - name='body', - field=django.contrib.postgres.fields.jsonb.JSONField(null=True), - ), - ] diff --git a/src/api/migrations/0019_auto_20210907_1448.py b/src/api/migrations/0019_auto_20210907_1448.py deleted file mode 100644 index 92140fb..0000000 --- a/src/api/migrations/0019_auto_20210907_1448.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2021-09-07 14:48 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0008_auto_20201109_1947'), - ('resource_inventory', '0020_cloudinitfile'), - ('api', '0018_cloudinitfile'), - ] - - operations = [ - migrations.CreateModel( - name='GeneratedCloudConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('resource_id', models.CharField(max_length=200)), - ('text', models.TextField(blank=True, null=True)), - ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')), - ('rconfig', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceConfiguration')), - ], - ), - migrations.DeleteModel( - name='CloudInitFile', - ), - ] diff --git a/src/api/migrations/0020_auto_20210322_2218.py b/src/api/migrations/0020_auto_20210322_2218.py deleted file mode 100644 index 0252c79..0000000 --- a/src/api/migrations/0020_auto_20210322_2218.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 22:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0019_auto_20210322_1823'), - ] - - operations = [ - migrations.AddField( - model_name='apilog', - name='method', - field=models.CharField(max_length=4, null=True), - ), - migrations.AlterField( - model_name='apilog', - name='endpoint', - field=models.CharField(max_length=300, null=True), - ), - ] diff --git a/src/api/migrations/0021_auto_20210405_1943.py b/src/api/migrations/0021_auto_20210405_1943.py deleted file mode 100644 index ca6e741..0000000 --- a/src/api/migrations/0021_auto_20210405_1943.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-04-05 19:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0020_auto_20210322_2218'), - ] - - operations = [ - migrations.AlterField( - model_name='apilog', - name='method', - field=models.CharField(max_length=6, null=True), - ), - ] diff --git a/src/api/migrations/0022_add_cifile_generated_field.py b/src/api/migrations/0022_add_cifile_generated_field.py deleted file mode 100644 index f83a102..0000000 --- a/src/api/migrations/0022_add_cifile_generated_field.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('api', '0018_cloudinitfile'), - ] - - operations = [ - migrations.AddField( - model_name="CloudInitFile", - name="generated", - field=models.BooleanField(default=False) - ), - ] diff --git a/src/api/migrations/0022_merge_20211102_2136.py b/src/api/migrations/0022_merge_20211102_2136.py deleted file mode 100644 index bb27ae4..0000000 --- a/src/api/migrations/0022_merge_20211102_2136.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2 on 2021-11-02 21:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0019_auto_20210907_1448'), - ('api', '0021_auto_20210405_1943'), - ] - - operations = [ - ] diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py deleted file mode 100644 index e0408fa..0000000 --- a/src/api/migrations/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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 -############################################################################## diff --git a/src/api/models.py b/src/api/models.py deleted file mode 100644 index 93168f5..0000000 --- a/src/api/models.py +++ /dev/null @@ -1,1453 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 -############################################################################## - - -from django.contrib.auth.models import User -from django.db import models -from django.core.exceptions import PermissionDenied, ValidationError -from django.shortcuts import get_object_or_404 -from django.contrib.postgres.fields import JSONField -from django.http import HttpResponseNotFound -from django.urls import reverse -from django.utils import timezone - -import json -import uuid -import yaml -import re - -from booking.models import Booking -from resource_inventory.models import ( - Lab, - ResourceProfile, - Image, - Opsys, - Interface, - ResourceOPNFVConfig, - RemoteInfo, - OPNFVConfig, - ConfigState, - ResourceQuery, - ResourceConfiguration, - CloudInitFile -) -from resource_inventory.idf_templater import IDFTemplater -from resource_inventory.pdf_templater import PDFTemplater -from account.models import Downtime, UserProfile -from dashboard.utils import AbstractModelQuery - - -class JobStatus: - """ - A poor man's enum for a job's status. - - A job is NEW if it has not been started or recognized by the Lab - A job is CURRENT if it has been started by the lab but it is not yet completed - a job is DONE if all the tasks are complete and the booking is ready to use - """ - - NEW = 0 - CURRENT = 100 - DONE = 200 - ERROR = 300 - - -class LabManagerTracker: - - @classmethod - def get(cls, lab_name, token): - """ - Get a LabManager. - - Takes in a lab name (from a url path) - returns a lab manager instance for that lab, if it exists - Also checks that the given API token is correct - """ - try: - lab = Lab.objects.get(name=lab_name) - except Exception: - raise PermissionDenied("Lab not found") - if lab.api_token == token: - return LabManager(lab) - raise PermissionDenied("Lab not authorized") - - -class LabManager: - """ - Handles all lab REST calls. - - handles jobs, inventory, status, etc - may need to create helper classes - """ - - def __init__(self, lab): - self.lab = lab - - def get_opsyss(self): - return Opsys.objects.filter(from_lab=self.lab) - - def get_images(self): - return Image.objects.filter(from_lab=self.lab) - - def get_image(self, image_id): - return Image.objects.filter(from_lab=self.lab, lab_id=image_id) - - def get_opsys(self, opsys_id): - return Opsys.objects.filter(from_lab=self.lab, lab_id=opsys_id) - - def get_downtime(self): - return Downtime.objects.filter(start__lt=timezone.now(), end__gt=timezone.now(), lab=self.lab) - - def get_downtime_json(self): - downtime = self.get_downtime().first() # should only be one item in queryset - if downtime: - return { - "is_down": True, - "start": downtime.start, - "end": downtime.end, - "description": downtime.description - } - return {"is_down": False} - - def create_downtime(self, form): - """ - Create a downtime event. - - Takes in a dictionary that describes the model. - { - "start": utc timestamp - "end": utc timestamp - "description": human text (optional) - } - For timestamp structure, https://docs.djangoproject.com/en/2.2/ref/forms/fields/#datetimefield - """ - Downtime.objects.create( - start=form.cleaned_data['start'], - end=form.cleaned_data['end'], - description=form.cleaned_data['description'], - lab=self.lab - ) - return self.get_downtime_json() - - def update_host_remote_info(self, data, res_id): - resource = ResourceQuery.filter(labid=res_id, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(res_id)) - resource = resource[0] - info = {} - try: - info['address'] = data['address'] - info['mac_address'] = data['mac_address'] - info['password'] = data['password'] - info['user'] = data['user'] - info['type'] = data['type'] - info['versions'] = json.dumps(data['versions']) - except Exception as e: - return {"error": "invalid arguement: " + str(e)} - remote_info = resource.remote_management - if "default" in remote_info.mac_address: - remote_info = RemoteInfo() - remote_info.address = info['address'] - remote_info.mac_address = info['mac_address'] - remote_info.password = info['password'] - remote_info.user = info['user'] - remote_info.type = info['type'] - remote_info.versions = info['versions'] - remote_info.save() - resource.remote_management = remote_info - resource.save() - booking = Booking.objects.get(resource=resource.bundle) - self.update_xdf(booking) - return {"status": "success"} - - def update_xdf(self, booking): - booking.pdf = PDFTemplater.makePDF(booking) - booking.idf = IDFTemplater().makeIDF(booking) - booking.save() - - def get_pdf(self, booking_id): - booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab) - return booking.pdf - - def get_idf(self, booking_id): - booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab) - return booking.idf - - def get_profile(self): - prof = {} - prof['name'] = self.lab.name - prof['contact'] = { - "phone": self.lab.contact_phone, - "email": self.lab.contact_email - } - prof['host_count'] = [{ - "type": profile.name, - "count": len(profile.get_resources(lab=self.lab))} - for profile in ResourceProfile.objects.filter(labs=self.lab)] - return prof - - def format_user(self, userprofile): - return { - "id": userprofile.user.id, - "username": userprofile.user.username, - "email": userprofile.email_addr, - "first_name": userprofile.user.first_name, - "last_name": userprofile.user.last_name, - "company": userprofile.company - } - - def get_users(self): - userlist = [self.format_user(profile) for profile in UserProfile.objects.select_related("user").all()] - - return json.dumps({"users": userlist}) - - def get_user(self, user_id): - user = User.objects.get(pk=user_id) - - profile = get_object_or_404(UserProfile, user=user) - - return json.dumps(self.format_user(profile)) - - def get_inventory(self): - inventory = {} - resources = ResourceQuery.filter(lab=self.lab) - images = Image.objects.filter(from_lab=self.lab) - profiles = ResourceProfile.objects.filter(labs=self.lab) - inventory['resources'] = self.serialize_resources(resources) - inventory['images'] = self.serialize_images(images) - inventory['host_types'] = self.serialize_host_profiles(profiles) - return inventory - - def get_host(self, hostname): - resource = ResourceQuery.filter(labid=hostname, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(hostname)) - resource = resource[0] - return { - "booked": resource.booked, - "working": resource.working, - "type": resource.profile.name - } - - def update_host(self, hostname, data): - resource = ResourceQuery.filter(labid=hostname, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(hostname)) - resource = resource[0] - if "working" in data: - working = data['working'] == "true" - resource.working = working - resource.save() - return self.get_host(hostname) - - def get_status(self): - return {"status": self.lab.status} - - def set_status(self, payload): - {} - - def get_current_jobs(self): - jobs = Job.objects.filter(booking__lab=self.lab) - - return self.serialize_jobs(jobs, status=JobStatus.CURRENT) - - def get_new_jobs(self): - jobs = Job.objects.filter(booking__lab=self.lab) - - return self.serialize_jobs(jobs, status=JobStatus.NEW) - - def get_done_jobs(self): - jobs = Job.objects.filter(booking__lab=self.lab) - - return self.serialize_jobs(jobs, status=JobStatus.DONE) - - def get_analytics_job(self): - """ Get analytics job with status new """ - jobs = Job.objects.filter( - booking__lab=self.lab, - job_type='DATA' - ) - - return self.serialize_jobs(jobs, status=JobStatus.NEW) - - def get_job(self, jobid): - return Job.objects.get(pk=jobid).to_dict() - - def update_job(self, jobid, data): - {} - - def serialize_jobs(self, jobs, status=JobStatus.NEW): - job_ser = [] - for job in jobs: - jsonized_job = job.get_delta(status) - if len(jsonized_job['payload']) < 1: - continue - job_ser.append(jsonized_job) - - return job_ser - - def serialize_resources(self, resources): - # TODO: rewrite for Resource model - host_ser = [] - for res in resources: - r = { - 'interfaces': [], - 'hostname': res.name, - 'host_type': res.profile.name - } - for iface in res.get_interfaces(): - r['interfaces'].append({ - 'mac': iface.mac_address, - 'busaddr': iface.bus_address, - 'name': iface.name, - 'switchport': {"switch_name": iface.switch_name, "port_name": iface.port_name} - }) - return host_ser - - def serialize_images(self, images): - images_ser = [] - for image in images: - images_ser.append( - { - "name": image.name, - "lab_id": image.lab_id, - "dashboard_id": image.id - } - ) - return images_ser - - def serialize_resource_profiles(self, profiles): - profile_ser = [] - for profile in profiles: - p = {} - p['cpu'] = { - "cores": profile.cpuprofile.first().cores, - "arch": profile.cpuprofile.first().architecture, - "cpus": profile.cpuprofile.first().cpus, - } - p['disks'] = [] - for disk in profile.storageprofile.all(): - d = { - "size": disk.size, - "type": disk.media_type, - "name": disk.name - } - p['disks'].append(d) - p['description'] = profile.description - p['interfaces'] = [] - for iface in profile.interfaceprofile.all(): - p['interfaces'].append( - { - "speed": iface.speed, - "name": iface.name - } - ) - - p['ram'] = {"amount": profile.ramprofile.first().amount} - p['name'] = profile.name - profile_ser.append(p) - return profile_ser - - -class GeneratedCloudConfig(models.Model): - resource_id = models.CharField(max_length=200) - booking = models.ForeignKey(Booking, on_delete=models.CASCADE) - rconfig = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE) - text = models.TextField(null=True, blank=True) - - def _normalize_username(self, username: str) -> str: - # TODO: make usernames posix compliant - s = re.sub(r'\W+', '', username) - return s - - def _get_ssh_string(self, username: str) -> str: - user = User.objects.get(username=username) - uprofile = user.userprofile - - ssh_file = uprofile.ssh_public_key - - escaped_file = ssh_file.open().read().decode(encoding="UTF-8").replace("\n", " ") - - return escaped_file - - def _serialize_users(self): - """ - returns the dictionary to be placed behind the `users` field of the toplevel c-i dict - """ - # conserves distro default user - user_array = ["default"] - - users = list(self.booking.collaborators.all()) - users.append(self.booking.owner) - for collaborator in users: - userdict = {} - - # TODO: validate if usernames are valid as linux usernames (and provide an override potentially) - userdict['name'] = self._normalize_username(collaborator.username) - - userdict['groups'] = "sudo" - userdict['sudo'] = "ALL=(ALL) NOPASSWD:ALL" - - userdict['ssh_authorized_keys'] = [self._get_ssh_string(collaborator.username)] - - user_array.append(userdict) - - # user_array.append({ - # "name": "opnfv", - # "passwd": "$6$k54L.vim1cLaEc4$5AyUIrufGlbtVBzuCWOlA1yV6QdD7Gr2MzwIs/WhuYR9ebSfh3Qlb7djkqzjwjxpnSAonK1YOabPP6NxUDccu.", - # "ssh_redirect_user": True, - # "sudo": "ALL=(ALL) NOPASSWD:ALL", - # "groups": "sudo", - # }) - - return user_array - - # TODO: make this configurable - def _serialize_sysinfo(self): - defuser = {} - defuser['name'] = 'opnfv' - defuser['plain_text_passwd'] = 'OPNFV_HOST' - defuser['home'] = '/home/opnfv' - defuser['shell'] = '/bin/bash' - defuser['lock_passwd'] = True - defuser['gecos'] = 'Lab Manager User' - defuser['groups'] = 'sudo' - - return {'default_user': defuser} - - # TODO: make this configurable - def _serialize_runcmds(self): - cmdlist = [] - - # have hosts run dhcp on boot - cmdlist.append(['sudo', 'dhclient', '-r']) - cmdlist.append(['sudo', 'dhclient']) - - return cmdlist - - def _serialize_netconf_v1(self): - # interfaces = {} # map from iface_name => dhcp_config - # vlans = {} # map from vlan_id => dhcp_config - - config_arr = [] - - for interface in self._resource().interfaces.all(): - interface_name = interface.profile.name - interface_mac = interface.mac_address - - iface_dict_entry = { - "type": "physical", - "name": interface_name, - "mac_address": interface_mac, - } - - for vlan in interface.config.all(): - if vlan.tagged: - vlan_dict_entry = {'type': 'vlan'} - vlan_dict_entry['name'] = str(interface_name) + "." + str(vlan.vlan_id) - vlan_dict_entry['vlan_link'] = str(interface_name) - vlan_dict_entry['vlan_id'] = int(vlan.vlan_id) - vlan_dict_entry['mac_address'] = str(interface_mac) - if vlan.public: - vlan_dict_entry["subnets"] = [{"type": "dhcp"}] - config_arr.append(vlan_dict_entry) - if (not vlan.tagged) and vlan.public: - iface_dict_entry["subnets"] = [{"type": "dhcp"}] - - # vlan_dict_entry['mtu'] = # TODO, determine override MTU if needed - - config_arr.append(iface_dict_entry) - - ns_dict = { - 'type': 'nameserver', - 'address': ['10.64.0.1', '8.8.8.8'] - } - - config_arr.append(ns_dict) - - full_dict = {'version': 1, 'config': config_arr} - - return full_dict - - @classmethod - def get(cls, booking_id: int, resource_lab_id: str, file_id: int): - return GeneratedCloudConfig.objects.get(resource_id=resource_lab_id, booking__id=booking_id, file_id=file_id) - - def _resource(self): - return ResourceQuery.get(labid=self.resource_id, lab=self.booking.lab) - - # def _get_facts(self): - # resource = self._resource() - - # hostname = self.rconfig.name - # iface_configs = for_config.interface_configs.all() - - def _to_dict(self): - main_dict = {} - - main_dict['users'] = self._serialize_users() - main_dict['network'] = self._serialize_netconf_v1() - main_dict['hostname'] = self.rconfig.name - - # add first startup commands - main_dict['runcmd'] = self._serialize_runcmds() - - # configure distro default user - main_dict['system_info'] = self._serialize_sysinfo() - - return main_dict - - def serialize(self) -> str: - return yaml.dump(self._to_dict(), width=float("inf")) - - -class APILog(models.Model): - user = models.ForeignKey(User, on_delete=models.PROTECT) - call_time = models.DateTimeField(auto_now=True) - method = models.CharField(null=True, max_length=6) - endpoint = models.CharField(null=True, max_length=300) - ip_addr = models.GenericIPAddressField(protocol="both", null=True, unpack_ipv4=False) - body = JSONField(null=True) - - def __str__(self): - return "Call to {} at {} by {}".format( - self.endpoint, - self.call_time, - self.user.username - ) - - -class AutomationAPIManager: - @staticmethod - def serialize_booking(booking): - sbook = {} - sbook['id'] = booking.pk - sbook['owner'] = booking.owner.username - sbook['collaborators'] = [user.username for user in booking.collaborators.all()] - sbook['start'] = booking.start - sbook['end'] = booking.end - sbook['lab'] = AutomationAPIManager.serialize_lab(booking.lab) - sbook['purpose'] = booking.purpose - sbook['resourceBundle'] = AutomationAPIManager.serialize_bundle(booking.resource) - return sbook - - @staticmethod - def serialize_lab(lab): - slab = {} - slab['id'] = lab.pk - slab['name'] = lab.name - return slab - - @staticmethod - def serialize_bundle(bundle): - sbundle = {} - sbundle['id'] = bundle.pk - sbundle['resources'] = [ - AutomationAPIManager.serialize_server(server) - for server in bundle.get_resources()] - return sbundle - - @staticmethod - def serialize_server(server): - sserver = {} - sserver['id'] = server.pk - sserver['name'] = server.name - return sserver - - @staticmethod - def serialize_resource_profile(profile): - sprofile = {} - sprofile['id'] = profile.pk - sprofile['name'] = profile.name - return sprofile - - @staticmethod - def serialize_template(rec_temp_and_count): - template = rec_temp_and_count[0] - count = rec_temp_and_count[1] - - stemplate = {} - stemplate['id'] = template.pk - stemplate['name'] = template.name - stemplate['count_available'] = count - stemplate['resourceProfiles'] = [ - AutomationAPIManager.serialize_resource_profile(config.profile) - for config in template.getConfigs() - ] - return stemplate - - @staticmethod - def serialize_image(image): - simage = {} - simage['id'] = image.pk - simage['name'] = image.name - return simage - - @staticmethod - def serialize_userprofile(up): - sup = {} - sup['id'] = up.pk - sup['username'] = up.user.username - return sup - - -class Job(models.Model): - """ - A Job to be performed by the Lab. - - The API uses Jobs and Tasks to communicate actions that need to be taken to the Lab - that is hosting a booking. A booking from a user has an associated Job which tells - the lab how to configure the hardware, networking, etc to fulfill the booking - for the user. - This is the class that is serialized and put into the api - """ - - JOB_TYPES = ( - ('BOOK', 'Booking'), - ('DATA', 'Analytics') - ) - - booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True) - status = models.IntegerField(default=JobStatus.NEW) - complete = models.BooleanField(default=False) - job_type = models.CharField( - max_length=4, - choices=JOB_TYPES, - default='BOOK' - ) - - def to_dict(self): - d = {} - for relation in self.get_tasklist(): - if relation.job_key not in d: - d[relation.job_key] = {} - d[relation.job_key][relation.task_id] = relation.config.to_dict() - - return {"id": self.id, "payload": d} - - def get_tasklist(self, status="all"): - if status != "all": - return JobTaskQuery.filter(job=self, status=status) - return JobTaskQuery.filter(job=self) - - def is_fulfilled(self): - """ - If a job has been completed by the lab. - - This method should return true if all of the job's tasks are done, - and false otherwise - """ - my_tasks = self.get_tasklist() - for task in my_tasks: - if task.status != JobStatus.DONE: - return False - return True - - def get_delta(self, status): - d = {} - for relation in self.get_tasklist(status=status): - if relation.job_key not in d: - d[relation.job_key] = {} - d[relation.job_key][relation.task_id] = relation.config.get_delta() - - return {"id": self.id, "payload": d} - - def to_json(self): - return json.dumps(self.to_dict()) - - -class TaskConfig(models.Model): - state = models.IntegerField(default=ConfigState.NEW) - - keys = set() # TODO: This needs to be an instance variable, not a class variable - delta_keys_list = models.CharField(max_length=200, default="[]") - - @property - def delta_keys(self): - return list(set(json.loads(self.delta_keys_list))) - - @delta_keys.setter - def delta_keys(self, keylist): - self.delta_keys_list = json.dumps(keylist) - - def to_dict(self): - raise NotImplementedError - - def get_delta(self): - raise NotImplementedError - - def format_delta(self, config, token): - delta = {k: config[k] for k in self.delta_keys} - delta['lab_token'] = token - return delta - - def to_json(self): - return json.dumps(self.to_dict()) - - def clear_delta(self): - self.delta_keys = [] - - def set(self, *args): - dkeys = self.delta_keys - for arg in args: - if arg in self.keys: - dkeys.append(arg) - self.delta_keys = dkeys - - -class BridgeConfig(models.Model): - """Displays mapping between jumphost interfaces and bridges.""" - - interfaces = models.ManyToManyField(Interface) - opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.CASCADE) - - def to_dict(self): - d = {} - hid = ResourceQuery.get(interface__pk=self.interfaces.first().pk).labid - d[hid] = {} - for interface in self.interfaces.all(): - d[hid][interface.mac_address] = [] - for vlan in interface.config.all(): - network_role = self.opnfv_model.networks().filter(network=vlan.network) - bridge = IDFTemplater.bridge_names[network_role.name] - br_config = { - "vlan_id": vlan.vlan_id, - "tagged": vlan.tagged, - "bridge": bridge - } - d[hid][interface.mac_address].append(br_config) - return d - - def to_json(self): - return json.dumps(self.to_dict()) - - -class ActiveUsersConfig(models.Model): - """ - Task for getting active VPN users - - StackStorm needs no information to run this job - so this task is very bare, but neccessary to fit - job creation convention. - """ - - def clear_delta(self): - self.delta = '{}' - - def get_delta(self): - return json.loads(self.to_json()) - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {} - - -class OpnfvApiConfig(models.Model): - - installer = models.CharField(max_length=200) - scenario = models.CharField(max_length=300) - roles = models.ManyToManyField(ResourceOPNFVConfig) - # pdf and idf are url endpoints, not the actual file - pdf = models.CharField(max_length=100) - idf = models.CharField(max_length=100) - bridge_config = models.OneToOneField(BridgeConfig, on_delete=models.CASCADE, null=True) - delta = models.TextField() - opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL) - - def to_dict(self): - d = {} - if not self.opnfv_config: - return d - if self.installer: - d['installer'] = self.installer - if self.scenario: - d['scenario'] = self.scenario - if self.pdf: - d['pdf'] = self.pdf - if self.idf: - d['idf'] = self.idf - if self.bridge_config: - d['bridged_interfaces'] = self.bridge_config.to_dict() - - hosts = self.roles.all() - if hosts.exists(): - d['roles'] = [] - for host in hosts: - d['roles'].append({ - host.labid: self.opnfv_config.host_opnfv_config.get( - host_config__pk=host.config.pk - ).role.name - }) - - return d - - def to_json(self): - return json.dumps(self.to_dict()) - - def set_installer(self, installer): - self.installer = installer - d = json.loads(self.delta) - d['installer'] = installer - self.delta = json.dumps(d) - - def set_scenario(self, scenario): - self.scenario = scenario - d = json.loads(self.delta) - d['scenario'] = scenario - self.delta = json.dumps(d) - - def set_xdf(self, booking, update_delta=True): - kwargs = {'lab_name': booking.lab.name, 'booking_id': booking.id} - self.pdf = reverse('get-pdf', kwargs=kwargs) - self.idf = reverse('get-idf', kwargs=kwargs) - if update_delta: - d = json.loads(self.delta) - d['pdf'] = self.pdf - d['idf'] = self.idf - self.delta = json.dumps(d) - - def add_role(self, host): - self.roles.add(host) - d = json.loads(self.delta) - if 'role' not in d: - d['role'] = [] - d['roles'].append({host.labid: host.config.opnfvRole.name}) - self.delta = json.dumps(d) - - def clear_delta(self): - self.delta = '{}' - - def get_delta(self): - return json.loads(self.to_json()) - - -class AccessConfig(TaskConfig): - access_type = models.CharField(max_length=50) - user = models.ForeignKey(User, on_delete=models.CASCADE) - revoke = models.BooleanField(default=False) - context = models.TextField(default="") - delta = models.TextField(default="{}") - - def to_dict(self): - d = {} - d['access_type'] = self.access_type - d['user'] = self.user.id - d['revoke'] = self.revoke - try: - d['context'] = json.loads(self.context) - except Exception: - pass - return d - - def get_delta(self): - d = json.loads(self.to_json()) - d["lab_token"] = self.accessrelation.lab_token - - return d - - def to_json(self): - return json.dumps(self.to_dict()) - - def clear_delta(self): - d = {} - d["lab_token"] = self.accessrelation.lab_token - self.delta = json.dumps(d) - - def set_access_type(self, access_type): - self.access_type = access_type - d = json.loads(self.delta) - d['access_type'] = access_type - self.delta = json.dumps(d) - - def set_user(self, user): - self.user = user - d = json.loads(self.delta) - d['user'] = self.user.id - self.delta = json.dumps(d) - - def set_revoke(self, revoke): - self.revoke = revoke - d = json.loads(self.delta) - d['revoke'] = revoke - self.delta = json.dumps(d) - - def set_context(self, context): - self.context = json.dumps(context) - d = json.loads(self.delta) - d['context'] = context - self.delta = json.dumps(d) - - -class SoftwareConfig(TaskConfig): - """Handles software installations, such as OPNFV or ONAP.""" - - opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE) - - def to_dict(self): - d = {} - if self.opnfv: - d['opnfv'] = self.opnfv.to_dict() - - d["lab_token"] = self.softwarerelation.lab_token - self.delta = json.dumps(d) - - return d - - def get_delta(self): - d = {} - d['opnfv'] = self.opnfv.get_delta() - d['lab_token'] = self.softwarerelation.lab_token - - return d - - def clear_delta(self): - self.opnfv.clear_delta() - - def to_json(self): - return json.dumps(self.to_dict()) - - -class HardwareConfig(TaskConfig): - """Describes the desired configuration of the hardware.""" - - image = models.CharField(max_length=100, default="defimage") - power = models.CharField(max_length=100, default="off") - hostname = models.CharField(max_length=100, default="hostname") - ipmi_create = models.BooleanField(default=False) - delta = models.TextField() - - keys = set(["id", "image", "power", "hostname", "ipmi_create"]) - - def to_dict(self): - return self.get_delta() - - def get_delta(self): - # TODO: grab the GeneratedCloudConfig urls from self.hosthardwarerelation.get_resource() - return self.format_delta( - self.hosthardwarerelation.get_resource().get_configuration(self.state), - self.hosthardwarerelation.lab_token) - - -class NetworkConfig(TaskConfig): - """Handles network configuration.""" - - interfaces = models.ManyToManyField(Interface) - delta = models.TextField() - - def to_dict(self): - d = {} - hid = self.hostnetworkrelation.resource_id - d[hid] = {} - for interface in self.interfaces.all(): - d[hid][interface.mac_address] = [] - if self.state != ConfigState.CLEAN: - for vlan in interface.config.all(): - # TODO: should this come from the interface? - # e.g. will different interfaces for different resources need different configs? - d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged}) - - return d - - def to_json(self): - return json.dumps(self.to_dict()) - - def get_delta(self): - d = json.loads(self.to_json()) - d['lab_token'] = self.hostnetworkrelation.lab_token - return d - - def clear_delta(self): - self.delta = json.dumps(self.to_dict()) - self.save() - - def add_interface(self, interface): - self.interfaces.add(interface) - d = json.loads(self.delta) - hid = self.hostnetworkrelation.resource_id - if hid not in d: - d[hid] = {} - d[hid][interface.mac_address] = [] - for vlan in interface.config.all(): - d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged}) - self.delta = json.dumps(d) - - -class SnapshotConfig(TaskConfig): - - resource_id = models.CharField(max_length=200, default="default_id") - image = models.CharField(max_length=200, null=True) # cobbler ID - dashboard_id = models.IntegerField() - delta = models.TextField(default="{}") - - def to_dict(self): - d = {} - if self.host: - d['host'] = self.host.labid - if self.image: - d['image'] = self.image - d['dashboard_id'] = self.dashboard_id - return d - - def to_json(self): - return json.dumps(self.to_dict()) - - def get_delta(self): - d = json.loads(self.to_json()) - return d - - def clear_delta(self): - self.delta = json.dumps(self.to_dict()) - self.save() - - def set_host(self, host): - self.host = host - d = json.loads(self.delta) - d['host'] = host.labid - self.delta = json.dumps(d) - - def set_image(self, image): - self.image = image - d = json.loads(self.delta) - d['image'] = self.image - self.delta = json.dumps(d) - - def clear_image(self): - self.image = None - d = json.loads(self.delta) - d.pop("image", None) - self.delta = json.dumps(d) - - def set_dashboard_id(self, dash): - self.dashboard_id = dash - d = json.loads(self.delta) - d['dashboard_id'] = self.dashboard_id - self.delta = json.dumps(d) - - def save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - -def get_task(task_id): - for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]: - try: - ret = taskclass.objects.get(task_id=task_id) - return ret - except taskclass.DoesNotExist: - pass - from django.core.exceptions import ObjectDoesNotExist - raise ObjectDoesNotExist("Could not find matching TaskRelation instance") - - -def get_task_uuid(): - return str(uuid.uuid4()) - - -class TaskRelation(models.Model): - """ - Relates a Job to a TaskConfig. - - superclass that relates a Job to tasks anc maintains information - like status and messages from the lab - """ - - status = models.IntegerField(default=JobStatus.NEW) - job = models.ForeignKey(Job, on_delete=models.CASCADE) - config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE) - task_id = models.CharField(default=get_task_uuid, max_length=37) - lab_token = models.CharField(default="null", max_length=50) - message = models.TextField(default="") - - job_key = None - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - def type_str(self): - return "Generic Task" - - class Meta: - abstract = True - - -class AccessRelation(TaskRelation): - config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE) - job_key = "access" - - def type_str(self): - return "Access Task" - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - -class SoftwareRelation(TaskRelation): - config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE) - job_key = "software" - - def type_str(self): - return "Software Configuration Task" - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - -class HostHardwareRelation(TaskRelation): - resource_id = models.CharField(max_length=200, default="default_id") - config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE) - job_key = "hardware" - - def type_str(self): - return "Hardware Configuration Task" - - def get_delta(self): - return self.config.to_dict() - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - def save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - def get_resource(self): - return ResourceQuery.get(labid=self.resource_id) - - -class HostNetworkRelation(TaskRelation): - resource_id = models.CharField(max_length=200, default="default_id") - config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE) - job_key = "network" - - def type_str(self): - return "Network Configuration Task" - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - def save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - def get_resource(self): - return ResourceQuery.get(labid=self.resource_id) - - -class SnapshotRelation(TaskRelation): - snapshot = models.ForeignKey(Image, on_delete=models.CASCADE) - config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE) - job_key = "snapshot" - - def type_str(self): - return "Snapshot Task" - - def get_delta(self): - return self.config.to_dict() - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - -class ActiveUsersRelation(TaskRelation): - config = models.OneToOneField(ActiveUsersConfig, on_delete=models.CASCADE) - job_key = "active users task" - - def type_str(self): - return "Active Users Task" - - -class JobFactory(object): - """This class creates all the API models (jobs, tasks, etc) needed to fulfill a booking.""" - - @classmethod - def reimageHost(cls, new_image, booking, host): - """Modify an existing job to reimage the given host.""" - job = Job.objects.get(booking=booking) - # make hardware task new - hardware_relation = HostHardwareRelation.objects.get(resource_id=host, job=job) - hardware_relation.config.image = new_image.lab_id - hardware_relation.config.save() - hardware_relation.status = JobStatus.NEW - - # re-apply networking after host is reset - net_relation = HostNetworkRelation.objects.get(resource_id=host, job=job) - net_relation.status = JobStatus.NEW - - # re-apply ssh access after host is reset - for relation in AccessRelation.objects.filter(job=job, config__access_type="ssh"): - relation.status = JobStatus.NEW - relation.save() - - hardware_relation.save() - net_relation.save() - - @classmethod - def makeSnapshotTask(cls, image, booking, host): - relation = SnapshotRelation() - job = Job.objects.get(booking=booking) - config = SnapshotConfig.objects.create(dashboard_id=image.id) - - relation.job = job - relation.config = config - relation.config.save() - relation.config = relation.config - relation.snapshot = image - relation.save() - - config.clear_delta() - config.set_host(host) - config.save() - - @classmethod - def makeActiveUsersTask(cls): - """ Append active users task to analytics job """ - config = ActiveUsersConfig() - relation = ActiveUsersRelation() - job = Job.objects.get(job_type='DATA') - - job.status = JobStatus.NEW - - relation.job = job - relation.config = config - relation.config.save() - relation.config = relation.config - relation.save() - config.save() - - @classmethod - def makeAnalyticsJob(cls, booking): - """ - Create the analytics job - - This will only run once since there will only be one analytics job. - All analytics tasks get appended to analytics job. - """ - - if len(Job.objects.filter(job_type='DATA')) > 0: - raise Exception("Cannot have more than one analytics job") - - if booking.resource: - raise Exception("Booking is not marker for analytics job, has resoure") - - job = Job() - job.booking = booking - job.job_type = 'DATA' - job.save() - - cls.makeActiveUsersTask() - - @classmethod - def makeCompleteJob(cls, booking): - """Create everything that is needed to fulfill the given booking.""" - resources = booking.resource.get_resources() - job = None - try: - job = Job.objects.get(booking=booking) - except Exception: - job = Job.objects.create(status=JobStatus.NEW, booking=booking) - cls.makeHardwareConfigs( - resources=resources, - job=job - ) - cls.makeNetworkConfigs( - resources=resources, - job=job - ) - cls.makeSoftware( - booking=booking, - job=job - ) - cls.makeGeneratedCloudConfigs( - resources=resources, - job=job - ) - all_users = list(booking.collaborators.all()) - all_users.append(booking.owner) - cls.makeAccessConfig( - users=all_users, - access_type="vpn", - revoke=False, - job=job - ) - for user in all_users: - try: - cls.makeAccessConfig( - users=[user], - access_type="ssh", - revoke=False, - job=job, - context={ - "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"), - "hosts": [r.labid for r in resources] - } - ) - except Exception: - continue - - @classmethod - def makeGeneratedCloudConfigs(cls, resources=[], job=Job()): - for res in resources: - cif = GeneratedCloudConfig.objects.create(resource_id=res.labid, booking=job.booking, rconfig=res.config) - cif.save() - - cif = CloudInitFile.create(priority=0, text=cif.serialize()) - cif.save() - - res.config.cloud_init_files.add(cif) - res.config.save() - - @classmethod - def makeHardwareConfigs(cls, resources=[], job=Job()): - """ - Create and save HardwareConfig. - - Helper function to create the tasks related to - configuring the hardware - """ - for res in resources: - hardware_config = None - try: - hardware_config = HardwareConfig.objects.get(relation__resource_id=res.labid) - except Exception: - hardware_config = HardwareConfig() - - relation = HostHardwareRelation() - relation.resource_id = res.labid - relation.job = job - relation.config = hardware_config - relation.config.save() - relation.config = relation.config - relation.save() - - hardware_config.set("id", "image", "hostname", "power", "ipmi_create") - hardware_config.save() - - @classmethod - def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False): - """ - Create and save AccessConfig. - - Helper function to create the tasks related to - configuring the VPN, SSH, etc access for users - """ - for user in users: - relation = AccessRelation() - relation.job = job - config = AccessConfig() - config.access_type = access_type - config.user = user - config.save() - relation.config = config - relation.save() - config.clear_delta() - if context: - config.set_context(context) - config.set_access_type(access_type) - config.set_revoke(revoke) - config.set_user(user) - config.save() - - @classmethod - def makeNetworkConfigs(cls, resources=[], job=Job()): - """ - Create and save NetworkConfig. - - Helper function to create the tasks related to - configuring the networking - """ - for res in resources: - network_config = None - try: - network_config = NetworkConfig.objects.get(relation__host=res) - except Exception: - network_config = NetworkConfig.objects.create() - - relation = HostNetworkRelation() - relation.resource_id = res.labid - relation.job = job - network_config.save() - relation.config = network_config - relation.save() - network_config.clear_delta() - - # TODO: use get_interfaces() on resource - for interface in res.interfaces.all(): - network_config.add_interface(interface) - network_config.save() - - @classmethod - def make_bridge_config(cls, booking): - if len(booking.resource.get_resources()) < 2: - return None - try: - jumphost_config = ResourceOPNFVConfig.objects.filter( - role__name__iexact="jumphost" - ) - jumphost = ResourceQuery.filter( - bundle=booking.resource, - config=jumphost_config.resource_config - )[0] - except Exception: - return None - br_config = BridgeConfig.objects.create(opnfv_config=booking.opnfv_config) - for iface in jumphost.interfaces.all(): - br_config.interfaces.add(iface) - return br_config - - @classmethod - def makeSoftware(cls, booking=None, job=Job()): - """ - Create and save SoftwareConfig. - - Helper function to create the tasks related to - configuring the desired software, e.g. an OPNFV deployment - """ - if not booking.opnfv_config: - return None - - opnfv_api_config = OpnfvApiConfig.objects.create( - opnfv_config=booking.opnfv_config, - installer=booking.opnfv_config.installer.name, - scenario=booking.opnfv_config.scenario.name, - bridge_config=cls.make_bridge_config(booking) - ) - - opnfv_api_config.set_xdf(booking, False) - opnfv_api_config.save() - - for host in booking.resource.get_resources(): - opnfv_api_config.roles.add(host) - software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config) - software_relation = SoftwareRelation.objects.create(job=job, config=software_config) - return software_relation - - -JOB_TASK_CLASSLIST = [ - HostHardwareRelation, - AccessRelation, - HostNetworkRelation, - SoftwareRelation, - SnapshotRelation, - ActiveUsersRelation -] - - -class JobTaskQuery(AbstractModelQuery): - model_list = JOB_TASK_CLASSLIST diff --git a/src/api/serializers/__init__.py b/src/api/serializers/__init__.py deleted file mode 100644 index e0408fa..0000000 --- a/src/api/serializers/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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 -############################################################################## diff --git a/src/api/serializers/booking_serializer.py b/src/api/serializers/booking_serializer.py deleted file mode 100644 index 993eb22..0000000 --- a/src/api/serializers/booking_serializer.py +++ /dev/null @@ -1,173 +0,0 @@ -############################################################################## -# 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 -############################################################################## - - -from rest_framework import serializers - -from resource_inventory.models import ( - ResourceConfiguration, - CpuProfile, - DiskProfile, - InterfaceProfile, - RamProfile, - Image, - Interface -) - - -class BookingField(serializers.Field): - - def to_representation(self, booking): - """ - Take in a booking object. - - Returns a dictionary of primitives representing that booking - """ - ser = {} - ser['id'] = booking.id - # main loop to grab relevant info out of booking - host_configs = {} # mapping hostname -> config - networks = {} # mapping vlan id -> network_hosts - for host in booking.resource.hosts.all(): - host_configs[host.name] = ResourceConfiguration.objects.get(host=host.template) - if "jumphost" not in ser and host_configs[host.name].opnfvRole.name.lower() == "jumphost": - ser['jumphost'] = host.name - # host is a Host model - for i in range(len(host.interfaces.all())): - interface = host.interfaces.all()[i] - # interface is an Interface model - for vlan in interface.config.all(): - # vlan is Vlan model - if vlan.id not in networks: - networks[vlan.id] = [] - net_host = {"hostname": host.name, "tagged": vlan.tagged, "interface": i} - networks[vlan.id].append(net_host) - # creates networking object of proper form - networking = [] - for vlanid in networks: - network = {} - network['vlan_id'] = vlanid - network['hosts'] = networks[vlanid] - - ser['networking'] = networking - - # creates hosts object of correct form - hosts = [] - for hostname in host_configs: - host = {"hostname": hostname} - host['deploy_image'] = True # TODO? - image = host_configs[hostname].image - host['image'] = { - "name": image.name, - "lab_id": image.lab_id, - "dashboard_id": image.id - } - hosts.append(host) - - ser['hosts'] = hosts - - return ser - - def to_internal_value(self, data): - """ - Take in a dictionary of primitives, and return a booking object. - - This is not going to be implemented or allowed. - If someone needs to create a booking through the api, - they will send a different booking object - """ - return None - - -class BookingSerializer(serializers.Serializer): - - booking = BookingField() - - -# Host Type stuff, for inventory -class CPUSerializer(serializers.ModelSerializer): - class Meta: - model = CpuProfile - fields = ('cores', 'architecture', 'cpus') - - -class DiskSerializer(serializers.ModelSerializer): - class Meta: - model = DiskProfile - fields = ('size', 'media_type', 'name') - - -class InterfaceProfileSerializer(serializers.ModelSerializer): - class Meta: - model = InterfaceProfile - fields = ('speed', 'name') - - -class RamSerializer(serializers.ModelSerializer): - class Meta: - model = RamProfile - fields = ('amount', 'channels') - - -class HostTypeSerializer(serializers.Serializer): - name = serializers.CharField(max_length=200) - ram = RamSerializer() - interface = InterfaceProfileSerializer() - description = serializers.CharField(max_length=1000) - disks = DiskSerializer() - cpu = CPUSerializer() - - -# the rest of the inventory stuff -class NetworkSerializer(serializers.Serializer): - cidr = serializers.CharField(max_length=200) - gateway = serializers.IPAddressField(max_length=200) - vlan = serializers.IntegerField() - - -class ImageSerializer(serializers.ModelSerializer): - lab_id = serializers.IntegerField() - id = serializers.IntegerField(source="dashboard_id") - name = serializers.CharField(max_length=50) - description = serializers.CharField(max_length=200) - - class Meta: - model = Image - - -class InterfaceField(serializers.Field): - def to_representation(self, interface): - pass - - def to_internal_value(self, data): - """Take in a serialized interface and creates an Interface model.""" - mac = data['mac'] - bus_address = data['busaddr'] - switch_name = data['switchport']['switch_name'] - port_name = data['switchport']['port_name'] - # TODO config?? - return Interface.objects.create( - mac_address=mac, - bus_address=bus_address, - switch_name=switch_name, - port_name=port_name - ) - - -class InventoryHostSerializer(serializers.Serializer): - hostname = serializers.CharField(max_length=100) - host_type = serializers.CharField(max_length=100) - interfaces = InterfaceField() - - -class InventorySerializer(serializers.Serializer): - hosts = InventoryHostSerializer() - networks = NetworkSerializer() - images = ImageSerializer() - host_types = HostTypeSerializer() diff --git a/src/api/serializers/old_serializers.py b/src/api/serializers/old_serializers.py deleted file mode 100644 index 0944881..0000000 --- a/src/api/serializers/old_serializers.py +++ /dev/null @@ -1,21 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from rest_framework import serializers - -from account.models import UserProfile - - -class UserSerializer(serializers.ModelSerializer): - username = serializers.CharField(source='user.username') - - class Meta: - model = UserProfile - fields = ('user', 'username', 'ssh_public_key', 'pgp_public_key', 'email_addr') diff --git a/src/api/tests/__init__.py b/src/api/tests/__init__.py deleted file mode 100644 index 2435a9f..0000000 --- a/src/api/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Parker Berberian 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 -############################################################################## diff --git a/src/api/tests/test_models_unittest.py b/src/api/tests/test_models_unittest.py deleted file mode 100644 index 2dee29b..0000000 --- a/src/api/tests/test_models_unittest.py +++ /dev/null @@ -1,271 +0,0 @@ -# Copyright (c) 2019 Sawyer Bergeron, Parker Berberian, 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 -############################################################################## - -from api.models import ( - Job, - JobStatus, - JobFactory, - HostNetworkRelation, - HostHardwareRelation, - SoftwareRelation, - AccessConfig, - SnapshotRelation -) - -from resource_inventory.models import ( - OPNFVRole, - HostProfile, - ConfigState, -) - -from django.test import TestCase, Client - -from dashboard.testing_utils import ( - make_host, - make_user, - make_user_profile, - make_lab, - make_installer, - make_image, - make_scenario, - make_os, - make_complete_host_profile, - make_booking, -) - - -class ValidBookingCreatesValidJob(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = make_user(False, username="newtestuser", password="testpassword") - cls.userprofile = make_user_profile(cls.user) - cls.lab = make_lab() - - cls.host_profile = make_complete_host_profile(cls.lab) - cls.scenario = make_scenario() - cls.installer = make_installer([cls.scenario]) - os = make_os([cls.installer]) - cls.image = make_image(cls.lab, 1, cls.user, os, cls.host_profile) - for i in range(30): - make_host(cls.host_profile, cls.lab, name="host" + str(i), labid="host" + str(i)) - cls.client = Client() - - def setUp(self): - self.booking, self.compute_hostnames, self.jump_hostname = self.create_multinode_generic_booking() - - def create_multinode_generic_booking(self): - topology = {} - - compute_hostnames = ["cmp01", "cmp02", "cmp03"] - - host_type = HostProfile.objects.first() - - universal_networks = [ - {"name": "public", "tagged": False, "public": True}, - {"name": "admin", "tagged": True, "public": False}] - compute_networks = [{"name": "private", "tagged": True, "public": False}] - jumphost_networks = [{"name": "external", "tagged": True, "public": True}] - - # generate a bunch of extra networks - for i in range(10): - net = {"tagged": False, "public": False} - net["name"] = "net" + str(i) - universal_networks.append(net) - - jumphost_info = { - "type": host_type, - "role": OPNFVRole.objects.get_or_create(name="Jumphost")[0], - "nets": self.make_networks(host_type, jumphost_networks + universal_networks), - "image": self.image - } - topology["jump"] = jumphost_info - - for hostname in compute_hostnames: - host_info = { - "type": host_type, - "role": OPNFVRole.objects.get_or_create(name="Compute")[0], - "nets": self.make_networks(host_type, compute_networks + universal_networks), - "image": self.image - } - topology[hostname] = host_info - - booking = make_booking( - owner=self.user, - lab=self.lab, - topology=topology, - installer=self.installer, - scenario=self.scenario - ) - - if not booking.resource: - raise Exception("Booking does not have a resource when trying to pass to makeCompleteJob") - return booking, compute_hostnames, "jump" - - def make_networks(self, hostprofile, nets): - """ - Distribute nets accross hostprofile's interfaces. - - returns a 2D array - """ - network_struct = [] - count = hostprofile.interfaceprofile.all().count() - for i in range(count): - network_struct.append([]) - while (nets): - index = len(nets) % count - network_struct[index].append(nets.pop()) - - return network_struct - - ################################################################# - # Complete Job Tests - ################################################################# - - def test_complete_job_makes_access_configs(self): - JobFactory.makeCompleteJob(self.booking) - job = Job.objects.get(booking=self.booking) - self.assertIsNotNone(job) - - access_configs = AccessConfig.objects.filter(accessrelation__job=job) - - vpn_configs = access_configs.filter(access_type="vpn") - ssh_configs = access_configs.filter(access_type="ssh") - - self.assertFalse(AccessConfig.objects.exclude(access_type__in=["vpn", "ssh"]).exists()) - - all_users = list(self.booking.collaborators.all()) - all_users.append(self.booking.owner) - - for user in all_users: - self.assertTrue(vpn_configs.filter(user=user).exists()) - self.assertTrue(ssh_configs.filter(user=user).exists()) - - def test_complete_job_makes_network_configs(self): - JobFactory.makeCompleteJob(self.booking) - job = Job.objects.get(booking=self.booking) - self.assertIsNotNone(job) - - booking_hosts = self.booking.resource.hosts.all() - - netrelations = HostNetworkRelation.objects.filter(job=job) - netconfigs = [r.config for r in netrelations] - - netrelation_hosts = [r.host for r in netrelations] - - for config in netconfigs: - for interface in config.interfaces.all(): - self.assertTrue(interface.host in booking_hosts) - - # if no interfaces are referenced that shouldn't have vlans, - # and no vlans exist outside those accounted for in netconfigs, - # then the api is faithfully representing networks - # as netconfigs reference resource_inventory models directly - - # this test relies on the assumption that - # every interface is configured, whether it does or does not have vlans - # if this is not true, the test fails - - for host in booking_hosts: - self.assertTrue(host in netrelation_hosts) - relation = HostNetworkRelation.objects.filter(job=job).get(host=host) - - # do 2 direction matching that interfaces are one to one - config = relation.config - for interface in config.interfaces.all(): - self.assertTrue(interface in host.interfaces) - for interface in host.interfaces.all(): - self.assertTrue(interface in config.interfaces) - - for host in netrelation_hosts: - self.assertTrue(host in booking_hosts) - - def test_complete_job_makes_hardware_configs(self): - JobFactory.makeCompleteJob(self.booking) - job = Job.objects.get(booking=self.booking) - self.assertIsNotNone(job) - - hardware_relations = HostHardwareRelation.objects.filter(job=job) - - job_hosts = [r.host for r in hardware_relations] - - booking_hosts = self.booking.resource.hosts.all() - - self.assertEqual(len(booking_hosts), len(job_hosts)) - - for relation in hardware_relations: - self.assertTrue(relation.host in booking_hosts) - self.assertEqual(relation.status, JobStatus.NEW) - config = relation.config - host = relation.host - self.assertEqual(config.get_delta()["hostname"], host.template.resource.name) - - def test_complete_job_makes_software_configs(self): - JobFactory.makeCompleteJob(self.booking) - job = Job.objects.get(booking=self.booking) - self.assertIsNotNone(job) - - srelation = SoftwareRelation.objects.filter(job=job).first() - self.assertIsNotNone(srelation) - - sconfig = srelation.config - self.assertIsNotNone(sconfig) - - oconfig = sconfig.opnfv - self.assertIsNotNone(oconfig) - - # not onetoone in models, but first() is safe here based on how ConfigBundle and a matching OPNFVConfig are created - # this should, however, be made explicit - self.assertEqual(oconfig.installer, self.booking.config_bundle.opnfv_config.first().installer.name) - self.assertEqual(oconfig.scenario, self.booking.config_bundle.opnfv_config.first().scenario.name) - - for host in oconfig.roles.all(): - role_name = host.config.host_opnfv_config.first().role.name - if str(role_name).lower() == "jumphost": - self.assertEqual(host.template.resource.name, self.jump_hostname) - elif str(role_name).lower() == "compute": - self.assertTrue(host.template.resource.name in self.compute_hostnames) - else: - self.fail(msg="Host with non-configured role name related to job: " + str(role_name)) - - def test_make_snapshot_task(self): - host = self.booking.resource.hosts.first() - image = make_image(self.lab, -1, None, None, host.profile) - - Job.objects.create(booking=self.booking) - - JobFactory.makeSnapshotTask(image, self.booking, host) - - snap_relation = SnapshotRelation.objects.get(job=self.booking.job) - config = snap_relation.config - self.assertEqual(host.id, config.host.id) - self.assertEqual(config.dashboard_id, image.id) - self.assertEqual(snap_relation.snapshot.id, image.id) - - def test_make_hardware_configs(self): - hosts = self.booking.resource.hosts.all() - job = Job.objects.create(booking=self.booking) - JobFactory.makeHardwareConfigs(hosts=hosts, job=job) - - hardware_relations = HostHardwareRelation.objects.filter(job=job) - - self.assertEqual(hardware_relations.count(), hosts.count()) - - host_set = set([h.id for h in hosts]) - - for relation in hardware_relations: - try: - host_set.remove(relation.host.id) - except KeyError: - self.fail("Hardware Relation/Config not created for host " + str(relation.host)) - # TODO: ConfigState needs to be fixed in factory methods - relation.config.state = ConfigState.NEW - self.assertEqual(relation.config.get_delta()["power"], "on") - self.assertTrue(relation.config.get_delta()["ipmi_create"]) - # TODO: the rest of hwconf attrs - - self.assertEqual(len(host_set), 0) diff --git a/src/api/urls.py b/src/api/urls.py deleted file mode 100644 index cbb453c..0000000 --- a/src/api/urls.py +++ /dev/null @@ -1,108 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 -############################################################################## - - -""" -laas_dashboard URL Configuration. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from django.urls import path - -from api.views import ( - lab_profile, - lab_status, - lab_inventory, - lab_downtime, - specific_job, - specific_task, - new_jobs, - current_jobs, - done_jobs, - update_host_bmc, - lab_host, - get_pdf, - get_idf, - lab_users, - lab_user, - GenerateTokenView, - analytics_job, - user_bookings, - specific_booking, - extend_booking, - make_booking, - list_labs, - all_users, - images_for_template, - available_templates, - resource_ci_metadata, - resource_ci_userdata, - resource_ci_userdata_directory, - all_images, - all_opsyss, - single_image, - single_opsys, - create_ci_file, - booking_details, -) - -urlpatterns = [ - path('labs//opsys/', single_opsys), - path('labs//image/', single_image), - path('labs//opsys', all_opsyss), - path('labs//image', all_images), - path('labs//profile', lab_profile), - path('labs//status', lab_status), - path('labs//inventory', lab_inventory), - path('labs//downtime', lab_downtime), - path('labs//hosts/', lab_host), - path('labs//hosts//bmc', update_host_bmc), - path('labs//booking//pdf', get_pdf, name="get-pdf"), - path('labs//booking//idf', get_idf, name="get-idf"), - path('labs//jobs/', specific_job), - path('labs//jobs//', specific_task), - path('labs//jobs//cidata//user-data', resource_ci_userdata_directory, name="specific-user-data"), - path('labs//jobs//cidata//meta-data', resource_ci_metadata, name="specific-meta-data"), - path('labs//jobs//cidata///user-data', resource_ci_userdata, name="user-data-dir"), - path('labs//jobs/new', new_jobs), - path('labs//jobs/current', current_jobs), - path('labs//jobs/done', done_jobs), - path('labs//jobs/getByType/DATA', analytics_job), - path('labs//users', lab_users), - path('labs//users/', lab_user), - - path('booking', user_bookings), - path('booking/', specific_booking), - path('booking//extendBooking/', extend_booking), - path('booking/makeBooking', make_booking), - path('booking//details', booking_details), - - path('resource_inventory/availableTemplates', available_templates), - path('resource_inventory//images', images_for_template), - - path('resource_inventory/cloud/create', create_ci_file), - - path('users', all_users), - path('labs', list_labs), - - url(r'^token$', GenerateTokenView.as_view(), name='generate_token'), -] 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 += '
' + 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) diff --git a/src/booking/__init__.py b/src/booking/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/booking/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/booking/admin.py b/src/booking/admin.py deleted file mode 100644 index 162777e..0000000 --- a/src/booking/admin.py +++ /dev/null @@ -1,16 +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 -############################################################################## - - -from django.contrib import admin - -from booking.models import Booking - -admin.site.register(Booking) diff --git a/src/booking/apps.py b/src/booking/apps.py deleted file mode 100644 index 99bf115..0000000 --- a/src/booking/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -from django.apps import AppConfig - - -class BookingConfig(AppConfig): - name = 'booking' diff --git a/src/booking/forms.py b/src/booking/forms.py deleted file mode 100644 index 9c9b053..0000000 --- a/src/booking/forms.py +++ /dev/null @@ -1,123 +0,0 @@ -############################################################################## -# 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 django.forms as forms -from django.forms.widgets import NumberInput - -from workflow.forms import ( - MultipleSelectFilterField, - MultipleSelectFilterWidget) -from account.models import UserProfile -from resource_inventory.models import Image, Installer, Scenario -from workflow.forms import SearchableSelectMultipleField -from booking.lib import get_user_items, get_user_field_opts - - -class QuickBookingForm(forms.Form): - # Django Form class for Express Booking - purpose = forms.CharField(max_length=1000) - project = forms.CharField(max_length=400) - hostname = forms.CharField(required=False, max_length=400) - global_cloud_config = forms.CharField(widget=forms.Textarea, required=False) - - installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False) - scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False) - - def __init__(self, data=None, user=None, lab_data=None, *args, **kwargs): - if "default_user" in kwargs: - default_user = kwargs.pop("default_user") - else: - default_user = "you" - self.default_user = default_user - - super(QuickBookingForm, self).__init__(data=data, **kwargs) - - image_help_text = 'Image can be set only for single-node bookings. For multi-node bookings set image through Design a POD.' - self.fields["image"] = forms.ModelChoiceField( - Image.objects.filter(public=True) | Image.objects.filter(owner=user), required=False - ) - - self.fields['image'].widget.attrs.update({ - 'class': 'has-popover', - 'data-content': image_help_text, - 'data-placement': 'bottom', - 'data-container': 'body' - }) - - self.fields['users'] = SearchableSelectMultipleField( - queryset=UserProfile.objects.filter(public_user=True).select_related('user').exclude(user=user), - items=get_user_items(exclude=user), - required=False, - **get_user_field_opts() - ) - - self.fields['length'] = forms.IntegerField( - widget=NumberInput( - attrs={ - "type": "range", - 'min': "1", - "max": "21", - "value": "1" - } - ) - ) - - self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**lab_data)) - - hostname_help_text = 'Hostname can be set only for single-node bookings. For multi-node bookings set hostname through Design a POD.' - self.fields['hostname'].widget.attrs.update({ - 'class': 'has-popover', - 'data-content': hostname_help_text, - 'data-placement': 'top', - 'data-container': 'body' - }) - - def build_user_list(self): - """ - Build list of UserProfiles. - - returns a mapping of UserProfile ids to displayable objects expected by - searchable multiple select widget - """ - try: - users = {} - d_qset = UserProfile.objects.select_related('user').all().exclude(user__username=self.default_user) - for userprofile in d_qset: - user = { - 'id': userprofile.user.id, - 'expanded_name': userprofile.full_name, - 'small_name': userprofile.user.username, - 'string': userprofile.email_addr - } - - users[userprofile.user.id] = user - - return users - except Exception: - pass - - def build_search_widget_attrs(self, chosen_users, default_user="you"): - - attrs = { - 'set': self.build_user_list(), - 'show_from_noentry': "false", - 'show_x_results': 10, - 'scrollable': "false", - 'selectable_limit': -1, - 'name': "users", - 'placeholder': "username", - 'initial': chosen_users, - 'edit': False - } - return attrs - - -class HostReImageForm(forms.Form): - - image_id = forms.IntegerField() - host_id = forms.IntegerField() diff --git a/src/booking/lib.py b/src/booking/lib.py deleted file mode 100644 index 8c87979..0000000 --- a/src/booking/lib.py +++ /dev/null @@ -1,36 +0,0 @@ -############################################################################## -# Copyright (c) 2019 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 -############################################################################## - -from account.models import UserProfile - - -def get_user_field_opts(): - return { - 'show_from_noentry': False, - 'show_x_results': 5, - 'results_scrollable': True, - 'selectable_limit': -1, - 'placeholder': 'Search for other users', - 'name': 'users', - 'disabled': False - } - - -def get_user_items(exclude=None): - qs = UserProfile.objects.filter(public_user=True).select_related('user').exclude(user=exclude) - items = {} - for up in qs: - item = { - 'id': up.id, - 'expanded_name': up.full_name if up.full_name else up.user.username, - 'small_name': up.user.username, - 'string': up.email_addr if up.email_addr else up.user.username, - } - items[up.id] = item - return items diff --git a/src/booking/migrations/0001_initial.py b/src/booking/migrations/0001_initial.py deleted file mode 100644 index 20415fe..0000000 --- a/src/booking/migrations/0001_initial.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 2.1 on 2018-09-14 14:48 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('account', '0001_initial'), - ('resource_inventory', '__first__'), - ] - - operations = [ - migrations.CreateModel( - name='Booking', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('start', models.DateTimeField()), - ('end', models.DateTimeField()), - ('reset', models.BooleanField(default=False)), - ('jira_issue_id', models.IntegerField(blank=True, null=True)), - ('jira_issue_status', models.CharField(blank=True, max_length=50)), - ('purpose', models.CharField(max_length=300)), - ('ext_count', models.IntegerField(default=2)), - ('project', models.CharField(blank=True, default='', max_length=100, null=True)), - ('collaborators', models.ManyToManyField(related_name='collaborators', to=settings.AUTH_USER_MODEL)), - ('config_bundle', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ConfigBundle')), - ('lab', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='account.Lab')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to=settings.AUTH_USER_MODEL)), - ('resource', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle')), - ], - options={ - 'db_table': 'booking', - }, - ), - migrations.CreateModel( - name='Installer', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=30)), - ], - ), - migrations.CreateModel( - name='Opsys', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100)), - ('sup_installers', models.ManyToManyField(blank=True, to='booking.Installer')), - ], - ), - migrations.CreateModel( - name='Scenario', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=300)), - ], - ), - migrations.AddField( - model_name='installer', - name='sup_scenarios', - field=models.ManyToManyField(blank=True, to='booking.Scenario'), - ), - ] diff --git a/src/booking/migrations/0002_booking_pdf.py b/src/booking/migrations/0002_booking_pdf.py deleted file mode 100644 index 53232c9..0000000 --- a/src/booking/migrations/0002_booking_pdf.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1 on 2018-11-09 16:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='pdf', - field=models.TextField(blank=True, default=''), - ), - ] diff --git a/src/booking/migrations/0003_auto_20190115_1733.py b/src/booking/migrations/0003_auto_20190115_1733.py deleted file mode 100644 index 70eecfe..0000000 --- a/src/booking/migrations/0003_auto_20190115_1733.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.1 on 2019-01-15 17:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0002_booking_pdf'), - ] - - operations = [ - migrations.RemoveField( - model_name='installer', - name='sup_scenarios', - ), - migrations.RemoveField( - model_name='opsys', - name='sup_installers', - ), - migrations.DeleteModel( - name='Installer', - ), - migrations.DeleteModel( - name='Opsys', - ), - migrations.DeleteModel( - name='Scenario', - ), - ] diff --git a/src/booking/migrations/0004_auto_20190124_1700.py b/src/booking/migrations/0004_auto_20190124_1700.py deleted file mode 100644 index baa32d2..0000000 --- a/src/booking/migrations/0004_auto_20190124_1700.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1 on 2019-01-24 17:00 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0003_auto_20190115_1733'), - ] - - operations = [ - migrations.AlterField( - model_name='booking', - name='owner', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='owner', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/src/booking/migrations/0005_booking_idf.py b/src/booking/migrations/0005_booking_idf.py deleted file mode 100644 index 31e9170..0000000 --- a/src/booking/migrations/0005_booking_idf.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1 on 2019-04-12 19:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0004_auto_20190124_1700'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='idf', - field=models.TextField(blank=True, default=''), - ), - ] diff --git a/src/booking/migrations/0006_booking_opnfv_config.py b/src/booking/migrations/0006_booking_opnfv_config.py deleted file mode 100644 index e5ffc71..0000000 --- a/src/booking/migrations/0006_booking_opnfv_config.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1 on 2019-05-01 18:02 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0010_auto_20190430_1405'), - ('booking', '0005_booking_idf'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='opnfv_config', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'), - ), - ] diff --git a/src/booking/migrations/0007_remove_booking_config_bundle.py b/src/booking/migrations/0007_remove_booking_config_bundle.py deleted file mode 100644 index dcd2e1c..0000000 --- a/src/booking/migrations/0007_remove_booking_config_bundle.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0006_booking_opnfv_config'), - ] - - operations = [ - migrations.RemoveField( - model_name='booking', - name='config_bundle', - ), - ] diff --git a/src/booking/migrations/0008_auto_20201109_1947.py b/src/booking/migrations/0008_auto_20201109_1947.py deleted file mode 100644 index 289e476..0000000 --- a/src/booking/migrations/0008_auto_20201109_1947.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 19:47 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0007_remove_booking_config_bundle'), - ] - - operations = [ - migrations.AlterField( - model_name='booking', - name='collaborators', - field=models.ManyToManyField(blank=True, related_name='collaborators', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='booking', - name='opnfv_config', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'), - ), - migrations.AlterField( - model_name='booking', - name='resource', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle'), - ), - ] diff --git a/src/booking/migrations/0009_booking_complete.py b/src/booking/migrations/0009_booking_complete.py deleted file mode 100644 index e291a83..0000000 --- a/src/booking/migrations/0009_booking_complete.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-09-07 15:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0008_auto_20201109_1947'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='complete', - field=models.BooleanField(default=False), - ), - ] diff --git a/src/booking/migrations/__init__.py b/src/booking/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/booking/models.py b/src/booking/models.py deleted file mode 100644 index 966f1c2..0000000 --- a/src/booking/models.py +++ /dev/null @@ -1,72 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 -############################################################################## - - -from resource_inventory.models import ResourceBundle, OPNFVConfig -from account.models import Lab -from django.contrib.auth.models import User -from django.db import models -import resource_inventory.resource_manager - - -class Booking(models.Model): - id = models.AutoField(primary_key=True) - # All bookings are owned by the user who requested it - owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='owner') - # an owner can add other users to the booking - collaborators = models.ManyToManyField(User, blank=True, related_name='collaborators') - # start and end time - start = models.DateTimeField() - end = models.DateTimeField() - reset = models.BooleanField(default=False) - jira_issue_id = models.IntegerField(null=True, blank=True) - jira_issue_status = models.CharField(max_length=50, blank=True) - purpose = models.CharField(max_length=300, blank=False) - # bookings can be extended a limited number of times - ext_count = models.IntegerField(default=2) - # the hardware that the user has booked - resource = models.ForeignKey(ResourceBundle, on_delete=models.SET_NULL, null=True, blank=True) - opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.SET_NULL, null=True, blank=True) - project = models.CharField(max_length=100, default="", blank=True, null=True) - lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL) - pdf = models.TextField(blank=True, default="") - idf = models.TextField(blank=True, default="") - - complete = models.BooleanField(default=False) - - class Meta: - db_table = 'booking' - - def save(self, *args, **kwargs): - """ - Save the booking if self.user is authorized and there is no overlapping booking. - - Raise PermissionError if the user is not authorized - Raise ValueError if there is an overlapping booking - """ - if self.start >= self.end: - raise ValueError('Start date is after end date') - # conflicts end after booking starts, and start before booking ends - conflicting_dates = Booking.objects.filter(resource=self.resource).exclude(id=self.id) - conflicting_dates = conflicting_dates.filter(end__gt=self.start) - conflicting_dates = conflicting_dates.filter(start__lt=self.end) - if conflicting_dates.count() > 0: - raise ValueError('This booking overlaps with another booking') - return super(Booking, self).save(*args, **kwargs) - - def delete(self, *args, **kwargs): - res = self.resource - self.resource = None - self.save() - resource_inventory.resource_manager.ResourceManager.getInstance().deleteResourceBundle(res) - return super(self.__class__, self).delete(*args, **kwargs) - - def __str__(self): - return str(self.purpose) + ' from ' + str(self.start) + ' until ' + str(self.end) diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py deleted file mode 100644 index 4b85d76..0000000 --- a/src/booking/quick_deployer.py +++ /dev/null @@ -1,343 +0,0 @@ -############################################################################## -# 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 yaml -from django.db.models import Q -from django.db import transaction -from datetime import timedelta -from django.utils import timezone -from django.core.exceptions import ValidationError -from account.models import Lab, UserProfile - -from resource_inventory.models import ( - ResourceTemplate, - Image, - OPNFVRole, - OPNFVConfig, - ResourceOPNFVConfig, - ResourceConfiguration, - NetworkConnection, - InterfaceConfiguration, - Network, - CloudInitFile, -) -from resource_inventory.resource_manager import ResourceManager -from resource_inventory.pdf_templater import PDFTemplater -from notifier.manager import NotificationHandler -from booking.models import Booking -from dashboard.exceptions import BookingLengthException -from api.models import JobFactory - - -def parse_resource_field(resource_json): - """ - Parse the json from the frontend. - - returns a reference to the selected Lab and ResourceTemplate objects - """ - lab, template = (None, None) - lab_dict = resource_json['lab'] - for lab_info in lab_dict.values(): - if lab_info['selected']: - lab = Lab.objects.get(lab_user__id=lab_info['id']) - - resource_dict = resource_json['resource'] - for resource_info in resource_dict.values(): - if resource_info['selected']: - template = ResourceTemplate.objects.get(pk=resource_info['id']) - - if lab is None: - raise ValidationError("No lab was selected") - if template is None: - raise ValidationError("No Host was selected") - - return lab, template - - -def update_template(old_template, image, hostname, user, global_cloud_config=None): - """ - Duplicate a template to the users account and update configured fields. - - The dashboard presents users with preconfigured resource templates, - but the user might want to make small modifications, e.g hostname and - linux distro. So we copy the public template and create a private version - to the user's profile, and mark it temporary. When the booking ends the - new template is deleted - """ - name = user.username + "'s Copy of '" + old_template.name + "'" - num_copies = ResourceTemplate.objects.filter(name__startswith=name).count() - template = ResourceTemplate.objects.create( - name=name if num_copies == 0 else name + " (" + str(num_copies) + ")", - xml=old_template.xml, - owner=user, - lab=old_template.lab, - description=old_template.description, - public=False, - temporary=True, - private_vlan_pool=old_template.private_vlan_pool, - public_vlan_pool=old_template.public_vlan_pool, - copy_of=old_template - ) - - for old_network in old_template.networks.all(): - Network.objects.create( - name=old_network.name, - bundle=template, - is_public=old_network.is_public - ) - # We are assuming there is only one opnfv config per public resource template - old_opnfv = template.opnfv_config.first() - if old_opnfv: - opnfv_config = OPNFVConfig.objects.create( - installer=old_opnfv.installer, - scenario=old_opnfv.installer, - template=template, - name=old_opnfv.installer, - ) - # I am explicitly leaving opnfv_config.networks empty to avoid - # problems with duplicated / shared networks. In the quick deploy, - # there is never multiple networks anyway. This may have to change in the future - - for old_config in old_template.getConfigs(): - image_to_set = image - if not image: - image_to_set = old_config.image - - config = ResourceConfiguration.objects.create( - profile=old_config.profile, - image=image_to_set, - template=template, - is_head_node=old_config.is_head_node, - name=hostname if len(old_template.getConfigs()) == 1 else old_config.name, - # cloud_init_files=old_config.cloud_init_files.set() - ) - - for file in old_config.cloud_init_files.all(): - config.cloud_init_files.add(file) - - if global_cloud_config: - config.cloud_init_files.add(global_cloud_config) - config.save() - - for old_iface_config in old_config.interface_configs.all(): - iface_config = InterfaceConfiguration.objects.create( - profile=old_iface_config.profile, - resource_config=config - ) - - for old_connection in old_iface_config.connections.all(): - iface_config.connections.add(NetworkConnection.objects.create( - network=template.networks.get(name=old_connection.network.name), - vlan_is_tagged=old_connection.vlan_is_tagged - )) - - for old_res_opnfv in old_config.resource_opnfv_config.all(): - if old_opnfv: - ResourceOPNFVConfig.objects.create( - role=old_opnfv.role, - resource_config=config, - opnfv_config=opnfv_config - ) - return template - - -def generate_opnfvconfig(scenario, installer, template): - return OPNFVConfig.objects.create( - scenario=scenario, - installer=installer, - template=template - ) - - -def generate_hostopnfv(hostconfig, opnfvconfig): - role = None - try: - role = OPNFVRole.objects.get(name="Jumphost") - except Exception: - role = OPNFVRole.objects.create( - name="Jumphost", - description="Single server jumphost role" - ) - return ResourceOPNFVConfig.objects.create( - role=role, - host_config=hostconfig, - opnfv_config=opnfvconfig - ) - - -def generate_resource_bundle(template): - resource_manager = ResourceManager.getInstance() - resource_bundle = resource_manager.instantiateTemplate(template) - return resource_bundle - - -def check_invariants(**kwargs): - # TODO: This should really happen in the BookingForm validation methods - image = kwargs['image'] - lab = kwargs['lab'] - length = kwargs['length'] - # check that image os is compatible with installer - if image: - if image.from_lab != lab: - raise ValidationError("The chosen image is not available at the chosen hosting lab") - # TODO - # if image.host_type != host_profile: - # raise ValidationError("The chosen image is not available for the chosen host type") - if not image.public and image.owner != kwargs['owner']: - raise ValidationError("You are not the owner of the chosen private image") - if length < 1 or length > 21: - raise BookingLengthException("Booking must be between 1 and 21 days long") - - -def create_from_form(form, request): - """ - Parse data from QuickBookingForm to create booking - """ - resource_field = form.cleaned_data['filter_field'] - # users_field = form.cleaned_data['users'] - hostname = 'opnfv_host' if not form.cleaned_data['hostname'] else form.cleaned_data['hostname'] - - global_cloud_config = None if not form.cleaned_data['global_cloud_config'] else form.cleaned_data['global_cloud_config'] - - if global_cloud_config: - form.cleaned_data['global_cloud_config'] = create_ci_file(global_cloud_config) - - # image = form.cleaned_data['image'] - # scenario = form.cleaned_data['scenario'] - # installer = form.cleaned_data['installer'] - - lab, resource_template = parse_resource_field(resource_field) - data = form.cleaned_data - data['hostname'] = hostname - data['lab'] = lab - data['resource_template'] = resource_template - data['owner'] = request.user - - return _create_booking(data) - - -def create_from_API(body, user): - """ - Parse data from Automation API to create booking - """ - booking_info = json.loads(body.decode('utf-8')) - - data = {} - data['purpose'] = booking_info['purpose'] - data['project'] = booking_info['project'] - data['users'] = [UserProfile.objects.get(user__username=username) - for username in booking_info['collaborators']] - data['hostname'] = booking_info['hostname'] - data['length'] = booking_info['length'] - data['installer'] = None - data['scenario'] = None - - data['image'] = Image.objects.get(pk=booking_info['imageLabID']) - - data['resource_template'] = ResourceTemplate.objects.get(pk=booking_info['templateID']) - data['lab'] = data['resource_template'].lab - data['owner'] = user - - if 'global_cloud_config' in data.keys(): - data['global_cloud_config'] = CloudInitFile.objects.get(id=data['global_cloud_config']) - - return _create_booking(data) - - -def create_ci_file(data: str) -> CloudInitFile: - try: - d = yaml.load(data) - if not (type(d) is dict): - raise Exception("CI file was valid yaml but was not a dict") - except Exception: - raise ValidationError("The provided Cloud Config is not valid yaml, please refer to the Cloud Init documentation for expected structure") - print("about to create global cloud config") - config = CloudInitFile.create(text=data, priority=CloudInitFile.objects.count()) - print("made global cloud config") - - return config - - -@transaction.atomic -def _create_booking(data): - check_invariants(**data) - - # check booking privileges - # TODO: use the canonical booking_allowed method because now template might have multiple - # machines - if Booking.objects.filter(owner=data['owner'], end__gt=timezone.now()).count() >= 3 and not data['owner'].userprofile.booking_privledge: - raise PermissionError("You do not have permission to have more than 3 bookings at a time.") - - ResourceManager.getInstance().templateIsReservable(data['resource_template']) - - resource_template = update_template(data['resource_template'], data['image'], data['hostname'], data['owner'], global_cloud_config=data['global_cloud_config']) - - # generate resource bundle - resource_bundle = generate_resource_bundle(resource_template) - - # generate booking - booking = Booking.objects.create( - purpose=data['purpose'], - project=data['project'], - lab=data['lab'], - owner=data['owner'], - start=timezone.now(), - end=timezone.now() + timedelta(days=int(data['length'])), - resource=resource_bundle, - opnfv_config=None - ) - - booking.pdf = PDFTemplater.makePDF(booking) - - for collaborator in data['users']: # list of Users (not UserProfile) - booking.collaborators.add(collaborator.user) - - booking.save() - - # generate job - JobFactory.makeCompleteJob(booking) - NotificationHandler.notify_new_booking(booking) - - return booking - - -def drop_filter(user): - """ - Return a dictionary that contains filters. - - Only certain installlers are supported on certain images, etc - so the image filter indexed at [imageid][installerid] is truthy if - that installer is supported on that image - """ - installer_filter = {} - scenario_filter = {} - - images = Image.objects.filter(Q(public=True) | Q(owner=user)) - image_filter = {} - for image in images: - image_filter[image.id] = { - 'lab': 'lab_' + str(image.from_lab.lab_user.id), - 'architecture': str(image.architecture), - 'name': image.name - } - - resource_filter = {} - templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user)) - for rt in templates: - profiles = [conf.profile for conf in rt.getConfigs()] - resource_filter["resource_" + str(rt.id)] = [str(p.architecture) for p in profiles] - - return { - 'installer_filter': json.dumps(installer_filter), - 'scenario_filter': json.dumps(scenario_filter), - 'image_filter': json.dumps(image_filter), - 'resource_profile_map': json.dumps(resource_filter), - } diff --git a/src/booking/stats.py b/src/booking/stats.py deleted file mode 100644 index 5a59d32..0000000 --- a/src/booking/stats.py +++ /dev/null @@ -1,109 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Parker Berberian, Sawyer Bergeron, Sean Smith 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 os -from booking.models import Booking -from resource_inventory.models import ResourceQuery, ResourceProfile -from datetime import datetime, timedelta -from collections import Counter -import pytz - - -class StatisticsManager(object): - - @staticmethod - def getContinuousBookingTimeSeries(span=28): - """ - Calculate Booking usage data points. - - Gathers all active bookings that fall in interval [(now - span), (now + 1 week)]. - x data points are every 12 hours - y values are the integer number of bookings/users active at time - """ - - anuket_colors = [ - '#6BDAD5', # Turquoise - '#E36386', # Pale Violet Red - '#F5B335', # Sandy Brown - '#007473', # Teal - '#BCE194', # Gainsboro - '#00CE7C', # Sea Green - ] - - lfedge_colors = [ - '#0049B0', - '#B481A5', - '#6CAFE4', - '#D33668', - '#28245A' - ] - - x = [] - y = [] - users = [] - projects = [] - profiles = {str(profile): [] for profile in ResourceProfile.objects.all()} - - now = datetime.now(pytz.utc) - delta = timedelta(days=span) - start = now - delta - end = now + timedelta(weeks=1) - - bookings = Booking.objects.filter( - start__lte=end, - end__gte=start - ).prefetch_related("collaborators") - - # get data - while start <= end: - active_users = 0 - - books = bookings.filter( - start__lte=start, - end__gte=start - ).prefetch_related("collaborators") - - for booking in books: - active_users += booking.collaborators.all().count() + 1 - - x.append(str(start.month) + '-' + str(start.day)) - y.append(books.count()) - - step_profiles = Counter([ - str(config.profile) - for book in books - for config in book.resource.template.getConfigs() - ]) - - for profile in ResourceProfile.objects.all(): - profiles[str(profile)].append(step_profiles[str(profile)]) - users.append(active_users) - - start += timedelta(hours=12) - - in_use = len(ResourceQuery.filter(working=True, booked=True)) - not_in_use = len(ResourceQuery.filter(working=True, booked=False)) - maintenance = len(ResourceQuery.filter(working=False)) - - projects = [x.project for x in bookings] - proj_count = sorted(Counter(projects).items(), key=lambda x: x[1]) - - project_keys = [proj[0] for proj in proj_count[-5:]] - project_keys = ['None' if x is None else x for x in project_keys] - project_counts = [proj[1] for proj in proj_count[-5:]] - - resources = {key: [x, value] for key, value in profiles.items()} - - return { - "resources": resources, - "booking": [x, y], - "user": [x, users], - "utils": [in_use, not_in_use, maintenance], - "projects": [project_keys, project_counts], - "colors": anuket_colors if os.environ.get('TEMPLATE_OVERRIDE_DIR') == 'laas' else lfedge_colors - } diff --git a/src/booking/tests/__init__.py b/src/booking/tests/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/booking/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/booking/tests/test_models.py b/src/booking/tests/test_models.py deleted file mode 100644 index 37eb655..0000000 --- a/src/booking/tests/test_models.py +++ /dev/null @@ -1,210 +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 -############################################################################## - - -from datetime import timedelta - -from django.contrib.auth.models import User -from django.test import TestCase -from django.utils import timezone - -from booking.models import Booking -from dashboard.testing_utils import make_resource_template, make_user - - -class BookingModelTestCase(TestCase): - """ - Test the Booking model. - - Creates all the scafolding needed and tests the Booking model - """ - - def setUp(self): - """ - Prepare for Booking model tests. - - Creates all the needed models, such as users, resources, and configurations - """ - self.owner = User.objects.create(username='owner') - self.res1 = make_resource_template(name="Test template 1") - self.res2 = make_resource_template(name="Test template 2") - self.user1 = make_user(username='user1') - - def test_start_end(self): - """ - Verify the start and end fields. - - if the start of a booking is greater or equal then the end, - saving should raise a ValueException - """ - start = timezone.now() - end = start - timedelta(weeks=1) - self.assertRaises( - ValueError, - Booking.objects.create, - start=start, - end=end, - resource=self.res1, - owner=self.user1, - ) - end = start - self.assertRaises( - ValueError, - Booking.objects.create, - start=start, - end=end, - resource=self.res1, - owner=self.user1, - ) - - def test_conflicts(self): - """ - Verify conflicting dates are dealt with. - - saving an overlapping booking on the same resource - should raise a ValueException - saving for different resources should succeed - """ - start = timezone.now() - end = start + timedelta(weeks=1) - self.assertTrue( - Booking.objects.create( - start=start, - end=end, - owner=self.user1, - resource=self.res1, - ) - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start, - end=end, - resource=self.res1, - owner=self.user1, - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start + timedelta(days=1), - end=end - timedelta(days=1), - resource=self.res1, - owner=self.user1, - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start - timedelta(days=1), - end=end, - resource=self.res1, - owner=self.user1, - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start - timedelta(days=1), - end=end - timedelta(days=1), - resource=self.res1, - owner=self.user1, - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start, - end=end + timedelta(days=1), - resource=self.res1, - owner=self.user1, - ) - - self.assertRaises( - ValueError, - Booking.objects.create, - start=start + timedelta(days=1), - end=end + timedelta(days=1), - resource=self.res1, - owner=self.user1, - ) - - self.assertTrue( - Booking.objects.create( - start=start - timedelta(days=1), - end=start, - owner=self.user1, - resource=self.res1, - ) - ) - - self.assertTrue( - Booking.objects.create( - start=end, - end=end + timedelta(days=1), - owner=self.user1, - resource=self.res1, - ) - ) - - self.assertTrue( - Booking.objects.create( - start=start - timedelta(days=2), - end=start - timedelta(days=1), - owner=self.user1, - resource=self.res1, - ) - ) - - self.assertTrue( - Booking.objects.create( - start=end + timedelta(days=1), - end=end + timedelta(days=2), - owner=self.user1, - resource=self.res1, - ) - ) - - self.assertTrue( - Booking.objects.create( - start=start, - end=end, - owner=self.user1, - resource=self.res2, - ) - ) - - def test_extensions(self): - """ - Test booking extensions. - - saving a booking with an extended end time is allows to happen twice, - and each extension must be a maximum of one week long - """ - start = timezone.now() - end = start + timedelta(weeks=1) - self.assertTrue( - Booking.objects.create( - start=start, - end=end, - owner=self.user1, - resource=self.res1, - ) - ) - - booking = Booking.objects.all().first() # should be only thing in db - - self.assertEquals(booking.ext_count, 2) - booking.end = booking.end + timedelta(days=3) - try: - booking.save() - except Exception: - self.fail("save() threw an exception") diff --git a/src/booking/tests/test_quick_booking.py b/src/booking/tests/test_quick_booking.py deleted file mode 100644 index f405047..0000000 --- a/src/booking/tests/test_quick_booking.py +++ /dev/null @@ -1,180 +0,0 @@ -############################################################################## -# 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 datetime -import json - -from django.test import TestCase, Client - -from booking.models import Booking -from dashboard.testing_utils import ( - make_user, - make_user_profile, - make_lab, - make_image, - make_os, - make_opnfv_role, - make_public_net, - make_resource_template, - make_server -) - - -class QuickBookingValidFormTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = make_user(False, username="newtestuser") - cls.user.set_password("testpassword") - cls.user.save() - make_user_profile(cls.user, True) - - cls.lab = make_lab() - - cls.res_template = make_resource_template(owner=cls.user, lab=cls.lab) - cls.res_profile = cls.res_template.getConfigs()[0].profile - os = make_os() - cls.image = make_image(cls.res_profile, lab=cls.lab, owner=cls.user, os=os) - cls.server = make_server(cls.res_profile, cls.lab) - cls.role = make_opnfv_role() - cls.pubnet = make_public_net(10, cls.lab) - - cls.post_data = cls.build_post_data() - cls.client = Client() - - @classmethod - def build_post_data(cls): - return { - 'filter_field': json.dumps({ - "resource": { - "resource_" + str(cls.res_profile.id): { - "selected": True, - "id": cls.res_template.id - } - }, - "lab": { - "lab_" + str(cls.lab.lab_user.id): { - "selected": True, - "id": cls.lab.lab_user.id - } - } - }), - 'purpose': 'my_purpose', - 'project': 'my_project', - 'length': '3', - 'ignore_this': 1, - 'users': '', - 'hostname': 'my_host', - 'image': str(cls.image.id), - } - - def post(self, changed_fields={}): - payload = self.post_data.copy() - payload.update(changed_fields) - response = self.client.post('/booking/quick/', payload) - return response - - def setUp(self): - self.client.login(username="newtestuser", password="testpassword") - - def assertValidBooking(self, booking): - self.assertEqual(booking.owner, self.user) - self.assertEqual(booking.purpose, 'my_purpose') - self.assertEqual(booking.project, 'my_project') - delta = booking.end - booking.start - delta -= datetime.timedelta(days=3) - self.assertLess(delta, datetime.timedelta(minutes=1)) - - resource_bundle = booking.resource - - host = resource_bundle.get_resources()[0] - self.assertEqual(host.profile, self.res_profile) - self.assertEqual(host.name, 'my_host') - - def test_with_too_long_length(self): - response = self.post({'length': '22'}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_negative_length(self): - response = self.post({'length': '-1'}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_invalid_installer(self): - response = self.post({'installer': str(self.installer.id + 100)}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_invalid_scenario(self): - response = self.post({'scenario': str(self.scenario.id + 100)}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_invalid_host_id(self): - response = self.post({'filter_field': json.dumps({ - "resource": { - "resource_" + str(self.res_profile.id + 100): { - "selected": True, - "id": self.res_profile.id + 100 - } - }, - "lab": { - "lab_" + str(self.lab.lab_user.id): { - "selected": True, - "id": self.lab.lab_user.id - } - } - })}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_invalid_lab_id(self): - response = self.post({'filter_field': json.dumps({ - "resource": { - "resource_" + str(self.res_profile.id): { - "selected": True, - "id": self.res_profile.id - } - }, - "lab": { - "lab_" + str(self.lab.lab_user.id + 100): { - "selected": True, - "id": self.lab.lab_user.id + 100 - } - } - })}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_invalid_empty_filter_field(self): - response = self.post({'filter_field': ''}) - - self.assertEqual(response.status_code, 200) - self.assertIsNone(Booking.objects.first()) - - def test_with_garbage_users_field(self): # expected behavior: treat as though field is empty if it has garbage data - response = self.post({'users': ['X�]QP�槰DP�+m���h�U�_�yJA:.rDi��QN|.��C��n�P��F!��D�����5ȅj�9�LV��']}) # output from /dev/urandom - - self.assertEqual(response.status_code, 200) - booking = Booking.objects.first() - self.assertIsNone(booking) - - def test_with_valid_form(self): - response = self.post() - - self.assertEqual(response.status_code, 302) # success should redirect - booking = Booking.objects.first() - self.assertIsNotNone(booking) - self.assertValidBooking(booking) diff --git a/src/booking/tests/test_stats.py b/src/booking/tests/test_stats.py deleted file mode 100644 index 5501355..0000000 --- a/src/booking/tests/test_stats.py +++ /dev/null @@ -1,59 +0,0 @@ -############################################################################# -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, Sean Smith, 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 pytz -from datetime import timedelta, datetime - -from django.test import TestCase - -from booking.models import Booking -from booking.stats import StatisticsManager as sm -from dashboard.testing_utils import make_user - - -class StatsTestCases(TestCase): - - def test_no_booking_outside_span(self): - now = datetime.now(pytz.utc) - - bad_date = now + timedelta(days=1200) - Booking.objects.create(start=now, end=bad_date, owner=make_user(username='jj')) - - actual = sm.getContinuousBookingTimeSeries() - dates = actual['booking'][0] - - for date in dates: - self.assertNotEqual(date, bad_date) - - def check_booking_and_user_counts(self): - now = datetime.now(pytz.utc) - - for i in range(20): - Booking.objects.create( - start=now, - end=now + timedelta(weeks=3), - owner=make_user(username='a')) - - for i in range(30): - Booking.objects.create( - start=now + timedelta(days=5), - end=now + timedelta(weeks=3, days=5), - owner=make_user(username='a')) - - for i in range(120): - Booking.objects.create( - start=now + timedelta(weeks=1), - end=now + timedelta(weeks=4), - owner=make_user(username='a')) - - dates = [[now, 20], [now + timedelta(days=5), 30], [now + timedelta(weeks=1), 120]] - actual = sm.getContinuousBookingTimeSeries() - - for date in dates: - self.assertEqual(date[1], actual['booking'][date[0]]) - self.assertEqual(date[1], actual['booking'][date[1]]) diff --git a/src/booking/urls.py b/src/booking/urls.py deleted file mode 100644 index 0b60351..0000000 --- a/src/booking/urls.py +++ /dev/null @@ -1,53 +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 -############################################################################## - - -""" -laas_dashboard URL Configuration. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url - -from booking.views import ( - booking_detail_view, - BookingDeleteView, - bookingDelete, - BookingListView, - booking_stats_view, - booking_stats_json, - quick_create, - booking_modify_image -) - -app_name = 'booking' -urlpatterns = [ - url(r'^detail/(?P[0-9]+)/$', booking_detail_view, name='detail'), - url(r'^(?P[0-9]+)/$', booking_detail_view, name='booking_detail'), - url(r'^delete/$', BookingDeleteView.as_view(), name='delete_prefix'), - url(r'^delete/(?P[0-9]+)/$', BookingDeleteView.as_view(), name='delete'), - url(r'^delete/(?P[0-9]+)/confirm/$', bookingDelete, name='delete_booking'), - url(r'^modify/(?P[0-9]+)/image/$', booking_modify_image, name='modify_booking_image'), - url(r'^list/$', BookingListView.as_view(), name='list'), - url(r'^stats/$', booking_stats_view, name='stats'), - url(r'^stats/json$', booking_stats_json, name='stats_json'), - url(r'^quick/$', quick_create, name='quick_create'), -] diff --git a/src/booking/views.py b/src/booking/views.py deleted file mode 100644 index 367a18d..0000000 --- a/src/booking/views.py +++ /dev/null @@ -1,211 +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 -############################################################################## - -from django.contrib import messages -from django.shortcuts import get_object_or_404 -from django.http import JsonResponse, HttpResponse -from django.utils import timezone -from django.views import View -from django.views.generic import TemplateView -from django.shortcuts import redirect, render -from django.db.models import Q -from django.urls import reverse - -from resource_inventory.models import ResourceBundle, ResourceProfile, Image, ResourceQuery -from account.models import Downtime, Lab -from booking.models import Booking -from booking.stats import StatisticsManager -from booking.forms import HostReImageForm -from workflow.forms import FormUtils -from api.models import JobFactory, GeneratedCloudConfig -from workflow.views import login -from booking.forms import QuickBookingForm -from booking.quick_deployer import create_from_form, drop_filter -import traceback - - -def quick_create_clear_fields(request): - request.session['quick_create_forminfo'] = None - - -def quick_create(request): - if not request.user.is_authenticated: - return login(request) - - if request.method == 'GET': - context = {} - attrs = FormUtils.getLabData(user=request.user) - context['form'] = QuickBookingForm(lab_data=attrs, default_user=request.user.username, user=request.user) - context['lab_profile_map'] = {} - context.update(drop_filter(request.user)) - context['contact_email'] = Lab.objects.filter(name="UNH_IOL").first().contact_email - return render(request, 'booking/quick_deploy.html', context) - - if request.method == 'POST': - attrs = FormUtils.getLabData(user=request.user) - form = QuickBookingForm(request.POST, lab_data=attrs, user=request.user) - - context = {} - context['lab_profile_map'] = {} - context['form'] = form - - if form.is_valid(): - try: - booking = create_from_form(form, request) - messages.success(request, "We've processed your request. " - "Check Account->My Bookings for the status of your new booking") - return redirect(reverse('booking:booking_detail', kwargs={'booking_id': booking.id})) - except Exception as e: - print("Error occurred while handling quick deployment:") - traceback.print_exc() - print(str(e)) - messages.error(request, "Whoops, an error occurred: " + str(e)) - context.update(drop_filter(request.user)) - return render(request, 'booking/quick_deploy.html', context) - else: - messages.error(request, "Looks like the form didn't validate. Check that you entered everything correctly") - context['status'] = 'false' - context.update(drop_filter(request.user)) - return render(request, 'booking/quick_deploy.html', context) - - -class BookingView(TemplateView): - template_name = "booking/booking_detail.html" - - def get_context_data(self, **kwargs): - booking = get_object_or_404(Booking, id=self.kwargs['booking_id']) - title = 'Booking Details' - contact = Lab.objects.filter(name="UNH_IOL").first().contact_email - downtime = Downtime.objects.filter(lab=booking.lab, start__lt=timezone.now, end__gt=timezone.now()).first() - context = super(BookingView, self).get_context_data(**kwargs) - context.update({ - 'title': title, - 'booking': booking, - 'downtime': downtime, - 'contact_email': contact - }) - return context - - -class BookingDeleteView(TemplateView): - template_name = "booking/booking_delete.html" - - def get_context_data(self, **kwargs): - booking = get_object_or_404(Booking, id=self.kwargs['booking_id']) - title = 'Delete Booking' - context = super(BookingDeleteView, self).get_context_data(**kwargs) - context.update({'title': title, 'booking': booking}) - return context - - -def bookingDelete(request, booking_id): - booking = get_object_or_404(Booking, id=booking_id) - booking.delete() - messages.add_message(request, messages.SUCCESS, 'Booking deleted') - return redirect('../../../../') - - -class BookingListView(TemplateView): - template_name = "booking/booking_list.html" - - def get_context_data(self, **kwargs): - bookings = Booking.objects.filter(end__gte=timezone.now()) - title = 'Search Booking' - context = super(BookingListView, self).get_context_data(**kwargs) - context.update({'title': title, 'bookings': bookings}) - return context - - -class ResourceBookingsJSON(View): - def get(self, request, *args, **kwargs): - resource = get_object_or_404(ResourceBundle, id=self.kwargs['resource_id']) - bookings = resource.booking_set.get_queryset().values( - 'id', - 'start', - 'end', - 'purpose', - 'config_bundle__name' - ) - return JsonResponse({'bookings': list(bookings)}) - - -def build_image_mapping(lab, user): - mapping = {} - for profile in ResourceProfile.objects.filter(labs=lab): - images = Image.objects.filter( - from_lab=lab, - architecture=profile.architecture - ).filter( - Q(public=True) | Q(owner=user) - ) - mapping[profile.name] = [{"name": image.name, "value": image.id} for image in images] - return mapping - - -def booking_detail_view(request, booking_id): - user = None - if request.user.is_authenticated: - user = request.user - else: - return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - - booking = get_object_or_404(Booking, id=booking_id) - allowed_users = set(list(booking.collaborators.all())) - allowed_users.add(booking.owner) - if user not in allowed_users: - return render(request, "dashboard/login.html", {'title': 'This page is private'}) - - context = { - 'title': 'Booking Details', - 'booking': booking, - 'pdf': booking.pdf, - 'user_id': user.id, - 'image_mapping': build_image_mapping(booking.lab, user), - 'posix_username': GeneratedCloudConfig._normalize_username(None, user.username) - } - - return render( - request, - "booking/booking_detail.html", - context - ) - - -def booking_modify_image(request, booking_id): - form = HostReImageForm(request.POST) - if form.is_valid(): - booking = Booking.objects.get(id=booking_id) - if request.user != booking.owner: - return HttpResponse("unauthorized") - if timezone.now() > booking.end: - return HttpResponse("unauthorized") - new_image = Image.objects.get(id=form.cleaned_data['image_id']) - host = ResourceQuery.get(id=form.cleaned_data['host_id']) - host.config.image = new_image - host.config.save() - JobFactory.reimageHost(new_image, booking, host) - return HttpResponse(new_image.name) - return HttpResponse("error") - - -def booking_stats_view(request): - return render( - request, - "booking/stats.html", - context={"data": StatisticsManager.getContinuousBookingTimeSeries(), "title": ""} - ) - - -def booking_stats_json(request): - try: - span = int(request.GET.get("days", 14)) - except Exception: - span = 14 - return JsonResponse(StatisticsManager.getContinuousBookingTimeSeries(span), safe=False) diff --git a/src/dashboard/__init__.py b/src/dashboard/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/dashboard/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## diff --git a/src/dashboard/admin.py b/src/dashboard/admin.py deleted file mode 100644 index bd4d96c..0000000 --- a/src/dashboard/admin.py +++ /dev/null @@ -1,16 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 -############################################################################## - - -from django.contrib import admin - - -admin.site.site_header = "Laas Dashboard Administration" -admin.site.site_title = "Laas Dashboard" diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py deleted file mode 100644 index 75e4f3e..0000000 --- a/src/dashboard/admin_utils.py +++ /dev/null @@ -1,811 +0,0 @@ -############################################################################## -# Copyright (c) 2021 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 -############################################################################## - -from resource_inventory.models import ( - ResourceTemplate, - Image, - Server, - ResourceBundle, - ResourceProfile, - InterfaceProfile, - PhysicalNetwork, - ResourceConfiguration, - NetworkConnection, - InterfaceConfiguration, - Network, - DiskProfile, - CpuProfile, - RamProfile, - Interface, - CloudInitFile, -) - -import json -import yaml -import sys -import inspect -import pydoc -import csv - -from django.contrib.auth.models import User - -from account.models import ( - Lab, - PublicNetwork -) - -from resource_inventory.resource_manager import ResourceManager -from resource_inventory.pdf_templater import PDFTemplater - -from booking.quick_deployer import update_template - -from datetime import timedelta, date, datetime, timezone - -from booking.models import Booking -from notifier.manager import NotificationHandler -from api.models import JobFactory - -from api.models import JobStatus, Job, GeneratedCloudConfig - - -def print_div(): - """ - Utility function for printing dividers, does nothing directly useful as a utility - """ - print("=" * 68) - - -def book_host(owner_username, host_labid, lab_username, hostname, image_id, template_name, length_days=21, collaborator_usernames=[], purpose="internal", project="LaaS"): - """ - creates a quick booking using the given host - - @owner_username is the simple username for the user who will own the resulting booking. - Do not set this to a lab username! - - @image_id is the django id of the image in question, NOT the labid of the image. - Query Image objects by their public status and compatible host types - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username for iol is `unh_iol`, other labs will be documented here - - @hostname the hostname that the resulting host should have set - - @template_name the name of the (public, or user accessible) template to use for this booking - - @length_days how long the booking should be, no hard limit currently - - @collaborator_usernames a list of usernames for collaborators to the booking - - @purpose what this booking will be used for - - @project what project/group this booking is on behalf of or the owner represents - """ - lab = Lab.objects.get(lab_user__username=lab_username) - host = Server.objects.filter(lab=lab).get(labid=host_labid) - if host.booked: - print("Can't book host, already marked as booked") - return - else: - host.booked = True - host.save() - - template = ResourceTemplate.objects.filter(public=True).get(name=template_name) - image = Image.objects.get(id=image_id) - - owner = User.objects.get(username=owner_username) - - new_template = update_template(template, image, hostname, owner) - - rmanager = ResourceManager.getInstance() - - vlan_map = rmanager.get_vlans(new_template) - - # only a single host so can reuse var for iter here - resource_bundle = ResourceBundle.objects.create(template=new_template) - res_configs = new_template.getConfigs() - - for config in res_configs: - try: - host.bundle = resource_bundle - host.config = config - rmanager.configureNetworking(resource_bundle, host, vlan_map) - host.save() - except Exception: - host.booked = False - host.save() - print("Failed to book host due to error configuring it") - return - - new_template.save() - - booking = Booking.objects.create( - purpose=purpose, - project=project, - lab=lab, - owner=owner, - start=timezone.now(), - end=timezone.now() + timedelta(days=int(length_days)), - resource=resource_bundle, - opnfv_config=None - ) - - booking.pdf = PDFTemplater.makePDF(booking) - - booking.save() - - for collaborator_username in collaborator_usernames: - try: - user = User.objects.get(username=collaborator_username) - booking.collaborators.add(user) - except Exception: - print("couldn't add user with username ", collaborator_username) - - booking.save() - - JobFactory.makeCompleteJob(booking) - NotificationHandler.notify_new_booking(booking) - - -def mark_working(host_labid, lab_username, working=True): - """ - Mark a host working/not working so that it is either bookable or hidden in the dashboard. - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username: param of the form `unh_iol` or similar - - @working: bool, whether by the end of execution the host should be considered working or not working - """ - - lab = Lab.objects.get(lab_user__username=lab_username) - server = Server.objects.filter(lab=lab).get(labid=host_labid) - print("changing server working status from ", server.working, "to", working) - server.working = working - server.save() - - -def mark_booked(host_labid, lab_username, booked=True): - """ - Mark a host as booked/unbooked - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username: param of the form `unh_iol` or similar - - @working: bool, whether by the end of execution the host should be considered booked or not booked - """ - - lab = Lab.objects.get(lab_user__username=lab_username) - server = Server.objects.filter(lab=lab).get(labid=host_labid) - print("changing server booked status from ", server.booked, "to", booked) - server.booked = booked - server.save() - - -def get_host(host_labid, lab_username): - """ - Returns host filtered by lab and then unique id within lab - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username: param of the form `unh_iol` or similar - """ - lab = Lab.objects.get(lab_user__username=lab_username) - return Server.objects.filter(lab=lab).get(labid=host_labid) - - -def get_info(host_labid, lab_username): - """ - Returns various information on the host queried by the given parameters - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username: param of the form `unh_iol` or similar - """ - info = {} - host = get_host(host_labid, lab_username) - info['host_labid'] = host_labid - info['booked'] = host.booked - info['working'] = host.working - info['profile'] = str(host.profile) - if host.bundle: - binfo = {} - info['bundle'] = binfo - if host.config: - cinfo = {} - info['config'] = cinfo - - return info - - -class CumulativeData: - use_days = 0 - count_bookings = 0 - count_extensions = 0 - - def __init__(self, file_writer): - self.file_writer = file_writer - - def account(self, booking, usage_days): - self.count_bookings += 1 - self.count_extensions += booking.ext_count - self.use_days += usage_days - - def write_cumulative(self): - self.file_writer.writerow([]) - self.file_writer.writerow([]) - self.file_writer.writerow(['Lab Use Days', 'Count of Bookings', 'Total Extensions Used']) - self.file_writer.writerow([self.use_days, self.count_bookings, (self.count_bookings * 2) - self.count_extensions]) - - -def get_years_booking_data(start_year=None, end_year=None): - """ - Outputs yearly booking information from the past 'start_year' years (default: current year) - until the last day of the end year (default current year) as a csv file. - """ - if start_year is None and end_year is None: - start = datetime.combine(date(datetime.now().year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - end = datetime.combine(date(start.year + 1, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - elif end_year is None: - start = datetime.combine(date(start_year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - end = datetime.combine(date(datetime.now().year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - else: - start = datetime.combine(date(start_year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - end = datetime.combine(date(end_year + 1, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc) - - if (start.year == end.year - 1): - file_name = "yearly_booking_data_" + str(start.year) + ".csv" - else: - file_name = "yearly_booking_data_" + str(start.year) + "-" + str(end.year - 1) + ".csv" - - with open(file_name, "w", newline="") as file: - file_writer = csv.writer(file) - cumulative_data = CumulativeData(file_writer) - file_writer.writerow( - [ - 'ID', - 'Project', - 'Purpose', - 'User', - 'Collaborators', - 'Extensions Left', - 'Usage Days', - 'Start', - 'End' - ] - ) - - for booking in Booking.objects.filter(start__gte=start, start__lte=end): - filtered = False - booking_filter = [279] - user_filter = ["ParkerBerberian", "ssmith", "ahassick", "sbergeron", "jhodgdon", "rhodgdon", "aburch", "jspewock"] - user = booking.owner.username if booking.owner.username is not None else "None" - - for b in booking_filter: - if b == booking.id: - filtered = True - - for u in user_filter: - if u == user: - filtered = True - # trims time delta to the the specified year(s) if between years - usage_days = ((end if booking.end > end else booking.end) - (start if booking.start < start else booking.start)).days - collaborators = [] - - for c in booking.collaborators.all(): - collaborators.append(c.username) - - if (not filtered): - cumulative_data.account(booking, usage_days) - file_writer.writerow([ - str(booking.id), - str(booking.project), - str(booking.purpose), - str(booking.owner.username), - ','.join(collaborators), - str(booking.ext_count), - str(usage_days), - str(booking.start), - str(booking.end) - ]) - cumulative_data.write_cumulative() - - -def map_cntt_interfaces(labid: str): - """ - Use this during cntt migrations, call it with a host labid and it will change profiles for this host - as well as mapping its interfaces across. interface ens1f2 should have the mac address of interface eno50 - as an invariant before calling this function - """ - host = get_host(labid, "unh_iol") - host.profile = ResourceProfile.objects.get(name="HPE x86 CNTT") - host.save() - host = get_host(labid, "unh_iol") - - for iface in host.interfaces.all(): - new_ifprofile = None - if iface.profile.name == "ens1f2": - new_ifprofile = InterfaceProfile.objects.get(host=host.profile, name="eno50") - else: - new_ifprofile = InterfaceProfile.objects.get(host=host.profile, name=iface.profile.name) - - iface.profile = new_ifprofile - - iface.save() - - -def detect_leaked_hosts(labid="unh_iol"): - """ - Use this to try to detect leaked hosts. - These hosts may still be in the process of unprovisioning, - but if they are not (or unprovisioning is frozen) then - these hosts are instead leaked - """ - working_servers = Server.objects.filter(working=True, lab__lab_user__username=labid) - booked = working_servers.filter(booked=True) - filtered = booked - print_div() - print("In use now:") - for booking in Booking.objects.filter(end__gte=timezone.now()): - res_for_booking = booking.resource.get_resources() - print(res_for_booking) - for resource in res_for_booking: - filtered = filtered.exclude(id=resource.id) - print_div() - print("Possibly leaked:") - for host in filtered: - print(host) - print_div() - return filtered - - -def booking_for_host(host_labid: str, lab_username="unh_iol"): - """ - Returns the booking that this server is a part of, if any. - Fails with an exception if no such booking exists - - @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object - - @lab_username: param of the form `unh_iol` or similar - """ - server = Server.objects.get(lab__lab_user__username=lab_username, labid=host_labid) - booking = server.bundle.booking_set.first() - print_div() - print(booking) - print("id:", booking.id) - print("owner:", booking.owner) - print("job (id):", booking.job, "(" + str(booking.job.id) + ")") - print_div() - return booking - - -def force_release_booking(booking_id: int): - """ - Takes a booking id and forces the booking to end whether or not the tasks have - completed normally. - - Use with caution! Hosts may or may not be released depending on other underlying issues - - @booking_id: the id of the Booking object to be released - """ - booking = Booking.objects.get(id=booking_id) - job = booking.job - tasks = job.get_tasklist() - for task in tasks: - task.status = JobStatus.DONE - task.save() - - -def free_leaked_public_vlans(safety_buffer_days=2): - for lab in Lab.objects.all(): - current_booking_set = Booking.objects.filter(end__gte=timezone.now() + timedelta(days=safety_buffer_days)) - - marked_nets = set() - - for booking in current_booking_set: - for network in get_network_metadata(booking.id): - marked_nets.add(network["vlan_id"]) - - for net in PublicNetwork.objects.filter(lab=lab).filter(in_use=True): - if net.vlan not in marked_nets: - lab.vlan_manager.release_public_vlan(net.vlan) - - -def get_network_metadata(booking_id: int): - """ - Takes a booking id and prints all (known) networks that are owned by it. - Returns an object of the form {: {"vlan_id": int, "netname": str , "public": bool BEFORE THIS - @filenames: array of host import file names to import - """ - - for filename in filenames: - - # open import file - file = open("dashboard/" + filename + "-import.yaml", "r") - data = yaml.safe_load(file) - - # if a new profile is needed create one and a matching template - if (data["new_profile"]): - add_profile(data) - print("Profile: " + data["name"] + " created!") - make_default_template( - ResourceProfile.objects.get(name=data["name"]), - Image.objects.get(lab_id=data["image"]).id, - None, - None, - False, - False, - data["owner"], - "unh_iol", - True, - False, - data["temp_desc"] - ) - - print(" Template: " + data["temp_name"] + " created!") - - # add the server - add_server( - ResourceProfile.objects.get(name=data["name"]), - data["hostname"], - data["interfaces"], - data["lab"], - data["vendor"], - data["model"] - ) - - print(data["hostname"] + " imported!") - - -def convert_inspect_results(files): - """ - Converts an array of inspection result files into templates (filename-import.yaml) to be filled out for importing the servers into the dashboard - @files an array of file names (not including the file type. i.e hpe44). Default: [] - """ - for filename in files: - # open host inspect file - file = open("dashboard/" + filename + ".yaml") - output = open("dashboard/" + filename + "-import.yaml", "w") - data = json.load(file) - - # gather data about disks - disk_data = {} - for i in data["disk"]: - - # don't include loops in disks - if "loop" not in i: - disk_data[i["name"]] = { - "capacity": i["size"][:-3], - "media_type": "<\"SSD\" or \"HDD\">", - "interface": "<\"sata\", \"sas\", \"ssd\", \"nvme\", \"scsi\", or \"iscsi\">", - } - - # gather interface data - interface_data = {} - for i in data["interfaces"]: - interface_data[data["interfaces"][i]["name"]] = { - "speed": data["interfaces"][i]["speed"], - "nic_type": "<\"onboard\" or \"pcie\">", - "order": "", - "mac_address": data["interfaces"][i]["mac"], - "bus_addr": data["interfaces"][i]["busaddr"], - } - - # gather cpu data - cpu_data = { - "cores": data["cpu"]["cores"], - "architecture": data["cpu"]["arch"], - "cpus": data["cpu"]["cpus"], - "cflags": "", - } - - # gather ram data - ram_data = { - "amount": data["memory"][:-1], - "channels": "", - } - - # assemble data for host import file - import_data = { - "new_profile": " (Set to True to create a new profile for the host's type)", - "name": " (Used to set the profile of a host and for creating a new profile)", - "description": "", - "labs": "", - "temp_name": "