summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorckonstanski <kostcarl@isu.edu>2026-07-30 18:17:14 -0600
committerckonstanski <kostcarl@isu.edu>2026-07-30 18:17:14 -0600
commit640ff61422bc0ee3966941b93d9889ddbbd38d77 (patch)
treeabf1c08f5d38ee53ec8b29dc4f425722517505e6
parent22dae02a86c1fce71091bfa5289cf99abba1b217 (diff)
more filesHEADmaster
-rw-r--r--.gitignore14
-rw-r--r--Dockerfile7
-rw-r--r--README.md31
-rw-r--r--doc/markdown/.gitignore1
-rw-r--r--doc/markdown/automation-stack-architecture.md767
-rw-r--r--doc/markdown/subcloud-install-remediation.md253
-rw-r--r--doc/swagger/restapi-swagger.yaml93
-rw-r--r--doc/tex/break-fix/.gitignore9
-rwxr-xr-xdoc/tex/break-fix/compile.sh6
-rw-r--r--doc/tex/break-fix/slides.tex82
-rw-r--r--doc/uml/.gitignore6
-rw-r--r--doc/uml/actor.pngbin0 -> 308 bytes
-rw-r--r--doc/uml/computer.pngbin0 -> 3147 bytes
-rw-r--r--doc/uml/drive-removable-media.pngbin0 -> 1934 bytes
-rw-r--r--doc/uml/hardware_install_activity_diagram.dot209
-rw-r--r--doc/uml/middleware-stack-mtce.dot200
-rw-r--r--doc/uml/middleware-stack-prod-birmingham.dot200
-rw-r--r--doc/uml/network-wired.pngbin0 -> 5783 bytes
-rw-r--r--doc/uml/security_use_case_diagram.dot153
-rw-r--r--doc/uml/security_use_case_diagram.pdfbin0 -> 32843 bytes
-rw-r--r--doc/uml/use_case_diagram.dot72
-rwxr-xr-xfix-virtualenv.sh24
-rw-r--r--logcollection-requirements.txt2
-rw-r--r--requirements.txt18
-rw-r--r--sql/caas/adhoc/migrate-mgmt-network-fields.sql40
-rw-r--r--sql/caas/adhoc/mtce-nominal.sql72
-rw-r--r--sql/caas/adhoc/wclab-dracut.sql159
-rw-r--r--sql/caas/adhoc/wclab-nominal.sql158
-rw-r--r--sql/schema/create-database.sql5
-rw-r--r--sql/schema/faredge.sql.example27
-rw-r--r--sql/schema/orchestration.testdata4
-rw-r--r--sql/schema/orchestration_test.sql27
-rw-r--r--src/automationstatus/README.md26
-rw-r--r--src/automationstatus/__init__.py0
-rw-r--r--src/automationstatus/admin.py3
-rw-r--r--src/automationstatus/apps.py5
-rw-r--r--src/automationstatus/crons.py151
-rw-r--r--src/automationstatus/migrations/0001_initial.py25
-rw-r--r--src/automationstatus/migrations/0002_auto_20200908_1720.py23
-rw-r--r--src/automationstatus/migrations/0003_deploymentworkflow_summary.py39
-rw-r--r--src/automationstatus/migrations/0004_auto_20201020_0232.py20
-rw-r--r--src/automationstatus/migrations/0005_auto_20201020_0253.py18
-rw-r--r--src/automationstatus/migrations/0006_auto_20201020_1416.py18
-rw-r--r--src/automationstatus/migrations/0007_auto_20201026_0129.py33
-rw-r--r--src/automationstatus/migrations/0008_auto_20201030_1654.py48
-rw-r--r--src/automationstatus/migrations/0009_orchestrationworkflow.py28
-rw-r--r--src/automationstatus/migrations/__init__.py0
-rw-r--r--src/automationstatus/models.py67
-rw-r--r--src/automationstatus/serializers.py9
-rw-r--r--src/automationstatus/tests.py3
-rw-r--r--src/automationstatus/urls.py14
-rw-r--r--src/automationstatus/views.py19
-rw-r--r--src/caas/forms.py110
-rw-r--r--src/caas/migrations/0001_initial.py100
-rw-r--r--src/caas/migrations/0002_auto_20200706_2113.py260
-rw-r--r--src/caas/migrations/0003_auto_20200706_2113.py35
-rw-r--r--src/caas/migrations/0004_delete_wrbatch.py16
-rw-r--r--src/caas/migrations/0005_auto_20200710_2235.py20
-rw-r--r--src/caas/migrations/0006_wrbatch.py43
-rw-r--r--src/caas/migrations/0007_auto_20200711_1126.py20
-rw-r--r--src/caas/migrations/0008_auto_20200711_1127.py20
-rw-r--r--src/caas/migrations/0009_auto_20200715_1630.py18
-rw-r--r--src/caas/migrations/0010_auto_20200720_1413.py20
-rw-r--r--src/caas/migrations/0011_auto_20200720_1525.py20
-rw-r--r--src/caas/migrations/0012_auto_20200721_1540.py20
-rw-r--r--src/caas/migrations/0013_auto_20200728_2359.py31
-rw-r--r--src/caas/migrations/0014_auto_20200803_1931.py20
-rw-r--r--src/caas/migrations/0015_auto_20200804_1404.py20
-rw-r--r--src/caas/migrations/0016_auto_20200804_1559.py20
-rw-r--r--src/caas/migrations/0017_cluster_is_central_controller.py18
-rw-r--r--src/caas/migrations/0018_auto_20200925_1855.py30
-rw-r--r--src/caas/migrations/0019_cluster_is_maint_window.py18
-rw-r--r--src/caas/migrations/0020_auto_20201001_1142.py20
-rw-r--r--src/caas/migrations/0021_auto_20201001_1232.py22
-rw-r--r--src/caas/migrations/0022_auto_20201001_1232.py20
-rw-r--r--src/caas/migrations/0023_auto_20201019_1631.py20
-rw-r--r--src/caas/migrations/0024_auto_20201023_1556.py20
-rw-r--r--src/caas/migrations/0025_auto_20201102_1545.py20
-rw-r--r--src/caas/migrations/0026_auto_20201110_0932.py29
-rw-r--r--src/caas/migrations/0027_auto_20201211_2103.py23
-rw-r--r--src/caas/migrations/0028_auto_20201212_1001.py20
-rw-r--r--src/caas/migrations/0029_auto_20201215_0832.py21
-rw-r--r--src/caas/migrations/0030_auto_20201215_0835.py20
-rw-r--r--src/caas/migrations/0031_auto_20201215_1430.py20
-rw-r--r--src/caas/models.py433
-rw-r--r--src/caas/services/ansibleservice.py52
-rw-r--r--src/caas/services/bmcservice.py50
-rw-r--r--src/caas/services/ciqservice.py193
-rw-r--r--src/caas/services/dnsservice.py60
-rw-r--r--src/caas/services/firmwareservice.py87
-rw-r--r--src/caas/services/helpers.py136
-rw-r--r--src/caas/services/httpclient.py18
-rw-r--r--src/caas/services/icingapollservice.py47
-rw-r--r--src/caas/services/icingaservice.py46
-rw-r--r--src/caas/services/jinja/bmc_host_vars.j213
-rw-r--r--src/caas/services/jinja/bmc_inventory.j26
-rw-r--r--src/caas/services/jinja/dns_host_vars.j212
-rw-r--r--src/caas/services/jinja/dns_inventory.j26
-rw-r--r--src/caas/services/jinja/firmware_host_vars.j213
-rw-r--r--src/caas/services/jinja/firmware_inventory.j27
-rw-r--r--src/caas/services/jinja/icinga_host_vars.j238
-rw-r--r--src/caas/services/jinja/icinga_inventory.j26
-rw-r--r--src/caas/services/jinja/icinga_poll_host_vars.j211
-rw-r--r--src/caas/services/jinja/icinga_poll_inventory.j26
-rw-r--r--src/caas/services/jinja/wr_host_vars.j274
-rw-r--r--src/caas/services/jinja/wr_inventory.j211
-rw-r--r--src/caas/services/macaddressservice.py38
-rw-r--r--src/caas/services/nicfirmwareupgradenowservice.py68
-rw-r--r--src/caas/services/nicfirmwareversionservice.py37
-rw-r--r--src/caas/services/passwordservice.py180
-rw-r--r--src/caas/services/patchservice.py106
-rw-r--r--src/caas/services/playbookreportservice.py147
-rw-r--r--src/caas/services/subcloudservice.py12
-rw-r--r--src/caas/services/wrinstallnowservice.py107
-rw-r--r--src/caas/services/wrservice.py75
-rw-r--r--src/caas/templates/adminpassword.xml12
-rw-r--r--src/caas/templates/autoremediate.xml12
-rw-r--r--src/caas/templates/ciq.xml13
-rw-r--r--src/caas/templates/ciq_success.xml4
-rw-r--r--src/caas/templates/dns.xml12
-rw-r--r--src/caas/templates/macaddress.xml12
-rw-r--r--src/caas/templates/mockwrremediate.xml12
-rw-r--r--src/caas/templates/nicfirmwareupgrade.xml12
-rw-r--r--src/caas/templates/nicfirmwareupgradenow.xml12
-rw-r--r--src/caas/templates/nicfirmwareversion.xml12
-rw-r--r--src/caas/templates/passwords.xml13
-rw-r--r--src/caas/templates/passwords_success.xml4
-rw-r--r--src/caas/templates/playbookreport.xml13
-rw-r--r--src/caas/templates/pushbutton.xml12
-rw-r--r--src/caas/templates/queuecustom.xml12
-rw-r--r--src/caas/templates/serialnumber.xml12
-rw-r--r--src/caas/templates/site.xml26
-rw-r--r--src/caas/templates/subcloud.xml58
-rw-r--r--src/caas/templates/wrinstall.xml12
-rw-r--r--src/caas/templates/wrinstallnow.xml12
-rw-r--r--src/caas/templates/wrremediate.xml12
-rw-r--r--src/caas/urls.py28
-rw-r--r--src/caas/views.py713
-rw-r--r--src/dashboard/README.md25
-rw-r--r--src/dashboard/__init__.py0
-rw-r--r--src/dashboard/apps.py6
-rw-r--r--src/dashboard/documentation/images/dashboard_layout.jpgbin0 -> 577041 bytes
-rw-r--r--src/dashboard/services/caasqueryservice.py31
-rw-r--r--src/dashboard/static/css/styles.css5242
-rw-r--r--src/dashboard/static/js/dashboard.js76
-rw-r--r--src/dashboard/static/js/firmware_version.js60
-rw-r--r--src/dashboard/static/js/orchestration_workflow.js55
-rw-r--r--src/dashboard/static/js/playbook_status.js75
-rw-r--r--src/dashboard/static/js/utils.js14
-rw-r--r--src/dashboard/static/js/workflow_status.js55
-rw-r--r--src/dashboard/templates/by_cluster_name.html50
-rw-r--r--src/dashboard/templates/by_playbook_name.html50
-rw-r--r--src/dashboard/templates/cards/clusters.html38
-rw-r--r--src/dashboard/templates/cards/clusters_by_name.html26
-rw-r--r--src/dashboard/templates/cards/firmware_version.html32
-rw-r--r--src/dashboard/templates/cards/map.html16
-rw-r--r--src/dashboard/templates/cards/playbook.html33
-rw-r--r--src/dashboard/templates/cards/playbook_by_name.html26
-rw-r--r--src/dashboard/templates/cards/summary.html315
-rw-r--r--src/dashboard/templates/dashboard.html7
-rw-r--r--src/dashboard/templates/deployment.html7
-rw-r--r--src/dashboard/templates/hardware.html7
-rw-r--r--src/dashboard/templates/includes/base.html35
-rw-r--r--src/dashboard/templates/includes/main-panel.html17
-rw-r--r--src/dashboard/templates/includes/sidebar.html26
-rw-r--r--src/dashboard/templates/includes/top_navbar.html13
-rw-r--r--src/dashboard/urls.py11
-rw-r--r--src/dashboard/views.py80
-rw-r--r--src/far_edge_ops_api/.gitignore2
-rw-r--r--src/far_edge_ops_api/settings.py.example213
-rw-r--r--src/far_edge_ops_api/urls.py7
-rw-r--r--src/far_edge_ops_api/wsgi.py22
-rw-r--r--src/orchestration/cluster-setup-files/application-ldap.yaml69
-rw-r--r--src/orchestration/cluster-setup-files/application-sa.yaml79
-rw-r--r--src/orchestration/cluster-setup-files/crd-role.yaml18
-rw-r--r--src/orchestration/cluster-setup-files/helm.yaml75
-rw-r--r--src/orchestration/cluster-setup-files/orchestration-ldap.yaml166
-rw-r--r--src/orchestration/cluster-setup-files/orchestration-sa.yaml181
-rw-r--r--src/orchestration/cluster-setup-files/rbd.yaml9
-rw-r--r--src/orchestration/cluster-setup-files/samsung-sa.yaml55
-rw-r--r--src/orchestration/cluster-setup-files/tiller-sa.yaml42
-rw-r--r--src/orchestration/cluster-setup-files/vdu_tiller_sa.yaml29
-rw-r--r--src/orchestration/data.json4
-rw-r--r--src/orchestration/db_updater.py83
-rw-r--r--src/orchestration/imagesynchandler.py622
-rw-r--r--src/orchestration/kubeconfighandler.py926
-rw-r--r--src/orchestration/migrations/0008_auto_20200514_1951.py19
-rw-r--r--src/orchestration/migrations/0008_imagesync_transaction_id.py18
-rw-r--r--src/orchestration/migrations/0009_auto_20200529_1929.py18
-rw-r--r--src/orchestration/migrations/0010_centralregiontoremoteregionmap.py21
-rw-r--r--src/orchestration/migrations/0011_auto_20200529_1944.py17
-rw-r--r--src/orchestration/migrations/0012_merge_20200702_0053.py14
-rw-r--r--src/orchestration/migrations/0013_imagesync_remoteregionsetup.py39
-rw-r--r--src/orchestration/migrations/0014_remoteregionsetup_transaction_id.py18
-rw-r--r--src/orchestration/migrations/0015_auto_20200707_2034.py23
-rw-r--r--src/orchestration/models.py11
-rw-r--r--src/orchestration/namespacehandler.py431
-rw-r--r--src/orchestration/remoteregionhandler.py375
-rw-r--r--src/orchestration/tests/cluster_status.json15
-rw-r--r--src/orchestration/tests/image_status.json22
-rw-r--r--src/orchestration/tests/kubeconfig.yaml17
-rw-r--r--src/orchestration/tests/namespace.json12
-rw-r--r--src/orchestration/urls.py12
-rw-r--r--src/orchestration/utils.py110
-rw-r--r--src/orchestration/vendorhandler.py308
-rw-r--r--src/orchestration/views.py572
-rw-r--r--src/orchestration/vmb_consumer.py88
-rw-r--r--src/orchestration/vmb_handler.py52
-rw-r--r--src/orchestration/vmb_messages.py174
-rw-r--r--src/orchestration/vmb_producer.py329
-rw-r--r--src/static/.gitignore3
-rw-r--r--steps.txt123
-rwxr-xr-xtests/paas-setup-check/audit-vmb-site-list.sh20
-rw-r--r--tests/paas-setup-check/paas-setup-check.py189
-rw-r--r--tests/paas-setup-check/send-vmb-messages.py73
-rw-r--r--tests/paas-setup-check/site_run_command.py130
-rw-r--r--tests/perf-tests/cluster-status.json14
-rw-r--r--tests/perf-tests/data-ss-prod.json4
-rwxr-xr-xtests/perf-tests/data-ss-test1.json4
-rw-r--r--tests/perf-tests/orchestration-tests.txt99
-rwxr-xr-xtests/perf-tests/test-caas-status.sh5
-rwxr-xr-xtests/perf-tests/test-conn-details.sh5
-rwxr-xr-xtests/perf-tests/test-image-upload.sh5
-rwxr-xr-xtools/audit-vmb.sh4
-rwxr-xr-xtools/check-images.sh12
-rw-r--r--tools/collect-logs.py524
-rw-r--r--tools/diff.py37
-rwxr-xr-xtools/find-image-status.sh23
-rw-r--r--tools/get-ss-kubeconfig.py92
229 files changed, 20418 insertions, 130 deletions
diff --git a/.gitignore b/.gitignore
index ac9782e..b44dc40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,17 @@ db.sqlite3
venv
*.pyc
__pyccache__
+caas.log
+orchestration.log*
+.idea
+
+# Mac files
+.DS_Store
+
+# VSCode files
+.vscode
+
+# Static Files
+/src/static/js/
+/src/static/css/
+/src/static/rest_framework/ \ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c4b1929
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM ubuntu:18.04
+RUN apt-get update
+RUN apt-get install -y python3.6 python3-pip python-dev libmysqlclient-dev apache2-dev git
+ADD . /src
+WORKDIR /src
+RUN pip3 install -r requirements.txt
+CMD python3.6 src/manage.py runserver
diff --git a/README.md b/README.md
index de98247..a759fac 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,28 @@
-# far_edge_ops_api
+# Installation
-This is a Python application using the Django web framework. It was
-written for a project at Verizon. It is here as a code sample for
-potential hiring managers.
+## Dependencies
+* Python 3.7 +
+* virtualenv
+* Install postgresql and add pg_config to PATH
+* Install Apache server (Alternatively, on a Mac, make sure you have xcode installed, which includes Apache server)
+
+## Install Python requirements
+Run the following commands in the root folder
+* Create a virtual environment. ```python3 -m venv virt_env```
+* Activate the virtual env ```source virt_env\bin\activate```
+* Install python requirements ```pip3 install -r requirements.txt```
+
+### Troubleshooting pip install errros on a Mac
+* Install python 3.7 if you don't already have it. Latest xcode upgrades to 3.9, which does not work.
+* psutil fails because of Mac architecture. Set env variable ```export ARCHFLAGS="-arch x86_64"```
+* Update requirements.txt to install ```psycopg2-binary``` instead of ```psycopg2```
+
+## Run Django migrations
+* ```cd src```
+* ```python3 manage.py migrate```
+
+## Run Django application
+* ```python3 manage.py runserver ```
+
+## Register cronjobs for generating Dashboard reports
+* Register - ```python3 manage.py crontab add```
diff --git a/doc/markdown/.gitignore b/doc/markdown/.gitignore
new file mode 100644
index 0000000..2d19fc7
--- /dev/null
+++ b/doc/markdown/.gitignore
@@ -0,0 +1 @@
+*.html
diff --git a/doc/markdown/automation-stack-architecture.md b/doc/markdown/automation-stack-architecture.md
new file mode 100644
index 0000000..2ae775c
--- /dev/null
+++ b/doc/markdown/automation-stack-architecture.md
@@ -0,0 +1,767 @@
+# Far-Edge Automation Stack Deployment Guide
+
+The Far-Edge automation stack consists of nine Ubuntu 18.04 hosts:
+
+- Two load balancers
+- Two middleware application hosts
+- Two ansible hosts
+- Three database hosts
+
+The provisioning of these hosts in openstack is outside the scope of
+this document. There will be a separate document to address this.
+
+There is an additional host in the production stack to handle LDAP
+dual-stack proxying to reach USWIN. This host does not live in the
+automation stack tenant space. It is a standalone VM managed by VCPe.
+
+## OpenStack tenant space
+
+In both production and MTCE the far-edge automation stack runs in an
+OpenStack tenant space. (Production runs in VCPe.) Creating the
+OpenStack objects (neutron networks, cinder volumes, instances) is
+beyond the scope of this document and will be documented separately.
+
+Secgroup rules must be created to allow network traffic. Use a script
+like the following to create the secgroup rules:
+
+ #!/bin/bash
+
+ private_network_cidr="2001:4888:a21:3102:245:29::/112"
+ public_network_cidrs="2001:4888::/32"
+
+ for name in default-grp; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for protocol in tcp udp; do
+ if [ "openstack security group show ${name} -f shell -c rules | grep -F 'ingress' | grep -F \"port_range_min='1'\" | grep -F \"port_range_max='65535'\" | grep -F \"${protocol}\" | grep -F 'IPv6'" == "" ]; then
+ openstack security group rule create --egress --protocol ${protocol} --src-ip ::/0 --dst-port 1:65535 --ethertype IPv6 ${name}
+ fi
+ done
+ for cidr in ${private_network_cidr} ${public_network_cidrs}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"icmp\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol icmp --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ for port in 22; do
+ for cidr in ${private_network_cidr} ${public_network_cidrs}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ for name in icinga; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for port in 5665; do
+ for cidr in ${private_network_cidr}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ for name in ldap; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for port in 636; do
+ for cidr in ${private_network_cidr}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ for name in web; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for port in 443 3000; do
+ for cidr in ${private_network_cidr} ${public_network_cidrs}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ for port in 80 3000 3080 3128:3130 8000; do
+ for cidr in ${private_network_cidr}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ for name in galera; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for port in 3306 4444 4567 9200 13306; do
+ for cidr in ${private_network_cidr}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ for name in zmq; do
+ openstack security group show ${name} || openstack security group create --description "${name}" ${name}
+ for port in 5555:5559; do
+ for cidr in ${private_network_cidr}; do
+ if [ "$(openstack security group show ${name} -f shell -c rules | grep -F \"port_range_min='${port}'\" | grep -F \"remote_ip_prefix='${cidr}'\")" == "" ]; then
+ openstack security group rule create --ingress --protocol tcp --dst-port ${port} --src-ip ${cidr} --ethertype IPv6 ${name}
+ fi
+ done
+ done
+ done
+
+ exit 0
+
+## Database
+
+```far_edge_ops_api``` and icinga2 require a database backend. We
+currently use postgresql. The goal is to migrate to
+galera/mariadb. For now we are using only one database host, but when
+we switch to galera we will use all three. The galera solution is
+dockerized. It is a mature and well-tested product of the VCP Metering
+project.
+
+### Installation
+
+Install postgresql on the first database host per the typical
+procedure that can be found on the internet. TODO: provide a link to
+an internet HOWTO that best fits our needs.
+
+#### postgresql.conf:
+
+ data_directory = '/var/lib/postgresql/12/data'
+ listen_addresses = '*'
+ port = 5432
+ max_connections = 2000
+ password_encryption = md5
+ ssl = off
+ shared_buffers = 2000MB
+ work_mem = 4MB
+ maintenance_work_mem = 256MB
+ dynamic_shared_memory_type = posix
+ wal_buffers = 2MB
+ checkpoint_timeout = 15min
+ max_wal_size = 1GB
+ min_wal_size = 80MB
+ checkpoint_completion_target = 0.9
+ logging_collector = on
+ log_directory = '/var/lib/postgresql/12_log/'
+ log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
+ log_rotation_size = 50MB
+ log_min_duration_statement = 5
+ log_checkpoints = on
+ log_connections = on
+ log_disconnections = on
+ log_duration = on
+ log_error_verbosity = default
+ log_line_prefix = '%m, %d, %a. %r, %s, %x, %p '
+ log_timezone = 'GMT'
+ autovacuum = on
+ idle_in_transaction_session_timeout = 43200000
+ datestyle = 'iso, dmy'
+ timezone = 'GMT'
+ lc_messages = 'en_US.utf8'
+ lc_monetary = 'en_US.utf8'
+ lc_numeric = 'en_US.utf8'
+ lc_time = 'en_US.utf8'
+ default_text_search_config = 'pg_catalog.simple'
+ plperl.on_init = 'use utf8; use re; package utf8; require "utf8_heavy.pl";'
+
+#### pg_hba.conf:
+
+ local icinga icinga md5
+ local all all password
+ local replication postgres trust
+ host icinga icinga 127.0.0.1/32 md5
+ host icinga icinga ::1/128 md5
+ host all all 127.0.0.1/32 password
+ host all all ::1/128 password
+ host icinga icinga 2001:4888:a21:3102::/32 md5
+ host all all 2001:4888:a21:3102::/32 password
+
+#### sysctl.conf:
+
+Configure ```kernel.shmmax``` and ```kernel.shmall``` to be 25\% of
+available RAM. For example on a 16GB host:
+
+ kernel.shmmax=4199411712
+ kernel.shmall=4199411712
+
+### User and database creation
+
+As the postgres user:
+
+ # createuser -l -P -e faredge
+ # createdb -e -O faredge faredge
+
+We do not need to populate the database with a schema. Django will do
+this automatically.
+
+## Load Balancers
+
+The services which run on the loadbalancer hosts are as follows:
+
+- apache
+- haproxy
+- icinga2
+
+### Apache
+
+#### SSL Certificates:
+
+A prerequisite for configuring apache is an SSL x509 certificate that
+answers to the following names:
+
+ middleware.<fqdn>
+ icinga.<fqdn>
+ dns-admin.<fqdn>
+
+Place the certificate in ```/etc/ssl/certs/``` and the key in
+```/etc/ssl/private```.
+
+#### Modules:
+
+Apache needs the following modules enabled:
+
+ - access_compat.load
+ - alias.conf
+ - alias.load
+ - auth_basic.load
+ - authn_core.load
+ - authn_file.load
+ - authz_host.load
+ - authz_user.load
+ - authz_core.load
+ - autoindex.conf
+ - autoindex.load
+ - deflate.conf
+ - deflate.load
+ - dir.conf
+ - dir.load
+ - env.load
+ - filter.load
+ - http2.load
+ - mime.conf
+ - mime.load
+ - mpm_prefork.conf
+ - mpm_prefork.load
+ - negotiation.conf
+ - negotiation.load
+ - php7.2.conf
+ - php7.2.load
+ - proxy.conf
+ - proxy.load
+ - proxy_http.load
+ - proxy_http2.load
+ - proxy_wstunnel.load
+ - reqtimeout.conf
+ - reqtimeout.load
+ - rewrite.load
+ - setenvif.conf
+ - setenvif.load
+ - socache_shmcb.load
+ - ssl.conf
+ - ssl.load
+ - status.conf
+ - status.load
+
+#### VirtualHosts:
+
+Apache needs the following sites enabled:
+
+ - dns-admin.conf
+ - far_edge_ops_api.conf
+ - icinga.conf
+ - webdav.conf
+
+```webdav.conf``` is only needed when an external HTTP share is not
+available.
+
+##### dns-admin.conf:
+
+ <VirtualHost [2607:f160:b:10f1::e]:443>
+ ServerName dns-admin.faredge.vzwops.com
+ ServerAdmin carlos.konstanski@verizonwireless.com
+ Protocols http/1.1
+ SSLEngine on
+ SSLProxyEngine on
+ SSLCertificateFile /etc/ssl/certs/vcpfe-lb-vip.faredge.vzwops.com.pem
+ SSLCertificateKeyFile /etc/ssl/private/vcpfe-lb-vip.faredge.vzwops.com.key
+ SSLProtocol all -SSLv2 -SSLv3
+ SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
+ SSLHonorCipherOrder on
+ SSLCompression off
+ SSLOptions +StrictRequire
+ ProxyPass "/" "http://[::1]:13000/"
+ ProxyPassReverse "/" "http://[::1]:13000/"
+ SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+ ErrorLog "/var/log/apache2/dnsadmin_error_log"
+ CustomLog "/var/log/apache2/dnsadmin_access_log" common
+ </VirtualHost>
+
+##### far\_edge\_ops\_api.conf:
+
+ <VirtualHost [2607:f160:b:10f1::e]:443>
+ ServerName middleware.faredge.vzwops.com
+ ServerAdmin carlos.konstanski@verizonwireless.com
+ Protocols http/1.1
+ SSLEngine on
+ SSLProxyEngine on
+ SSLCertificateFile /etc/ssl/certs/vcpfe-lb-vip.faredge.vzwops.com.pem
+ SSLCertificateKeyFile /etc/ssl/private/vcpfe-lb-vip.faredge.vzwops.com.key
+ SSLProtocol all -SSLv2 -SSLv3
+ SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
+ SSLHonorCipherOrder on
+ SSLCompression off
+ SSLOptions +StrictRequire
+ ProxyPass "/" "http://[::1]:13080/"
+ ProxyPassReverse "/" "http://[::1]:13080/"
+ ErrorLog "/var/log/apache2/middleware_error_log"
+ CustomLog "/var/log/apache2/middleware_access_log" common
+ </VirtualHost>
+
+##### icinga.conf:
+
+ <VirtualHost [2607:f160:b:10f1::e]:443>
+ ServerName icinga.faredge.vzwops.com
+ ServerAdmin carlos.konstanski@verizonwireless.com
+ DocumentRoot "/usr/share/icingaweb2/public"
+ SSLEngine on
+ SSLProxyEngine on
+ SSLCertificateFile /etc/ssl/certs/vcpfe-lb-vip.faredge.vzwops.com.pem
+ SSLCertificateKeyFile /etc/ssl/private/vcpfe-lb-vip.faredge.vzwops.com.key
+ SSLProtocol all -SSLv2 -SSLv3
+ SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
+ SSLHonorCipherOrder on
+ SSLCompression off
+ SSLOptions +StrictRequire
+
+ <Directory "/usr/share/icingaweb2/public">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ <IfModule mod_authz_core.c>
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "/etc/icingaweb2"
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteBase /
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+ </Directory>
+ </VirtualHost>
+
+##### webdav.conf:
+
+ <VirtualHost *:80>
+ ServerName webdav.faredge.vzwops.com
+ ServerAdmin carlos.konstanski@verizonwireless.com
+ DocumentRoot "/var/www/html/webdav"
+ SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+ ErrorLog "/var/log/apache2/webdav_error_log"
+ CustomLog "/var/log/apache2/webdav_access_log" common
+ <Directory "/var/www/html/webdav">
+ AllowOverride limit
+ Options Indexes FollowSymLinks
+ </Directory>
+ </VirtualHost>
+
+##### Webdav directory:
+
+Create this directory only if you don't have access to an external HTTP share and you are using the webdav VirtualHost above:
+
+ - /var/www/html/webdav
+
+### HAProxy
+
+Use the following haproxy config, adjusting IP addresses, hostnames and credentials as needed:
+
+ global
+ tune.ssl.default-dh-param 2048
+ log /dev/log local0
+ log /dev/log local1 notice
+ chroot /var/lib/haproxy
+ stats socket /run/haproxy/admin.sock mode 660 level admin
+ stats timeout 30s
+ maxconn 5000
+ user haproxy
+ group haproxy
+ daemon
+ ca-base /etc/ssl/certs
+ crt-base /etc/ssl/private
+ ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
+ ssl-default-bind-options no-sslv3
+
+ defaults
+ log global
+ option dontlognull
+ timeout connect 10s
+ timeout client 60s
+ timeout server 180s
+ errorfile 400 /etc/haproxy/errors/400.http
+ errorfile 403 /etc/haproxy/errors/403.http
+ errorfile 408 /etc/haproxy/errors/408.http
+ errorfile 500 /etc/haproxy/errors/500.http
+ errorfile 502 /etc/haproxy/errors/502.http
+ errorfile 503 /etc/haproxy/errors/503.http
+ errorfile 504 /etc/haproxy/errors/504.http
+
+ listen stats
+ bind 127.0.0.1:1993
+ mode http
+ option httplog
+ option forwardfor
+ stats enable
+ stats uri /stats
+ stats auth someuser:somepass
+ stats refresh 30s
+ stats show-node
+ stats hide-version
+ log global
+
+ frontend dns-admin
+ bind 0.0.0.0:13000
+ bind :::13000
+ mode http
+ option httplog
+ option forwardfor
+ default_backend dns-admin-backend
+
+ backend dns-admin-backend
+ balance roundrobin
+ mode http
+ option log-health-checks
+ option httpchk GET /dns HTTP/1.1\r\nHost:\ dns-admin.faredge.vzwops.com\r\nUser-Agent:\ curl/7.35.0\r\nAccept:\ */*\r\nAuthorization:\ Basic\ b3BlbmJvb2s6WnlsejNmZE1TZDhTVmlod0w5TnlaTHpU
+ default-server inter 10s fall 1 rise 1
+ server server-1 [2607:f160:b:10f1::7]:80 check
+ #server server-2 [2607:f160:b:10f1::8]:80 check
+
+ frontend far_edge_ops_api
+ bind 0.0.0.0:13080
+ bind :::13080
+ mode http
+ option httplog
+ option forwardfor
+ reqdel ^Host:
+ reqadd Host:\ middleware.faredge.vzwops.com
+ default_backend far_edge_ops_api-backend
+
+ backend far_edge_ops_api-backend
+ balance roundrobin
+ mode http
+ option log-health-checks
+ option httpchk GET /caas/ HTTP/1.1\r\nHost:\ middleware.faredge.vzwops.com\r\nUser-Agent:\ curl/7.35.0\r\nAccept:\ */*\r\nAuthorization:\ Basic\ bWlkZGxld2FyZXVzZXI6bWlkZGxld2FyZXBhc3N3b3Jk
+ default-server inter 10s fall 1 rise 1
+ server server-1 [2607:f160:b:10f1::6]:3080 check
+ #server server-2 [2607:f160:b:10f1::5]:3080 check
+
+We currently only proxy to one backend for both ```dns-admin``` and
+```far_edge_ops_api``` because there has been no opportunity to test
+the behavior of the apps in a multiple-backend configuration. This is
+why ```server-2``` is commented out. TODO: update this document when
+this testing is complete.
+
+### Icinga2
+
+The icinga2 configuration is mostly standard. TODO: provide a link to
+an internet HOWTO from which the rest of this documentation can
+build. There will be only minor additions to the standard base
+install.
+
+## Jenkins
+
+The applications are built and deployed from jenkins. The build jobs
+already exist. But a new deploy job needs to be added for the new
+environment which you are building.
+
+### env.yaml
+
+All of the far-edge builds and deploymenets are run on the jenkins
+slave ```openbook-docker-slave-4.meter.vzwops.com```. Log onto this
+host and edit three files in the jenkins home directory
+```/var/lib/jenkins/```:
+
+ - env.yaml.ansible-queue
+ - env.yaml.dns-admin
+ - env.yaml.far-edge-ops-api
+
+A new deployment target section needs to be added to each of these
+files. Give the environment a new unique name and use the same name in
+all three files.
+
+### jenkins-job-builder (jjb)
+
+Clone the following git repo onto your laptop:
+
+ git@gitlab.verizon.com:ONV6661_VCPSPUBLIC/jjb.git
+
+Create an ini file called ```verizoncloudplatform.com.ini``` in the
+top-level directory of your jjb git checkout with the following
+contents:
+
+#### verizoncloudplatform.com.ini:
+ [job_builder]
+ ignore_cache=True
+ keep_descriptions=False
+ include_path=.:scripts:~/git/
+ recursive=False
+ exclude=.*:manual:./development
+ allow_duplicates=False
+
+ [jenkins]
+ user=<your_jenkins_username>
+ password=<your_jenkins_api_token>
+ url=https://openbook-jenkins-master.meter.vzwops.com:8080/
+ timeout=120
+ query_plugins_info=False
+
+Follow these steps to create the new deploy jobs:
+
+- Open the file ```yaml/verizoncloudplatform.com/deploy-vcpfe.yaml```
+ in an editor.
+
+- Add three new jobs to the list, one for each app
+ (```ansible-queue```, ```dns-admin``` and
+ ```far-edge-ops-api```). Set the target attribute to the new ansible
+ target which you created in env.yaml in the previous section.
+
+- ```# ./run.sh verizoncloudplatform.com```
+
+Once this is done, log into the jenkins UI to verify that the new jobs
+were created.
+
+Push your jjb changes to git. (The ini file is gitignored; it is your
+private file.)
+
+## Application hosts
+
+The services which run on the application hosts are as follows:
+
+ - far_edge_ops_api
+
+### far\_edge\_ops\_api
+
+The middleware (as ```far_edge_ops_api``` is ubiquitously known) is a
+docker containerized django web application.
+
+#### Build/deploy
+
+The application can be built and deployed from jenkins:
+
+https://openbook-jenkins-master.meter.vzwops.com:8080
+
+Run the job ```docker-container-far-edge-ops-api``` to build the
+docker container, and the appropriate
+```far-edge-ops-api-deploy-vcpfe-<env>``` job to deploy it to an
+environment.
+
+#### Configure
+
+It might seem backwards to perform the build and deployment before
+configuring. It's a chicken-and-egg problem: the deployment needs to
+create the required files and directories before configuration can
+commence. For first-time deployments the process will look like: build
+-> deploy -> configure -> deploy. This is true of all three
+applications.
+
+The main configuration directory is ```/etc/far-edge-ops-api/```. It
+looks like the following on a working system:
+
+ $ tree /etc/far-edge-ops-api/
+ /etc/far-edge-ops-api/
+ ├── modules.d
+ │   ├── wsgi.conf
+ │   └── wsgi.load
+ ├── settings.py
+ └── vhosts.d
+ └── far-edge-ops-api.conf
+
+```settings.py``` is a rather long file, too long to include here. Get
+a copy from a known good source (a production or lab server) and edit
+to suit the new environment.
+
+```far-edge-ops-api.conf``` is a rather typical-looking apache
+VirtualHosts file, but with LDAP auth added. First the file:
+
+##### far-edge-ops-api.conf:
+
+ LDAPTrustedGlobalCert CA_BASE64 "/etc/apache2/auth/uswin_ca.pem"
+ LDAPTrustedGlobalCert CA_BASE64 "/etc/ssl/certs/selfsigned_root_cert.crt"
+ LDAPTrustedMode SSL
+ LDAPLibraryDebug 7
+
+ <VirtualHost *:3080>
+ ServerName middleware.<fqdn>
+ DocumentRoot "/opt/far-edge-ops-api/src/src"
+ WSGIScriptAlias / /opt/far-edge-ops-api/src/src/far_edge_ops_api/wsgi.py
+ WSGIDaemonProcess middleware.<fqdn> processes=20 threads=15 display-name=%{GROUP} python-home=/opt/far-edge-ops-api/src/venv
+ WSGIProcessGroup middleware.<fqdn>
+ Alias /static/ /opt/far-edge-ops-api/src/src/static/
+ TimeOut 600
+ SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+ ErrorLog "/var/log/apache2/far-edge-ops-api_error_log"
+ CustomLog "/var/log/apache2/far-edge-ops-api_access_log" common
+ </VirtualHost>
+
+ <Directory "/opt/far-edge-ops-api/src/src">
+ AllowOverride all
+ Options FollowSymlinks
+ AuthName "USWIN"
+ AuthType Basic
+ AuthBasicProvider ldap
+ AuthLDAPURL "ldaps://vcpfe-ldap-prod.vcpfe.vzwops.com:636/DC=uswin,DC=ad,DC=vzwcorp,DC=com?sAMAccountName?sub?(objectclass=*)"
+ AuthLDAPBindDN "CN=SVC-VCP-LDAP,OU=SVC,OU=FNA,DC=uswin,DC=ad,DC=vzwcorp,DC=com"
+ AuthLDAPBindPassword "<password_for_SVC-VCP-LDAP_account>"
+ Require valid-user
+ </Directory>
+
+ <Directory "/opt/far-edge-ops-api/src/src/static">
+ AllowOverride limit
+ Require all granted
+ Options Indexes FollowSymLinks
+ </Directory>
+
+There are two SSL certs because the far-edge stack is IPv6-only while
+the production LDAP endpoint (USWIN) is IPv4-only. So we must use a
+proxy. The first cert is the actual uswin cert, while the second cert
+is the one for the proxy. Both of these certs must be placed on the
+app hosts.
+
+Replace ```<fqdn>``` with the DNS domain. Replace
+```<password_for_SVC-VCP-LDAP_account>``` with the correct password.
+
+OpenLDAP needs to be configured with these same certs. Since they are
+self-signed, openldap will reject them unless they are listed as
+exceptions.
+
+#### /etc/ldap/ldap.conf:
+
+ TLS_CACERT /etc/apache2/auth/uswin_ca.pem
+ LDAPTLS_CACERT /etc/ssl/certs/self_ca_signed_cert_and_key_2.pem
+
+Now that the configuration is complete, rerun the deployment from
+jenkins. This time the application should start normally.
+
+## Queue hosts
+
+The services which run on the application hosts are as follows:
+
+ - ansible-queue
+ - dns-admin
+
+### ansible-queue
+
+```ansible-queue``` is a docker containerized common lisp
+application.
+
+#### Build/deploy
+
+The application can be built and deployed from jenkins:
+
+https://openbook-jenkins-master.meter.vzwops.com:8080
+
+Run the job ```docker-container-ansible-queue``` to build the docker
+container, and the appropriate ```ansible-queue-deploy-vcpfe-<env>```
+job to deploy it to an environment.
+
+#### Configure
+
+Two files are required in the directory ```/etc/ansible-queue/```:
+
+ - .ansible_pass.far_edge
+ - options.lisp
+
+```.ansible_pass.far_edge``` is the ansible vault password
+file. Obtain a copy from an existing queue host.
+
+```options.lisp``` is the config file for the ```ansible-queue```
+application.
+
+##### options.lisp:
+
+ ((:queue (:num-icinga-process-threads 1
+ :num-bmc-process-threads 30
+ :num-nic-process-threads 20
+ :num-wr-process-threads 25
+ :num-dns-process-threads 1
+ :num-patch-process-threads 1
+ :num-hw-process-threads 20
+ :num-fix-process-threads 20
+ :icinga-wait-interval 0
+ :bmc-wait-interval 1
+ :nic-wait-interval 30
+ :wr-wait-interval 0
+ :dns-wait-interval 0
+ :patch-wait-interval 30
+ :hw-wait-interval 1
+ :fix-wait-interval 1
+ :sleep-interval 60
+ :ipv6 t
+ :vault-password-file "/etc/ansible-queue/.ansible_pass.far_edge"
+ :icinga-queue-url "tcp://*:5555"
+ :bmc-queue-url "tcp://*:5556"
+ :nic-queue-url "tcp://*:5557"
+ :wr-queue-url "tcp://*:5558"
+ :dns-queue-url "tcp://*:5559"
+ :patch-queue-url "tcp://*:5560"
+ :hw-queue-url "tcp://*:5561"
+ :fix-queue-url "tcp://*:5562"
+ :middleware-url "https://middleware.vcpfe.vzwops.com"
+ :middleware-username "SVC-Far-Edge"
+ :middleware-password "<password_for_SVC-Far-Edge_account>")))
+
+Replace ```<password_for_SVC-Far-Edge_account>``` with the correct
+password.
+
+Now that the configuration is complete, rerun the deployment from
+jenkins. This time the application should start normally.
+
+### dns-admin
+
+```dns-admin``` is a docker containerized web application written in
+common lisp and clojurescript.
+
+#### Build/deploy
+
+The application can be built and deployed from jenkins:
+
+https://openbook-jenkins-master.meter.vzwops.com:8080
+
+Run the job ```docker-container-dns-admin``` to build the docker
+container, and the appropriate ```dns-admin-deploy-vcpfe-<env>``` job
+to deploy it to an environment.
+
+#### Configure
+
+Only one new config file is required. Use the following content
+verbatim:
+
+##### /etc/dns-admin/conf.lisp:
+
+ (:name "dns-admin"
+ :document-root "dns-admin"
+ :title "DNS Administration"
+ :meta-description "A website for administering DNS. Supports nsupdate and infoblox."
+ :dns (:label "CDS Infoblox"
+ :backend-type "infoblox"
+ :url "https://infoblox.alias/wapi/v2.6"))
+
+Notice the odd-looking hostname ```infoblox.alias```. The following
+needs to be added to ```/etc/hosts```:
+
+ 2607:f160:8a02:8016:a0:103:0:10 infoblox.alias
+
+Now that the configuration is complete, rerun the deployment from
+jenkins. This time the application should start normally.
diff --git a/doc/markdown/subcloud-install-remediation.md b/doc/markdown/subcloud-install-remediation.md
new file mode 100644
index 0000000..753c5fb
--- /dev/null
+++ b/doc/markdown/subcloud-install-remediation.md
@@ -0,0 +1,253 @@
+# Far-Edge Subcloud Installation and Remediation Guide
+
+## Installation
+
+A Wind River subcloud installation normally runs with no manual
+intervention. In the event that an installation needs to be started
+manually:
+
+- Visit https://middleware.vcpfe.vzwops.com/caas/wrfix/ in a browser.
+
+- Enter the iLO IP address into the first text field (even though it's
+ labeled "cluster name").
+
+- Type ```wr``` in the playbook key field.
+
+- Click the submit button.
+
+If an OK message appears, the job has been successfully sent to
+ansible-queue. The remaining steps require an SSH shell on
+```root@vcpfe-queue-2-prod-birmingham```:
+
+- View ```/var/log/lisp/ansible-queue.log``` to ensure that the
+ playbook started.
+
+- View the specific ansible log in ```/var/log/ansible/queue/``` to
+ watch the playbook's progress.
+
+If you want to watch the OS installation (the first stage of the
+overall deployment), attach to the iLO console:
+
+- Find the subcloud data (see below for the query) and find the
+ ```ilo_host_address``` and ```bmc_password```.
+
+- ```# ssh bladecenter-lb-vip.meter.vzwops.com```
+
+- ```# do-bmc <ilo_host_address>```
+
+- Enter the password when prompted.
+
+- Type ```vsp``` to start the serial console.
+
+## Remediation
+
+All too often it is necessary to remediate failed Wind River
+installations. Even successful ones need a visit by an engineer to
+ensure that they are in fact complete. It is not possible to discover
+this information from the middleware database, the subcloud, nor the
+central controller alone. All three of these data sources must be
+accessed. TODO: write a dashboard which aggregates the required
+information from the central controller, subcloud and database.
+
+Common causes of failure (this is not an exhaustive list):
+
+- OAM network unreachable.
+- MGMT network unreachable.
+- MGMT subnet overlaps with another already-installed subcloud.
+- The subcloud does not sync in time after coming online.
+- The platform-integ-apps application fails to apply.
+
+(This does not include servers that have issues which prevent them
+from advancing through the automation pipeline far enough to begin a
+Wind River installation. These include, but are not limited to,
+hardware problems and incorrect firmware versions.)
+
+### Inspecting a subcloud
+
+Three data sources were listed above. Let's visit each one in detail.
+
+#### Middleware database
+
+The middlware database is our record of what should be installed and
+whether or not we have marked it as complete. The ```caas_wrbatch```
+view and ```caas_wrinstallschedule``` table are important in this
+context.
+
+Here is a handy query to pull up all relevant information by cluster
+name:
+
+ select b.cluster_name || '.vcpfe.vzwops.com' as cluster_name,
+ b.*,
+ s.*
+ from caas_wrbatch b
+ left outer join caas_wrinstallschedule s
+ on b.cluster_id = s.cluster_id
+ inner join caas_location l
+ on b.fuze_spm_site_id = l.fuze_spm_site_id
+ where b.cluster_name in ('<cluster_name>')
+ order by b.parent_cluster_name asc,
+ b.cluster_name asc;
+
+(The expression in the first column is a convenience: it builds a FQDN
+for the subcloud that can be directly used in an SSH command from your
+laptop.)
+
+Here is a sample record showing a completed subcloud:
+
+ cluster_name | wsbomagj-d663366-001.vcpfe.vzwops.com
+ id | 423
+ cluster_id | 423
+ fuze_spm_site_id | 663366
+ parent_oam_vip_hostname |
+ parent_oam_vip_address | 2001:4888:a12:3221:106:290:0:10
+ parent_mgmt_address_range_start | 2001:4888:a12:3220:106:290:0:10
+ parent_mgmt_address_range_end | 2001:4888:a12:3220:106:290:0:ffff
+ parent_mgmt_default_gateway | 2001:4888:a12:3220:106:28::
+ parent_mgmt_subnet | 2001:4888:a12:3220::
+ pxe_mac_address | 48:df:37:e2:67:34
+ ilo_hostname | wsbomagj-663366-rh-pe0e910-001
+ ilo_host_address | 2001:4888:2a10:90fd:103:40a:0:e001
+ oam_hostname | wsbomagj-663366-rh-pe2e910-001
+ oam_host_address | 2001:4888:2a10:90fd:103:40a:0:400
+ oam_vip_address | 2001:4888:2a10:90fd:103:40a:0:f400
+ oam_default_gateway | 2001:4888:2a10:90fd:103:2a0::
+ mgmt_address_range_start | 2001:4888:2a10:982b:103:40a::
+ mgmt_address_range_end | 2001:4888:2a10:982b:103:40a:0:f
+ mgmt_default_gateway | 2001:4888:2a10:982b:103:2a0::
+ mgmt_subnet | 2001:4888:2a10:982b:103:40a::
+ host_vlan | 2845
+ oam_vlan | 410
+ mgmt_vlan | 420
+ cluster_name | wsbomagj-d663366-001
+ parent_cluster_name | wsbomagj-c319918-003
+ vendor_name | HPE
+ intel_nic_firmware_version | 1.2585.0
+ maint_window_p | f
+ namespace_id | 425
+ namespace_name | wsbomagj-663366vzwcvdu-y-ss-x-05690012222
+ bmc_username | k8sctl
+ bmc_password | <redacted>
+ id | 709
+ date_scheduled | 2020-11-19 19:15:16.885864+00
+ date_completed | 2020-12-04 19:28:17.644994+00
+ date_last_failed | 2020-12-03 20:47:18.199344+00
+ kirke_ticket_number | 709
+ kirke_ticket_status |
+ kirke_ticket_completed | 2020-11-20 02:28:30.908054+00
+ cluster_id | 423
+
+The key points:
+
+- ```pxe_mac_address``` is not null
+- ```intel_nic_firmware_version``` == 1.2585.0
+- ```date_completed``` is not null
+- ```kirke_ticket_number``` is not null
+
+The last point is the crucial one. Setting ```kirke_ticket_number```
+to something other than null is the way we manually mark the Wind
+River installation as complete. It must be done with a query similar
+to the following:
+
+ update caas_wrinstallschedule
+ set kirke_ticket_number = id
+ where id = 709;
+
+#### Central controller
+
+Use the following command to see the status of every subcloud attached
+to a central controller:
+
+ dcmanager subcloud list
+
+If the subcloud in question is complete and healthy from the central
+controller's point of view, it will look like the following. This does
+not necessarily mean that all is well. The subcloud itself must be
+visited and checked.
+
+ [sysadmin@controller-0 ~(keystone_admin)]$ dcmanager subcloud list | grep wsbomagj-d663366-001
+ | 546 | wsbomagj-d663366-001 | managed | online | complete | in-sync |
+
+#### Subcloud
+
+If the central controller says that the subcloud is offline, the first
+thing to check is the mgmt network. If you cannot ping the central
+controller's mgmt IP, there is no hope for remediating the subcloud.
+
+ ping6 <parent_mgmt_address_range_start>
+
+If the subcloud is managed, online and in-sync, then the remaining
+checks can be performed. Check the alarms:
+
+ fm alarm-list
+
+Check the applied status of the applications:
+
+ [sysadmin@controller-0 ~(keystone_admin)]$ system application-list
+ +--------------------------+----------+-----------------------------------+------------------------------------+---------+-----------+
+ | application | version | manifest name | manifest file | status | progress |
+ +--------------------------+----------+-----------------------------------+------------------------------------+---------+-----------+
+ | cert-manager | 20.06-4 | cert-manager-manifest | certmanager-manifest.yaml | applied | completed |
+ | nginx-ingress-controller | 20.06-0 | nginx-ingress-controller-manifest | nginx_ingress_controller_manifest. | applied | completed |
+ | | | | yaml | | |
+ | | | | | | |
+ | oidc-auth-apps | 20.06-26 | oidc-auth-manifest | manifest.yaml | applied | completed |
+ | platform-integ-apps | 20.06-9 | platform-integration-manifest | manifest.yaml | applied | completed |
+ +--------------------------+----------+-----------------------------------+------------------------------------+---------+-----------+
+
+Check the drbd resize. drbd8 should be set to 32GB:
+
+ [sysadmin@controller-0 ~(keystone_admin)]$ lsblk | grep drbd8
+ │ └─drbd8 147:8 0 32G 0 disk /var/lib/docker-distribution
+
+Check the PTP configuration. ```tx_timestamp_timeout``` should be set
+to 50:
+
+ [sysadmin@controller-0 ~(keystone_admin)]$ grep tx_timestamp_timeout /etc/ptp4l.conf
+ tx_timestamp_timeout 50
+
+Check the ISOL CPU configuration which should be enabled on cores 3,
+4, 27 and 28:
+
+ [sysadmin@controller-0 ~(keystone_admin)]$ system host-cpu-list controller-0 | grep -F 'Application-isolated'
+ | 72499a12-e2f2-40fc-a069-a2b002908843 | 3 | 0 | 3 | 0 | Intel(R) Xeon(R) Gold 6212U CPU @ 2.40GHz | Application-isolated |
+ | 8498669d-9048-45f6-b9dd-c81262934b70 | 4 | 0 | 4 | 0 | Intel(R) Xeon(R) Gold 6212U CPU @ 2.40GHz | Application-isolated |
+ | 4a70893f-b2cf-4405-9b69-11ed823a81b3 | 27 | 0 | 3 | 1 | Intel(R) Xeon(R) Gold 6212U CPU @ 2.40GHz | Application-isolated |
+ | 1d6e330d-7d2f-416c-b375-9b909d6a1f3c | 28 | 0 | 4 | 1 | Intel(R) Xeon(R) Gold 6212U CPU @ 2.40GHz | Application-isolated |
+
+### Marking an installation as complete
+
+If the subcloud passes all of the checks, it should be marked complete
+with the update query:
+
+ update caas_wrinstallschedule
+ set kirke_ticket_number = id
+ where id = <id>;
+
+### Remediating
+
+If the subcloud fails to pass all of the checks, it must be
+remediated. Deciding which steps to take requires operational
+experience. Here is a guide to some common problems and solutions:
+
+- The subcloud is unmanaged: ping the central controller mgmt IP. If
+ it fails, the network is misconfigured and must be fixed by the
+ region.
+
+- ```platform-integ-apps``` is not applied: perform a wipedisk and
+ reinstall.
+
+- ```oidc-auth-apps``` is uploaded but not applied: run the
+ ```wr_remediate``` playbook.
+
+- The drbd resize hasn't happened: run the ```wr_remediate```
+ playbook.
+
+- PTP isn't configured: run the ```wr_remediate``` playbook.
+
+- ISOL CPUS are not configured: run the ```wr_remediate``` playbook.
+
+The very last thing that the ```wr-installer``` playbook does is a
+```collect all```. You can tell if the playbook ran to completion by
+checking the contents of the ```/scratch``` directory on the
+subcloud. If it contains a collect tarball, the playbook completed.
+
diff --git a/doc/swagger/restapi-swagger.yaml b/doc/swagger/restapi-swagger.yaml
new file mode 100644
index 0000000..b0a2752
--- /dev/null
+++ b/doc/swagger/restapi-swagger.yaml
@@ -0,0 +1,93 @@
+swagger: '2.0'
+info:
+ description: FarEdge REST API
+ version: 0.0.1
+ title: FarEdge REST API
+ contact:
+ email: subin.ren@verizonwireless.com
+host: faredge.restapi.io
+basePath: /v1
+tags:
+- name: "CAAS"
+ description: "cluster status API"
+schemes:
+ - https
+paths:
+ /caas-status:
+ post:
+ tags:
+ - CAAS
+ summary: Send cluster status to VMB
+ description: Send cluster status to VMB
+ operationId: clusterStatus
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - name: api_key
+ in: header
+ required: true
+ type: string
+ - in: body
+ name: body
+ description: caas readyness status
+ required: true
+ schema:
+ $ref: '#/definitions/CaaSStatusMap'
+ responses:
+ '200':
+ description: "successful operation"
+ '403':
+ description: Forbidden
+ '405':
+ description: Invalid input
+securityDefinitions:
+ api_key:
+ type: apiKey
+ name: api_key
+ in: header
+definitions:
+ CaaSStatusMap:
+ type: object
+ properties:
+ cluster_status:
+ type: array
+ items:
+ $ref: "#/definitions/Status"
+ Status:
+ type: object
+ required:
+ - name
+ - location
+ - software_version
+ - availability
+ - deploy_status
+ - created_at
+ properties:
+ name:
+ type: string
+ example: wsbomagj-d654321-001
+ description:
+ type: string
+ example: NE CONCORD 8_N
+ location:
+ type: string
+ example: 654321
+ software_version:
+ type: string
+ example: 20.06
+ availability:
+ type: string
+ enum:
+ - ONLINE
+ deploy_status:
+ type: string
+ enum:
+ - COMPLETE
+ created_at:
+ type: string
+ example: "2020-01-07 04:16:15.743617"
+ updated_at:
+ type: string
+ example: "2020-01-07 06:03:10.854598"
diff --git a/doc/tex/break-fix/.gitignore b/doc/tex/break-fix/.gitignore
new file mode 100644
index 0000000..99eff73
--- /dev/null
+++ b/doc/tex/break-fix/.gitignore
@@ -0,0 +1,9 @@
+*.aux
+*.bbl
+*.blg
+*.log
+*.nav
+*.out
+*.pdf
+*.snm
+*.toc
diff --git a/doc/tex/break-fix/compile.sh b/doc/tex/break-fix/compile.sh
new file mode 100755
index 0000000..6e2d731
--- /dev/null
+++ b/doc/tex/break-fix/compile.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+rm *-blx.bib *.aux *.bbl *.bcf *.blg *.log *.nav *.out *.run.xml *.snm *.toc
+file="$(ls *.tex | awk -F. '{print $1}')"
+pdflatex ${file}
+exit 0
diff --git a/doc/tex/break-fix/slides.tex b/doc/tex/break-fix/slides.tex
new file mode 100644
index 0000000..a3a0215
--- /dev/null
+++ b/doc/tex/break-fix/slides.tex
@@ -0,0 +1,82 @@
+% To make a PDF from this source, use the pdflatex command.
+\documentclass[helvetica,english,utf8,notitle,nologo,8pt]{beamer}
+\usetheme{boxes}
+\usecolortheme{seahorse}
+\usepackage[american]{babel}
+\usepackage[babel]{csquotes}
+
+\begin{document}
+
+\title{Wind River Subcloud Break-Fix Procedure}
+\author{Carlos Konstanski\\
+Far-Edge Engineering/Operations}
+
+\frame{\titlepage}
+
+\begin{frame}
+ \frametitle{Introduction}
+
+ This document will describe the steps required to replace a Wind
+ River subcloud server, specifically the portion of the process that
+ is handled by Far-Edge Engineering/Operations. This group is
+ concerned with the installation of Wind River CaaS and the vDU
+ workloads. It is assumed that the prerequisite steps have already
+ been completed. These include but are not necessarily limited to:
+
+ \begin{itemize}
+ \item Physical server racking
+ \item Network cabling
+ \item Capturing the serial number and iLO password from the sticker
+ on the bottom of the blade
+ \item Capturing the iLO IPv6 address
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{What Eng/Ops Needs in the Handoff}
+
+ Once a new server has been racked and cabled, Far-Edge Eng/Ops needs
+ to be notified so that we can provision the new host. We require
+ three pieces of information in order to do this:
+
+ \begin{enumerate}
+ \item The iLO IPv6 address. This is the key for looking up the
+ remainder of the server information in our system.
+ \item The iLO password. It is printed on a sticker on the bottom of
+ the blade.
+ \item The blade serial number. It is printed on the same sticker.
+ \end{enumerate}
+
+ Workbench 360 has been proposed as the notification channel.
+\end{frame}
+
+\begin{frame}
+ \frametitle{How to Procure the Subcloud Information}
+
+ An interim web UI page has been set up to query server information
+ based on the FUZE ID:
+
+ https://middleware.vcpfe.vzwops.com/caas/subcloud/
+\end{frame}
+
+\begin{frame}
+ \frametitle{What Eng/Ops Does Next}
+
+ We will watch the automation pipeline to ensure that the server
+ completes the zero-touch provisioning process successfully.
+
+ \begin{itemize}
+ \item On the central controller: dcmanager subcloud unmanage cluster\_name
+ \item On the central controller: dcmanager subcloud delete cluster\_name
+ \item Disassociate the server record from the old blade record in
+ the middleware database.
+ \item Ingest the new blade info (serial number and password).
+ \item Update icinga to pick up the new model\_check service which
+ will relink the server to the new blade.
+ \end{itemize}
+
+ At this point the server will enter the automation pipeline and
+ proceed just like a normal installation.
+\end{frame}
+
+\end{document}
diff --git a/doc/uml/.gitignore b/doc/uml/.gitignore
new file mode 100644
index 0000000..fb59d0f
--- /dev/null
+++ b/doc/uml/.gitignore
@@ -0,0 +1,6 @@
+hardware_install_activity_diagram.png
+wr_install_schedule_activity_diagram.png
+use_case_diagram.png
+security_use_case_diagram.png
+middleware-stack-prod-birmingham.png
+middleware-stack-mtce.png
diff --git a/doc/uml/actor.png b/doc/uml/actor.png
new file mode 100644
index 0000000..5fa3808
--- /dev/null
+++ b/doc/uml/actor.png
Binary files differ
diff --git a/doc/uml/computer.png b/doc/uml/computer.png
new file mode 100644
index 0000000..36f5cdb
--- /dev/null
+++ b/doc/uml/computer.png
Binary files differ
diff --git a/doc/uml/drive-removable-media.png b/doc/uml/drive-removable-media.png
new file mode 100644
index 0000000..673284c
--- /dev/null
+++ b/doc/uml/drive-removable-media.png
Binary files differ
diff --git a/doc/uml/hardware_install_activity_diagram.dot b/doc/uml/hardware_install_activity_diagram.dot
new file mode 100644
index 0000000..446773f
--- /dev/null
+++ b/doc/uml/hardware_install_activity_diagram.dot
@@ -0,0 +1,209 @@
+// This is a graphviz file. To generate a diagram from this source,
+// you must first install graphviz (available from homebrew). Then run
+// the following command:
+//
+// dot -Tpng -o hardware_install_activity_diagram.png hardware_install_activity_diagram.dot
+
+digraph G {
+ graph [fontsize = 14, penwidth = 0, rankdir = LR];
+ node [shape = "rect"];
+ edge [fontzize = 9, arrowhead = "empty"];
+
+ subgraph cluster_activity {
+ label = "CaaS/PaaS Hardware Installation";
+ color = black;
+ start_automation [shape = "circle", style = "filled", color = "grey", label = "Start\nautomation"];
+ start_server_install [shape = "circle", style = "filled", color = "grey", label = "Start\nphysical server\ninstallation"];
+ start_monitoring [shape = "circle", style = "filled", color = "grey", label = "Start\nmonitoring"];
+ end_monitoring_fail [shape = "doublecircle", style = "filled", color = "brown1", label = "Monitoring\nCRIT"];
+ start_firmware_upgrade [shape = "circle", style = "filled", color = "grey", label = "Schedule\nIntel NIC\nfirmware\nupgrade"];
+ start_wr_install [shape = "circle", style = "filled", color = "grey", label = "Schedule\nWind River\ninstallation"];
+ end_schedulewr_fail [shape = "doublecircle", style = "filled", color = "brown1", label = "Wind River\ninstallation\nfailed"];
+ end_schedulefirmware_fail [shape = "doublecircle", style = "filled", color = "brown1", label = "NIC\nfirmware\nupgrade\nfailed"];
+ start_orchestration [shape = "doublecircle", style = "filled", color = "grey", label = "Start\norchestration"];
+
+ subgraph cluster_hardwareinstall {
+ label = "Install hardware";
+ penwidth = 1;
+ color = "chocolate1";
+
+ subgraph cluster_dataimport {
+ label = "Data import";
+ dataimport_password_import [label = "Import HP passwords"];
+ dataimport_ciq_import [label = "Import CIQ"];
+ dataimport_icinga_batch [label = "Icinga job is sent to ansible-queue"];
+ }
+
+ subgraph cluster_serialnumber {
+ label = "Link serial numbers/passwords to CIQ";
+ serialnumber_server_hostalive_p [shape = "diamond", label = "Server pingble\nfrom model_check?"];
+ serialnumber_collect_vendor [label = "Icinga passes vendor,\nserial number and\nBMC IP to middleware"];
+ serialnumber_link_blade [label = "Middleware links\nblade to server"];
+ }
+
+ subgraph cluster_biosconfig {
+ label = "Configure passwords, DNS, icinga and BIOS";
+ biosconfig_generate_password [label = "Middleware generates\nunique BMC passwords"];
+ biosconfig_bmc_batch [label = "BMC job is sent\nto ansible-queue"];
+ biosconfig_dns_batch [label = "DNS job is sent\nto ansible-queue"];
+ }
+ }
+
+ subgraph cluster_icingamonitoring {
+ label = "Monitor hardware";
+ penwidth = 1;
+ color = "deepskyblue";
+
+ subgraph cluster_macnic {
+ label = "Collect BMC MAC and NIC firmware version";
+ macnic_bmc_push_pxe_mac [label = "BMC playbook pushes\nMAC address and\nNIC firmware version\nto middleware"];
+ macnic_icinga_monitoring [label = "icinga job is sent\nto ansible-queue"];
+ macnic_poll_monitoring [label = "icinga_poll job is sent\nto ansible-queue"];
+ }
+
+ subgraph cluster_nicfirmware {
+ label = "Intel NIC firmware version";
+ firmware_upgrade_p [shape = "diamond", label = "Is\nan Intel NIC\nfirmware upgrade\nneeded?"];
+ }
+ }
+
+ subgraph cluster_firmwarescheduler {
+ label = "Intel NIC firmware upgrade";
+ penwidth = 1;
+ color = "darkorchid1";
+
+ subgraph cluster_schedulefirmwareschedule {
+ label = "Schedule";
+ schedulefirmwareschedule_schedule [label = "Schedule firmware\nupgrade"];
+ }
+
+ subgraph cluster_schedulefirmwarerun {
+ label = "Run";
+ schedulefirmwarerun_run [label = "firmware upgrade\njob is sent\nto ansible-queue"];
+ }
+
+ subgraph cluster_schedulefirmwareresults {
+ label = "Results";
+ schedulefirmwareresults_p [shape = "diamond", label = "Did firmware\ninstall successfully?"];
+ }
+ }
+
+ subgraph cluster_wrscheduler {
+ label = "Schedule Wind River installation";
+ penwidth = 1;
+ color = "darkolivegreen4";
+
+ subgraph cluster_schedulewrschedule {
+ label = "Schedule";
+ schedulewrschedule_schedule [label = "Schedule Wind\nRiver install"];
+ }
+
+ subgraph cluster_schedulewrrun {
+ label = "Run";
+ schedulewrrun_run [label = "Wind River\ninstall job is sent\nto ansible-queue"];
+ }
+
+ subgraph cluster_schedulewrresults {
+ label = "Results";
+ schedulewrresults_p [shape = "diamond", label = "Did Wind River\ninstall successfully?"];
+ }
+
+ subgraph cluster_schedulewrorchestration {
+ label = "Orchestration";
+ schedulewrorchestration_orch [label = "Call\norchestration\nendpoint"];
+ }
+ }
+
+ subgraph cluster_ansiblequeue {
+ label = "ansible-queue";
+ penwidth = 1;
+ color = "yellow3";
+
+ subgraph cluster_icingaplaybook {
+ label = "Icinga queue";
+ icingaplaybook_run [label = "Manage ansible host_vars\nand run playbook"];
+ }
+
+ subgraph cluster_icingapollplaybook {
+ label = "Icinga Poll queue";
+ icingapollplaybook_run [label = "Manage ansible host_vars\nand run playbook"];
+ icingapollplaybook_report [label = "Playbook informs\nmiddleware of result"];
+ }
+
+ subgraph cluster_bmcplaybook {
+ label = "BMC queue";
+ bmcplaybook_run_1 [label = "Manage ansible host_vars\nand run playbook"];
+ bmcplaybook_run_2 [label = "Manage ansible host_vars\nand run playbook"];
+ }
+
+ subgraph cluster_dnsplaybook {
+ label = "DNS queue";
+ dnsplaybook_run [label = "Manage ansible host_vars\nand run playbook"];
+ }
+
+ subgraph cluster_firmwareplaybook {
+ label = "NIC queue";
+ firmwareplaybook_run [label = "Manage ansible host_vars\nand run playbook"];
+ firmwareplaybook_report [label = "Playbook informs\nmiddleware of result"];
+ }
+
+ subgraph cluster_wrplaybook {
+ label = "Wind River queue";
+ wrplaybook_run [label = "Manage ansible host_vars\nand run playbook"];
+ wrplaybook_report [label = "Playbook informs\nmiddleware of result"];
+ }
+ }
+
+ subgraph cluster_legend {
+ label = "Legend";
+ penwidth = 1;
+ node [shape = "rect", style = "filled"];
+ legend_hardware [label = "Hardware", fillcolor = "chocolate1"];
+ legend_monitoring [label = "Monitoring", fillcolor = "deepskyblue"];
+ legend_firmware [label = "NIC firmware", fillcolor = "darkorchid1"];
+ legend_wr [label = "Wind River", fillcolor = "darkolivegreen4"];
+ legend_ansible [label = "ansible-queue", fillcolor = "yellow3"];
+ }
+
+ start_automation -> dataimport_password_import [color = "chocolate1"];
+ dataimport_password_import -> dataimport_ciq_import [color = "chocolate1"];
+ dataimport_ciq_import -> dataimport_icinga_batch [color = "chocolate1"];
+ dataimport_icinga_batch -> icingaplaybook_run [color = "chocolate1"];
+ dataimport_icinga_batch -> serialnumber_server_hostalive_p [color = "chocolate1"];
+ start_server_install -> serialnumber_server_hostalive_p [color = "chocolate1"];
+ serialnumber_server_hostalive_p -> serialnumber_server_hostalive_p [color = "chocolate1", label = "No"];
+ serialnumber_server_hostalive_p -> serialnumber_collect_vendor [color = "chocolate1", label = "Yes"];
+ serialnumber_collect_vendor -> serialnumber_link_blade [color = "chocolate1"];
+ serialnumber_link_blade -> biosconfig_generate_password [color = "chocolate1"];
+ biosconfig_generate_password -> biosconfig_dns_batch [color = "chocolate1"];
+ biosconfig_generate_password -> biosconfig_bmc_batch [color = "chocolate1"];
+ schedulefirmwareschedule_schedule -> schedulefirmwarerun_run [color = "darkorchid1"];
+ schedulefirmwarerun_run -> firmwareplaybook_run [color = "darkorchid1"];
+ firmwareplaybook_run -> firmwareplaybook_report [color = "yellow3"];
+ firmwareplaybook_report -> schedulefirmwareresults_p [color = "darkorchid1"];
+ schedulefirmwareresults_p -> end_schedulefirmware_fail [color = "darkorchid1", label = "No"];
+ schedulefirmwareresults_p -> start_wr_install [color = "darkorchid1", label = "Yes"];
+ biosconfig_bmc_batch -> bmcplaybook_run_1 [color = "chocolate1"];
+ biosconfig_dns_batch -> dnsplaybook_run [color = "chocolate1"];
+ bmcplaybook_run_1 -> start_monitoring [color = "deepskyblue"];
+ start_monitoring -> macnic_bmc_push_pxe_mac [color = "deepskyblue"];
+ macnic_bmc_push_pxe_mac -> macnic_icinga_monitoring [color = "deepskyblue"];
+ macnic_bmc_push_pxe_mac -> macnic_poll_monitoring [color = "deepskyblue"];
+ macnic_icinga_monitoring -> icingaplaybook_run [color = "deepskyblue"];
+ macnic_poll_monitoring -> icingapollplaybook_run [color = "deepskyblue"];
+ icingapollplaybook_run -> icingapollplaybook_report [color = "yellow3"];
+ icingapollplaybook_report -> firmware_upgrade_p [color = "deepskyblue", label = "Cleared"];
+ icingapollplaybook_report -> end_monitoring_fail [color = "deepskyblue", label = "Not cleared"];
+ firmware_upgrade_p -> start_firmware_upgrade [color = "deepskyblue", label = "Yes"];
+ firmware_upgrade_p -> start_wr_install [color = "deepskyblue", label = "No"];
+ start_firmware_upgrade -> schedulefirmwareschedule_schedule [color = "darkorchid1"];
+ start_wr_install -> schedulewrschedule_schedule [color = "darkolivegreen4"];
+ schedulewrschedule_schedule -> schedulewrrun_run [color = "darkolivegreen4"];
+ schedulewrrun_run -> wrplaybook_run [color = "darkolivegreen4"];
+ wrplaybook_run -> wrplaybook_report [color = "yellow3"];
+ wrplaybook_report -> schedulewrresults_p [color = "darkolivegreen4"];
+ schedulewrresults_p -> end_schedulewr_fail [color = "darkolivegreen4", label = "No"];
+ schedulewrresults_p -> schedulewrorchestration_orch [color = "darkolivegreen4", label = "Yes"];
+ schedulewrorchestration_orch -> start_orchestration [color = "darkolivegreen4"];
+ }
+}
diff --git a/doc/uml/middleware-stack-mtce.dot b/doc/uml/middleware-stack-mtce.dot
new file mode 100644
index 0000000..3bde052
--- /dev/null
+++ b/doc/uml/middleware-stack-mtce.dot
@@ -0,0 +1,200 @@
+// This is a graphviz file. To generate a diagram from this source,
+// you must first install graphviz (available from homebrew). Then run
+// the following command:
+//
+// dot -Tpng -o security_use_case_diagram.png security_use_case_diagram.dot
+
+digraph G {
+ graph [fontsize = 14, penwidth = 0, rankdir = LR];
+ node [shape = "plaintext"];
+ edge [color = "deepskyblue", fontsize = 9, dir = none];
+
+ subgraph cluster_vm {
+ label = "Nova instances";
+ penwidth = 1;
+
+ subgraph cluster_vmmiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmmiddlewareapp1 {
+ label = "Django/icinga host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-MW-001";
+ penwidth = 0;
+ vmmiddlewareapp1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewareapp2 {
+ label = "Django/icinga host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-MW-002";
+ penwidth = 0;
+ vmmiddlewareapp2_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase1 {
+ label = "Database host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-001";
+ penwidth = 0;
+ vmmiddlewaredatabase1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase2 {
+ label = "Database host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-002";
+ penwidth = 0;
+ vmmiddlewaredatabase2_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase3 {
+ label = "Database host 3\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-003";
+ penwidth = 0;
+ vmmiddlewaredatabase3_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_vmservices {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmservices1 {
+ label = "ansible-queue/dns-admin host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-CM-001";
+ penwidth = 0;
+ vmservices1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmservices2 {
+ label = "ansible-queue/dns-admin host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-CM-002";
+ penwidth = 0;
+ vmservices2_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_vmlb {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmlb1 {
+ label = "Load balancer host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-LB-001";
+ penwidth = 0;
+ vmlb1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmlb2 {
+ label = "Load balancer host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-LB-002";
+ penwidth = 0;
+ vmlb2_icon [label = "", image = "computer.png"];
+ }
+ }
+ }
+
+ subgraph cluster_port {
+ label = "Neutron ports";
+ penwidth = 1;
+
+ subgraph cluster_portmiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portmiddlewareapp1 {
+ label = "Django/icinga host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-MW-001\n2607:f160:b:10f1::6";
+ penwidth = 0;
+ portmiddlewareapp1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewareapp2 {
+ label = "Django/icinga host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-MW-002\n2607:f160:b:10f1::5";
+ penwidth = 0;
+ portmiddlewareapp2_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase1 {
+ label = "Database host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-001\n2607:f160:b:10f1::b";
+ penwidth = 0;
+ portmiddlewaredatabase1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase2 {
+ label = "Database host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-002\n2607:f160:b:10f1::3";
+ penwidth = 0;
+ portmiddlewaredatabase2_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase3 {
+ label = "Database host 3\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-003\n2607:f160:b:10f1::c";
+ penwidth = 0;
+ portmiddlewaredatabase3_icon [label = "", image = "network-wired.png"];
+ }
+ }
+
+ subgraph cluster_portservices {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portservices1 {
+ label = "ansible-queue/\ndns-admin host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-CM-001\n2607:f160:b:10f1::7";
+ penwidth = 0;
+ portservices1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portservices2 {
+ label = "ansible-queue/\ndns-admin host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-CM-002\n2607:f160:b:10f1::8";
+ penwidth = 0;
+ portservices2_icon [label = "", image = "network-wired.png"];
+ }
+ }
+
+ subgraph cluster_portlb {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portlb1 {
+ label = "Load balancer host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-LB-001\n2607:f160:b:10f1::e";
+ penwidth = 0;
+ portlb1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portlb2 {
+ label = "Load balancer host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-LB-002\n2607:f160:b:10f1::d";
+ penwidth = 0;
+ portlb2_icon [label = "", image = "network-wired.png"];
+ }
+ }
+ }
+
+ subgraph cluster_volume {
+ label = "Cinder volumes";
+ penwidth = 1;
+
+ subgraph cluster_volumemiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_volumemiddlewaredatabase1 {
+ label = "Database host 1\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-01";
+ penwidth = 0;
+ volumemiddlewaredatabase1_icon [label = "", image = "drive-removable-media.png"];
+ }
+
+ subgraph cluster_volumemiddlewaredatabase2 {
+ label = "Database host 2\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-02";
+ penwidth = 0;
+ volumemiddlewaredatabase2_icon [label = "", image = "drive-removable-media.png"];
+ }
+
+ subgraph cluster_volumemiddlewaredatabase3 {
+ label = "Database host 3\nSOLKTX01VZBvVFE-Y-WR-X-00-DB-03";
+ penwidth = 0;
+ volumemiddlewaredatabase3_icon [label = "", image = "drive-removable-media.png"];
+ }
+ }
+ }
+
+ vmmiddlewareapp1_icon -> portmiddlewareapp1_icon;
+ vmmiddlewareapp2_icon -> portmiddlewareapp2_icon;
+ vmmiddlewaredatabase1_icon -> portmiddlewaredatabase1_icon;
+ vmmiddlewaredatabase2_icon -> portmiddlewaredatabase2_icon;
+ vmmiddlewaredatabase3_icon -> portmiddlewaredatabase3_icon;
+ vmservices1_icon -> portservices1_icon;
+ vmservices2_icon -> portservices2_icon;
+ vmlb1_icon -> portlb1_icon;
+ vmlb2_icon -> portlb2_icon;
+ vmmiddlewaredatabase1_icon -> volumemiddlewaredatabase1_icon;
+ vmmiddlewaredatabase2_icon -> volumemiddlewaredatabase2_icon;
+ vmmiddlewaredatabase3_icon -> volumemiddlewaredatabase3_icon;
+}
diff --git a/doc/uml/middleware-stack-prod-birmingham.dot b/doc/uml/middleware-stack-prod-birmingham.dot
new file mode 100644
index 0000000..5250381
--- /dev/null
+++ b/doc/uml/middleware-stack-prod-birmingham.dot
@@ -0,0 +1,200 @@
+// This is a graphviz file. To generate a diagram from this source,
+// you must first install graphviz (available from homebrew). Then run
+// the following command:
+//
+// dot -Tpng -o security_use_case_diagram.png security_use_case_diagram.dot
+
+digraph G {
+ graph [fontsize = 14, penwidth = 0, rankdir = LR];
+ node [shape = "plaintext"];
+ edge [color = "deepskyblue", fontsize = 9, dir = none];
+
+ subgraph cluster_vm {
+ label = "Nova instances";
+ penwidth = 1;
+
+ subgraph cluster_vmmiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmmiddlewareapp1 {
+ label = "Django/icinga host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-MW-001";
+ penwidth = 0;
+ vmmiddlewareapp1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewareapp2 {
+ label = "Django/icinga host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-MW-002";
+ penwidth = 0;
+ vmmiddlewareapp2_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase1 {
+ label = "Database host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-001";
+ penwidth = 0;
+ vmmiddlewaredatabase1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase2 {
+ label = "Database host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-002";
+ penwidth = 0;
+ vmmiddlewaredatabase2_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmmiddlewaredatabase3 {
+ label = "Database host 3\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-003";
+ penwidth = 0;
+ vmmiddlewaredatabase3_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_vmservices {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmservices1 {
+ label = "ansible-queue/dns-admin host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-CM-001";
+ penwidth = 0;
+ vmservices1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmservices2 {
+ label = "ansible-queue/dns-admin host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-CM-002";
+ penwidth = 0;
+ vmservices2_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_vmlb {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_vmlb1 {
+ label = "Load balancer host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-LB-001";
+ penwidth = 0;
+ vmlb1_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_vmlb2 {
+ label = "Load balancer host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-LB-002";
+ penwidth = 0;
+ vmlb2_icon [label = "", image = "computer.png"];
+ }
+ }
+ }
+
+ subgraph cluster_port {
+ label = "Neutron ports";
+ penwidth = 1;
+
+ subgraph cluster_portmiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portmiddlewareapp1 {
+ label = "Django/icinga host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-MW-001\n2001:4888:a21:3102:245:29::";
+ penwidth = 0;
+ portmiddlewareapp1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewareapp2 {
+ label = "Django/icinga host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-MW-002\n2001:4888:a21:3102:245:29:0:a";
+ penwidth = 0;
+ portmiddlewareapp2_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase1 {
+ label = "Database host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-001\n2001:4888:a21:3102:245:29:0:1";
+ penwidth = 0;
+ portmiddlewaredatabase1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase2 {
+ label = "Database host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-002\n2001:4888:a21:3102:245:29:0:7";
+ penwidth = 0;
+ portmiddlewaredatabase2_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portmiddlewaredatabase3 {
+ label = "Database host 3\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-003\n2001:4888:a21:3102:245:29:0:2";
+ penwidth = 0;
+ portmiddlewaredatabase3_icon [label = "", image = "network-wired.png"];
+ }
+ }
+
+ subgraph cluster_portservices {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portservices1 {
+ label = "ansible-queue/\ndns-admin host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-CM-001\n2001:4888:a21:3102:245:29:0:b";
+ penwidth = 0;
+ portservices1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portservices2 {
+ label = "ansible-queue/\ndns-admin host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-CM-002\n2001:4888:a21:3102:245:29:0:10";
+ penwidth = 0;
+ portservices2_icon [label = "", image = "network-wired.png"];
+ }
+ }
+
+ subgraph cluster_portlb {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_portlb1 {
+ label = "Load balancer host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-LB-001\n2001:4888:a21:3102:245:29:0:11";
+ penwidth = 0;
+ portlb1_icon [label = "", image = "network-wired.png"];
+ }
+
+ subgraph cluster_portlb2 {
+ label = "Load balancer host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-LB-002\n2001:4888:a21:3102:245:29:0:8";
+ penwidth = 0;
+ portlb2_icon [label = "", image = "network-wired.png"];
+ }
+ }
+ }
+
+ subgraph cluster_volume {
+ label = "Cinder volumes";
+ penwidth = 1;
+
+ subgraph cluster_volumemiddleware {
+ label = "";
+ penwidth = 0;
+
+ subgraph cluster_volumemiddlewaredatabase1 {
+ label = "Database host 1\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-01";
+ penwidth = 0;
+ volumemiddlewaredatabase1_icon [label = "", image = "drive-removable-media.png"];
+ }
+
+ subgraph cluster_volumemiddlewaredatabase2 {
+ label = "Database host 2\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-02";
+ penwidth = 0;
+ volumemiddlewaredatabase2_icon [label = "", image = "drive-removable-media.png"];
+ }
+
+ subgraph cluster_volumemiddlewaredatabase3 {
+ label = "Database host 3\nBRHOALTBVZBvVFE-Y-WR-X-00-DB-03";
+ penwidth = 0;
+ volumemiddlewaredatabase3_icon [label = "", image = "drive-removable-media.png"];
+ }
+ }
+ }
+
+ vmmiddlewareapp1_icon -> portmiddlewareapp1_icon;
+ vmmiddlewareapp2_icon -> portmiddlewareapp2_icon;
+ vmmiddlewaredatabase1_icon -> portmiddlewaredatabase1_icon;
+ vmmiddlewaredatabase2_icon -> portmiddlewaredatabase2_icon;
+ vmmiddlewaredatabase3_icon -> portmiddlewaredatabase3_icon;
+ vmservices1_icon -> portservices1_icon;
+ vmservices2_icon -> portservices2_icon;
+ vmlb1_icon -> portlb1_icon;
+ vmlb2_icon -> portlb2_icon;
+ vmmiddlewaredatabase1_icon -> volumemiddlewaredatabase1_icon;
+ vmmiddlewaredatabase2_icon -> volumemiddlewaredatabase2_icon;
+ vmmiddlewaredatabase3_icon -> volumemiddlewaredatabase3_icon;
+}
diff --git a/doc/uml/network-wired.png b/doc/uml/network-wired.png
new file mode 100644
index 0000000..6417065
--- /dev/null
+++ b/doc/uml/network-wired.png
Binary files differ
diff --git a/doc/uml/security_use_case_diagram.dot b/doc/uml/security_use_case_diagram.dot
new file mode 100644
index 0000000..c939cc6
--- /dev/null
+++ b/doc/uml/security_use_case_diagram.dot
@@ -0,0 +1,153 @@
+// This is a graphviz file. To generate a diagram from this source,
+// you must first install graphviz (available from homebrew). Then run
+// the following command:
+//
+// dot -Tpng -o security_use_case_diagram.png security_use_case_diagram.dot
+
+digraph G {
+ graph [fontsize = 14, penwidth = 0, rankdir = UD];
+ node [shape = "plaintext"];
+ edge [fontsize = 9, style = dotted, arrowhead = empty];
+
+ subgraph cluster_external {
+ label = "External systems";
+ penwidth = 1;
+
+ subgraph cluster_externalbrowser {
+ label = "EDN-attached web browser";
+ penwidth = 0;
+ externalbrowser_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalorchestration {
+ label = "ATLAS/Orchestration";
+ penwidth = 0;
+ externalorchestration_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalvmb{
+ label = "VMB";
+ penwidth = 0;
+ externalvmb_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalgitlab {
+ label = "GitLab";
+ penwidth = 0;
+ externalgitlab_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalartifactory {
+ label = "Artifactory";
+ penwidth = 0;
+ externalartifactory_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalinfoblox {
+ label = "CDS Infoblox";
+ penwidth = 0;
+ externalinfoblox_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_externalldap {
+ label = "USWIN LDAP";
+ penwidth = 0;
+ externalldap_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_middlewarestack {
+ label = "Automation stack";
+ penwidth = 1;
+
+ subgraph cluster_middlewarestackansible {
+ label = "Ansible/DNS hosts";
+ penwidth = 0;
+ middlewarestackansible_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_middlewarestackapp {
+ label = "Middleware/Icinga hosts";
+ penwidth = 0;
+ middlewarestackapp_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_middlewarestackdatabase {
+ label = "Database hosts";
+ penwidth = 0;
+ middlewarestackdatabase_icon [label = "", image = "drive-removable-media.png"];
+ }
+
+ subgraph cluster_middlewarestacklb {
+ label = "Load balancer hosts";
+ penwidth = 0;
+ middlewarestacklb_icon [label = "", image = "computer.png"];
+ }
+ }
+
+ subgraph cluster_wr {
+ label = "Wind River platform";
+ penwidth = 1;
+
+ subgraph cluster_wrcentral {
+ label = "Central controller cluster";
+
+ subgraph cluster_wrcentralhosts {
+ label = "Central controller hosts";
+ penwidth = 0;
+ wrcentralhosts_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_wrcentralbmc {
+ label = "Central controller BMC";
+ penwidth = 0;
+ wrcentralbmc_icon [label = "", image = "network-wired.png"];
+ }
+ }
+
+ subgraph cluster_wrremote {
+ label = "Remote subcloud cluster";
+
+ subgraph cluster_wrremotehosts {
+ label = "Remote subcloud hosts";
+ penwidth = 0;
+ wrremotehosts_icon [label = "", image = "computer.png"];
+ }
+
+ subgraph cluster_wrremotebmc {
+ label = "Remote subcloud BMC";
+ penwidth = 0;
+ wrremotebmc_icon [label = "", image = "network-wired.png"];
+ }
+ }
+ }
+
+ subgraph cluster_legend {
+ label = "Legend";
+ penwidth = 1;
+ node [shape = "rect", style = filled];
+ legend_external [label = "Connections\nto external\nsystems", fillcolor = "chocolate1"];
+ legend_wr [label = "Connections\nto Wind River\nplatform", fillcolor = "darkolivegreen3"];
+ legend_middleware_external [label = "Connections\nto middleware", fillcolor = "deepskyblue"];
+ }
+
+ externalbrowser_icon -> middlewarestacklb_icon [color = "deepskyblue", headlabel = "TCP *:443"];
+ externalorchestration_icon -> middlewarestacklb_icon [color = "deepskyblue"];
+ middlewarestackapp_icon -> externalvmb_icon [color = "chocolate1", headlabel = "TCP *:6651"];
+ externalgitlab_icon -> middlewarestacklb_icon [color = "deepskyblue"];
+ middlewarestackansible_icon -> externalartifactory_icon [color = "chocolate1", headlabel = "TCP *:443"];
+ middlewarestackapp_icon -> externalgitlab_icon [color = "chocolate1", headlabel = "TCP *:22"];
+ middlewarestackansible_icon -> externalgitlab_icon [color = "chocolate1"];
+ middlewarestackansible_icon -> wrcentralhosts_icon [color = "darkolivegreen3", headlabel = "TCP *:22"];
+ middlewarestackansible_icon -> wrremotehosts_icon [color = "darkolivegreen3", headlabel = "TCP *:22"];
+ middlewarestackapp_icon -> middlewarestackansible_icon [color = "deepskyblue", headlabel = "TCP *:80, *:5555-5561"];
+ middlewarestacklb_icon -> externalldap_icon [color = "chocolate1", headlabel = "TCP *:636"];
+ middlewarestackansible_icon -> externalinfoblox_icon [color = "chocolate1", headlabel = "TCP *:443"];
+ middlewarestackapp_icon -> wrcentralbmc_icon [color = "darkolivegreen3", headlabel = "TCP *:443"];
+ middlewarestackapp_icon -> wrremotebmc_icon [color = "darkolivegreen3", headlabel = "TCP *:443"];
+ middlewarestacklb_icon -> middlewarestackapp_icon [color = "deepskyblue", headlabel = "TCP *:80"];
+ middlewarestackapp_icon -> middlewarestackdatabase_icon [color = "deepskyblue", headlabel = "TCP *:5432"];
+ middlewarestacklb_icon -> middlewarestackansible_icon [color = "deepskyblue"];
+ wrcentralhosts_icon -> externalartifactory_icon [color = "chocolate1"];
+ wrcentralhosts_icon -> wrremotehosts_icon [color = "darkolivegreen3"];
+}
diff --git a/doc/uml/security_use_case_diagram.pdf b/doc/uml/security_use_case_diagram.pdf
new file mode 100644
index 0000000..2a5bfc0
--- /dev/null
+++ b/doc/uml/security_use_case_diagram.pdf
Binary files differ
diff --git a/doc/uml/use_case_diagram.dot b/doc/uml/use_case_diagram.dot
new file mode 100644
index 0000000..dfa7692
--- /dev/null
+++ b/doc/uml/use_case_diagram.dot
@@ -0,0 +1,72 @@
+// This is a graphviz file. To generate a diagram from this source,
+// you must first install graphviz (available from homebrew). Then run
+// the following command:
+//
+// dot -Tpng -o use_case_diagram.png use_case_diagram.dot
+
+digraph G {
+ fontname = "Bitstream Vera Sans";
+ fontsize = 12;
+
+ node [
+ fontname = "Bitstream Vera Sans";
+ fontsize = 12;
+ shape = "ellipse";
+ ]
+
+ edge [
+ fontname = "Bitstream Vera Sans";
+ fontsize = 12;
+ ]
+
+ edge [
+ arrowhead = "empty";
+ ]
+
+ subgraph cluster_actors {
+ color = white;
+ node [
+ shape = custom;
+ shapefile = "actor.png";
+ width = 0.352;
+ height = 0.77;
+ fixedsize = true;
+ color = "#ffffff";
+ ]
+ ciq [label = "CIQ"];
+ cron [label = "cron"]
+ technician [label = "Technician"];
+ }
+
+ subgraph cluster_processes {
+ style = filled;
+ color = azure2;
+ populate_database [label = "Populate database"];
+ monitor_hardware [label = "Monitor hardware"];
+ install_caas [label = "Install CaaS"];
+ }
+
+ {
+ edge [color = red;]
+ ciq -> populate_database [label = "Populates"];
+ }
+
+ {
+ edge [color = chocolate4;]
+ cron -> install_caas [label = "Starts"];
+ }
+
+ {
+ edge [color = blue;]
+ technician -> monitor_hardware [label = "Waits for"];
+ }
+
+ {
+ edge [
+ label = "Uses";
+ color = green4;
+ ]
+ install_caas -> monitor_hardware;
+ monitor_hardware -> populate_database;
+ }
+}
diff --git a/fix-virtualenv.sh b/fix-virtualenv.sh
new file mode 100755
index 0000000..c3ec426
--- /dev/null
+++ b/fix-virtualenv.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+for pkg in virtualenv; do
+ which ${pkg}
+ ret=${?}
+ if [ "${ret}" != "0" ]; then
+ echo "${pkg} not found. Please install it and try again." 1>&2
+ exit ${ret}
+ fi
+done
+
+basedir=$(dirname $0)
+rm -rf ${basedir}/venv
+
+if [ ! -d ${basedir}/venv ] || [ "$(ls -A ${basedir}/venv)" == "" ]; then
+ virtualenv -p python3.6 ${basedir}/venv
+fi
+
+pushd ${basedir} >/dev/null 2>&1
+. venv/bin/activate
+pip3 install --upgrade --use-feature=2020-resolver -r requirements.txt
+popd >/dev/null 2>&1
+
+exit 0
diff --git a/logcollection-requirements.txt b/logcollection-requirements.txt
new file mode 100644
index 0000000..1696256
--- /dev/null
+++ b/logcollection-requirements.txt
@@ -0,0 +1,2 @@
+pexpect
+psycopg2-binary
diff --git a/requirements.txt b/requirements.txt
index 8be9a44..acbfc32 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,20 @@
dj-database-url
dj-static
Django
+djangorestframework
+django-crontab
asgiref
-pymysql
-mysqlclient
+# pymysql
+# mysqlclient
+psycopg2
+jinja2
+mod_wsgi
+django-db-views
+psutil
+python-language-server[rope]
+pyzmq
+pulsar-client==2.5.2
+pexpect
+pyyaml
+requests
+django-crispy-forms
diff --git a/sql/caas/adhoc/migrate-mgmt-network-fields.sql b/sql/caas/adhoc/migrate-mgmt-network-fields.sql
new file mode 100644
index 0000000..f2559a9
--- /dev/null
+++ b/sql/caas/adhoc/migrate-mgmt-network-fields.sql
@@ -0,0 +1,40 @@
+do $$
+declare
+ rec caas_cluster%rowtype;
+ l_mgmt_subnet character varying;
+ l_mgmt_default_gateway character varying;
+begin
+ for rec in
+ select id,
+ cluster_name,
+ oam_vip_hostname,
+ oam_vip_address,
+ mgmt_address_range_start,
+ mgmt_address_range_end,
+ csr_id,
+ location_id,
+ namespace_id,
+ parent_cluster_id,
+ sap_clli_id,
+ vlan_id,
+ is_central_controller,
+ mgmt_default_gateway,
+ mgmt_subnet
+ from caas_cluster
+ where is_central_controller = 0
+ loop
+ select mgmt_subnet into l_mgmt_subnet
+ from caas_cellsiterouter
+ where id = rec.csr_id;
+
+ select mgmt_default_gateway into l_mgmt_default_gateway
+ from caas_cellsiterouter
+ where id = rec.csr_id;
+
+ update caas_cluster
+ set mgmt_subnet = l_mgmt_subnet,
+ mgmt_default_gateway = l_mgmt_default_gateway
+ where id = rec.id;
+ end loop;
+end;
+$$ language plpgsql;
diff --git a/sql/caas/adhoc/mtce-nominal.sql b/sql/caas/adhoc/mtce-nominal.sql
new file mode 100644
index 0000000..d1d2137
--- /dev/null
+++ b/sql/caas/adhoc/mtce-nominal.sql
@@ -0,0 +1,72 @@
+do $$
+declare
+ csr_name_central character varying := 'WELKTXEF-ROUTER-000004';
+ cluster_name_central character varying := 'welktxef-c000004-001';
+ ilo_hostname_central character varying := 'welktxef-000004-rh-p30e910-001';
+ oam_vip_hostname_central character varying := 'welktxef-000004-nh-pe1e910-001';
+ ilo_gateway_address_central character varying := '2607:f160:10:9051:ce:23::';
+ ilo_subnet_central character varying := '2607:f160:10:9051::';
+ oam_subnet_central character varying := '2607:f160:10:9051::';
+ oam_gateway_address_central character varying := ilo_gateway_address_central;
+ oam_floating_address_central character varying := '2607:f160:10:9051:ce:290:0:1000';
+ mgmt_start_address_central character varying := '2607:f160:10:9050:ce:290::';
+ mgmt_end_address_central character varying := '2607:f160:10:9050:ce:290:0:f';
+ mgmt_subnet_central character varying := '2607:f160:10:9050::';
+ mgmt_gateway_address_central character varying := '2607:f160:10:9050:ce:23::';
+
+ oam_hostname_central character varying := 'welktxef-000004-nh-pe2e910-001';
+ ilo_host_address_central character varying := '2607:f160:10:9221:ce:fe0:0:800b';
+ oam_host_address_central character varying := '2607:f160:10:9051:ce:290:0:1001';
+begin
+ update caas_cellsiterouter
+ set ilo_subnet = ilo_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set ilo_default_gateway = ilo_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_subnet = oam_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_default_gateway = oam_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_subnet = mgmt_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_default_gateway = mgmt_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cluster
+ set oam_vip_hostname = oam_vip_hostname_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set oam_vip_address = oam_floating_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_start = mgmt_start_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_end = mgmt_end_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central
+ where ilo_hostname = ilo_hostname_central;
+
+ update caas_server
+ set oam_host_address = oam_host_address_central
+ where ilo_hostname = ilo_hostname_central;
+
+ update caas_server
+ set ilo_host_address = ilo_host_address_central
+ where ilo_hostname = ilo_hostname_central;
+end $$;
diff --git a/sql/caas/adhoc/wclab-dracut.sql b/sql/caas/adhoc/wclab-dracut.sql
new file mode 100644
index 0000000..579fb27
--- /dev/null
+++ b/sql/caas/adhoc/wclab-dracut.sql
@@ -0,0 +1,159 @@
+-- oam amd mgmt subnets are swapped to create the dracut condition.
+do $$
+declare
+ csr_name_central character varying := 'ye-olde-csr-hostname-1';
+ cluster_name_central character varying := 'walnutcr-d0000000-001';
+ oam_vip_hostname_central character varying := 'walnutcr-0000000-nh-pe1e910-001';
+ ilo_gateway_address_central character varying := 'fd00:4888:2000:7f3:528:23::';
+ ilo_subnet_central character varying := 'fd00:4888:2000:7f3:528:';
+ oam_subnet_central character varying := 'fd00:4888:2000:07fd::';
+ oam_floating_address_central character varying := 'fd00:4888:2000:07fd::290';
+ mgmt_start_address_central character varying := 'fd00:4888:2000:1100::a';
+ mgmt_end_address_central character varying := 'fd00:4888:2000:1100::2a';
+ mgmt_subnet_central character varying := 'fd00:4888:2000:1100::';
+ mgmt_gateway_address_central character varying := 'fd00:4888:2000:1100:524:23::';
+ csr_name_remote character varying := 'ye-olde-csr-hostname-2';
+ cluster_name_remote character varying := 'walnutcr-d9999999-001';
+ ilo_subnet_remote character varying := 'fd00:4888:2000:1205::';
+ ilo_gateway_address_remote character varying := 'fd00:4888:2000:1205::1';
+ oam_floating_address_remote character varying := 'fd00:4888:2000:1209::a';
+ oam_gateway_address_remote character varying := 'fd00:4888:2000:1209::1';
+ oam_subnet_remote character varying := 'fd00:4888:2000:1209::';
+ mgmt_start_address_remote character varying := 'fd00:4888:2000:120a::10';
+ mgmt_end_address_remote character varying := 'fd00:4888:2000:120a::1f';
+ mgmt_gateway_address_remote character varying := 'fd00:4888:2000:120a::1';
+ mgmt_subnet_remote character varying := 'fd00:4888:2000:120a::';
+
+ oam_hostname_central_0 character varying := 'walnutcr-0000000-nh-pe2e910-001';
+ ilo_host_address_central_0 character varying := 'fd00:4888:2000:7f3:528:ff2:0:8';
+ oam_host_address_central_0 character varying := 'fd00:4888:2000:07fd::292';
+
+ oam_hostname_central_1 character varying := 'walnutcr-0000000-nh-pe2e910-002';
+ ilo_host_address_central_1 character varying := 'fd00:4888:2000:7f3:528:ff2:0:7';
+ oam_host_address_central_1 character varying := 'fd00:4888:2000:07fd::291';
+
+ oam_hostname_central_2 character varying := 'walnutcr-0000000-nh-pe2e910-003';
+ ilo_host_address_central_2 character varying := 'fd00:4888:2000:7f3:528:ff2:0:9';
+ oam_host_address_central_2 character varying := 'fd00:4888:2000:07fd::293';
+
+ ilo_host_address_remote_0 character varying := 'fd00:4888:2000:1205::26';
+ oam_host_address_remote_0 character varying := 'fd00:4888:2000:120a::b';
+begin
+ update caas_cellsiterouter
+ set ilo_subnet = ilo_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set ilo_default_gateway = ilo_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_subnet = oam_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_subnet = oam_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set oam_default_gateway = oam_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set mgmt_subnet = mgmt_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_default_gateway = mgmt_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_subnet = mgmt_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set mgmt_default_gateway = mgmt_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set ilo_subnet = ilo_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set ilo_default_gateway = ilo_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cluster
+ set oam_vip_hostname = oam_vip_hostname_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set oam_vip_address = oam_floating_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_start = mgmt_start_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_end = mgmt_end_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set oam_vip_address = oam_floating_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_cluster
+ set mgmt_address_range_start = mgmt_start_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_cluster
+ set mgmt_address_range_end = mgmt_end_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_0
+ where ilo_host_address = ilo_host_address_central_0;
+
+ update caas_server
+ set oam_host_address = oam_host_address_remote_0
+ where ilo_host_address = ilo_host_address_remote_0;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_1
+ where ilo_host_address = ilo_host_address_central_1;
+
+ update caas_server
+ set oam_host_address = oam_host_address_central_1
+ where ilo_host_address = ilo_host_address_central_1;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_2
+ where ilo_host_address = ilo_host_address_central_2;
+
+ update caas_server
+ set oam_host_address = oam_host_address_central_2
+ where ilo_host_address = ilo_host_address_central_2;
+
+ update caas_server
+ set oam_host_address = oam_host_address_remote_0
+ where ilo_host_address = ilo_host_address_remote_0;
+end $$;
+
+/*
+
+truncate table caas_blade cascade;
+truncate table caas_cellsiterouter cascade;
+truncate table caas_chassis cascade;
+truncate table caas_cluster cascade;
+truncate table caas_credential cascade;
+truncate table caas_firmwareupgradeschedule cascade;
+truncate table caas_location cascade;
+truncate table caas_namespace cascade;
+truncate table caas_sapclli cascade;
+truncate table caas_server cascade;
+truncate table caas_vendor cascade;
+truncate table caas_vlan cascade;
+truncate table caas_wrinstallschedule cascade;
+
+*/
diff --git a/sql/caas/adhoc/wclab-nominal.sql b/sql/caas/adhoc/wclab-nominal.sql
new file mode 100644
index 0000000..0db2f9e
--- /dev/null
+++ b/sql/caas/adhoc/wclab-nominal.sql
@@ -0,0 +1,158 @@
+do $$
+declare
+ csr_name_central character varying := 'ye-olde-csr-hostname-1';
+ cluster_name_central character varying := 'walnutcr-d0000000-001';
+ oam_vip_hostname_central character varying := 'walnutcr-0000000-nh-pe1e910-001';
+ ilo_gateway_address_central character varying := 'fd00:4888:2000:7f3:528:23::';
+ ilo_subnet_central character varying := 'fd00:4888:2000:7f3::';
+ oam_subnet_central character varying := 'fd00:4888:2000:07fd::';
+ oam_floating_address_central character varying := 'fd00:4888:2000:07fd::290';
+ mgmt_start_address_central character varying := 'fd00:4888:2000:1100::a';
+ mgmt_end_address_central character varying := 'fd00:4888:2000:1100::2a';
+ mgmt_subnet_central character varying := 'fd00:4888:2000:1100::';
+ mgmt_gateway_address_central character varying := 'fd00:4888:2000:1100:524:23::';
+ csr_name_remote character varying := 'ye-olde-csr-hostname-2';
+ cluster_name_remote character varying := 'walnutcr-d9999999-001';
+ ilo_subnet_remote character varying := 'fd00:4888:2000:1205::';
+ ilo_gateway_address_remote character varying := 'fd00:4888:2000:1205::1';
+ oam_floating_address_remote character varying := 'fd00:4888:2000:120a::a';
+ oam_gateway_address_remote character varying := 'fd00:4888:2000:120a::1';
+ oam_subnet_remote character varying := 'fd00:4888:2000:120a::';
+ mgmt_start_address_remote character varying := 'fd00:4888:2000:1209::10';
+ mgmt_end_address_remote character varying := 'fd00:4888:2000:1209::1f';
+ mgmt_gateway_address_remote character varying := 'fd00:4888:2000:1209::1';
+ mgmt_subnet_remote character varying := 'fd00:4888:2000:1209::';
+
+ oam_hostname_central_0 character varying := 'walnutcr-0000000-nh-pe2e910-001';
+ ilo_host_address_central_0 character varying := 'fd00:4888:2000:7f3:528:ff2:0:8';
+ oam_host_address_central_0 character varying := 'fd00:4888:2000:07fd::292';
+
+ oam_hostname_central_1 character varying := 'walnutcr-0000000-nh-pe2e910-002';
+ ilo_host_address_central_1 character varying := 'fd00:4888:2000:7f3:528:ff2:0:7';
+ oam_host_address_central_1 character varying := 'fd00:4888:2000:07fd::291';
+
+ oam_hostname_central_2 character varying := 'walnutcr-0000000-nh-pe2e910-003';
+ ilo_host_address_central_2 character varying := 'fd00:4888:2000:7f3:528:ff2:0:9';
+ oam_host_address_central_2 character varying := 'fd00:4888:2000:07fd::293';
+
+ ilo_host_address_remote_0 character varying := 'fd00:4888:2000:1205::26';
+ oam_host_address_remote_0 character varying := 'fd00:4888:2000:120a::b';
+begin
+ update caas_cellsiterouter
+ set ilo_subnet = ilo_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set ilo_default_gateway = ilo_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_subnet = oam_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set oam_subnet = oam_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set oam_default_gateway = oam_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set mgmt_subnet = mgmt_subnet_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_default_gateway = mgmt_gateway_address_central
+ where csr_hostname = csr_name_central;
+
+ update caas_cellsiterouter
+ set mgmt_subnet = mgmt_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set mgmt_default_gateway = mgmt_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set ilo_subnet = ilo_subnet_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cellsiterouter
+ set ilo_default_gateway = ilo_gateway_address_remote
+ where csr_hostname = csr_name_remote;
+
+ update caas_cluster
+ set oam_vip_hostname = oam_vip_hostname_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set oam_vip_address = oam_floating_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_start = mgmt_start_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set mgmt_address_range_end = mgmt_end_address_central
+ where cluster_name = cluster_name_central;
+
+ update caas_cluster
+ set oam_vip_address = oam_floating_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_cluster
+ set mgmt_address_range_start = mgmt_start_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_cluster
+ set mgmt_address_range_end = mgmt_end_address_remote
+ where cluster_name = cluster_name_remote;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_0
+ where ilo_host_address = ilo_host_address_central_0;
+
+ update caas_server
+ set oam_host_address = oam_host_address_remote_0
+ where ilo_host_address = ilo_host_address_remote_0;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_1
+ where ilo_host_address = ilo_host_address_central_1;
+
+ update caas_server
+ set oam_host_address = oam_host_address_central_1
+ where ilo_host_address = ilo_host_address_central_1;
+
+ update caas_server
+ set oam_hostname = oam_hostname_central_2
+ where ilo_host_address = ilo_host_address_central_2;
+
+ update caas_server
+ set oam_host_address = oam_host_address_central_2
+ where ilo_host_address = ilo_host_address_central_2;
+
+ update caas_server
+ set oam_host_address = oam_host_address_remote_0
+ where ilo_host_address = ilo_host_address_remote_0;
+end $$;
+
+/*
+
+truncate table caas_blade cascade;
+truncate table caas_cellsiterouter cascade;
+truncate table caas_chassis cascade;
+truncate table caas_cluster cascade;
+truncate table caas_credential cascade;
+truncate table caas_firmwareupgradeschedule cascade;
+truncate table caas_location cascade;
+truncate table caas_namespace cascade;
+truncate table caas_sapclli cascade;
+truncate table caas_server cascade;
+truncate table caas_vendor cascade;
+truncate table caas_vlan cascade;
+truncate table caas_wrinstallschedule cascade;
+
+*/
diff --git a/sql/schema/create-database.sql b/sql/schema/create-database.sql
new file mode 100644
index 0000000..088893f
--- /dev/null
+++ b/sql/schema/create-database.sql
@@ -0,0 +1,5 @@
+CREATE DATABASE IF NOT EXISTS `faredge` /*!40100 DEFAULT CHARACTER SET utf8 */;
+
+GRANT ALL PRIVILEGES ON faredge.* TO faredge@'%' IDENTIFIED BY 'password_changeme';
+GRANT ALL PRIVILEGES ON faredge.* TO faredge@localhost IDENTIFIED BY 'password_changeme';
+FLUSH PRIVILEGES;
diff --git a/sql/schema/faredge.sql.example b/sql/schema/faredge.sql.example
index e324d8a..e31606b 100644
--- a/sql/schema/faredge.sql.example
+++ b/sql/schema/faredge.sql.example
@@ -17,30 +17,3 @@ GRANT ALL PRIVILEGES ON faredge.* TO faredge@'%' IDENTIFIED BY 'password_changem
GRANT ALL PRIVILEGES ON faredge.* TO faredge@localhost IDENTIFIED BY 'password_changeme';
FLUSH PRIVILEGES;
-USE `faredge`;
-
-DROP TABLE IF EXISTS orch_image_sync;
-CREATE TABLE orch_image_sync (
- id int(10) unsigned NOT NULL AUTO_INCREMENT,
- remote_region_name varchar(64) not null,
- docker_image text not null,
- upload_start_time datetime not null,
- upload_end_time datetime,
- upload_status enum('STARTED', 'UPLOADING', 'COMPLETE', 'FAILED'),
- upload_message varchar(255),
- PRIMARY KEY (id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-DROP TABLE IF EXISTS orch_remote_region_setup;
-CREATE TABLE orch_remote_region_setup (
- id int(10) unsigned NOT NULL AUTO_INCREMENT,
- caas_vendor_version varchar(20) not null,
- kubernetes_version varchar(20) not null,
- central_region_name varchar(64) not null,
- remote_region_name varchar(64) not null,
- kubernetes_namespace varchar(64) not null,
- serviceaccount varchar(64) not null,
- kubeconfig mediumtext,
- PRIMARY KEY (id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/sql/schema/orchestration.testdata b/sql/schema/orchestration.testdata
new file mode 100644
index 0000000..74ade22
--- /dev/null
+++ b/sql/schema/orchestration.testdata
@@ -0,0 +1,4 @@
+use faredge;
+insert into orchestration_centraltoremotemap(central_region_name, remote_region_name) values ("fd00:4888:2000:07f7::290", "fd00:4888:2000:120c::290");
+insert into orchestration_centraltoremotemap(central_region_name, remote_region_name) values ("fd00:4888:2000:07f7::290", "fd00:4888:2000:120e::290");
+insert into orchestration_centraltoremotemap(central_region_name, remote_region_name) values ("fd00:4888:2000:07fd::290", "fd00:4888:2000:120a::a"); \ No newline at end of file
diff --git a/sql/schema/orchestration_test.sql b/sql/schema/orchestration_test.sql
new file mode 100644
index 0000000..62bc981
--- /dev/null
+++ b/sql/schema/orchestration_test.sql
@@ -0,0 +1,27 @@
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'cmp:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'dmp:1.0.0', '2020-05-26 00:00:00', 'UPLOADING','Uploading');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'pmp:1.0.0', '2020-05-26 00:00:00', 'COMPLETE','Uploading successful');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'rmp:1.0.0', '2020-05-26 00:00:00', 'FAILED','Can not find image');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'dip:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'wsbomagj-d654321-001', 'dpp:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'cmp:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'dmp:1.0.0', '2020-05-26 00:00:00', 'UPLOADING','Uploading');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'pmp:1.0.0', '2020-05-26 00:00:00', 'COMPLETE','Uploading successful');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'rmp:1.0.0', '2020-05-26 00:00:00', 'FAILED','Can not find image');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'dip:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('dd99eaa604db4b46b33196dac06b9685', 'bbtpnj33-d123456-001', 'dpp:1.0.0', '2020-05-26 00:00:00', 'STARTED','Uploading started');
+
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'cmp:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'dmp:1.0.0', '2020-05-25 14:29:36', 'UPLOADING','uploading');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'pmp:1.0.0', '2020-05-25 14:29:36', 'COMPLETE','uploading successful');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'rmp:1.0.0', '2020-05-25 14:29:36', 'FAILED','can not find image');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'dip:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'wsbomagj-d654321-002', 'dpp:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
+
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'cmp:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'dmp:1.0.0', '2020-05-25 14:29:36', 'UPLOADING','uploading');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'pmp:1.0.0', '2020-05-25 14:29:36', 'COMPLETE','uploading successful');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'rmp:1.0.0', '2020-05-25 14:29:36', 'FAILED','can not find image');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'dip:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
+INSERT INTO orch_image_sync(transaction_id, remote_region_name, docker_image, upload_start_time, upload_status, upload_message) values('940483182efe40e4ab8dd066ee0c9444', 'bbtpnj33-d123456-002', 'dpp:1.0.0', '2020-05-25 14:29:36', 'STARTED','uploading started');
diff --git a/src/automationstatus/README.md b/src/automationstatus/README.md
new file mode 100644
index 0000000..3adf86a
--- /dev/null
+++ b/src/automationstatus/README.md
@@ -0,0 +1,26 @@
+## How to use the automation status API
+
+The automation status API is a REST api for playbooks to report startus as they are running.
+
+URL: /automationstatus/playbook/
+
+## Sending a status updates
+
+POST: /automationstatus/playbook/
+
+Body:
+{
+ "ilo_host_address": "2001:4888:2a10:30c2:101:40a:0:e001",
+ "playbook_name": "test_playbook123",
+ "status": "test_status223"
+}
+
+## Retrieve updates
+
+GET: /automationstatus/playbook/
+
+GET: /automationstatus/playbook/1
+
+
+
+
diff --git a/src/automationstatus/__init__.py b/src/automationstatus/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/automationstatus/__init__.py
diff --git a/src/automationstatus/admin.py b/src/automationstatus/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/src/automationstatus/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/src/automationstatus/apps.py b/src/automationstatus/apps.py
new file mode 100644
index 0000000..0df5294
--- /dev/null
+++ b/src/automationstatus/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AutomationStatusConfig(AppConfig):
+ name = 'automationstatus'
diff --git a/src/automationstatus/crons.py b/src/automationstatus/crons.py
new file mode 100644
index 0000000..da1d59d
--- /dev/null
+++ b/src/automationstatus/crons.py
@@ -0,0 +1,151 @@
+from .models import DeploymentWorkflow, Summary
+from .models import OrchestrationWorkflow
+from datetime import datetime
+from django.apps import apps
+from django.db import connection
+from django.core import serializers
+from django.utils import timezone
+
+
+def count():
+ queryset = DeploymentWorkflow.objects.all()
+
+ in_progress_clusters = 0
+ failed_clusters = 0
+ completed_clusters = 0
+ ingested = 0
+ online = 0
+ bmc = 0
+ firmware_scheduled = 0
+ firmware_upgraded = 0
+ wr_scheduled = 0
+ wr_installed = 0
+
+ for workflow in queryset:
+ if workflow.wr_installed:
+ completed_clusters = completed_clusters + 1
+ else:
+ in_progress_clusters = in_progress_clusters + 1
+ # summary.failed = failed_clusters
+
+ if workflow.ingested:
+ ingested = ingested + 1
+
+ if workflow.online:
+ online = online + 1
+
+ if workflow.bmc:
+ bmc = bmc + 1
+
+ if workflow.firmware_scheduled and not workflow.firmware_upgraded:
+ firmware_scheduled = firmware_scheduled + 1
+
+ if workflow.firmware_upgraded:
+ firmware_upgraded = firmware_upgraded + 1
+
+ if workflow.wr_scheduled and not workflow.wr_installed:
+ wr_scheduled = wr_scheduled + 1
+
+ if workflow.wr_installed:
+ wr_installed = wr_installed + 1
+
+
+ summary = Summary()
+ summary.clusters = len(queryset)
+ summary.in_progress = in_progress_clusters
+ summary.completed = completed_clusters
+ summary.ingested = ingested
+ summary.online = online
+ summary.bmc = bmc
+ summary.firmware_scheduled = firmware_scheduled
+ summary.firmware_upgraded = firmware_upgraded
+ summary.wr_scheduled = wr_scheduled
+ summary.wr_installed = wr_installed
+ summary.created_at = timezone.now()
+ print(summary)
+ try:
+ summary.save()
+ except Exception as e:
+ print(e)
+
+
+def summary():
+ print("**** running cron: summary **** : {}".format(datetime.now()))
+ servers = apps.get_model('caas', 'Server')
+ query_set = servers.objects.select_related('cluster').select_related('blade')
+ for server in query_set:
+ # Set server id and date
+ status = DeploymentWorkflow()
+ status.cluster = server.cluster
+ status.created_at = timezone.now()
+ # CIQ ingestion
+ status.ingested = True
+ # Server online check
+ status.online = True if server.blade_id else False
+ #BMC done check
+ if server.pxe_mac_address and server.intel_nic_firmware_version:
+ status.bmc = True
+ # Check Firmware schedule and installation status
+ # Get server list from
+ Server = apps.get_model('caas', 'Server')
+ server = Server.objects.get(cluster=server.cluster)
+ FirmwareUpgradeSchedule = apps.get_model('caas', 'FirmwareUpgradeSchedule')
+ try:
+ firmware_schedule = FirmwareUpgradeSchedule.objects.get(server=server)
+ # Firmware installation Scheduled and completiong check
+ status.firmware_scheduled = True if firmware_schedule.date_scheduled else False
+ status.firmware_upgraded = True if firmware_schedule.date_completed else False
+ except FirmwareUpgradeSchedule.DoesNotExist:
+ status.firmware_schedule = False
+ status.firmware_upgraded = False
+ except Exception as e:
+ print(e)
+
+ # Check WindRiver schedule and installation status
+ WrInstallSchedule = apps.get_model('caas', 'WrInstallSchedule')
+ try:
+ wr_schedule = WrInstallSchedule.objects.select_related('cluster').get(cluster=server.cluster)
+ # WindRiver installation Scheduled and completiong check
+ status.wr_scheduled = True if wr_schedule.date_scheduled else False
+ status.wr_installed = True if wr_schedule.date_completed else False
+ except WrInstallSchedule.DoesNotExist:
+ status.wr_scheduled = False
+ status.wr_installed = False
+ except Exception as e:
+ print(e)
+ # Save status object
+ status.save()
+ count()
+
+def orchestration_summary():
+ print("**** running cron: orchestration_summary **** : {}".format(datetime.now()))
+ regions = apps.get_model('orchestration', 'remoteregionsetup')
+ status = OrchestrationWorkflow()
+
+ # Namespace count and Timestamp
+ status.namespaces = regions.objects.count()
+ status.created_at = timezone.now()
+
+ # Service accounts
+ status.orch_service_account = regions.objects.filter(serviceaccount__in=['orchestration-sa', 'SVC-FE-Atlas']).count()
+ status.edge_eng_service_account = regions.objects.filter(serviceaccount='SVC-Edge-Eng').count() + regions.objects.filter(serviceaccount__contains='application-sa-').count()
+ status.samsung_service_account = regions.objects.filter(serviceaccount='samsung-sa').count()
+
+ # Orchestration Kubeconfigs
+ status.orch_kubeconfig = regions.objects.filter(serviceaccount__in=['orchestration-sa', 'SVC-FE-Atlas']).exclude(kubeconfig='').count()
+
+ # Edge engineering Kubeconfigs
+ status.edge_eng_kubeconfig = regions.objects.filter(serviceaccount='SVC-Edge-Eng').exclude(kubeconfig='').count()
+ status.edge_eng_kubeconfig += regions.objects.filter(serviceaccount__contains='application-sa-').exclude(kubeconfig='').count()
+
+ # Samsung Kubeconfigs
+ status.samsung_kubeconfig= regions.objects.filter(serviceaccount__in=['orchestration-sa', 'SVC-FE-Atlas']).exclude(kubeconfig='').count()
+
+ # Uncomment and use once Dev completes tracking on his side
+ # Setting to 0 for now
+ status.data_network_setup = 0
+
+ status.save()
+
+
+
diff --git a/src/automationstatus/migrations/0001_initial.py b/src/automationstatus/migrations/0001_initial.py
new file mode 100644
index 0000000..fe2cd95
--- /dev/null
+++ b/src/automationstatus/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.1.1 on 2020-09-08 04:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AutomationStatus',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('playbook_name', models.CharField(blank=True, max_length=255)),
+ ('status', models.CharField(blank=True, max_length=50)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255)),
+ ('cluster_name', models.CharField(blank=True, max_length=255)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0002_auto_20200908_1720.py b/src/automationstatus/migrations/0002_auto_20200908_1720.py
new file mode 100644
index 0000000..12a4044
--- /dev/null
+++ b/src/automationstatus/migrations/0002_auto_20200908_1720.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.1.1 on 2020-09-08 17:20
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='automationstatus',
+ name='fuze_spm_site_id',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AddField(
+ model_name='automationstatus',
+ name='fuze_spm_site_name',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0003_deploymentworkflow_summary.py b/src/automationstatus/migrations/0003_deploymentworkflow_summary.py
new file mode 100644
index 0000000..1fc490f
--- /dev/null
+++ b/src/automationstatus/migrations/0003_deploymentworkflow_summary.py
@@ -0,0 +1,39 @@
+# Generated by Django 3.1.1 on 2020-10-20 01:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0022_auto_20201001_1232'),
+ ('automationstatus', '0002_auto_20200908_1720'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DeploymentWorkflow',
+ fields=[
+ ('cluster', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='caas.cluster')),
+ ('ingested', models.BooleanField(blank=True, default=False, null=True)),
+ ('online', models.BooleanField(blank=True, default=False, null=True)),
+ ('bmc', models.BooleanField(blank=True, default=False, null=True)),
+ ('firmware_wr_scheduling', models.BooleanField(blank=True, default=False, null=True)),
+ ('firmware_upgraded', models.BooleanField(blank=True, default=False, null=True)),
+ ('wr_installed', models.BooleanField(blank=True, default=False, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Summary',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('servers', models.PositiveIntegerField()),
+ ('in_progress', models.PositiveIntegerField()),
+ ('completed', models.PositiveIntegerField()),
+ ('failed', models.PositiveIntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0004_auto_20201020_0232.py b/src/automationstatus/migrations/0004_auto_20201020_0232.py
new file mode 100644
index 0000000..410f8ac
--- /dev/null
+++ b/src/automationstatus/migrations/0004_auto_20201020_0232.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.1 on 2020-10-20 02:32
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0022_auto_20201001_1232'),
+ ('automationstatus', '0003_deploymentworkflow_summary'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deploymentworkflow',
+ name='cluster',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='caas.cluster'),
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0005_auto_20201020_0253.py b/src/automationstatus/migrations/0005_auto_20201020_0253.py
new file mode 100644
index 0000000..0ca406f
--- /dev/null
+++ b/src/automationstatus/migrations/0005_auto_20201020_0253.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.1 on 2020-10-20 02:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0004_auto_20201020_0232'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='summary',
+ old_name='servers',
+ new_name='clusters',
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0006_auto_20201020_1416.py b/src/automationstatus/migrations/0006_auto_20201020_1416.py
new file mode 100644
index 0000000..2e4804b
--- /dev/null
+++ b/src/automationstatus/migrations/0006_auto_20201020_1416.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.1 on 2020-10-20 14:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0005_auto_20201020_0253'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deploymentworkflow',
+ name='created_at',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0007_auto_20201026_0129.py b/src/automationstatus/migrations/0007_auto_20201026_0129.py
new file mode 100644
index 0000000..dd7f547
--- /dev/null
+++ b/src/automationstatus/migrations/0007_auto_20201026_0129.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.1.1 on 2020-10-26 01:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0006_auto_20201020_1416'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='deploymentworkflow',
+ old_name='firmware_wr_scheduling',
+ new_name='firmware_scheduled',
+ ),
+ migrations.AddField(
+ model_name='deploymentworkflow',
+ name='wr_scheduled',
+ field=models.BooleanField(blank=True, default=False, null=True),
+ ),
+ migrations.AlterField(
+ model_name='summary',
+ name='created_at',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='summary',
+ name='failed',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0008_auto_20201030_1654.py b/src/automationstatus/migrations/0008_auto_20201030_1654.py
new file mode 100644
index 0000000..d775f5d
--- /dev/null
+++ b/src/automationstatus/migrations/0008_auto_20201030_1654.py
@@ -0,0 +1,48 @@
+# Generated by Django 3.1.1 on 2020-10-30 16:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0007_auto_20201026_0129'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='summary',
+ name='bmc',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='firmware_scheduled',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='firmware_upgraded',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='ingested',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='online',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='wr_installed',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ migrations.AddField(
+ model_name='summary',
+ name='wr_scheduled',
+ field=models.PositiveIntegerField(default=0, null=True),
+ ),
+ ]
diff --git a/src/automationstatus/migrations/0009_orchestrationworkflow.py b/src/automationstatus/migrations/0009_orchestrationworkflow.py
new file mode 100644
index 0000000..78aa2b4
--- /dev/null
+++ b/src/automationstatus/migrations/0009_orchestrationworkflow.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.1.2 on 2020-12-09 19:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('automationstatus', '0008_auto_20201030_1654'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='OrchestrationWorkflow',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('namespaces', models.PositiveIntegerField()),
+ ('orch_service_account', models.PositiveIntegerField()),
+ ('edge_eng_service_account', models.PositiveIntegerField()),
+ ('samsung_service_account', models.PositiveIntegerField()),
+ ('orch_kubeconfig', models.PositiveIntegerField()),
+ ('edge_eng_kubeconfig', models.PositiveIntegerField()),
+ ('samsung_kubeconfig', models.PositiveIntegerField()),
+ ('data_network_setup', models.PositiveIntegerField()),
+ ('created_at', models.DateTimeField(blank=True, null=True)),
+ ],
+ ),
+ ]
diff --git a/src/automationstatus/migrations/__init__.py b/src/automationstatus/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/automationstatus/migrations/__init__.py
diff --git a/src/automationstatus/models.py b/src/automationstatus/models.py
new file mode 100644
index 0000000..9e249c6
--- /dev/null
+++ b/src/automationstatus/models.py
@@ -0,0 +1,67 @@
+from django.db import models
+from caas.models import Cluster
+
+class AutomationStatus(models.Model):
+ playbook_name = models.CharField(max_length=255, blank=True, null=False)
+ status = models.CharField(max_length=50, blank=True, null=False)
+ ilo_host_address = models.CharField(max_length=255, blank=True, null=False)
+ cluster_name = models.CharField(max_length=255, blank=True, null=False)
+ fuze_spm_site_name = models.CharField(max_length=255, blank=True, null=False)
+ fuze_spm_site_id = models.CharField(max_length=255, blank=True, null=False)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ str = "pk: %s, playbook_name: %s, status: %s, cluster_name: %s, cluster_name: %s, fuze_spm_site_name: %s, fuze_spm_site_id: %s"
+ return str % (self.pk, self.playbook_name, self.status, self.cluster_name, self.fuze_spm_site_name, self.fuze_spm_site_id, self.created_at)
+
+
+class Summary(models.Model):
+ clusters = models.PositiveIntegerField(null=False)
+ in_progress = models.PositiveIntegerField(null=False)
+ completed = models.PositiveIntegerField(null=False)
+ ingested = models.PositiveIntegerField(default=0, null=True)
+ online = models.PositiveIntegerField(default=0, null=True)
+ bmc = models.PositiveIntegerField(default=0, null=True)
+ firmware_scheduled = models.PositiveIntegerField(default=0, null=True)
+ firmware_upgraded = models.PositiveIntegerField(default=0, null=True)
+ wr_scheduled = models.PositiveIntegerField(default=0, null=True)
+ wr_installed = models.PositiveIntegerField(default=0, null=True)
+ failed = models.PositiveIntegerField(default=0, null=True)
+ created_at = models.DateTimeField(blank=True, null=True)
+
+ def __str__(self):
+ str = "pk: %s, clusters: %s, in_progress: %s, completed: %s, failed: %s, created_at: %s"
+ return str % (self.pk, self.clusters, self.in_progress, self.completed, self.failed, self.created_at)
+
+
+class DeploymentWorkflow(models.Model):
+ cluster = models.OneToOneField("caas.Cluster", on_delete=models.CASCADE, primary_key=True)
+ ingested = models.BooleanField(blank=True, default=False, null=True)
+ online = models.BooleanField(blank=True, default=False, null=True)
+ bmc = models.BooleanField(blank=True, default=False, null=True)
+ firmware_scheduled = models.BooleanField(blank=True, default=False, null=True)
+ firmware_upgraded = models.BooleanField(blank=True, default=False, null=True)
+ wr_scheduled = models.BooleanField(blank=True, default=False, null=True)
+ wr_installed = models.BooleanField(blank=True, default=False, null=True)
+ created_at = models.DateTimeField(blank=True, null=True)
+
+ def __str__(self):
+ str = "pk: %s, cluster: %s, ingested: %s, online: %s, bmc: %s, firmware_upgraded: %s, wr_installed: %s, created_at: %s"
+ return str % (self.pk, self.cluster, self.ingested, self.online, self.bmc, self.firmware_upgraded, self.wr_installed, self.created_at)
+
+
+class OrchestrationWorkflow(models.Model):
+ # cluster = models.OneToOneField("caas.Cluster", on_delete=models.CASCADE, primary_key=True)
+ namespaces = models.PositiveIntegerField(null=False)
+ orch_service_account = models.PositiveIntegerField(null=False)
+ edge_eng_service_account = models.PositiveIntegerField(null=False)
+ samsung_service_account = models.PositiveIntegerField(null=False)
+ orch_kubeconfig = models.PositiveIntegerField(null=False)
+ edge_eng_kubeconfig = models.PositiveIntegerField(null=False)
+ samsung_kubeconfig= models.PositiveIntegerField(null=False)
+ data_network_setup = models.PositiveIntegerField(null=False) # Update once Dev completes tracking
+ created_at = models.DateTimeField(blank=True, null=True)
+
+ def __str__(self):
+ str = "pk: %s, namespaces: %s, created_at: %s"
+ return str % (self.pk, self.namespaces, self.created_at) \ No newline at end of file
diff --git a/src/automationstatus/serializers.py b/src/automationstatus/serializers.py
new file mode 100644
index 0000000..0afdf8c
--- /dev/null
+++ b/src/automationstatus/serializers.py
@@ -0,0 +1,9 @@
+from rest_framework import serializers
+
+from .models import AutomationStatus
+
+class AutomationStatusSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = AutomationStatus
+ fields = ('pk', 'ilo_host_address', 'playbook_name', 'status', 'cluster_name', 'fuze_spm_site_name', 'fuze_spm_site_id', 'created_at')
+
diff --git a/src/automationstatus/tests.py b/src/automationstatus/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/src/automationstatus/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/src/automationstatus/urls.py b/src/automationstatus/urls.py
new file mode 100644
index 0000000..5976610
--- /dev/null
+++ b/src/automationstatus/urls.py
@@ -0,0 +1,14 @@
+from django.urls import include, path
+from rest_framework import routers
+from . import views
+
+router = routers.DefaultRouter()
+router.register(r'playbook', views.AutomationStatusViewSet)
+router.register(r'playbook/(?P<playbook_name>\d+)', views.AutomationStatusViewSet)
+
+# Wire up our API using automatic URL routing.
+# Additionally, we include login URLs for the browsable API.
+urlpatterns = [
+ path('', include(router.urls)),
+ path('', include('rest_framework.urls', namespace='rest_framework'))
+] \ No newline at end of file
diff --git a/src/automationstatus/views.py b/src/automationstatus/views.py
new file mode 100644
index 0000000..df75868
--- /dev/null
+++ b/src/automationstatus/views.py
@@ -0,0 +1,19 @@
+from rest_framework import viewsets
+from caas.models import Cluster, Location, Server
+from .models import AutomationStatus
+from .serializers import AutomationStatusSerializer
+
+class AutomationStatusViewSet(viewsets.ModelViewSet):
+ queryset = AutomationStatus.objects.all().order_by('created_at')
+ serializer_class = AutomationStatusSerializer
+
+ def perform_create(self, serializer):
+ # Lookup cluster name based on the ilo_host_address
+ server = Server.objects.get(ilo_host_address=self.request.data['ilo_host_address'])
+ cluster = Cluster.objects.get(id=server.cluster_id)
+ fuze_id = cluster.location_id
+ location = Location.objects.get(id=fuze_id)
+ serializer.save(
+ cluster_name=cluster.cluster_name,
+ fuze_spm_site_name=location.fuze_spm_site_name,
+ fuze_spm_site_id=location.fuze_spm_site_id)
diff --git a/src/caas/forms.py b/src/caas/forms.py
new file mode 100644
index 0000000..d485ee6
--- /dev/null
+++ b/src/caas/forms.py
@@ -0,0 +1,110 @@
+from django import forms
+
+
+class CiqUploadForm(forms.Form):
+ filehandle = forms.FileField(label = "CIQ (in CSV format with header rows deleted)")
+ assign_parent = forms.BooleanField(label = "Assign subclouds to central controllers?", required = False)
+
+
+class CiqUpload2Form(forms.Form):
+ filehandle = forms.FileField(label = "CIQ (in CSV format, servers over CC capacity will be not be assigned parents, servers with parents won't be affected)")
+
+
+class CiqAssignOrphanSubcloudsForm(forms.Form):
+ filehandle = forms.FileField(label = "CIQ (in CSV format, servers without parents will be assigned to new controllers if any)")
+
+
+class PasswordUploadForm(forms.Form):
+ filehandle = forms.FileField(label = "Password CSV (with header rows deleted)")
+
+
+class SerialNumberForm(forms.Form):
+ vendor = forms.CharField(label = "Vendor")
+ serial_number = forms.CharField(label = "Serial number")
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+
+
+class AutoremediateForm(forms.Form):
+ ilo_host_address_list = forms.CharField(label = "BMC IP addresses separated by space")
+
+
+class DnsForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+
+
+class QueueCustomForm(forms.Form):
+ key = forms.CharField(label = "key")
+ playbook_key = forms.CharField(label = "playbook_key")
+ target = forms.CharField(label = "target")
+ cluster_name = forms.CharField(label = "cluster-name (optional)")
+
+
+class MacAddressForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+ pxe_mac_address = forms.CharField(label = "BMC MAC address")
+ intel_nic_firmware_version = forms.CharField(label = "Intel NIC firmware version")
+
+
+class AdminPasswordForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+ icinga_poll_status = forms.CharField(label = "Icinga poll status")
+
+
+class NicFirmwareVersionForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+ nic_firmware_version = forms.CharField(label = "NIC firmware version")
+
+
+class NicFirmwareUpgradeForm(forms.Form):
+ ilo_host_address_list = forms.CharField(label = "BMC IP addresses separated by space")
+
+
+class NicFirmwareUpgradeNowForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+ icinga_poll_status = forms.CharField(label = "Icinga poll status")
+
+
+class WrInstallForm(forms.Form):
+ ilo_host_address_list = forms.CharField(label = "BMC IP addresses separated by space")
+
+
+class WrInstallNowForm(forms.Form):
+ ilo_host_address = forms.CharField(label = "BMC IP address")
+ icinga_poll_status = forms.CharField(label = "Icinga poll status")
+
+
+class PushButtonForm(forms.Form):
+ pass
+
+
+class WrRemediateForm(forms.Form):
+ cluster_name_list = forms.CharField(label = "Cluster names separated by space")
+ playbook = forms.ChoiceField(label = "Playbook dict key in settings.py",
+ choices = (("wr", "wr"),
+ ("wr_remediate", "wr_remediate"),
+ ("wr_wipedisk", "wr_wipedisk"),
+ ("wr_ptp", "wr_ptp"),
+ ("wr_ptp_config", "wr_ptp_config"),
+ ("wr_unlock", "wr_unlock"),
+ ("wr_reboot", "wr_reboot"),
+ ("wr_wrap", "wr_wrap")))
+
+
+class WrRebootForm(forms.Form):
+ namespace_name_list = forms.CharField(label = "Namespace names separated by space")
+
+
+#created Oct30 2020 for Raj reboot testing delete later - Soda
+class MockWrRebootForm(forms.Form):
+ namespace_name_list = forms.CharField(label = "Mock namespace names separated by space")
+
+
+class PlaybookReportForm(forms.Form):
+ git = forms.CharField(label = "git remote")
+ playbook = forms.CharField(label = "Playbook dict key in settings.py")
+ target = forms.CharField(label = "Target")
+ failed = forms.CharField(label = "Failed count (0 or 1)")
+
+
+class SubcloudForm(forms.Form):
+ fuze_id = forms.CharField(label = "FUZE ID")
diff --git a/src/caas/migrations/0001_initial.py b/src/caas/migrations/0001_initial.py
index 409c48f..d524cf3 100644
--- a/src/caas/migrations/0001_initial.py
+++ b/src/caas/migrations/0001_initial.py
@@ -1,7 +1,6 @@
-# Generated by Django 3.0.5 on 2020-05-04 16:28
+# Generated by Django 3.0.8 on 2020-07-07 03:12
from django.db import migrations, models
-import django.db.models.deletion
class Migration(migrations.Migration):
@@ -13,62 +12,81 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
- name='VcpfeLocation',
+ name='FirmwareBatch',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('fuze_spm_site_id', models.CharField(max_length=16)),
- ('fuze_spm_site_name', models.CharField(max_length=255)),
- ('fuze_spm_market', models.CharField(max_length=255)),
- ('fuze_spm_submarket', models.CharField(max_length=255)),
- ('fuze_spm_site_type', models.CharField(choices=[('DRAN', 'DRAN'), ('CRAN', 'CRAN')], max_length=4)),
- ('central_controller_sap_clli', models.CharField(max_length=255)),
- ('vcu_sap_clli', models.CharField(max_length=255)),
- ('csr_hostname', models.CharField(max_length=255)),
- ('f1c_f1u_subnet', models.CharField(max_length=255)),
- ('f1c_f1u_default_gateway', models.CharField(max_length=255)),
- ('oam_subnet', models.CharField(max_length=255)),
- ('oam_default_gateway', models.CharField(max_length=255)),
- ('mgmt_subnet', models.CharField(max_length=255)),
- ('mgmt_default_gateway', models.CharField(max_length=255)),
- ('ilo_subnet', models.CharField(max_length=255)),
- ('ilo_default_gateway', models.CharField(max_length=255)),
- ('fhgw_subnet', models.CharField(max_length=255)),
- ('fhgw_default_gateway', models.CharField(max_length=255)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_username', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password', models.CharField(blank=True, max_length=255, null=True)),
],
+ options={
+ 'db_table': 'caas_firmwarebatch',
+ 'managed': False,
+ },
),
migrations.CreateModel(
- name='Vlan',
+ name='IcingaBmc',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('host_vlan', models.PositiveSmallIntegerField()),
- ('oam_vlan', models.PositiveSmallIntegerField()),
- ('mgmt_vlan', models.PositiveSmallIntegerField()),
+ ('ilo_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('csr_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('latitude', models.CharField(blank=True, max_length=255, null=True)),
+ ('longitude', models.CharField(blank=True, max_length=255, null=True)),
+ ('serial_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('vendor_name', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password_admin_default', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password_admin', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_username', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password', models.CharField(blank=True, max_length=255, null=True)),
],
+ options={
+ 'db_table': 'caas_icingabmc',
+ 'managed': False,
+ },
),
migrations.CreateModel(
- name='VcpfeServer',
+ name='IcingaRouter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('server_number', models.PositiveSmallIntegerField()),
- ('server_hostname', models.CharField(max_length=255)),
- ('cluster_name', models.CharField(max_length=255)),
- ('f1u_address', models.CharField(max_length=255)),
- ('f1c_address', models.CharField(max_length=255)),
- ('oam_host_address', models.CharField(max_length=255)),
- ('oam_vip_address', models.CharField(max_length=255)),
- ('mgmt_address_range_start', models.CharField(max_length=255)),
- ('mgmt_address_range_end', models.CharField(max_length=255)),
- ('ilo_host_address', models.CharField(max_length=255)),
- ('fhgw_host_address', models.CharField(max_length=255)),
- ('vlan_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Vlan')),
+ ('csr_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
],
+ options={
+ 'db_table': 'caas_icingarouter',
+ 'managed': False,
+ },
),
migrations.CreateModel(
- name='Namespace',
+ name='WrBatch',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('namespace_name', models.CharField(max_length=255)),
- ('server_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.VcpfeServer')),
+ ('cluster_id', models.IntegerField()),
+ ('parent_oam_vip_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('parent_oam_vip_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('pxe_mac_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_vip_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_start', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_end', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_subnet', models.CharField(blank=True, max_length=255, null=True)),
+ ('host_vlan', models.PositiveSmallIntegerField()),
+ ('oam_vlan', models.PositiveSmallIntegerField()),
+ ('mgmt_vlan', models.PositiveSmallIntegerField()),
+ ('cluster_name', models.CharField(blank=True, max_length=255, null=True)),
+ ('vendor_name', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_username', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password', models.CharField(blank=True, max_length=255, null=True)),
],
+ options={
+ 'db_table': 'caas_wrbatch',
+ 'managed': False,
+ },
),
]
diff --git a/src/caas/migrations/0002_auto_20200706_2113.py b/src/caas/migrations/0002_auto_20200706_2113.py
new file mode 100644
index 0000000..553a4e4
--- /dev/null
+++ b/src/caas/migrations/0002_auto_20200706_2113.py
@@ -0,0 +1,260 @@
+# Generated by Django 3.0.8 on 2020-07-07 03:13
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Blade',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('serial_number', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CellSiteRouter',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('csr_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_subnet', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_subnet', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_subnet', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Chassis',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('serial_number', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Cluster',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('cluster_name', models.CharField(max_length=255)),
+ ('oam_vip_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_vip_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_start', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_end', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Credential',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('bmc_password_admin_default', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password_admin', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_username', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='FirmwareUpgradeSchedule',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date_scheduled', models.DateTimeField(blank=True, null=True)),
+ ('date_completed', models.DateTimeField(blank=True, null=True)),
+ ('date_last_failed', models.DateTimeField(blank=True, null=True)),
+ ('kirke_ticket_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_status', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_completed', models.DateTimeField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Location',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('fuze_spm_site_id', models.CharField(max_length=16)),
+ ('fuze_spm_site_name', models.CharField(max_length=255)),
+ ('fuze_spm_market', models.CharField(max_length=255)),
+ ('fuze_spm_submarket', models.CharField(max_length=255)),
+ ('fuze_spm_site_type', models.CharField(choices=[('DRAN', 'DRAN'), ('CRAN', 'CRAN')], max_length=4)),
+ ('latitude', models.DecimalField(blank=True, decimal_places=9, max_digits=12, null=True)),
+ ('longitude', models.DecimalField(blank=True, decimal_places=9, max_digits=12, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Namespace',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('namespace_name', models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='SapClli',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('clli', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Server',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('server_number', models.PositiveSmallIntegerField(blank=True, null=True)),
+ ('oam_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_hostname', models.CharField(max_length=255)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('pxe_mac_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('csr_vdu_port_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('csr_oam_port_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('csr_mgmt_port_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('csr_nfs_port_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('intel_nic_firmware_version', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Vendor',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('vendor_name', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Vlan',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('host_vlan', models.PositiveSmallIntegerField()),
+ ('oam_vlan', models.PositiveSmallIntegerField()),
+ ('mgmt_vlan', models.PositiveSmallIntegerField()),
+ ('nfs_vlan', models.PositiveSmallIntegerField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='WrInstallSchedule',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date_scheduled', models.DateTimeField(blank=True, null=True)),
+ ('date_completed', models.DateTimeField(blank=True, null=True)),
+ ('date_last_failed', models.DateTimeField(blank=True, null=True)),
+ ('kirke_ticket_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_status', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_completed', models.DateTimeField(blank=True, null=True)),
+ ('cluster', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Cluster')),
+ ],
+ ),
+ migrations.AddConstraint(
+ model_name='vlan',
+ constraint=models.UniqueConstraint(fields=('host_vlan', 'oam_vlan', 'mgmt_vlan', 'nfs_vlan'), name='unique_vlan'),
+ ),
+ migrations.AddConstraint(
+ model_name='vendor',
+ constraint=models.UniqueConstraint(fields=('vendor_name',), name='unique_vendor'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='blade',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Blade'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='cluster',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Cluster'),
+ ),
+ migrations.AddConstraint(
+ model_name='sapclli',
+ constraint=models.UniqueConstraint(fields=('clli',), name='unique_sapclli'),
+ ),
+ migrations.AddConstraint(
+ model_name='namespace',
+ constraint=models.UniqueConstraint(fields=('namespace_name',), name='unique_namespace'),
+ ),
+ migrations.AddConstraint(
+ model_name='location',
+ constraint=models.UniqueConstraint(fields=('fuze_spm_site_id', 'fuze_spm_site_name', 'fuze_spm_market', 'fuze_spm_submarket', 'fuze_spm_site_type'), name='unique_location'),
+ ),
+ migrations.AddField(
+ model_name='firmwareupgradeschedule',
+ name='server',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Server'),
+ ),
+ migrations.AddConstraint(
+ model_name='credential',
+ constraint=models.UniqueConstraint(fields=('bmc_password_admin_default', 'bmc_password_admin', 'bmc_username', 'bmc_password'), name='unique_credential'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='csr',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.CellSiteRouter'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='location',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Location'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='namespace',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Namespace'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='parent_cluster',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Cluster'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='sap_clli',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.SapClli'),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='vlan',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Vlan'),
+ ),
+ migrations.AddConstraint(
+ model_name='chassis',
+ constraint=models.UniqueConstraint(fields=('serial_number',), name='unique_chassis'),
+ ),
+ migrations.AddConstraint(
+ model_name='cellsiterouter',
+ constraint=models.UniqueConstraint(fields=('csr_hostname',), name='unique_cellsiterouter'),
+ ),
+ migrations.AddField(
+ model_name='blade',
+ name='chassis',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Chassis'),
+ ),
+ migrations.AddField(
+ model_name='blade',
+ name='credential',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Credential'),
+ ),
+ migrations.AddField(
+ model_name='blade',
+ name='vendor',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Vendor'),
+ ),
+ migrations.AddConstraint(
+ model_name='wrinstallschedule',
+ constraint=models.UniqueConstraint(fields=('cluster_id',), name='unique_wrinstallschedule'),
+ ),
+ migrations.AddConstraint(
+ model_name='server',
+ constraint=models.UniqueConstraint(fields=('ilo_host_address',), name='unique_server_byiloaddress'),
+ ),
+ migrations.AddConstraint(
+ model_name='firmwareupgradeschedule',
+ constraint=models.UniqueConstraint(fields=('server_id',), name='unique_firmwareupgradeschedule'),
+ ),
+ migrations.AddConstraint(
+ model_name='cluster',
+ constraint=models.UniqueConstraint(fields=('cluster_name',), name='unique_cluster'),
+ ),
+ migrations.AddConstraint(
+ model_name='blade',
+ constraint=models.UniqueConstraint(fields=('serial_number',), name='unique_blade'),
+ ),
+ ]
diff --git a/src/caas/migrations/0003_auto_20200706_2113.py b/src/caas/migrations/0003_auto_20200706_2113.py
new file mode 100644
index 0000000..fb3c69d
--- /dev/null
+++ b/src/caas/migrations/0003_auto_20200706_2113.py
@@ -0,0 +1,35 @@
+# Generated by Django 3.0.8 on 2020-07-07 03:13
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0002_auto_20200706_2113'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n l.id as location_id,\n csr.oam_default_gateway,\n l.latitude,\n l.longitude,\n csr.csr_hostname,\n b.serial_number,\n v.vendor_name,\n cr.bmc_password_admin_default,\n cr.bmc_password_admin,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_icingabmc'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('', 'caas_icingabmc'),
+ atomic=False,
+ ),
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select location_id as id,\n csr_hostname,\n oam_default_gateway\nfrom caas_icingabmc\ngroup by location_id,\n csr_hostname,\n oam_default_gateway', 'caas_icingarouter'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('', 'caas_icingarouter'),
+ atomic=False,
+ ),
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_firmwarebatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('', 'caas_firmwarebatch'),
+ atomic=False,
+ ),
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration("select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null\n and s.intel_nic_firmware_version = '1.2585.0'", 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0004_delete_wrbatch.py b/src/caas/migrations/0004_delete_wrbatch.py
new file mode 100644
index 0000000..a3e0e4d
--- /dev/null
+++ b/src/caas/migrations/0004_delete_wrbatch.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.0.8 on 2020-07-11 04:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0003_auto_20200706_2113'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='WrBatch',
+ ),
+ ]
diff --git a/src/caas/migrations/0005_auto_20200710_2235.py b/src/caas/migrations/0005_auto_20200710_2235.py
new file mode 100644
index 0000000..8ca77db
--- /dev/null
+++ b/src/caas/migrations/0005_auto_20200710_2235.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-11 04:35
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0004_delete_wrbatch'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n s.intel_nic_firmware_version,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_firmwarebatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_firmwarebatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0006_wrbatch.py b/src/caas/migrations/0006_wrbatch.py
new file mode 100644
index 0000000..52cb599
--- /dev/null
+++ b/src/caas/migrations/0006_wrbatch.py
@@ -0,0 +1,43 @@
+# Generated by Django 3.0.8 on 2020-07-11 04:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0005_auto_20200710_2235'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='WrBatch',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('cluster_id', models.IntegerField()),
+ ('parent_oam_vip_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('parent_oam_vip_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('pxe_mac_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('ilo_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_hostname', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_host_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_vip_address', models.CharField(blank=True, max_length=255, null=True)),
+ ('oam_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_start', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_address_range_end', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_default_gateway', models.CharField(blank=True, max_length=255, null=True)),
+ ('mgmt_subnet', models.CharField(blank=True, max_length=255, null=True)),
+ ('host_vlan', models.PositiveSmallIntegerField()),
+ ('oam_vlan', models.PositiveSmallIntegerField()),
+ ('mgmt_vlan', models.PositiveSmallIntegerField()),
+ ('cluster_name', models.CharField(blank=True, max_length=255, null=True)),
+ ('vendor_name', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_username', models.CharField(blank=True, max_length=255, null=True)),
+ ('bmc_password', models.CharField(blank=True, max_length=255, null=True)),
+ ],
+ options={
+ 'db_table': 'caas_wrbatch',
+ 'managed': False,
+ },
+ ),
+ ]
diff --git a/src/caas/migrations/0007_auto_20200711_1126.py b/src/caas/migrations/0007_auto_20200711_1126.py
new file mode 100644
index 0000000..a8911d6
--- /dev/null
+++ b/src/caas/migrations/0007_auto_20200711_1126.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-11 17:26
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0006_wrbatch'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration("select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null\n and s.intel_nic_firmware_version = '1.2585.0'", 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0008_auto_20200711_1127.py b/src/caas/migrations/0008_auto_20200711_1127.py
new file mode 100644
index 0000000..8808699
--- /dev/null
+++ b/src/caas/migrations/0008_auto_20200711_1127.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-11 17:27
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0007_auto_20200711_1126'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0009_auto_20200715_1630.py b/src/caas/migrations/0009_auto_20200715_1630.py
new file mode 100644
index 0000000..204b615
--- /dev/null
+++ b/src/caas/migrations/0009_auto_20200715_1630.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.8 on 2020-07-15 22:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0008_auto_20200711_1127'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='location',
+ name='fuze_spm_site_type',
+ field=models.CharField(choices=[('SAP', 'SAP'), ('DRAN', 'DRAN'), ('CRAN', 'CRAN')], max_length=4),
+ ),
+ ]
diff --git a/src/caas/migrations/0010_auto_20200720_1413.py b/src/caas/migrations/0010_auto_20200720_1413.py
new file mode 100644
index 0000000..544b7f3
--- /dev/null
+++ b/src/caas/migrations/0010_auto_20200720_1413.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-20 20:13
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0009_auto_20200715_1630'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0011_auto_20200720_1525.py b/src/caas/migrations/0011_auto_20200720_1525.py
new file mode 100644
index 0000000..158bdb1
--- /dev/null
+++ b/src/caas/migrations/0011_auto_20200720_1525.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-20 21:25
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0010_auto_20200720_1413'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0012_auto_20200721_1540.py b/src/caas/migrations/0012_auto_20200721_1540.py
new file mode 100644
index 0000000..dc5169b
--- /dev/null
+++ b/src/caas/migrations/0012_auto_20200721_1540.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-07-21 21:40
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0011_auto_20200720_1525'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0013_auto_20200728_2359.py b/src/caas/migrations/0013_auto_20200728_2359.py
new file mode 100644
index 0000000..83e8998
--- /dev/null
+++ b/src/caas/migrations/0013_auto_20200728_2359.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.0.8 on 2020-07-29 05:59
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0012_auto_20200721_1540'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PatchSchedule',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date_scheduled', models.DateTimeField(blank=True, null=True)),
+ ('date_completed', models.DateTimeField(blank=True, null=True)),
+ ('date_last_failed', models.DateTimeField(blank=True, null=True)),
+ ('kirke_ticket_number', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_status', models.CharField(blank=True, max_length=255, null=True)),
+ ('kirke_ticket_completed', models.DateTimeField(blank=True, null=True)),
+ ('cluster', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='caas.Cluster')),
+ ],
+ ),
+ migrations.AddConstraint(
+ model_name='patchschedule',
+ constraint=models.UniqueConstraint(fields=('cluster_id',), name='unique_patchschedule'),
+ ),
+ ]
diff --git a/src/caas/migrations/0014_auto_20200803_1931.py b/src/caas/migrations/0014_auto_20200803_1931.py
new file mode 100644
index 0000000..793d1c3
--- /dev/null
+++ b/src/caas/migrations/0014_auto_20200803_1931.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.0.8 on 2020-08-04 01:31
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0013_auto_20200728_2359'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0015_auto_20200804_1404.py b/src/caas/migrations/0015_auto_20200804_1404.py
new file mode 100644
index 0000000..11c1150
--- /dev/null
+++ b/src/caas/migrations/0015_auto_20200804_1404.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1 on 2020-08-04 20:04
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0014_auto_20200803_1931'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0016_auto_20200804_1559.py b/src/caas/migrations/0016_auto_20200804_1559.py
new file mode 100644
index 0000000..64fd575
--- /dev/null
+++ b/src/caas/migrations/0016_auto_20200804_1559.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1 on 2020-08-04 21:59
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0015_auto_20200804_1404'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n s.intel_nic_firmware_version,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0017_cluster_is_central_controller.py b/src/caas/migrations/0017_cluster_is_central_controller.py
new file mode 100644
index 0000000..d2e500d
--- /dev/null
+++ b/src/caas/migrations/0017_cluster_is_central_controller.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.8 on 2020-09-24 17:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0016_auto_20200804_1559'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cluster',
+ name='is_central_controller',
+ field=models.SmallIntegerField(blank=True, default=0, null=True),
+ ),
+ ]
diff --git a/src/caas/migrations/0018_auto_20200925_1855.py b/src/caas/migrations/0018_auto_20200925_1855.py
new file mode 100644
index 0000000..e8bc107
--- /dev/null
+++ b/src/caas/migrations/0018_auto_20200925_1855.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.0.8 on 2020-09-25 18:55
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0017_cluster_is_central_controller'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select l.id as id,\n csr.csr_hostname as csr_hostname,\n csr.oam_default_gateway as oam_default_gateway\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.is_central_controller=0\ngroup by l.id,\n csr.csr_hostname,\n csr.oam_default_gateway', 'caas_icingarouter'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select location_id as id,\n csr_hostname,\n oam_default_gateway\nfrom caas_icingabmc\ngroup by location_id,\n csr_hostname,\n oam_default_gateway', 'caas_icingarouter'),
+ atomic=False,
+ ),
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n l.id as location_id,\n csr.oam_default_gateway,\n l.latitude,\n l.longitude,\n csr.csr_hostname,\n b.serial_number,\n v.vendor_name,\n cr.bmc_password_admin_default,\n cr.bmc_password_admin,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.is_central_controller=0', 'caas_icingabmc'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n l.id as location_id,\n csr.oam_default_gateway,\n l.latitude,\n l.longitude,\n csr.csr_hostname,\n b.serial_number,\n v.vendor_name,\n cr.bmc_password_admin_default,\n cr.bmc_password_admin,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_icingabmc'),
+ atomic=False,
+ ),
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n s.intel_nic_firmware_version,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.is_central_controller=0', 'caas_firmwarebatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n s.intel_nic_firmware_version,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_firmwarebatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0019_cluster_is_maint_window.py b/src/caas/migrations/0019_cluster_is_maint_window.py
new file mode 100644
index 0000000..cd297c5
--- /dev/null
+++ b/src/caas/migrations/0019_cluster_is_maint_window.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.2 on 2020-10-01 17:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0018_auto_20200925_1855'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cluster',
+ name='is_maint_window',
+ field=models.SmallIntegerField(blank=True, default=0, null=True),
+ ),
+ ]
diff --git a/src/caas/migrations/0020_auto_20201001_1142.py b/src/caas/migrations/0020_auto_20201001_1142.py
new file mode 100644
index 0000000..3e238eb
--- /dev/null
+++ b/src/caas/migrations/0020_auto_20201001_1142.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.2 on 2020-10-01 17:42
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0019_cluster_is_maint_window'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n c.is_maint_window,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0021_auto_20201001_1232.py b/src/caas/migrations/0021_auto_20201001_1232.py
new file mode 100644
index 0000000..bb59147
--- /dev/null
+++ b/src/caas/migrations/0021_auto_20201001_1232.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.1.2 on 2020-10-01 18:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0020_auto_20201001_1142'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='cluster',
+ name='is_maint_window',
+ ),
+ migrations.AddField(
+ model_name='location',
+ name='maint_window_p',
+ field=models.BooleanField(blank=True, default=False, null=True),
+ ),
+ ]
diff --git a/src/caas/migrations/0022_auto_20201001_1232.py b/src/caas/migrations/0022_auto_20201001_1232.py
new file mode 100644
index 0000000..c18cf10
--- /dev/null
+++ b/src/caas/migrations/0022_auto_20201001_1232.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.2 on 2020-10-01 18:32
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0021_auto_20201001_1232'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n c.is_maint_window,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0023_auto_20201019_1631.py b/src/caas/migrations/0023_auto_20201019_1631.py
new file mode 100644
index 0000000..557884b
--- /dev/null
+++ b/src/caas/migrations/0023_auto_20201019_1631.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.2 on 2020-10-19 22:31
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0022_auto_20201001_1232'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n l.id as location_id,\n csr.oam_default_gateway,\n l.latitude,\n l.longitude,\n csr.csr_hostname,\n b.serial_number,\n v.vendor_name,\n cr.bmc_password_admin_default,\n cr.bmc_password_admin,\n cr.bmc_username,\n cr.bmc_password,\n ws.date_completed\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nleft outer join caas_wrinstallschedule ws\n on c.id = ws.cluster_id\nwhere c.is_central_controller = 0', 'caas_icingabmc'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n s.ilo_hostname,\n s.ilo_host_address,\n l.id as location_id,\n csr.oam_default_gateway,\n l.latitude,\n l.longitude,\n csr.csr_hostname,\n b.serial_number,\n v.vendor_name,\n cr.bmc_password_admin_default,\n cr.bmc_password_admin,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.is_central_controller=0', 'caas_icingabmc'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0024_auto_20201023_1556.py b/src/caas/migrations/0024_auto_20201023_1556.py
new file mode 100644
index 0000000..e6cb4a9
--- /dev/null
+++ b/src/caas/migrations/0024_auto_20201023_1556.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.2 on 2020-10-23 21:56
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0023_auto_20201019_1631'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0025_auto_20201102_1545.py b/src/caas/migrations/0025_auto_20201102_1545.py
new file mode 100644
index 0000000..b37f422
--- /dev/null
+++ b/src/caas/migrations/0025_auto_20201102_1545.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.3 on 2020-11-02 22:45
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0024_auto_20201023_1556'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0026_auto_20201110_0932.py b/src/caas/migrations/0026_auto_20201110_0932.py
new file mode 100644
index 0000000..3956e28
--- /dev/null
+++ b/src/caas/migrations/0026_auto_20201110_0932.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.1.3 on 2020-11-10 16:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0025_auto_20201102_1545'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='server',
+ name='csr_mgmt_port_number',
+ ),
+ migrations.RemoveField(
+ model_name='server',
+ name='csr_nfs_port_number',
+ ),
+ migrations.RemoveField(
+ model_name='server',
+ name='csr_oam_port_number',
+ ),
+ migrations.RemoveField(
+ model_name='server',
+ name='csr_vdu_port_number',
+ ),
+ ]
diff --git a/src/caas/migrations/0027_auto_20201211_2103.py b/src/caas/migrations/0027_auto_20201211_2103.py
new file mode 100644
index 0000000..751411b
--- /dev/null
+++ b/src/caas/migrations/0027_auto_20201211_2103.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.1.4 on 2020-12-12 04:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0026_auto_20201110_0932'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cluster',
+ name='mgmt_default_gateway',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ migrations.AddField(
+ model_name='cluster',
+ name='mgmt_subnet',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ ]
diff --git a/src/caas/migrations/0028_auto_20201212_1001.py b/src/caas/migrations/0028_auto_20201212_1001.py
new file mode 100644
index 0000000..cf6c1d0
--- /dev/null
+++ b/src/caas/migrations/0028_auto_20201212_1001.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.4 on 2020-12-12 17:01
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0027_auto_20201211_2103'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n c.mgmt_default_gateway,\n c.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n csr.mgmt_default_gateway,\n csr.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0029_auto_20201215_0832.py b/src/caas/migrations/0029_auto_20201215_0832.py
new file mode 100644
index 0000000..49adea4
--- /dev/null
+++ b/src/caas/migrations/0029_auto_20201215_0832.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.1.4 on 2020-12-15 15:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0028_auto_20201212_1001'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='cellsiterouter',
+ name='mgmt_default_gateway',
+ ),
+ migrations.RemoveField(
+ model_name='cellsiterouter',
+ name='mgmt_subnet',
+ ),
+ ]
diff --git a/src/caas/migrations/0030_auto_20201215_0835.py b/src/caas/migrations/0030_auto_20201215_0835.py
new file mode 100644
index 0000000..c8ee59d
--- /dev/null
+++ b/src/caas/migrations/0030_auto_20201215_0835.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.4 on 2020-12-15 15:35
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0029_auto_20201215_0832'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n c.mgmt_default_gateway as parent_mgmt_default_gateway,\n c.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n c.mgmt_default_gateway,\n c.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n csrp.mgmt_default_gateway as parent_mgmt_default_gateway,\n csrp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n c.mgmt_default_gateway,\n c.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/migrations/0031_auto_20201215_1430.py b/src/caas/migrations/0031_auto_20201215_1430.py
new file mode 100644
index 0000000..5f31f82
--- /dev/null
+++ b/src/caas/migrations/0031_auto_20201215_1430.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1.4 on 2020-12-15 21:30
+
+from django.db import migrations
+import django_db_views.migration_functions
+import django_db_views.operations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('caas', '0030_auto_20201215_0835'),
+ ]
+
+ operations = [
+ django_db_views.operations.ViewRunPython(
+ code=django_db_views.migration_functions.ForwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n cp.mgmt_default_gateway as parent_mgmt_default_gateway,\n cp.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n c.mgmt_default_gateway,\n c.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ reverse_code=django_db_views.migration_functions.BackwardViewMigration('select s.id,\n c.id as cluster_id,\n l.fuze_spm_site_id,\n cp.oam_vip_hostname as parent_oam_vip_hostname,\n cp.oam_vip_address as parent_oam_vip_address,\n cp.mgmt_address_range_start as parent_mgmt_address_range_start,\n cp.mgmt_address_range_end as parent_mgmt_address_range_end,\n c.mgmt_default_gateway as parent_mgmt_default_gateway,\n c.mgmt_subnet as parent_mgmt_subnet,\n s.pxe_mac_address,\n s.ilo_hostname,\n s.ilo_host_address,\n s.oam_hostname,\n s.oam_host_address,\n c.oam_vip_address,\n csr.oam_default_gateway,\n c.mgmt_address_range_start,\n c.mgmt_address_range_end,\n c.mgmt_default_gateway,\n c.mgmt_subnet,\n vl.host_vlan,\n vl.oam_vlan,\n vl.mgmt_vlan,\n c.cluster_name,\n cp.cluster_name as parent_cluster_name,\n v.vendor_name,\n s.intel_nic_firmware_version,\n l.maint_window_p,\n n.id as namespace_id,\n n.namespace_name,\n cr.bmc_username,\n cr.bmc_password\nfrom caas_server s\ninner join caas_cluster c\n on s.cluster_id = c.id\nleft outer join caas_cluster cp\n on c.parent_cluster_id = cp.id\ninner join caas_cellsiterouter csr\n on c.csr_id = csr.id\ninner join caas_cellsiterouter csrp\n on cp.csr_id = csrp.id\ninner join caas_location l\n on c.location_id = l.id\ninner join caas_vlan vl\n on c.vlan_id = vl.id\ninner join caas_namespace n\n on c.namespace_id = n.id\nleft outer join caas_blade b\n on s.blade_id = b.id\nleft outer join caas_vendor v\n on b.vendor_id = v.id\nleft outer join caas_credential cr\n on b.credential_id = cr.id\nwhere c.parent_cluster_id is not null', 'caas_wrbatch'),
+ atomic=False,
+ ),
+ ]
diff --git a/src/caas/models.py b/src/caas/models.py
index 6ad754f..8c7e4da 100644
--- a/src/caas/models.py
+++ b/src/caas/models.py
@@ -1,59 +1,426 @@
from django.db import models
+from django_db_views.db_view import DBView
+
class Vlan(models.Model):
host_vlan = models.PositiveSmallIntegerField()
oam_vlan = models.PositiveSmallIntegerField()
mgmt_vlan = models.PositiveSmallIntegerField()
+ nfs_vlan = models.PositiveSmallIntegerField(blank=True, null=True)
def __str__(self):
- return "host_vlan: %s , oam_vlan: %s , mgmt_vlan: %s" % (self.host_vlan, self.oam_vlan, self.mgmt_vlan)
+ return "pk: %s , host_vlan: %s , oam_vlan: %s , mgmt_vlan: %s , nfs_vlan: %s" % (self.pk, self.host_vlan, self.oam_vlan, self.mgmt_vlan, self.nfs_vlan)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["host_vlan", "oam_vlan", "mgmt_vlan", "nfs_vlan"], name="unique_vlan")]
class Namespace(models.Model):
namespace_name = models.CharField(max_length=255)
- server_id = models.ForeignKey('VcpfeServer', on_delete=models.SET_NULL, blank=True, null=True)
-
+
+ def __str__(self):
+ return "pk: %s , namespace_name: %s" % (self.pk, self.namespace_name)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["namespace_name"], name="unique_namespace")]
+
+
+class SapClli(models.Model):
+ clli = models.CharField(max_length=255, blank=True, null=True)
+
def __str__(self):
- return "namespace_name: %s , server_id : %s" % (self.namespace_name, self.server_id)
+ return "pk: %s , clli: %s" % (self.pk, self.clli)
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["clli"], name="unique_sapclli")]
-class VcpfeLocation(models.Model):
+
+class Location(models.Model):
fuze_spm_site_id = models.CharField(max_length=16)
fuze_spm_site_name = models.CharField(max_length=255)
fuze_spm_market = models.CharField(max_length=255)
fuze_spm_submarket = models.CharField(max_length=255)
- fuze_spm_site_type = models.CharField(max_length=4, choices=[('DRAN', 'DRAN'), ('CRAN', 'CRAN')])
- central_controller_sap_clli = models.CharField(max_length=255)
- vcu_sap_clli = models.CharField(max_length=255)
- csr_hostname = models.CharField(max_length=255)
- f1c_f1u_subnet = models.CharField(max_length=255)
- f1c_f1u_default_gateway = models.CharField(max_length=255)
- oam_subnet = models.CharField(max_length=255)
- oam_default_gateway = models.CharField(max_length=255)
- mgmt_subnet = models.CharField(max_length=255)
- mgmt_default_gateway = models.CharField(max_length=255)
- ilo_subnet = models.CharField(max_length=255)
- ilo_default_gateway = models.CharField(max_length=255)
- fhgw_subnet = models.CharField(max_length=255)
- fhgw_default_gateway = models.CharField(max_length=255)
+ fuze_spm_site_type = models.CharField(max_length=4, choices=[("SAP", "SAP"), ("DRAN", "DRAN"), ("CRAN", "CRAN")])
+ latitude = models.DecimalField(max_digits=12, decimal_places=9, blank=True, null=True)
+ longitude = models.DecimalField(max_digits=12, decimal_places=9, blank=True, null=True)
+ maint_window_p = models.BooleanField(default=False, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , fuze_spm_site_id: %s , fuze_spm_site_name: %s , fuze_spm_market: %s , fuze_spm_submarket: %s , fuze_spm_site_type: %s" % (self.pk, self.fuze_spm_site_id, self.fuze_spm_site_name, self.fuze_spm_market, self.fuze_spm_submarket, self.fuze_spm_site_type)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["fuze_spm_site_id",
+ "fuze_spm_site_name",
+ "fuze_spm_market",
+ "fuze_spm_submarket",
+ "fuze_spm_site_type"],
+ name="unique_location")]
+
+
+class CellSiteRouter(models.Model):
+ csr_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_subnet = models.CharField(max_length=255, blank=True, null=True)
+ oam_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ ilo_subnet = models.CharField(max_length=255, blank=True, null=True)
+ ilo_default_gateway = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
- return "%s : %s : %s : %s : %s" % (self.fuze_spm_site_id, self.fuze_spm_site_name, self.fuze_spm_market, self.fuze_spm_submarket, self.fuze_spm_site_type)
+ return "pk: %s , csr_hostname: %s" % (self.pk, self.csr_hostname)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["csr_hostname"], name="unique_cellsiterouter")]
-class VcpfeServer(models.Model):
- server_number = models.PositiveSmallIntegerField()
- server_hostname = models.CharField(max_length=255)
+class Cluster(models.Model):
cluster_name = models.CharField(max_length=255)
- f1u_address = models.CharField(max_length=255)
- f1c_address = models.CharField(max_length=255)
- oam_host_address = models.CharField(max_length=255)
- oam_vip_address = models.CharField(max_length=255)
- mgmt_address_range_start = models.CharField(max_length=255)
- mgmt_address_range_end = models.CharField(max_length=255)
- ilo_host_address = models.CharField(max_length=255)
- fhgw_host_address = models.CharField(max_length=255)
- vlan_id = models.ForeignKey('Vlan', on_delete=models.SET_NULL, blank=True, null=True)
+ oam_vip_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_vip_address = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_subnet = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_address_range_start = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_address_range_end = models.CharField(max_length=255, blank=True, null=True)
+ is_central_controller = models.SmallIntegerField(default=0, blank=True, null=True)
+ sap_clli = models.ForeignKey("SapClli", on_delete=models.SET_NULL, blank=True, null=True)
+ parent_cluster = models.ForeignKey("Cluster", on_delete=models.SET_NULL, blank=True, null=True)
+ location = models.ForeignKey("Location", on_delete=models.SET_NULL, blank=True, null=True)
+ vlan = models.ForeignKey("Vlan", on_delete=models.SET_NULL, blank=True, null=True)
+ csr = models.ForeignKey("CellSiteRouter", on_delete=models.SET_NULL, blank=True, null=True)
+ namespace = models.ForeignKey("Namespace", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , cluster_name: %s" % (self.pk, self.cluster_name)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["cluster_name"], name="unique_cluster")]
+
+
+class Server(models.Model):
+ server_number = models.PositiveSmallIntegerField(blank=True, null=True)
+ oam_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_host_address = models.CharField(max_length=255, blank=True, null=True)
+ ilo_hostname = models.CharField(max_length=255)
+ ilo_host_address = models.CharField(max_length=255, blank=True, null=True)
+ pxe_mac_address = models.CharField(max_length=255, blank=True, null=True)
+ intel_nic_firmware_version = models.CharField(max_length=255, blank=True, null=True)
+ cluster = models.ForeignKey("Cluster", on_delete=models.SET_NULL, blank=True, null=True)
+ blade = models.ForeignKey("Blade", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , ilo_hostname: %s , cluster_id: %s" % (self.pk, self.ilo_hostname, self.cluster_id)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["ilo_hostname"], name="unique_server_byhostname")]
+ constraints = [models.UniqueConstraint(fields=["ilo_host_address"], name="unique_server_byiloaddress")]
+
+
+class Credential(models.Model):
+ bmc_password_admin_default = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password_admin = models.CharField(max_length=255, blank=True, null=True)
+ bmc_username = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password = models.CharField(max_length=255, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , bmc_password_admin_default: %s , bmc_password_admin: %s , bmc_username: %s , bmc_password: %s" % (self.pk, self.bmc_password_admin_default, self.bmc_password_admin, self.bmc_username, self.bmc_password)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["bmc_password_admin_default", "bmc_password_admin", "bmc_username", "bmc_password"], name="unique_credential")]
+
+
+class Chassis(models.Model):
+ serial_number = models.CharField(max_length=255, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , serial_number: %s" % (self.pk, self.serial_number)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["serial_number"], name="unique_chassis")]
+
+
+class Blade(models.Model):
+ serial_number = models.CharField(max_length=255, blank=True, null=True)
+ credential = models.ForeignKey("Credential", on_delete=models.SET_NULL, blank=True, null=True)
+ chassis = models.ForeignKey("Chassis", on_delete=models.SET_NULL, blank=True, null=True)
+ vendor = models.ForeignKey("Vendor", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , serial_number: %s , chassis_id: %s , vendor_id: %s" % (self.pk, self.serial_number, self.chassis_id, self.vendor_id)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["serial_number"], name="unique_blade")]
+
+
+class Vendor(models.Model):
+ vendor_name = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
- return "%s : %s : %s" % (self.server_hostname, self.cluster_name, self.namespace_name)
+ return "pk: %s , vendor_name: %s" % (self.pk, self.vendor_name)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["vendor_name"], name="unique_vendor")]
+
+
+class FirmwareUpgradeSchedule(models.Model):
+ date_scheduled = models.DateTimeField(blank=True, null=True)
+ date_completed = models.DateTimeField(blank=True, null=True)
+ date_last_failed = models.DateTimeField(blank=True, null=True)
+ kirke_ticket_number = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_status = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_completed = models.DateTimeField(blank=True, null=True)
+ server = models.ForeignKey("Server", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , date_scheduled: %s , kirke_ticket_number: %s , server_id: %s" % (self.pk, self.date_scheduled, self.kirke_ticket_number, self.server_id)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["server_id"], name="unique_firmwareupgradeschedule")]
+
+
+class WrInstallSchedule(models.Model):
+ date_scheduled = models.DateTimeField(blank=True, null=True)
+ date_completed = models.DateTimeField(blank=True, null=True)
+ date_last_failed = models.DateTimeField(blank=True, null=True)
+ kirke_ticket_number = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_status = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_completed = models.DateTimeField(blank=True, null=True)
+ cluster = models.ForeignKey("Cluster", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , date_scheduled: %s , kirke_ticket_number: %s , cluster_id: %s, date_completed: %s" % (self.pk, self.date_scheduled, self.kirke_ticket_number, self.cluster_id, self.date_completed)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["cluster_id"], name="unique_wrinstallschedule")]
+
+
+class PatchSchedule(models.Model):
+ date_scheduled = models.DateTimeField(blank=True, null=True)
+ date_completed = models.DateTimeField(blank=True, null=True)
+ date_last_failed = models.DateTimeField(blank=True, null=True)
+ kirke_ticket_number = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_status = models.CharField(max_length=255, blank=True, null=True)
+ kirke_ticket_completed = models.DateTimeField(blank=True, null=True)
+ cluster = models.ForeignKey("Cluster", on_delete=models.SET_NULL, blank=True, null=True)
+
+ def __str__(self):
+ return "pk: %s , date_scheduled: %s , kirke_ticket_number: %s , cluster_id: %s" % (self.pk, self.date_scheduled, self.kirke_ticket_number, self.cluster_id)
+
+ class Meta:
+ constraints = [models.UniqueConstraint(fields=["cluster_id"], name="unique_patchschedule")]
+
+
+class IcingaBmc(DBView):
+ ilo_hostname = models.CharField(max_length=255, blank=True, null=True)
+ ilo_host_address = models.CharField(max_length=255, blank=True, null=True)
+ location = models.ForeignKey("Location", on_delete=models.DO_NOTHING, blank=True, null=True)
+ csr_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ latitude = models.CharField(max_length=255, blank=True, null=True)
+ longitude = models.CharField(max_length=255, blank=True, null=True)
+ serial_number = models.CharField(max_length=255, blank=True, null=True)
+ vendor_name = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password_admin_default = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password_admin = models.CharField(max_length=255, blank=True, null=True)
+ bmc_username = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password = models.CharField(max_length=255, blank=True, null=True)
+ date_completed = models.DateTimeField(blank=True, null=True)
+ view_definition = """
+select s.id,
+ s.ilo_hostname,
+ s.ilo_host_address,
+ l.id as location_id,
+ csr.oam_default_gateway,
+ l.latitude,
+ l.longitude,
+ csr.csr_hostname,
+ b.serial_number,
+ v.vendor_name,
+ cr.bmc_password_admin_default,
+ cr.bmc_password_admin,
+ cr.bmc_username,
+ cr.bmc_password,
+ ws.date_completed
+from caas_server s
+inner join caas_cluster c
+ on s.cluster_id = c.id
+inner join caas_location l
+ on c.location_id = l.id
+inner join caas_cellsiterouter csr
+ on c.csr_id = csr.id
+left outer join caas_blade b
+ on s.blade_id = b.id
+left outer join caas_vendor v
+ on b.vendor_id = v.id
+left outer join caas_credential cr
+ on b.credential_id = cr.id
+left outer join caas_wrinstallschedule ws
+ on c.id = ws.cluster_id
+where c.is_central_controller = 0;
+"""
+
+ def __str__(self):
+ return "pk: %s , ilo_hostname: %s , ilo_host_address: %s , csr_hostname: %s , oam_default_gateway: %s, latitude: %s , longitude: %s , bmc_password_admin_default: %s , bmc_password_admin: %s , bmc_username: %s , bmc_password: %s" % (self.pk, self.ilo_hostname, self.ilo_host_address, self.csr_hostname, self.oam_default_gateway, self.latitude, self.longitude, self.bmc_password_admin_default, self.bmc_password_admin, self.bmc_username, self.bmc_password, self.date_completed)
+
+ class Meta:
+ managed = False
+ db_table = "caas_icingabmc"
+
+
+class IcingaRouter(DBView):
+ csr_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ view_definition = """
+select l.id as id,
+ csr.csr_hostname as csr_hostname,
+ csr.oam_default_gateway as oam_default_gateway
+from caas_server s
+inner join caas_cluster c
+ on s.cluster_id = c.id
+inner join caas_location l
+ on c.location_id = l.id
+inner join caas_cellsiterouter csr
+ on c.csr_id = csr.id
+left outer join caas_blade b
+ on s.blade_id = b.id
+left outer join caas_vendor v
+ on b.vendor_id = v.id
+left outer join caas_credential cr
+ on b.credential_id = cr.id
+where c.is_central_controller=0
+group by l.id,
+ csr.csr_hostname,
+ csr.oam_default_gateway;
+"""
+
+ def __str__(self):
+ return "pk: %s , csr_hostname: %s , oam_default_gateway: %s" % (self.pk, self.csr_hostname, self.oam_default_gateway)
+
+ class Meta:
+ managed = False
+ db_table = "caas_icingarouter"
+
+
+class FirmwareBatch(DBView):
+ ilo_host_address = models.CharField(max_length=255, blank=True, null=True)
+ ilo_hostname = models.CharField(max_length=255, blank=True, null=True)
+ intel_nic_firmware_version = models.CharField(max_length=255, blank=True, null=True)
+ bmc_username = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password = models.CharField(max_length=255, blank=True, null=True)
+ view_definition = """
+select s.id,
+ s.ilo_hostname,
+ s.ilo_host_address,
+ s.intel_nic_firmware_version,
+ cr.bmc_username,
+ cr.bmc_password
+from caas_server s
+inner join caas_cluster c
+ on s.cluster_id = c.id
+left outer join caas_blade b
+ on s.blade_id = b.id
+left outer join caas_credential cr
+ on b.credential_id = cr.id
+where c.is_central_controller=0;
+"""
+
+ def __str__(self):
+ return "pk: %s , ilo_host_address: %s" % (self.pk, self.ilo_host_address)
+
+ class Meta:
+ managed = False
+ db_table = "caas_firmwarebatch"
+
+
+class WrBatch(DBView):
+ cluster_id = models.IntegerField()
+ fuze_spm_site_id = models.CharField(max_length=16)
+ parent_oam_vip_hostname = models.CharField(max_length=255, blank=True, null=True)
+ parent_oam_vip_address = models.CharField(max_length=255, blank=True, null=True)
+ parent_mgmt_address_range_start = models.CharField(max_length=255, blank=True, null=True)
+ parent_mgmt_address_range_end = models.CharField(max_length=255, blank=True, null=True)
+ parent_mgmt_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ parent_mgmt_subnet = models.CharField(max_length=255, blank=True, null=True)
+ pxe_mac_address = models.CharField(max_length=255, blank=True, null=True)
+ ilo_hostname = models.CharField(max_length=255, blank=True, null=True)
+ ilo_host_address = models.CharField(max_length=255, blank=True, null=True)
+ oam_hostname = models.CharField(max_length=255, blank=True, null=True)
+ oam_host_address = models.CharField(max_length=255, blank=True, null=True)
+ oam_vip_address = models.CharField(max_length=255, blank=True, null=True)
+ oam_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_address_range_start = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_address_range_end = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_default_gateway = models.CharField(max_length=255, blank=True, null=True)
+ mgmt_subnet = models.CharField(max_length=255, blank=True, null=True)
+ host_vlan = models.PositiveSmallIntegerField()
+ oam_vlan = models.PositiveSmallIntegerField()
+ mgmt_vlan = models.PositiveSmallIntegerField()
+ cluster_name = models.CharField(max_length=255, blank=True, null=True)
+ parent_cluster_name = models.CharField(max_length=255, blank=True, null=True)
+ vendor_name = models.CharField(max_length=255, blank=True, null=True)
+ intel_nic_firmware_version = models.CharField(max_length=255, blank=True, null=True)
+ maint_window_p = models.BooleanField(default=False, blank=True, null=True)
+ namespace_id = models.IntegerField()
+ namespace_name = models.CharField(max_length=255, blank=True, null=True)
+ bmc_username = models.CharField(max_length=255, blank=True, null=True)
+ bmc_password = models.CharField(max_length=255, blank=True, null=True)
+ view_definition = """
+select s.id,
+ c.id as cluster_id,
+ l.fuze_spm_site_id,
+ cp.oam_vip_hostname as parent_oam_vip_hostname,
+ cp.oam_vip_address as parent_oam_vip_address,
+ cp.mgmt_address_range_start as parent_mgmt_address_range_start,
+ cp.mgmt_address_range_end as parent_mgmt_address_range_end,
+ cp.mgmt_default_gateway as parent_mgmt_default_gateway,
+ cp.mgmt_subnet as parent_mgmt_subnet,
+ s.pxe_mac_address,
+ s.ilo_hostname,
+ s.ilo_host_address,
+ s.oam_hostname,
+ s.oam_host_address,
+ c.oam_vip_address,
+ csr.oam_default_gateway,
+ c.mgmt_address_range_start,
+ c.mgmt_address_range_end,
+ c.mgmt_default_gateway,
+ c.mgmt_subnet,
+ vl.host_vlan,
+ vl.oam_vlan,
+ vl.mgmt_vlan,
+ c.cluster_name,
+ cp.cluster_name as parent_cluster_name,
+ v.vendor_name,
+ s.intel_nic_firmware_version,
+ l.maint_window_p,
+ n.id as namespace_id,
+ n.namespace_name,
+ cr.bmc_username,
+ cr.bmc_password
+from caas_server s
+inner join caas_cluster c
+ on s.cluster_id = c.id
+left outer join caas_cluster cp
+ on c.parent_cluster_id = cp.id
+inner join caas_cellsiterouter csr
+ on c.csr_id = csr.id
+inner join caas_cellsiterouter csrp
+ on cp.csr_id = csrp.id
+inner join caas_location l
+ on c.location_id = l.id
+inner join caas_vlan vl
+ on c.vlan_id = vl.id
+inner join caas_namespace n
+ on c.namespace_id = n.id
+left outer join caas_blade b
+ on s.blade_id = b.id
+left outer join caas_vendor v
+ on b.vendor_id = v.id
+left outer join caas_credential cr
+ on b.credential_id = cr.id
+where c.parent_cluster_id is not null;
+"""
+
+ def __str__(self):
+ return "pk: %s , ilo_host_address: %s , cluster_name : %s" % (self.pk, self.ilo_host_address, self.cluster_name)
+
+ class Meta:
+ managed = False
+ db_table = "caas_wrbatch"
diff --git a/src/caas/services/ansibleservice.py b/src/caas/services/ansibleservice.py
new file mode 100644
index 0000000..148bcf4
--- /dev/null
+++ b/src/caas/services/ansibleservice.py
@@ -0,0 +1,52 @@
+from django.conf import settings
+import json
+import zmq
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+"""
+Superclass for classes that manage ansible playbooks, for example
+icinga and wr-installer.
+"""
+class AnsibleService():
+ def __init__(self, ansible_queue, target, git, branch, playbook, zmq, maint_window_p = False):
+ self.ansible_queue = ansible_queue
+ self.target = target
+ self.git = git
+ self.branch = branch
+ self.playbook = playbook
+ self.zmq = zmq
+ self.maint_window_p = maint_window_p
+
+ def run_playbook(self, inventory, inventory_content, hostvars, hostvars_content, extra_args = {}):
+ payload = [{"git": self.git,
+ "playbook": self.playbook,
+ "maintWindowP": self.maint_window_p,
+ "inventory": inventory,
+ "inventoryContent": inventory_content,
+ "hostvars": hostvars,
+ "hostvarsContent": hostvars_content}]
+ payload[0].update(extra_args)
+ payload_json = json.dumps(payload)
+ context = zmq.Context()
+ retries = 10
+ while True:
+ socket = context.socket(zmq.REQ)
+ if settings.ZEROMQ_IPV6:
+ socket.setsockopt(zmq.IPV6, 1)
+ # logger.debug("sending playbook: %s" % payload_json)
+ socket.connect(self.zmq)
+ socket.send_string(payload_json)
+ logger.debug("sent playbook")
+ if (socket.poll(5000) & zmq.POLLIN) != 0:
+ return socket.recv()
+ retries -= 1
+ socket.setsockopt(zmq.LINGER, 0)
+ socket.close()
+ if retries == 0:
+ errmsg = "Failed to send zmq message to ansible-queue: %s" % payload_json
+ logger.error(errmsg)
+ raise Exception(errmsg)
diff --git a/src/caas/services/bmcservice.py b/src/caas/services/bmcservice.py
new file mode 100644
index 0000000..43fdb51
--- /dev/null
+++ b/src/caas/services/bmcservice.py
@@ -0,0 +1,50 @@
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+from ..models import IcingaBmc
+from django.conf import settings
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class BmcService(AnsibleService):
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("bmc", target, git, branch, playbook, zmq)
+
+ def deploy(self):
+ logger.debug("%s Enter" % self.target)
+ try:
+ bmc_kwargs = {"ilo_host_address": self.target}
+ bmc = IcingaBmc.objects.get(**bmc_kwargs)
+ except IcingaBmc.DoesNotExist:
+ errmsg = "%s FAILED: while running BMC playbook: failed to look up BMC" % self.target
+ logger.error(errmsg)
+ return -1
+ # The ansible target is the hostname, but until now we only had the ilo IP.
+ self.target = bmc.ilo_hostname
+ # host_vars file
+ hostvars_config = {"bmc_ip": bmc.ilo_host_address,
+ "bmc_password_admin_default": bmc.bmc_password_admin_default,
+ "bmc_password_admin": bmc.bmc_password_admin,
+ "bmc_username": bmc.bmc_username,
+ "bmc_password": bmc.bmc_password,
+ "dns_nameservers": (" ").join(settings.DNS_SERVERS),
+ "syslog_server": settings.SYSLOG_SERVER,
+ "syslog_server_port": settings.SYSLOG_SERVER_PORT,
+ "ntp_servers": (" ").join(settings.NTP_SERVERS),
+ "middleware_endpoint": settings.CAAS_MIDDLEWARE_ENDPOINT,
+ "middleware_username": settings.CAAS_MIDDLEWARE_USERNAME,
+ "middleware_password": settings.CAAS_MIDDLEWARE_PASSWORD}
+ hostvars_content = Jinja.render("bmc_host_vars.j2", **hostvars_config)
+ hostvars = self.target
+ # inventory file
+ inventory_config = {"vendor_name": bmc.vendor_name,
+ "target": self.target}
+ inventory_content = Jinja.render("bmc_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content)
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
+
diff --git a/src/caas/services/ciqservice.py b/src/caas/services/ciqservice.py
new file mode 100644
index 0000000..a97e9e0
--- /dev/null
+++ b/src/caas/services/ciqservice.py
@@ -0,0 +1,193 @@
+import csv
+import ipaddress
+from io import TextIOWrapper
+from django.db import connections
+from django.conf import settings
+from .helpers import Address, RawQuery
+from ..models import Vlan, Namespace, CellSiteRouter, SapClli, Location, Cluster, Server
+
+
+"""
+Derive IPs from rules defined in the LLD:
+https://oneconfluence.verizon.com/display/NTD/VRAN+2.0+Far+Edge?preview=/554294908/592448982/vRAN%202.0%20on%20VCP%20Far%20Edge_WebScale%20Transport%20LLD%20v2.3.pdf
+"""
+class CiqService():
+ @staticmethod
+ def import_ciq_data(filehandle, assign_parent = False):
+ with connections["default"].cursor() as cursor:
+ f = TextIOWrapper(filehandle, encoding = "ascii", errors = "replace")
+ csv_reader = csv.reader(f, delimiter = ",")
+ for row in csv_reader:
+ # Fetch data from CSV row
+ fuze_spm_site_id = row[1]
+ fuze_spm_site_name = row[3]
+ fuze_spm_market = row[4]
+ fuze_spm_submarket = row[5]
+ fuze_spm_site_type = row[6]
+ central_controller_sap_clli = row[7]
+ server_number = int(row[9])
+ ilo_hostname = row[10].lower()
+ cluster_name = row[11].lower()
+ namespace_name = row[12].lower()
+ host_vlan = int(row[13])
+ oam_vlan = int(row[14])
+ mgmt_vlan = int(row[15])
+ csr_hostname = row[17]
+ ilo_host_address = ipaddress.IPv6Address(row[23])
+ ilo_default_gateway = ipaddress.IPv6Address(row[24])
+ mgmt_default_gateway = ipaddress.IPv6Address(row[25])
+ oam_default_gateway = ipaddress.IPv6Address(row[26])
+ central_controller_p = bool(row[28])
+
+ # Use the minimum uniquely identifying data to load existing matching records if available
+
+ # location
+ location_kwargs = {"fuze_spm_site_id": fuze_spm_site_id}
+ location, location_created = Location.objects.get_or_create(**location_kwargs)
+ location.fuze_spm_site_name = fuze_spm_site_name
+ location.fuze_spm_market = fuze_spm_market
+ location.fuze_spm_submarket = fuze_spm_submarket
+ location.fuze_spm_site_type = fuze_spm_site_type
+
+ # vlan
+ vlan_kwargs = {"host_vlan": host_vlan,
+ "oam_vlan": oam_vlan,
+ "mgmt_vlan": mgmt_vlan}
+ vlan, vlan_created = Vlan.objects.get_or_create(**vlan_kwargs)
+
+ # csr
+ csr_kwargs = {"csr_hostname": csr_hostname}
+ csr, csr_created = CellSiteRouter.objects.get_or_create(**csr_kwargs)
+
+ # sap_clli
+ sap_clli_kwargs = {"clli": central_controller_sap_clli}
+ sap_clli, sap_clli_created = SapClli.objects.get_or_create(**sap_clli_kwargs)
+
+ # cluster
+ cluster_kwargs = {"cluster_name": cluster_name}
+ cluster, cluster_created = Cluster.objects.get_or_create(**cluster_kwargs)
+
+ # namespaces
+ namespace_kwargs = {"namespace_name": namespace_name}
+ namespace, namespace_created = Namespace.objects.get_or_create(**namespace_kwargs)
+
+ # server
+ server_kwargs = {"ilo_hostname": ilo_hostname}
+ server, server_created = Server.objects.get_or_create(**server_kwargs)
+
+ parent_cluster = None
+
+ if not central_controller_p:
+ # find least-used parent central controller cluster
+ parent_clusters_kwargs = {"sap_clli_id__exact": sap_clli.pk,
+ "parent_cluster_id__isnull": True,
+ "is_central_controller": 1}
+ parent_clusters = Cluster.objects.filter(**parent_clusters_kwargs)
+ subcloud_counts = {}
+
+ for parent in parent_clusters:
+ num_children = RawQuery.query_single_value(cursor, """
+select count(*) as num_children
+from caas_cluster
+where parent_cluster_id = %s and is_central_controller = 0""" % parent.pk, "num_children")
+
+ if num_children < settings.WR_MAX_CHILD_CLUSTERS:
+ subcloud_counts[parent.pk] = [num_children, parent]
+
+ if len(subcloud_counts) != 0:
+ parent_cluster = min(subcloud_counts.items(), key=lambda x: x[1][0])[1][1]
+
+ # Derive subnets from gateways
+ oam_subnet = Address.add_40a(Address.gateway_to_subnet(oam_default_gateway))
+ mgmt_subnet = Address.add_40a(Address.gateway_to_subnet(mgmt_default_gateway))
+ ilo_subnet = Address.add_40a(Address.gateway_to_subnet(ilo_default_gateway))
+
+ # oam vip
+ need_ip = False
+
+ if csr.oam_subnet == str(oam_subnet) and csr.oam_default_gateway == str(oam_default_gateway):
+ if cluster.oam_vip_address is None:
+ need_ip = True
+ else:
+ csr.oam_subnet = str(oam_subnet)
+ csr.oam_default_gateway = str(oam_default_gateway)
+ need_ip = True
+ if need_ip:
+ oam_vip_address_max = RawQuery.query_single_value(cursor, """
+with oam_ips as (
+ select csr.id as csr_id,
+ c.oam_vip_address
+ from caas_server s
+ inner join caas_cluster c
+ on s.cluster_id = c.id
+ inner join caas_cellsiterouter csr
+ on c.csr_id = csr.id
+)
+select max(oam_vip_address) as max
+from oam_ips
+where csr_id = %s""" % csr.id, "max")
+ if oam_vip_address_max:
+ oam_vip_address_max = str(Address.next_address(oam_vip_address_max, oam_subnet, 0x10))
+ else:
+ oam_vip_address_max = str(Address.next_address(oam_vip_address_max, oam_subnet, 0xf400))
+ cluster.oam_vip_address = oam_vip_address_max
+ # oam host
+ need_ip = False
+ if csr.oam_subnet == str(oam_subnet) and csr.oam_default_gateway == str(oam_default_gateway):
+ if server.oam_host_address is None:
+ need_ip = True
+ else:
+ csr.oam_subnet = str(oam_subnet)
+ csr.oam_default_gateway = str(oam_default_gateway)
+ need_ip = True
+ if need_ip:
+ oam_host_address_max = RawQuery.query_single_value(cursor, """
+with oam_ips as (
+ select csr.id as csr_id,
+ s.oam_host_address
+ from caas_server s
+ inner join caas_cluster c
+ on s.cluster_id = c.id
+ inner join caas_cellsiterouter csr
+ on c.csr_id = csr.id
+)
+select max(oam_host_address) as max
+from oam_ips
+where csr_id = %s""" % csr.id, "max")
+ if oam_host_address_max:
+ oam_host_address_max = str(Address.next_address(oam_host_address_max, oam_subnet, 0x1))
+ else:
+ oam_host_address_max = str(Address.next_address(oam_host_address_max, oam_subnet, 0x400))
+ server.oam_host_address = oam_host_address_max
+
+ # mgmt
+ cluster.mgmt_subnet = str(mgmt_subnet)
+ cluster.mgmt_default_gateway = str(mgmt_default_gateway)
+ cluster.mgmt_address_range_start = str(mgmt_subnet)
+ cluster.mgmt_address_range_end = str(Address.address_add(cluster.mgmt_address_range_start, 0xf))
+
+ # don't step on central controllers TODO: separate central and remote CIQ ingestion
+ csr.ilo_subnet = str(ilo_subnet)
+ csr.ilo_default_gateway = str(ilo_default_gateway)
+ server.server_number = server_number
+ server.ilo_host_address = str(ilo_host_address)
+ else:
+ cluster.is_central_controller = 1
+
+ # foreign-key relationships
+ cluster.sap_clli = sap_clli
+ if assign_parent is True and cluster.parent_cluster is None:
+ cluster.parent_cluster = parent_cluster
+ cluster.location = location
+ cluster.vlan = vlan
+ cluster.csr = csr
+ cluster.namespace = namespace
+ server.cluster = cluster
+ # upsert
+ vlan.save()
+ namespace.save()
+ csr.save()
+ sap_clli.save()
+ location.save()
+ cluster.save()
+ server.save()
diff --git a/src/caas/services/dnsservice.py b/src/caas/services/dnsservice.py
new file mode 100644
index 0000000..bd8c388
--- /dev/null
+++ b/src/caas/services/dnsservice.py
@@ -0,0 +1,60 @@
+from django.conf import settings
+from .helpers import Jinja, Hostname
+from .ansibleservice import AnsibleService
+from ..models import Server, Cluster
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class DnsService(AnsibleService):
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("dns", target, git, branch, playbook, zmq)
+
+ def deploy(self):
+ logger.debug("%s Enter" % self.target)
+ try:
+ server_kwargs = {"ilo_host_address": self.target}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ errmsg = "%s FAILED: while running DNS playbook: failed to look up server" % self.target
+ logger.error(errmsg)
+ return -1
+ try:
+ cluster_kwargs = {"id": server.cluster.pk}
+ cluster = Cluster.objects.get(**cluster_kwargs)
+ except Cluster.DoesNotExist:
+ errmsg = "%s FAILED: while running DNS playbook: failed to look up cluster" % self.target
+ logger.error(errmsg)
+ return -1
+ server.oam_hostname = Hostname.make_oam_hostname(server.ilo_hostname)
+ if cluster.oam_vip_hostname is None:
+ cluster.oam_vip_hostname = Hostname.make_vip_hostname(server.ilo_hostname, cluster.cluster_name)
+ cluster.save()
+ server.save()
+ # The ansible target is the ilo hostname, but until now we only had the ilo IP.
+ self.target = server.ilo_hostname
+ # host_vars file
+ hostvars_config = {"ilo_hostname": server.ilo_hostname,
+ "ilo_host_address": server.ilo_host_address,
+ "oam_hostname": server.oam_hostname,
+ "oam_host_address": server.oam_host_address,
+ "oam_vip_hostname": cluster.oam_vip_hostname,
+ "oam_vip_address": cluster.oam_vip_address,
+ "cluster_name": cluster.cluster_name,
+ "dns_admin_endpoint": settings.DNS_ADMIN_ENDPOINT,
+ "dns_domain": settings.DNS_DOMAIN,
+ "infoblox_username": settings.INFOBLOX_USERNAME,
+ "infoblox_password": settings.INFOBLOX_PASSWORD}
+ hostvars_content = Jinja.render("dns_host_vars.j2", **hostvars_config)
+ hostvars = server.ilo_hostname
+ # inventory file
+ inventory_config = {"target": self.target}
+ inventory_content = Jinja.render("dns_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content)
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
+
diff --git a/src/caas/services/firmwareservice.py b/src/caas/services/firmwareservice.py
new file mode 100644
index 0000000..97bfecc
--- /dev/null
+++ b/src/caas/services/firmwareservice.py
@@ -0,0 +1,87 @@
+import datetime
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+from ..models import FirmwareUpgradeSchedule, FirmwareBatch
+from django.conf import settings
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+"""
+Expected versions. The bottom one is OK; the top one is too old.
+firmware-version: 6.00 0x800036cb 1.1747.0
+firmware-version: 7.20 0x80007fdf 1.2585.0
+"""
+class FirmwareService(AnsibleService):
+ """
+ target == ilo_host_address
+ """
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("nic", target, git, branch, playbook, zmq)
+
+ def schedule_upgrade(self):
+ logger.debug("%s Enter schedule_upgrade" % self.target)
+ try:
+ firmwarebatch_kwargs = {"ilo_host_address": self.target}
+ firmwarebatch = FirmwareBatch.objects.get(**firmwarebatch_kwargs)
+ except FirmwareBatch.DoesNotExist:
+ errmsg = "%s FAILED: while checking Intel NIC firmware version: cannot find server" % self.target
+ logger.error(errmsg)
+ return -1
+ if firmwarebatch.intel_nic_firmware_version != settings.INTEL_NIC_FIRMWARE_VERSION:
+ firmware_kwargs = {"server_id": firmwarebatch.pk}
+ firmware, firmware_created = FirmwareUpgradeSchedule.objects.get_or_create(**firmware_kwargs)
+ if firmware.date_scheduled == None:
+ firmware.date_scheduled = datetime.datetime.now()
+ if firmware.kirke_ticket_number == None:
+ # TODO bealer: create KIRKE ticket
+ pass
+ firmware.save()
+ return 0
+
+ def deploy(self):
+ logger.debug("%s Enter deploy" % self.target)
+ kirke_inprocess_kwargs = {"kirke_ticket_number__isnull": False,
+ "kirke_ticket_completed__isnull": True}
+ kirke_inprocess = FirmwareUpgradeSchedule.objects.filter(**kirke_inprocess_kwargs)
+ for rec in kirke_inprocess:
+ # TODO bealer: query KIRKE and update status
+ rec.save()
+ firmware_schedule_kwargs = {"kirke_ticket_completed__isnull": False,
+ "date_completed__isnull": True}
+ firmware_schedule_ready = FirmwareUpgradeSchedule.objects.filter(**firmware_schedule_kwargs)
+ for sched in firmware_schedule_ready:
+ try:
+ firmwarebatch_kwargs = {"pk": sched.server.pk}
+ firmwarebatch = FirmwareBatch.objects.get(**firmwarebatch_kwargs)
+ except FirmwareBatch.DoesNotExist:
+ errmsg = "%s FAILED: while running firmware batch playbook: failed to look up batch" % self.target
+ logger.error(errmsg)
+ return -1
+ self.target = firmwarebatch.ilo_hostname
+ # host_vars file
+ hostvars_config = {"ilo_host_address": firmwarebatch.ilo_host_address,
+ "wr_bmc_username": firmwarebatch.bmc_username,
+ "wr_bmc_password": firmwarebatch.bmc_password,
+ "wr_webdav_firmwarebatch": settings.WR_WEBDAV_SERVER,
+ "wr_webdav_server": settings.WR_WEBDAV_SERVER,
+ "wr_webdav_username": settings.WR_WEBDAV_USERNAME,
+ "wr_webdav_password": settings.WR_WEBDAV_PASSWORD,
+ "middleware_endpoint": settings.CAAS_MIDDLEWARE_ENDPOINT,
+ "middleware_username": settings.CAAS_MIDDLEWARE_USERNAME,
+ "middleware_password": settings.CAAS_MIDDLEWARE_PASSWORD,
+ "wr_registry_server": settings.WR_REGISTRY_SERVER,
+ "firmware_iso": settings.FIRMWARE_ISO}
+ hostvars_content = Jinja.render("firmware_host_vars.j2", **hostvars_config)
+ hostvars = self.target
+ # inventory file
+ inventory_config = {"target": firmwarebatch.ilo_hostname,
+ "ilo_host_address": firmwarebatch.ilo_host_address}
+ inventory_content = Jinja.render("firmware_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content, extra_args = {"autoremediate": firmwarebatch.ilo_host_address})
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
diff --git a/src/caas/services/helpers.py b/src/caas/services/helpers.py
new file mode 100644
index 0000000..9145d1f
--- /dev/null
+++ b/src/caas/services/helpers.py
@@ -0,0 +1,136 @@
+import ipaddress
+import jinja2
+import os
+import subprocess
+
+
+class Shell():
+ @staticmethod
+ def run_shell_command(cmd):
+ p = subprocess.Popen([cmd],
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env={})
+ out, err = p.communicate()
+ if p.returncode != 0:
+ raise Exception("Error running shell command:\n%s\n%s" % (out, err))
+ return out, err
+
+
+class Jinja():
+ @staticmethod
+ def render(template_name, **kwargs):
+ cwd = os.path.dirname(os.path.abspath(__file__))
+ loader = jinja2.FileSystemLoader([".", "/"])
+ extensions = ["jinja2.ext.with_", "jinja2.ext.loopcontrols"]
+ environment = jinja2.Environment(trim_blocks = True,
+ lstrip_blocks = True,
+ extensions = extensions,
+ loader = loader)
+ template_names = ["%s/jinja/%s" % (cwd, template_name)]
+ template = environment.select_template(template_names)
+ return template.render(kwargs)
+
+
+class RawQuery():
+ @staticmethod
+ def dict_fetchall(cursor):
+ rows = cursor.fetchall()
+ if len(rows) == 0:
+ return None
+ else:
+ columns = [col[0] for col in cursor.description]
+ return [dict(zip(columns, row)) for row in rows]
+
+ @staticmethod
+ def query_single_value(cursor, query, column_name):
+ cursor.execute(query)
+ rows = RawQuery.dict_fetchall(cursor)
+ if rows == {}:
+ return None
+ else:
+ return rows[0][column_name]
+
+
+class Address():
+ @staticmethod
+ def coalesce(ip, default):
+ if ip is None:
+ return default
+ else:
+ return ip
+
+ @staticmethod
+ def get_quads(addr):
+ return list(filter(lambda x: x, str(addr).split("/")[0].split(":")))
+
+ @staticmethod
+ def address_add(ip, index):
+ return ipaddress.IPv6Address(ip) + index
+
+ """
+ Assumes a 5-quad 'subnet' as import input. Adds the 6th quad which is always 40a.
+ """
+ @staticmethod
+ def add_40a(subnet):
+ return Address.address_add(subnet, 0x040a00000000)
+
+ @staticmethod
+ def next_address(ip, subnet, block_begin_hex):
+ beginning_address = Address.coalesce(ip, Address.address_add(subnet, block_begin_hex))
+ if ip is None:
+ return beginning_address
+ else:
+ return Address.address_add(beginning_address, 0x1)
+
+ @staticmethod
+ def add_cidr(subnet, cidr = 64):
+ subnet_quads = Address.get_quads(subnet)
+ return "%s::/%s" % (":".join(subnet_quads), cidr)
+
+ @staticmethod
+ def remove_cidr(subnet):
+ return ipaddress.IPv6Address(str(subnet).split("/")[0])
+
+ @staticmethod
+ def gateway_to_subnet(gateway, numquads = 5):
+ # The subnet is always the first 5 quads of the gateway.
+ # TODO: make sure this holds true in MTCE and labs as well as production.
+ quads = Address.get_quads(gateway)
+ return ipaddress.IPv6Address("%s::" % ":".join(quads[:numquads]))
+
+ @staticmethod
+ def gateway_to_subnet_new(gateway):
+ quads = Address.get_quads(gateway)
+ return ipaddress.IPv6Address("%s:40a::" % ":".join(quads[:5]))
+
+ @staticmethod
+ def subnet_to_4_quads(subnet):
+ quads = Address.get_quads(gateway)
+ return ipaddress.IPv6Address("%s::" % ":".join(quads[:4]))
+
+ @staticmethod
+ def strip_brackets(ip):
+ return ip.replace("[", "").replace("]", "")
+
+
+class Hostname():
+ @staticmethod
+ def make_oam_hostname(ilo_hostname):
+ return ilo_hostname.replace("-pe0", "-pe2")
+
+ """
+ Note: this method does not deal with the index at the end of the
+ hostname. Doing so would require database access. This is the
+ responsibility of the service classes. See dnsservice.py
+ """
+ @staticmethod
+ def make_vip_hostname(ilo_hostname, cluster_name):
+ index = int(cluster_name[-3:])
+ return "%s-%s" % (ilo_hostname.replace("-pe0", "-pe1")[:-4], f"{index + 0:03}")
+
+ @staticmethod
+ def increment_hostname(hostname):
+ index = int(hostname[-3:])
+ return "%s-%s" % (hostname[:-4], f"{index + 1:03}")
diff --git a/src/caas/services/httpclient.py b/src/caas/services/httpclient.py
new file mode 100644
index 0000000..ad8270b
--- /dev/null
+++ b/src/caas/services/httpclient.py
@@ -0,0 +1,18 @@
+import requests
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+from requests.auth import HTTPBasicAuth
+
+
+class HttpClient(object):
+ def __init__(self):
+ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+ self.session = requests.Session()
+
+ def post(self, url, username, password, data = None, headers = {}, timeout = 60):
+ resp = self.session.post(url = url,
+ auth = HTTPBasicAuth(username, password),
+ json = data,
+ headers = headers,
+ timeout = timeout,
+ verify = False)
+ return resp
diff --git a/src/caas/services/icingapollservice.py b/src/caas/services/icingapollservice.py
new file mode 100644
index 0000000..d1796d0
--- /dev/null
+++ b/src/caas/services/icingapollservice.py
@@ -0,0 +1,47 @@
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+from ..models import IcingaBmc
+from django.conf import settings
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class IcingaPollService(AnsibleService):
+ def __init__(self, target, git, branch, playbook, zmq, url):
+ super().__init__("icinga_poll", target, git, branch, playbook, zmq)
+ self.url = url
+
+ def deploy(self):
+ logger.debug("%s Enter" % self.target)
+ try:
+ icinga_poll_kwargs = {"ilo_host_address": self.target}
+ icinga_poll = IcingaBmc.objects.get(**icinga_poll_kwargs)
+ except IcingaBmc.DoesNotExist:
+ logger.error("%s FAILED: while running icinga_poll playbook: failed to look up BMC" % self.target)
+ return -1
+
+ self.target = icinga_poll.ilo_hostname
+ # host_vars file
+ hostvars_config = {"bmc_ip": icinga_poll.ilo_host_address,
+ "bmc_username": icinga_poll.bmc_username,
+ "bmc_password": icinga_poll.bmc_password,
+ "icinga_api_url": settings.ICINGA_API_URL,
+ "icinga_api_username": settings.ICINGA_API_USERNAME,
+ "icinga_api_password": settings.ICINGA_API_PASSWORD,
+ "middleware_endpoint": settings.CAAS_MIDDLEWARE_ENDPOINT,
+ "middleware_username": settings.CAAS_MIDDLEWARE_USERNAME,
+ "middleware_password": settings.CAAS_MIDDLEWARE_PASSWORD,
+ "middleware_url": self.url}
+ hostvars_content = Jinja.render("icinga_poll_host_vars.j2", **hostvars_config)
+ hostvars = self.target
+ # inventory file
+ inventory_config = {"vendor_name": icinga_poll.vendor_name, "target": self.target}
+ inventory_content = Jinja.render("icinga_poll_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content)
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
+
diff --git a/src/caas/services/icingaservice.py b/src/caas/services/icingaservice.py
new file mode 100644
index 0000000..4d1ff67
--- /dev/null
+++ b/src/caas/services/icingaservice.py
@@ -0,0 +1,46 @@
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+from .macaddressservice import MacAddressService
+from ..models import IcingaBmc, IcingaRouter
+from django.conf import settings
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class IcingaService(AnsibleService):
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("icinga", target, git, branch, playbook, zmq)
+
+ def deploy(self, mockciq = False):
+ logger.debug("%s Enter" % self.target)
+ # host_vars file
+ hostvars_config = {"icinga_routers": [], "icinga_bmcs": []}
+ for router in IcingaRouter.objects.all():
+ hostvars_config["icinga_routers"].append({"name": router.csr_hostname,
+ "address6": router.oam_default_gateway})
+ server_kwargs = {"date_completed": None}
+ servers = IcingaBmc.objects.filter(**server_kwargs)
+ for server in servers:
+ hostvars_config["icinga_bmcs"].append({"name": server.ilo_hostname,
+ "address6": server.ilo_host_address,
+ "preauth": True if mockciq or server.bmc_password_admin_default is None or MacAddressService.get_server_mac_address(server.ilo_host_address) is None else False,
+ "bmc_password_admin_default": server.bmc_password_admin_default,
+ "bmc_password_admin": server.bmc_password_admin,
+ "bmc_username": server.bmc_username,
+ "bmc_password": server.bmc_password,
+ "middleware_endpoint": settings.CAAS_MIDDLEWARE_ENDPOINT,
+ "middleware_username": settings.CAAS_MIDDLEWARE_USERNAME,
+ "middleware_password": settings.CAAS_MIDDLEWARE_PASSWORD})
+
+ hostvars_content = Jinja.render("icinga_host_vars.j2", **hostvars_config)
+ hostvars = self.target
+ # inventory file
+ inventory_config = {"target": self.target}
+ inventory_content = Jinja.render("icinga_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content)
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
diff --git a/src/caas/services/jinja/bmc_host_vars.j2 b/src/caas/services/jinja/bmc_host_vars.j2
new file mode 100644
index 0000000..eb56bdd
--- /dev/null
+++ b/src/caas/services/jinja/bmc_host_vars.j2
@@ -0,0 +1,13 @@
+---
+bmc_ip: "{{ bmc_ip }}"
+bmc_password_admin_default: "{{ bmc_password_admin_default }}"
+bmc_password_admin: "{{ bmc_password_admin }}"
+bmc_username: "{{ bmc_username }}"
+bmc_password: "{{ bmc_password }}"
+dns_nameservers: "{{ dns_nameservers }}"
+syslog_server: "{{ syslog_server }}"
+syslog_server_port: "{{ syslog_server_port }}"
+ntp_servers: "{{ ntp_servers }}"
+middleware_endpoint: "{{ middleware_endpoint }}"
+middleware_username: "{{ middleware_username }}"
+middleware_password: "{{ middleware_password }}"
diff --git a/src/caas/services/jinja/bmc_inventory.j2 b/src/caas/services/jinja/bmc_inventory.j2
new file mode 100644
index 0000000..6ef8ca2
--- /dev/null
+++ b/src/caas/services/jinja/bmc_inventory.j2
@@ -0,0 +1,6 @@
+---
+all:
+ children:
+ {{ vendor_name }}:
+ hosts:
+ {{ target }}
diff --git a/src/caas/services/jinja/dns_host_vars.j2 b/src/caas/services/jinja/dns_host_vars.j2
new file mode 100644
index 0000000..2e66a8f
--- /dev/null
+++ b/src/caas/services/jinja/dns_host_vars.j2
@@ -0,0 +1,12 @@
+---
+ilo_hostname: "{{ ilo_hostname }}"
+ilo_host_address: "{{ ilo_host_address }}"
+oam_hostname: "{{ oam_hostname }}"
+oam_host_address: "{{ oam_host_address }}"
+oam_vip_hostname: "{{ oam_vip_hostname }}"
+oam_vip_address: "{{ oam_vip_address }}"
+cluster_name: "{{ cluster_name }}"
+dns_admin_endpoint: "{{ dns_admin_endpoint }}"
+dns_domain: "{{ dns_domain }}"
+infoblox_username: "{{ infoblox_username }}"
+infoblox_password: "{{ infoblox_password }}"
diff --git a/src/caas/services/jinja/dns_inventory.j2 b/src/caas/services/jinja/dns_inventory.j2
new file mode 100644
index 0000000..c003afe
--- /dev/null
+++ b/src/caas/services/jinja/dns_inventory.j2
@@ -0,0 +1,6 @@
+---
+all:
+ children:
+ target:
+ hosts:
+ {{ target }}
diff --git a/src/caas/services/jinja/firmware_host_vars.j2 b/src/caas/services/jinja/firmware_host_vars.j2
new file mode 100644
index 0000000..c029def
--- /dev/null
+++ b/src/caas/services/jinja/firmware_host_vars.j2
@@ -0,0 +1,13 @@
+---
+wr_distribution_zip: "WRCP-2006.20.03.zip"
+ilo_host_address: "{{ ilo_host_address }}"
+wr_bmc_username: "{{ wr_bmc_username }}"
+wr_bmc_password: "{{ wr_bmc_password }}"
+wr_webdav_server: "{{ wr_webdav_server }}"
+wr_webdav_username: "{{ wr_webdav_username }}"
+wr_webdav_password: "{{ wr_webdav_password }}"
+middleware_endpoint: "{{ middleware_endpoint }}"
+middleware_username: "{{ middleware_username }}"
+middleware_password: "{{ middleware_password }}"
+wr_registry_server: "{{ wr_registry_server }}"
+firmware_iso: "{{ firmware_iso }}"
diff --git a/src/caas/services/jinja/firmware_inventory.j2 b/src/caas/services/jinja/firmware_inventory.j2
new file mode 100644
index 0000000..b7d566c
--- /dev/null
+++ b/src/caas/services/jinja/firmware_inventory.j2
@@ -0,0 +1,7 @@
+---
+all:
+ children:
+ target:
+ hosts:
+ {{ target }}:
+ ansible_host: {{ ilo_host_address }}
diff --git a/src/caas/services/jinja/icinga_host_vars.j2 b/src/caas/services/jinja/icinga_host_vars.j2
new file mode 100644
index 0000000..c25b0ec
--- /dev/null
+++ b/src/caas/services/jinja/icinga_host_vars.j2
@@ -0,0 +1,38 @@
+---
+icinga_routers:
+{% for router in icinga_routers %}
+ - name: "{{ router.name }}"
+ address6: "{{ router.address6 }}"
+ group: "icingaadmins"
+{% endfor %}
+
+icinga_bmcs:
+{% for bmc in icinga_bmcs %}
+ - name: "{{ bmc.name }}"
+ address6: "{{ bmc.address6 }}"
+ group: "icingaadmins"
+{% if bmc.preauth %}
+ preauth: True
+{% endif %}
+{% if bmc.bmc_password_admin_default is not none %}
+ bmc_username_admin_default: "Administrator"
+ bmc_password_admin_default: "{{ bmc.bmc_password_admin_default }}"
+{% endif %}
+{% if bmc.bmc_password_admin is not none %}
+ bmc_username_admin: "Administrator"
+ bmc_password_admin: "{{ bmc.bmc_password_admin }}"
+{% endif %}
+{% if bmc.bmc_username is not none and bmc.bmc_password is not none %}
+ bmc_username: "{{ bmc.bmc_username }}"
+ bmc_password: "{{ bmc.bmc_password }}"
+{% endif %}
+{% if bmc.middleware_endpoint is not none %}
+ middleware_endpoint: "{{ bmc.middleware_endpoint }}"
+{% endif %}
+{% if bmc.middleware_username is not none %}
+ middleware_username: "{{ bmc.middleware_username }}"
+{% endif %}
+{% if bmc.middleware_password is not none %}
+ middleware_password: "{{ bmc.middleware_password }}"
+{% endif %}
+{% endfor %}
diff --git a/src/caas/services/jinja/icinga_inventory.j2 b/src/caas/services/jinja/icinga_inventory.j2
new file mode 100644
index 0000000..c003afe
--- /dev/null
+++ b/src/caas/services/jinja/icinga_inventory.j2
@@ -0,0 +1,6 @@
+---
+all:
+ children:
+ target:
+ hosts:
+ {{ target }}
diff --git a/src/caas/services/jinja/icinga_poll_host_vars.j2 b/src/caas/services/jinja/icinga_poll_host_vars.j2
new file mode 100644
index 0000000..e2aaac7
--- /dev/null
+++ b/src/caas/services/jinja/icinga_poll_host_vars.j2
@@ -0,0 +1,11 @@
+---
+bmc_ip: "{{ bmc_ip }}"
+bmc_username: "{{ bmc_username }}"
+bmc_password: "{{ bmc_password }}"
+icinga_api_url: "{{ icinga_api_url }}"
+icinga_api_username: "{{ icinga_api_username }}"
+icinga_api_password: "{{ icinga_api_password }}"
+middleware_endpoint: "{{ middleware_endpoint }}"
+middleware_username: "{{ middleware_username }}"
+middleware_password: "{{ middleware_password }}"
+middleware_url: "{{ middleware_url }}"
diff --git a/src/caas/services/jinja/icinga_poll_inventory.j2 b/src/caas/services/jinja/icinga_poll_inventory.j2
new file mode 100644
index 0000000..6ef8ca2
--- /dev/null
+++ b/src/caas/services/jinja/icinga_poll_inventory.j2
@@ -0,0 +1,6 @@
+---
+all:
+ children:
+ {{ vendor_name }}:
+ hosts:
+ {{ target }}
diff --git a/src/caas/services/jinja/wr_host_vars.j2 b/src/caas/services/jinja/wr_host_vars.j2
new file mode 100644
index 0000000..15a6f4d
--- /dev/null
+++ b/src/caas/services/jinja/wr_host_vars.j2
@@ -0,0 +1,74 @@
+---
+wr_vendor_name: "{{ wr_vendor_name }}"
+wr_region_name: "{{ wr_region_name }}"
+wr_location: "{{ wr_region_name }}"
+wr_bmc_username: "{{ wr_bmc_username }}"
+wr_bmc_password: "{{ wr_bmc_password }}"
+wr_bmc_controller_0_address: "{{ wr_bmc_controller_0_address }}"
+{% if wr_bmc_controller_1_address %}
+wr_bmc_controller_1_address: "{{ wr_bmc_controller_1_address }}"
+{% endif %}
+wr_mgmt_controller_0_mac: "{{ wr_mgmt_controller_0_mac }}"
+{% if wr_bmc_controller_1_mac %}
+wr_mgmt_controller_1_mac: "{{ wr_mgmt_controller_1_mac }}"
+{% endif %}
+wr_oam_floating_address: "{{ wr_oam_floating_address }}/64"
+wr_oam_node_0_address: "{{ wr_oam_node_0_address }}/64"
+{% if wr_oam_node_1_address %}
+wr_oam_node_1_address: "{{ wr_oam_node_1_address }}/64"
+{% endif %}
+wr_oam_gateway_address: "{{ wr_oam_gateway_address }}"
+wr_mgmt_start_address: "{{ wr_mgmt_start_address }}"
+wr_mgmt_end_address: "{{ wr_mgmt_end_address }}"
+wr_mgmt_gateway_address: "{{ wr_mgmt_gateway_address }}"
+wr_mgmt_subnet: "{{ wr_mgmt_subnet }}/64"
+wr_central_mgmt_start_address: "{{ wr_central_mgmt_start_address }}"
+wr_central_mgmt_default_gateway: "{{ wr_central_mgmt_default_gateway }}"
+wr_central_mgmt_subnet: "{{ wr_central_mgmt_subnet }}"
+wr_host_vlan: {{ wr_host_vlan }}
+wr_oam_vlan: {{ wr_oam_vlan }}
+wr_mgmt_vlan: {{ wr_mgmt_vlan }}
+wr_cluster_pod_subnet: "fd00:4888::/64"
+wr_cluster_host_subnet: "fd00:4888:0:1::/64"
+wr_cluster_service_subnet: "fd00:4888:0:2::/112"
+wr_mgmt_multicast_subnet: "ff05::18:1:0/124"
+{% if wr_dns_servers %}
+wr_dns_servers:
+{% for server in wr_dns_servers %}
+ - "{{ server }}"
+{% endfor %}
+{% endif %}
+{% if wr_ntp_servers %}
+wr_ntp_servers:
+{% for server in wr_ntp_servers %}
+ - "{{ server }}"
+{% endfor %}
+{% endif %}
+wr_no_check_cert: True
+wr_registry_server: "{{ wr_registry_server }}"
+wr_registry_username: "{{ wr_registry_username }}"
+wr_registry_password: "{{ wr_registry_password }}"
+wr_webdav_server: "{{ wr_webdav_server }}"
+wr_webdav_username: "{{ wr_webdav_username }}"
+wr_webdav_password: "{{ wr_webdav_password }}"
+wr_middleware_endpoint: "{{ wr_middleware_endpoint }}"
+wr_middleware_username: "{{ wr_middleware_username }}"
+wr_middleware_password: "{{ wr_middleware_password }}"
+{% if wr_docker_http_proxy %}
+wr_docker_http_proxy: "{{ wr_docker_http_proxy }}"
+{% endif %}
+{% if wr_docker_https_proxy %}
+wr_docker_https_proxy: "{{ wr_docker_https_proxy }}"
+{% endif %}
+{% if wr_docker_no_proxy %}
+wr_docker_no_proxy: {{ wr_docker_no_proxy }}
+{% endif %}
+wr_oam_nic_0: ens3f0
+wr_oam_nic_1: ens3f1
+wr_nic_main: ens3f0
+wr_no_check_cert: True
+
+wr_ssl_ca_cert: |
+{% filter indent(width=2) %}
+ {{ wr_ssl_ca_cert }}
+{% endfilter %}
diff --git a/src/caas/services/jinja/wr_inventory.j2 b/src/caas/services/jinja/wr_inventory.j2
new file mode 100644
index 0000000..814d692
--- /dev/null
+++ b/src/caas/services/jinja/wr_inventory.j2
@@ -0,0 +1,11 @@
+---
+all:
+ children:
+ central:
+ hosts:
+ {{ central_target }}:
+ ansible_host: {{ central_target_ip }}
+ remote:
+ hosts:
+ {{ remote_target }}:
+ ansible_host: {{ remote_target_ip }}
diff --git a/src/caas/services/macaddressservice.py b/src/caas/services/macaddressservice.py
new file mode 100644
index 0000000..745f5fa
--- /dev/null
+++ b/src/caas/services/macaddressservice.py
@@ -0,0 +1,38 @@
+from ..models import Server
+from .helpers import Address
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class MacAddressService():
+ @staticmethod
+ def set_server_mac_address(ilo_host_address, pxe_mac_address, intel_nic_firmware_version):
+ try:
+ server_kwargs = {"ilo_host_address": Address.strip_brackets(ilo_host_address)}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ logger.error("%s FAILED: while setting server MAC address: failed to retrieve server" % ilo_host_address)
+ return -1
+ changed_p = False
+ if server.pxe_mac_address != pxe_mac_address:
+ server.pxe_mac_address = pxe_mac_address
+ changed_p = True
+ if server.intel_nic_firmware_version != intel_nic_firmware_version:
+ server.intel_nic_firmware_version = intel_nic_firmware_version
+ changed_p = True
+ if changed_p:
+ server.save()
+ return 0
+
+ @staticmethod
+ def get_server_mac_address(ilo_host_address):
+ try:
+ server_kwargs = {"ilo_host_address": Address.strip_brackets(ilo_host_address)}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ logger.error("%s FAILED: while getting server MAC address: failed to retrieve server" % ilo_host_address)
+ return None
+
+ return server.pxe_mac_address
diff --git a/src/caas/services/nicfirmwareupgradenowservice.py b/src/caas/services/nicfirmwareupgradenowservice.py
new file mode 100644
index 0000000..d31fd74
--- /dev/null
+++ b/src/caas/services/nicfirmwareupgradenowservice.py
@@ -0,0 +1,68 @@
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+from .firmwareservice import FirmwareService
+from .wrinstallnowservice import WrInstallNowService
+from ..models import FirmwareBatch, WrBatch, WrInstallSchedule, FirmwareUpgradeSchedule
+from django.conf import settings
+import logging
+import datetime
+
+
+logger = logging.getLogger("caas")
+
+
+class NicFirmwareUpgradeNowService(AnsibleService):
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("nic", target, git, branch, playbook, zmq)
+
+ def deploy(self):
+ try:
+ firmwarebatch_kwargs = {"ilo_host_address": self.target}
+ firmwarebatch = FirmwareBatch.objects.get(**firmwarebatch_kwargs)
+ except FirmwareBatch.DoesNotExist:
+ errmsg = "FAILED: while checking Intel NIC firmware version: cannot find server with ilo_host_address: %s" % self.target
+ logger.error(errmsg)
+ return -1
+ completed = datetime.datetime.utcnow()
+ if firmwarebatch.intel_nic_firmware_version == settings.INTEL_NIC_FIRMWARE_VERSION:
+ try:
+ wrbatch_kwargs = {"ilo_host_address": firmwarebatch.ilo_host_address}
+ wrbatch = WrBatch.objects.get(**wrbatch_kwargs)
+ except WrBatch.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find wrbatch with identifier: %s" % icingabmc.ilo_host_address)
+ raise
+ try:
+ wr_kwargs = {"cluster_id": wrbatch.cluster_id}
+ wr = WrInstallSchedule.objects.get(**wr_kwargs)
+ except WrInstallSchedule.DoesNotExist:
+ return "FAILED: while performing Wind River playbook callback: cannot find WrInstallSchedule associated with the cluster with cluster_id: %s" % cluster.pk
+ # never run an installation on top of a finished subcloud
+ if wr.kirke_ticket_number is None:
+ if wr.kirke_ticket_completed is None:
+ wr.kirke_ticket_completed = completed
+ wr.date_completed = None
+ wr.kirke_ticket_number = None
+ wr.save()
+ wrinstallnow = WrInstallNowService("wr",
+ firmwarebatch.ilo_host_address,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wrinstallnow.deploy()
+ else:
+ try:
+ nic_kwargs = {"server_id": firmwarebatch.pk}
+ nic = FirmwareUpgradeSchedule.objects.get(**nic_kwargs)
+ except FirmwareUpgradeSchedule.DoesNotExist:
+ logger.error("FAILED: while deploying Wind River installation: cannot find FirmwareUpgradeSchedule with ID: %s" % icingabmc.pk)
+ raise
+ if nic.kirke_ticket_completed is None:
+ nic.kirke_ticket_completed = completed
+ nic.date_completed = None
+ nic.kirke_ticket_number = None
+ nic.save()
+ firmwareservice = FirmwareService(firmwarebatch.ilo_host_address, self.git, self.branch, self.playbook, self.zmq)
+ firmwareservice.deploy()
+ # The playbook callback will trigger the wr install
+ return 0
diff --git a/src/caas/services/nicfirmwareversionservice.py b/src/caas/services/nicfirmwareversionservice.py
new file mode 100644
index 0000000..b8a9d43
--- /dev/null
+++ b/src/caas/services/nicfirmwareversionservice.py
@@ -0,0 +1,37 @@
+import datetime
+from .helpers import Address
+from ..models import FirmwareUpgradeSchedule, Server
+from django.conf import settings
+import logging
+
+
+logger = logging.getLogger("caas")
+
+class NicFirmwareVersionService():
+ @staticmethod
+ def set_server_nic_firmware_version(ilo_host_address, intel_nic_firmware_version):
+ try:
+ server_kwargs = {"ilo_host_address": ilo_host_address}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ errmsg = "FAILED: while upgrading Intel NIC firmware: cannot find server with ilo_host_address: %s" % ilo_host_address
+ logger.error(errmsg)
+ return -1
+
+ try:
+ firmware_kwargs = {"server_id": server.pk}
+ firmware = FirmwareUpgradeSchedule.objects.get(**firmware_kwargs)
+ except FirmwareUpgradeSchedule.DoesNotExist:
+ errmsg = "FAILED: while upgrading Intel NIC firmware: cannot find FirmwareUpgradeSchedule associated with the server with ilo_hostname: %s" % server.ilo_hostname
+ logger.error(errmsg)
+ return -1
+
+ if intel_nic_firmware_version == settings.INTEL_NIC_FIRMWARE_VERSION:
+ firmware.date_completed = datetime.datetime.now()
+ else:
+ firmware.date_last_failed = datetime.datetime.now()
+
+ server.intel_nic_firmware_version = intel_nic_firmware_version
+ server.save()
+ firmware.save()
+ return 0
diff --git a/src/caas/services/passwordservice.py b/src/caas/services/passwordservice.py
new file mode 100644
index 0000000..ab666c8
--- /dev/null
+++ b/src/caas/services/passwordservice.py
@@ -0,0 +1,180 @@
+import csv
+import hashlib
+import os
+from io import TextIOWrapper
+from django.conf import settings
+from ..models import Cluster, Server, Credential, Chassis, Blade, Vendor
+import logging
+
+logger = logging.getLogger("caas")
+
+class PasswordService():
+ @staticmethod
+ def import_password_data(filehandle):
+ f = TextIOWrapper(filehandle, encoding = "ascii", errors = "replace")
+ csv_reader = csv.reader(f, delimiter=",")
+ for row in csv_reader:
+ serial_number_chassis = "ILO%s" % row[1]
+ serial_number_blade = "ILO%s" % row[2]
+ password = row[3]
+ credential_kwargs = {"bmc_password_admin_default": password,
+ "bmc_password_admin__isnull": True,
+ "bmc_username__isnull": True,
+ "bmc_password__isnull": True}
+ credential, credential_created = Credential.objects.get_or_create(**credential_kwargs)
+ chassis_kwargs = {"serial_number": serial_number_chassis}
+ chassis, chassis_created = Chassis.objects.get_or_create(**chassis_kwargs)
+ blade_kwargs = {"serial_number": serial_number_blade}
+ blade, blade_created = Blade.objects.get_or_create(**blade_kwargs)
+ blade.credential = credential
+ blade.chassis = chassis
+ credential.save()
+ chassis.save()
+ blade.save()
+
+ @staticmethod
+ def get_server_link_data(ilo_host_address):
+ try:
+ server_kwargs = {"ilo_host_address": ilo_host_address}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up server ilo_host_address=%s" % ilo_host_address)
+ return None
+ try:
+ blade_kwargs = {"id": server.blade.pk}
+ blade = Blade.objects.get(**blade_kwargs)
+ except Blade.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up blade ilo_host_address=%s" % ilo_host_address)
+ return None
+ try:
+ vendor_kwargs = {"id": blade.vendor.pk}
+ vendor = Vendor.objects.get(**vendor_kwargs)
+ except Vendor.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up vendor ilo_host_address=%s" % ilo_host_address)
+ return None
+ return {"vendor": vendor.vendor_name,
+ "serial_number": blade.serial_number,
+ "ilo_host_address": server.ilo_host_address}
+
+ @staticmethod
+ def link_server_to_blade(vendor_name, serial_number, ilo_host_address):
+ try:
+ server_kwargs = {"ilo_host_address": ilo_host_address}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up server! ilo_host_address=%s" % ilo_host_address)
+ return None
+ try:
+ blade_kwargs = {"serial_number": serial_number}
+ blade = Blade.objects.get(**blade_kwargs)
+ except Blade.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up blade! ilo_host_address=%s" % ilo_host_address)
+ return None
+ try:
+ vendor_kwargs = {"vendor_name": vendor_name}
+ vendor = Vendor.objects.get(**vendor_kwargs)
+ except Vendor.DoesNotExist:
+ logger.error("FAILED: while linking server to blade: failed to look up vendor! ilo_host_address=%s" % ilo_host_address)
+ return None
+ server.blade = blade
+ blade.vendor = vendor
+ server.save()
+ blade.save()
+ return server
+
+ @staticmethod
+ def is_server_linked_to_blade(ilo_host_address):
+ try:
+ server_kwargs = {"ilo_host_address": ilo_host_address}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ return True # server doesn't exist, no action should be triggered
+
+ if server.blade is None or server.pxe_mac_address is None:
+ return False # server is not yet linked with blade
+ else:
+ return True # server is already linked with blade
+
+ @staticmethod
+ def generate_unique_password():
+ m = hashlib.sha1()
+ m.update(os.urandom(16))
+ return m.hexdigest()[:20]
+
+ @staticmethod
+ def create_bmc_admin_password(serial_number):
+ try:
+ blade_kwargs = {"serial_number": serial_number}
+ blade = Blade.objects.get(**blade_kwargs)
+ except Blade.DoesNotExist:
+ logger.error("FAILED: while creating BMC account: failed to look up blade for serial %s" % serial_number)
+ return -1
+ if blade.credential is None:
+ logger.error("FAILED: while creating BMC account: failed to look up credential for serial %s" % serial_number)
+ return -1
+ if blade.credential.bmc_password is None:
+ # We don't want to update this Credential because it will affect
+ # every Blade that is tied to it. We need to create a new
+ # Credential and link the Blade to the new Credential.
+ admin_password = PasswordService.generate_unique_password()
+ ospctl_password = PasswordService.generate_unique_password()
+ credential_kwargs = {"bmc_password_admin_default": blade.credential.bmc_password_admin_default}
+ credential, credential_created = Credential.objects.get_or_create(**credential_kwargs)
+ blade.credential = credential
+ credential.bmc_password_admin = admin_password
+ credential.bmc_username = settings.BMC_USERNAME
+ credential.bmc_password = ospctl_password
+ blade.save()
+ credential.save()
+ logger.debug("OK: passwords created for serial %s" % serial_number)
+ return 0
+ logger.debug("OK: no-op for serial %s" % serial_number)
+ return 0
+
+ @staticmethod
+ def central_server_p(ilo_host_address):
+ try:
+ server_kwargs = {"ilo_host_address": ilo_host_address}
+ server = Server.objects.get(**server_kwargs)
+ except Server.DoesNotExist:
+ logger.error("FAILED: while determining if the server with ilo_host_address %s is a central server: server not found." % ilo_host_address)
+ return True
+ try:
+ cluster_kwargs = {"id": server.cluster.pk}
+ cluster = Cluster.objects.get(**cluster_kwargs)
+ except Cluster.DoesNotExist:
+ logger.error("FAILED: while determining if the server with ilo_host_address %s is a central server: cluster not found." % ilo_host_address)
+ return True
+ return False if cluster.is_central_controller==0 else True
+
+ @staticmethod
+ def fix_creds():
+ bad_creds = Credential.objects.raw("""
+with dupes (bmc_password_admin_default, zahl) as (
+ select bmc_password_admin_default,
+ count(bmc_password_admin_default) as zahl
+ from caas_credential
+ group by bmc_password_admin_default
+)
+select c.id,
+ c.bmc_password_admin_default,
+ c.bmc_password_admin,
+ c.bmc_username,
+ c.bmc_password
+from caas_credential c
+left outer join dupes d
+ on c.bmc_password_admin_default = d.bmc_password_admin_default
+where d.zahl > 1
+ and bmc_password is null""")
+ for bad_cred in bad_creds:
+ good_cred_kwargs = {"bmc_password_admin_default": bad_cred.bmc_password_admin_default,
+ "bmc_password__isnull": False}
+ good_cred = Credential.objects.get(**good_cred_kwargs)
+ try:
+ blade_kwargs = {"credential_id": bad_cred.id}
+ blade = Blade.objects.get(**blade_kwargs)
+ blade.credential_id = good_cred.pk
+ blade.save()
+ bad_cred.delete()
+ except Blade.DoesNotExist:
+ pass
diff --git a/src/caas/services/patchservice.py b/src/caas/services/patchservice.py
new file mode 100644
index 0000000..c986051
--- /dev/null
+++ b/src/caas/services/patchservice.py
@@ -0,0 +1,106 @@
+import datetime
+from ..models import Cluster, WrInstallSchedule, PatchSchedule, WrBatch
+from django.conf import settings
+from .helpers import Jinja
+from .ansibleservice import AnsibleService
+
+
+class PatchService(AnsibleService):
+ """
+ target == ilo_host_address, later becomes cluster_name
+ """
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("patch", target, git, branch, playbook, zmq)
+
+ def schedule_install(self):
+ pass
+ # try:
+ # patchbatch_kwargs = {"ilo_host_address": self.target}
+ # # repurpose WrBatch
+ # patchbatch = WrBatch.objects.get(**patchbatch_kwargs)
+ # except WrBatch.DoesNotExist:
+ # return "FAILED: while scheduling Wind River installation: cannot find wrbatch with ilo_host_address: %s" % self.target
+ # try:
+ # cluster_kwargs = {"pk": patchbatch.cluster_id}
+ # cluster = Cluster.objects.get(**cluster_kwargs)
+ # except Cluster.DoesNotExist:
+ # return "FAILED: while scheduling Wind River installation: cannot find cluster associated with wrbatch with ilo_host_address: %s" % patchbatch.ilo_host_address
+ # patch_kwargs = {"cluster_id": cluster.pk}
+ # patch, patch_created = PatchSchedule.objects.get_or_create(**patch_kwargs)
+ # if patch.date_scheduled is None:
+ # patch.date_scheduled = datetime.datetime.now()
+ # if patch.kirke_ticket_number is None:
+ # # TODO bealer: create KIRKE ticket
+ # pass
+ # patch.save()
+ # # one playbook per cluster
+ # self.target = cluster.cluster_name
+ # with tempfile.TemporaryDirectory() as tempdir:
+ # builddir = "%s/%s" % (tempdir, self.repo_name)
+ # self.clone_repo(builddir)
+ # # host_vars file
+ # patch_config = {"wr_vendor_name": patchbatch.vendor_name,
+ # "wr_region_name": cluster.cluster_name,
+ # "wr_central_p": False if patchbatch.parent_oam_vip_hostname else True,
+ # "wr_bmc_controller_0_address": patchbatch.ilo_host_address,
+ # "wr_mgmt_controller_0_mac": patchbatch.pxe_mac_address,
+ # "wr_oam_floating_address": patchbatch.oam_vip_address,
+ # "wr_oam_node_0_address": patchbatch.oam_host_address,
+ # "wr_oam_gateway_address": patchbatch.oam_default_gateway,
+ # "wr_mgmt_start_address": patchbatch.mgmt_address_range_start,
+ # "wr_mgmt_end_address": patchbatch.mgmt_address_range_end,
+ # "wr_mgmt_gateway_address": patchbatch.mgmt_default_gateway,
+ # "wr_mgmt_subnet": patchbatch.mgmt_subnet,
+ # "wr_central_mgmt_start_address": patchbatch.parent_mgmt_address_range_start,
+ # "wr_central_mgmt_default_gateway": patchbatch.parent_mgmt_default_gateway,
+ # "wr_host_vlan": patchbatch.host_vlan,
+ # "wr_oam_vlan": patchbatch.oam_vlan,
+ # "wr_mgmt_vlan": patchbatch.mgmt_vlan,
+ # "wr_bmc_username": patchbatch.bmc_username,
+ # "wr_bmc_password": patchbatch.bmc_password,
+ # "wr_dns_servers": settings.DNS_SERVERS,
+ # "wr_ntp_servers": settings.NTP_SERVERS,
+ # "wr_registry_server": settings.WR_REGISTRY_SERVER,
+ # "wr_registry_username": settings.WR_REGISTRY_USERNAME,
+ # "wr_registry_password": settings.WR_REGISTRY_PASSWORD,
+ # "wr_webdav_server": settings.WR_WEBDAV_SERVER,
+ # "wr_webdav_username": settings.WR_WEBDAV_USERNAME,
+ # "wr_webdav_password": settings.WR_WEBDAV_PASSWORD,
+ # "wr_docker_http_proxy": settings.WR_DOCKER_HTTP_PROXY,
+ # "wr_docker_https_proxy": settings.WR_DOCKER_HTTPS_PROXY,
+ # "wr_docker_no_proxy": settings.WR_DOCKER_NO_PROXY,
+ # "wr_ssl_ca_cert": settings.WR_SSL_CA_CERT}
+ # content = Jinja.render("patch_host_vars.j2", **patch_config)
+ # filename = "%s/host_vars/%s" % (builddir, patchbatch.oam_hostname)
+ # with AnsibleVaultFile(filename) as f:
+ # f.write(content)
+ # # inventory file
+ # patch_config = {"central_target": patchbatch.parent_cluster_name,
+ # "central_target_ip": patchbatch.parent_oam_vip_address,
+ # "remote_target": patchbatch.oam_hostname,
+ # "remote_target_ip": patchbatch.oam_vip_address}
+ # content = Jinja.render("patch_inventory.j2", **patch_config)
+ # filename = "%s/inventory/%s.yaml" % (builddir, cluster.cluster_name)
+ # with open(filename, "w") as f:
+ # f.write(content)
+ # self.push()
+
+ def deploy(self):
+ pass
+ # kirke_inprocess = PatchSchedule.objects.filter(kirke_ticket_number__isnull = False,
+ # kirke_ticket_completed__isnull = True)
+ # for rec in kirke_inprocess:
+ # # TODO bealer: query KIRKE and update status
+ # rec.save()
+ # patch_schedule_kwargs = {"kirke_ticket_completed__isnull": False,
+ # "date_completed__isnull": True}
+ # patch_schedule_ready = PatchSchedule.objects.filter(**patch_schedule_kwargs)
+ # for sched in patch_schedule_ready:
+ # try:
+ # patchbatch_kwargs = {"cluster_id": sched.cluster.pk,
+ # "intel_nic_firmware_version": settings.INTEL_NIC_FIRMWARE_VERSION}
+ # patchbatch = WrBatch.objects.filter(**patchbatch_kwargs)
+ # except WrBatch.DoesNotExist:
+ # return "FAILED: while running PATCH batch playbook: failed to look up wrbatch"
+ # self.target = patchbatch.cluster_name
+ # self.run_playbook()
diff --git a/src/caas/services/playbookreportservice.py b/src/caas/services/playbookreportservice.py
new file mode 100644
index 0000000..73abdfa
--- /dev/null
+++ b/src/caas/services/playbookreportservice.py
@@ -0,0 +1,147 @@
+import datetime
+from django.conf import settings
+from ..models import FirmwareUpgradeSchedule, WrInstallSchedule, Location, Cluster, Server, IcingaBmc, WrBatch
+from .wrinstallnowservice import WrInstallNowService
+from .icingapollservice import IcingaPollService
+from .bmcservice import BmcService
+from .httpclient import HttpClient
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class PlaybookReportService():
+ def __init__(self, git, playbook, target, failed):
+ self.git = git
+ self.playbook = playbook
+ self.target = target
+ self.failed = failed
+
+ def process(self):
+ if ".yaml" in self.playbook:
+ ansible_config = {k: v for (k, v) in settings.ANSIBLE.items() if self.playbook in v.values() and v["git"] == self.git}
+ else:
+ ansible_config = {k: v for (k, v) in settings.ANSIBLE.items() if k == self.playbook and v["git"] == self.git}
+ key = [k for k in ansible_config.keys()][0]
+ if key == "wr":
+ self.target = self.target.split(".yaml")[0]
+ try:
+ cluster_kwargs = {"cluster_name": self.target}
+ cluster = Cluster.objects.get(**cluster_kwargs)
+ except Server.DoesNotExist:
+ return "FAILED: while performing Wind River playbook callback: cannot find cluster with cluster_name: %s" % self.target
+ try:
+ wr_kwargs = {"cluster_id": cluster.pk}
+ wr = WrInstallSchedule.objects.get(**wr_kwargs)
+ except WrInstallSchedule.DoesNotExist:
+ return "FAILED: while performing Wind River playbook callback: cannot find WrInstallSchedule associated with the cluster with cluster_name: %s" % cluster.cluster_name
+ completed = datetime.datetime.utcnow()
+ if wr.kirke_ticket_completed is None:
+ wr.kirke_ticket_completed = completed
+ if self.failed == "0":
+ wr.date_completed = completed
+ wr.kirke_ticket_number = None # Still needs manual check/remediation
+ # # Change default BMC password
+ # server_kwargs = {"cluster_id": cluster.pk}
+ # servers = Server.objects.filter(**server_kwargs)
+ # for server in servers:
+ # bmc = BmcService(server.ilo_host_address,
+ # settings.ANSIBLE["bmc"]["git"],
+ # settings.ANSIBLE["bmc"]["branch"],
+ # settings.ANSIBLE["bmc"]["playbook_adminpassword"],
+ # settings.ANSIBLE["bmc"]["zmq"])
+ # bmc.deploy()
+ # Orchstration callback
+ try:
+ location_kwargs = {"pk": cluster.location_id}
+ location = Location.objects.get(**location_kwargs)
+ except Location.DoesNotExist:
+ return "FAILED: while performing Wind River playbook callback: cannot find Location associated with the cluster with cluster_name: %s" % cluster.cluster_name
+ payload = {"cluster_status": [{
+ "name": cluster.cluster_name,
+ "description": location.fuze_spm_site_name,
+ "location": location.fuze_spm_site_id,
+ "software_version": settings.WR_VERSION,
+ "availability": "ONLINE",
+ "deploy_status": "COMPLETE",
+ "created_at": completed.strftime("%Y-%m-%d %H:%M:%S"),
+ "updated_at": completed.strftime("%Y-%m-%d %H:%M:%S")}]}
+ httpclient = HttpClient()
+ resp = httpclient.post(url = "%s/orchestration/caas-status" % settings.CAAS_MIDDLEWARE_ENDPOINT,
+ username = settings.CAAS_MIDDLEWARE_USERNAME,
+ password = settings.CAAS_MIDDLEWARE_PASSWORD,
+ data = payload)
+ if resp.status_code >= 300:
+ return "Error calling orchestration endpoint. Status code: %s , message: %s" % (resp.status_code, resp.text)
+ else:
+ wr.date_completed = None
+ wr.kirke_ticket_number = None
+ wr.date_last_failed = completed
+ wr.save()
+ elif key == "nic":
+ self.target = self.target.split(".yaml")[0]
+ try:
+ icingabmc_kwargs = {"ilo_hostname": self.target}
+ icingabmc = IcingaBmc.objects.get(**icingabmc_kwargs)
+ except IcingaBmc.DoesNotExist:
+ logger.error("FAILED: while deploying Wind River installation: cannot find IcingaBmc with identifier: %s" % self.target)
+ raise
+ try:
+ nic_kwargs = {"server_id": icingabmc.pk}
+ nic = FirmwareUpgradeSchedule.objects.get(**nic_kwargs)
+ except FirmwareUpgradeSchedule.DoesNotExist:
+ logger.error("FAILED: while deploying Wind River installation: cannot find FirmwareUpgradeSchedule with ID: %s" % icingabmc.pk)
+ raise
+ completed = datetime.datetime.utcnow()
+ if nic.kirke_ticket_completed is None:
+ nic.kirke_ticket_completed = completed
+ if self.failed == "0":
+ try:
+ wrbatch_kwargs = {"ilo_host_address": icingabmc.ilo_host_address}
+ wrbatch = WrBatch.objects.get(**wrbatch_kwargs)
+ except WrBatch.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find wrbatch with identifier: %s" % icingabmc.ilo_host_address)
+ raise
+ # never run an installation on top of a finished subcloud
+ try:
+ wr_kwargs = {"cluster_id": wrbatch.cluster_id}
+ wr = WrInstallSchedule.objects.get(**wr_kwargs)
+ except WrInstallSchedule.DoesNotExist:
+ return "FAILED: while performing Wind River playbook callback: cannot find WrInstallSchedule associated with the cluster with cluster_name: %s" % cluster.cluster_name
+ if wr.kirke_ticket_number is None:
+ nic.date_completed = completed
+ nic.kirke_ticket_number = nic.pk
+ wrinstallnow = WrInstallNowService("wr",
+ wrbatch.ilo_host_address,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wrinstallnow.deploy()
+ else:
+ nic.date_completed = None
+ nic.kirke_ticket_number = None
+ nic.date_last_failed = completed
+ nic.save()
+ elif key == "bmc":
+ self.target = self.target.split(".yaml")[0]
+ if self.failed == "0":
+ try:
+ icingabmc_kwargs = {"ilo_hostname": self.target}
+ icingabmc = IcingaBmc.objects.get(**icingabmc_kwargs)
+ except IcingaBmc.DoesNotExist:
+ logger.error("FAILED: while deploying Wind River installation: cannot find IcingaBmc with identifier: %s" % self.target)
+ raise
+ icinga_poll = IcingaPollService(icingabmc.ilo_host_address,
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "adminpassword")
+ rc = icinga_poll.deploy()
+ return {"git": self.git,
+ "playbook": self.playbook,
+ "target": self.target,
+ "failed": self.failed,
+ "key": key}
diff --git a/src/caas/services/subcloudservice.py b/src/caas/services/subcloudservice.py
new file mode 100644
index 0000000..5095bbb
--- /dev/null
+++ b/src/caas/services/subcloudservice.py
@@ -0,0 +1,12 @@
+from ..models import WrBatch
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class SubcloudService():
+ @staticmethod
+ def find_subclouds(fuze_id):
+ wrbatch_kwargs = {"fuze_spm_site_id": fuze_id}
+ return WrBatch.objects.filter(**wrbatch_kwargs)
diff --git a/src/caas/services/wrinstallnowservice.py b/src/caas/services/wrinstallnowservice.py
new file mode 100644
index 0000000..4db0ec4
--- /dev/null
+++ b/src/caas/services/wrinstallnowservice.py
@@ -0,0 +1,107 @@
+import tempfile
+import datetime
+from ..models import Cluster, WrInstallSchedule, WrBatch, Namespace
+from django.conf import settings
+from .helpers import Address, Jinja
+from .ansibleservice import AnsibleService
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class WrInstallNowService(AnsibleService):
+ """
+ target == ilo_host_address, later becomes cluster_name
+ """
+ def __init__(self, ansible_queue, target, git, branch, playbook, zmq):
+ super().__init__(ansible_queue, target, git, branch, playbook, zmq)
+
+ def get_wrbatch(self):
+ if self.ansible_queue == "wr":
+ wrbatch_kwargs = {"ilo_host_address": self.target}
+ elif self.ansible_queue in ("wr_remediate", "wr_wipedisk", "wr_ptp", "wr_ptp_config", "wr_unlock", "wr_wrap"):
+ wrbatch_kwargs = {"cluster_name": self.target}
+ elif self.ansible_queue == "wr_reboot":
+ wrbatch_kwargs = {"namespace_name": self.target}
+ try:
+ wrbatch = WrBatch.objects.get(**wrbatch_kwargs)
+ except WrBatch.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find wrbatch with identifier: %s" % self.target)
+ raise
+ return wrbatch
+
+ def deploy(self):
+ logger.debug("%s Enter" % self.target)
+ wrbatch = self.get_wrbatch()
+ self.do_deploy(wrbatch)
+
+ def remediate(self):
+ logger.debug("%s Enter" % self.target)
+ wrbatch = self.get_wrbatch()
+ self.do_deploy(wrbatch)
+
+ def do_deploy(self, wrbatch):
+ try:
+ cluster_kwargs = {"pk": wrbatch.cluster_id}
+ cluster = Cluster.objects.get(**cluster_kwargs)
+ except Cluster.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find cluster associated with wrbatch with identifier: %s" % wrbatch.ilo_host_address)
+ raise
+
+ # one playbook per cluster
+ self.target = cluster.cluster_name
+ self.maint_window_p = wrbatch.maint_window_p
+ # host_vars file
+ hostvars_config = {"wr_vendor_name": wrbatch.vendor_name,
+ "wr_region_name": cluster.cluster_name,
+ "wr_bmc_controller_0_address": wrbatch.ilo_host_address,
+ "wr_mgmt_controller_0_mac": wrbatch.pxe_mac_address,
+ "wr_oam_floating_address": wrbatch.oam_vip_address,
+ "wr_oam_node_0_address": wrbatch.oam_host_address,
+ "wr_oam_gateway_address": wrbatch.oam_default_gateway,
+ "wr_mgmt_start_address": wrbatch.mgmt_address_range_start,
+ "wr_mgmt_end_address": wrbatch.mgmt_address_range_end,
+ "wr_mgmt_gateway_address": wrbatch.mgmt_default_gateway,
+ "wr_mgmt_subnet": Address.gateway_to_subnet(wrbatch.mgmt_subnet, numquads = 4),
+ "wr_central_mgmt_start_address": wrbatch.parent_mgmt_address_range_start,
+ "wr_central_mgmt_default_gateway": wrbatch.parent_mgmt_default_gateway,
+ "wr_central_mgmt_subnet": Address.gateway_to_subnet(wrbatch.parent_mgmt_subnet, numquads = 4),
+ "wr_host_vlan": wrbatch.host_vlan,
+ "wr_oam_vlan": wrbatch.oam_vlan,
+ "wr_mgmt_vlan": wrbatch.mgmt_vlan,
+ "wr_bmc_username": wrbatch.bmc_username,
+ "wr_bmc_password": wrbatch.bmc_password,
+ "wr_dns_servers": settings.DNS_SERVERS,
+ "wr_ntp_servers": settings.NTP_SERVERS,
+ "wr_registry_server": settings.WR_REGISTRY_SERVER,
+ "wr_registry_username": settings.WR_REGISTRY_USERNAME,
+ "wr_registry_password": settings.WR_REGISTRY_PASSWORD,
+ "wr_webdav_server": settings.WR_WEBDAV_SERVER,
+ "wr_webdav_username": settings.WR_WEBDAV_USERNAME,
+ "wr_webdav_password": settings.WR_WEBDAV_PASSWORD,
+ "wr_middleware_endpoint": settings.CAAS_MIDDLEWARE_ENDPOINT,
+ "wr_middleware_username": settings.CAAS_MIDDLEWARE_USERNAME,
+ "wr_middleware_password": settings.CAAS_MIDDLEWARE_PASSWORD,
+ "wr_docker_http_proxy": settings.WR_DOCKER_HTTP_PROXY,
+ "wr_docker_https_proxy": settings.WR_DOCKER_HTTPS_PROXY,
+ "wr_docker_no_proxy": settings.WR_DOCKER_NO_PROXY,
+ "wr_ssl_ca_cert": settings.WR_SSL_CA_CERT}
+ # revisit for NVPA-279
+ # if len(servers) > 1:
+ # wr_config["wr_bmc_controller_1_address"] = servers[1].ilo_host_address
+ # wr_config["wr_mgmt_controller_1_mac"] = servers[1].pxe_mac_address
+ # wr_config["wr_oam_node_1_address"] = servers[1].oam_host_address
+ hostvars_content = Jinja.render("wr_host_vars.j2", **hostvars_config)
+ hostvars = wrbatch.oam_hostname
+ # inventory file
+ inventory_config = {"central_target": wrbatch.parent_cluster_name,
+ "central_target_ip": wrbatch.parent_oam_vip_address,
+ "remote_target": wrbatch.oam_hostname,
+ "remote_target_ip": wrbatch.oam_vip_address}
+ inventory_content = Jinja.render("wr_inventory.j2", **inventory_config)
+ inventory = "%s.yaml" % self.target
+ logger.debug("%s Sending playbook to queue" % self.target)
+ self.run_playbook(inventory, inventory_content, hostvars, hostvars_content, extra_args = {"clusterName": wrbatch.parent_cluster_name})
+ logger.debug("%s Sent playbook to queue" % self.target)
+ return 0
diff --git a/src/caas/services/wrservice.py b/src/caas/services/wrservice.py
new file mode 100644
index 0000000..51b3c02
--- /dev/null
+++ b/src/caas/services/wrservice.py
@@ -0,0 +1,75 @@
+import datetime
+from ..models import Cluster, WrInstallSchedule, WrBatch
+from django.conf import settings
+from .helpers import Address, Jinja
+from .ansibleservice import AnsibleService
+from .icingapollservice import IcingaPollService
+import logging
+
+
+logger = logging.getLogger("caas")
+
+
+class WrService(AnsibleService):
+ """
+ target == ilo_host_address, later becomes cluster_name
+ """
+ def __init__(self, target, git, branch, playbook, zmq):
+ super().__init__("wr", target, git, branch, playbook, zmq)
+
+ def schedule_install(self):
+ logger.debug("%s Enter schedule_install" % self.target)
+ try:
+ wrbatch_kwargs = {"ilo_host_address": self.target}
+ wrbatch = WrBatch.objects.get(**wrbatch_kwargs)
+ except WrBatch.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find wrbatch with ilo_host_address: %s" % self.target)
+ raise
+ try:
+ cluster_kwargs = {"pk": wrbatch.cluster_id}
+ cluster = Cluster.objects.get(**cluster_kwargs)
+ except Cluster.DoesNotExist:
+ logger.error("FAILED: while scheduling Wind River installation: cannot find cluster associated with wrbatch with ilo_host_address: %s" % wrbatch.ilo_host_address)
+ raise
+ wr_kwargs = {"cluster_id": cluster.pk}
+ wr, wr_created = WrInstallSchedule.objects.get_or_create(**wr_kwargs)
+ if wr.date_scheduled is None:
+ wr.date_scheduled = datetime.datetime.now()
+ if wr.kirke_ticket_number is None:
+ # TODO bealer: create KIRKE ticket
+ pass
+ wr.save()
+
+ def deploy(self):
+ kirke_inprocess_kwargs = {"kirke_ticket_number__isnull": False,
+ "kirke_ticket_completed__isnull": True}
+ kirke_inprocess = WrInstallSchedule.objects.filter(**kirke_inprocess_kwargs)
+
+ for rec in kirke_inprocess:
+ # TODO bealer: query KIRKE and update status
+ rec.save()
+
+ wr_schedule_kwargs = {"kirke_ticket_completed__isnull": False,
+ "date_completed__isnull": True}
+ wr_schedule_ready = WrInstallSchedule.objects.filter(**wr_schedule_kwargs)
+
+ for sched in wr_schedule_ready:
+ try:
+ wrbatch_kwargs = {"cluster_id": sched.cluster.pk,
+ "intel_nic_firmware_version": settings.INTEL_NIC_FIRMWARE_VERSION}
+ wrbatch_results = WrBatch.objects.filter(**wrbatch_kwargs)
+
+ except WrBatch.DoesNotExist:
+ logger.error("FAILED: while running WR batch playbook: failed to look up wrbatch")
+ raise
+
+ # check hardware before wr isntall
+ wrbatch = wrbatch_results[0]
+ icingapoll = IcingaPollService(wrbatch.ilo_host_address,
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "wrinstallnow")
+ icingapoll.deploy()
+
diff --git a/src/caas/templates/adminpassword.xml b/src/caas/templates/adminpassword.xml
new file mode 100644
index 0000000..b362a25
--- /dev/null
+++ b/src/caas/templates/adminpassword.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="admin_password_form"
+ id="admin_password_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Change BMC administrator password"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/autoremediate.xml b/src/caas/templates/autoremediate.xml
new file mode 100644
index 0000000..31a87ac
--- /dev/null
+++ b/src/caas/templates/autoremediate.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="autoremediate_form"
+ id="autoremediate_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Autoremediate server"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/ciq.xml b/src/caas/templates/ciq.xml
new file mode 100644
index 0000000..baf97f1
--- /dev/null
+++ b/src/caas/templates/ciq.xml
@@ -0,0 +1,13 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="ciq_upload_form"
+ id="ciq_upload_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{% csrf_token %}
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upload and import CIQ"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/ciq_success.xml b/src/caas/templates/ciq_success.xml
new file mode 100644
index 0000000..831a75c
--- /dev/null
+++ b/src/caas/templates/ciq_success.xml
@@ -0,0 +1,4 @@
+{% extends "site.xml" %}
+{% block content %}
+CIQ import was a success!
+{% endblock %}
diff --git a/src/caas/templates/dns.xml b/src/caas/templates/dns.xml
new file mode 100644
index 0000000..1c57337
--- /dev/null
+++ b/src/caas/templates/dns.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="dns_form"
+ id="dns_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Update DNS records"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/macaddress.xml b/src/caas/templates/macaddress.xml
new file mode 100644
index 0000000..3aded91
--- /dev/null
+++ b/src/caas/templates/macaddress.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="mac_address_form"
+ id="mac_address_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upload BMC IP and MAC address"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/mockwrremediate.xml b/src/caas/templates/mockwrremediate.xml
new file mode 100644
index 0000000..0d8f26b
--- /dev/null
+++ b/src/caas/templates/mockwrremediate.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="mockwr_remediate_form"
+ id="mockwr_remediate_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Mock Remediate Wind River CaaS subcloud"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/nicfirmwareupgrade.xml b/src/caas/templates/nicfirmwareupgrade.xml
new file mode 100644
index 0000000..146e641
--- /dev/null
+++ b/src/caas/templates/nicfirmwareupgrade.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="nic_firmware_upgrade_form"
+ id="nic_firmware_upgrade_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Check hardware and upgrade NIC firmware"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/nicfirmwareupgradenow.xml b/src/caas/templates/nicfirmwareupgradenow.xml
new file mode 100644
index 0000000..9cf3511
--- /dev/null
+++ b/src/caas/templates/nicfirmwareupgradenow.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="nic_firmware_upgrade_now_form"
+ id="nic_firmware_upgrade_now_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upgrade NIC firmware now"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/nicfirmwareversion.xml b/src/caas/templates/nicfirmwareversion.xml
new file mode 100644
index 0000000..8d4cdf2
--- /dev/null
+++ b/src/caas/templates/nicfirmwareversion.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="firmware_version_form"
+ id="firmware_version_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upload BMC IP and NIC firmware version"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/passwords.xml b/src/caas/templates/passwords.xml
new file mode 100644
index 0000000..99d465e
--- /dev/null
+++ b/src/caas/templates/passwords.xml
@@ -0,0 +1,13 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="password_upload_form"
+ id="password_upload_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{% csrf_token %}
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upload and import serial numbers and passwords"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/passwords_success.xml b/src/caas/templates/passwords_success.xml
new file mode 100644
index 0000000..01be059
--- /dev/null
+++ b/src/caas/templates/passwords_success.xml
@@ -0,0 +1,4 @@
+{% extends "site.xml" %}
+{% block content %}
+Serial number and password import was a success!
+{% endblock %}
diff --git a/src/caas/templates/playbookreport.xml b/src/caas/templates/playbookreport.xml
new file mode 100644
index 0000000..c768b46
--- /dev/null
+++ b/src/caas/templates/playbookreport.xml
@@ -0,0 +1,13 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="playbook_report_form"
+ id="playbook_report_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{% csrf_token %}
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Post playbook status"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/pushbutton.xml b/src/caas/templates/pushbutton.xml
new file mode 100644
index 0000000..bc51019
--- /dev/null
+++ b/src/caas/templates/pushbutton.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="push_button_form"
+ id="push_button_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Make it so"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/queuecustom.xml b/src/caas/templates/queuecustom.xml
new file mode 100644
index 0000000..0cd5f58
--- /dev/null
+++ b/src/caas/templates/queuecustom.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="queue_custom_form"
+ id="queue_custom_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Send job to ansible-queue"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/serialnumber.xml b/src/caas/templates/serialnumber.xml
new file mode 100644
index 0000000..233cdfc
--- /dev/null
+++ b/src/caas/templates/serialnumber.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="serial_number_form"
+ id="serial_number_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Upload vendor, BMC IP and serial number"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/site.xml b/src/caas/templates/site.xml
new file mode 100644
index 0000000..7c46498
--- /dev/null
+++ b/src/caas/templates/site.xml
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1"></meta>
+ <meta charset="utf-8"></meta>
+ <title>Far-Edge Ops Middleware</title>
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"></link>
+ <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
+ <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
+ </head>
+ <body>
+ <div class="container-fluid">
+ <div class="row" style="padding: 20px">
+ <div class="col">
+ <div class="page-header">
+ <h2 align="center">Far-Edge Ops Middleware</h2>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+ </body>
+</html>
diff --git a/src/caas/templates/subcloud.xml b/src/caas/templates/subcloud.xml
new file mode 100644
index 0000000..fec8cfb
--- /dev/null
+++ b/src/caas/templates/subcloud.xml
@@ -0,0 +1,58 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="subcloud_form"
+ id="subcloud_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Find subcloud"/>
+</form>
+{% for subcloud in subclouds %}
+<div>
+ <pre>
+ fqdn: {{ subcloud.cluster_name }}.{{ fqdn }}
+ server_id: {{ subcloud.id }}
+ cluster_id: {{ subcloud.cluster_id }}
+ fuze_spm_site_id: {{ subcloud.fuze_spm_site_id }}
+ parent_oam_vip_hostname: {{ subcloud.parent_oam_vip_hostname }}
+ parent_oam_vip_address: {{ subcloud.parent_oam_vip_address }}
+ parent_mgmt_address_range_start: {{ subcloud.parent_mgmt_address_range_start }}
+ parent_mgmt_address_range_end: {{ subcloud.parent_mgmt_address_range_end }}
+ parent_mgmt_default_gateway: {{ subcloud.parent_mgmt_default_gateway }}
+ parent_mgmt_subnet: {{ subcloud.parent_mgmt_subnet }}
+ pxe_mac_address: {{ subcloud.pxe_mac_address }}
+ ilo_hostname: {{ subcloud.ilo_hostname }}
+ ilo_host_address: {{ subcloud.ilo_host_address }}
+ oam_hostname: {{ subcloud.oam_hostname }}
+ oam_host_address: {{ subcloud.oam_host_address }}
+ oam_vip_address: {{ subcloud.oam_vip_address }}
+ oam_default_gateway: {{ subcloud.oam_default_gateway }}
+ mgmt_address_range_start: {{ subcloud.mgmt_address_range_start }}
+ mgmt_address_range_end: {{ subcloud.mgmt_address_range_end }}
+ mgmt_default_gateway: {{ subcloud.mgmt_default_gateway }}
+ mgmt_subnet: {{ subcloud.mgmt_subnet }}
+ host_vlan: {{ subcloud.host_vlan }}
+ oam_vlan: {{ subcloud.oam_vlan }}
+ mgmt_vlan: {{ subcloud.mgmt_vlan }}
+ cluster_name: {{ subcloud.cluster_name }}
+ parent_cluster_name: {{ subcloud.parent_cluster_name }}
+ vendor_name: {{ subcloud.vendor_name }}
+ intel_nic_firmware_version: {{ subcloud.intel_nic_firmware_version }}
+ maint_window_p: {{ subcloud.maint_window_p }}
+ namespace_id: {{ subcloud.namespace_id }}
+ namespace_name: {{ subcloud.namespace_name }}
+ bmc_username: {{ subcloud.bmc_username }}
+ bmc_password: {{ subcloud.bmc_password }}
+ wrinstallschedule_id: {{ subcloud.wrinstallschedule_id }}
+ date_scheduled: {{ subcloud.date_scheduled }}
+ date_completed: {{ subcloud.date_completed }}
+ date_last_failed: {{ subcloud.date_last_failed }}
+ kirke_ticket_number: {{ subcloud.kirke_ticket_number }}
+ kirke_ticket_status: {{ subcloud.kirke_ticket_status }}
+ kirke_ticket_completed: {{ subcloud.kirke_ticket_completed }}
+ </pre>
+</div>
+{% endfor %}
+{% endblock %}
diff --git a/src/caas/templates/wrinstall.xml b/src/caas/templates/wrinstall.xml
new file mode 100644
index 0000000..f77e37f
--- /dev/null
+++ b/src/caas/templates/wrinstall.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="wr_install_form"
+ id="wr_install_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Check hardware and install Wind River CaaS"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/wrinstallnow.xml b/src/caas/templates/wrinstallnow.xml
new file mode 100644
index 0000000..50fe961
--- /dev/null
+++ b/src/caas/templates/wrinstallnow.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="wr_install_now_form"
+ id="wr_install_now_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Install Wind River CaaS now"/>
+</form>
+{% endblock %}
diff --git a/src/caas/templates/wrremediate.xml b/src/caas/templates/wrremediate.xml
new file mode 100644
index 0000000..9509ae1
--- /dev/null
+++ b/src/caas/templates/wrremediate.xml
@@ -0,0 +1,12 @@
+{% extends "site.xml" %}
+{% block content %}
+{% load crispy_forms_tags %}
+<form name="wr_remediate_form"
+ id="wr_remediate_form"
+ method="POST"
+ action=""
+ enctype="multipart/form-data">
+{{ form|crispy }}
+<input type="submit" class="btn btn-success" value="Remediate Wind River CaaS subcloud"/>
+</form>
+{% endblock %}
diff --git a/src/caas/urls.py b/src/caas/urls.py
index 97d2641..6d9b02d 100644
--- a/src/caas/urls.py
+++ b/src/caas/urls.py
@@ -2,5 +2,31 @@ from django.urls import path
from . import views
urlpatterns = [
- path('', views.index, name='index')
+ # path("accounts/", include("django.contrib.auth.urls")),
+ path("", views.index, name="index"),
+ path("count/<int:count>/", views.count, name="count"),
+ path("ciq/", views.ciq, name="ciq"),
+ path("ciq/success/", views.ciq_success, name="ciq_success"),
+ path("mockciq/", views.mockciq, name="mockciq"),
+ path("passwords/", views.passwords, name="passwords"),
+ path("passwords/success/", views.passwords_success, name="passwords_success"),
+ path("serialnumber/", views.serialnumber, name="serialnumber"),
+ path("autoremediate/", views.autoremediate, name="autoremediate"),
+ path("dns/", views.dns, name="dns"),
+ # path("queuecustom/", views.queuecustom, name="queuecustom"),
+ path("macaddress/", views.macaddress, name="macaddress"),
+ path("adminpassword/", views.adminpassword, name="adminpassword"),
+ path("nicfirmwareversion/", views.nicfirmwareversion, name="nicfirmwareversion"),
+ path("nicfirmwareupgradebatch/", views.nicfirmwareupgradebatch, name="nicfirmwareupgradebatch"),
+ path("nicfirmwareupgrade/", views.nicfirmwareupgrade, name="nicfirmwareupgrade"),
+ path("nicfirmwareupgradenow/", views.nicfirmwareupgradenow, name="nicfirmwareupgradenow"),
+ path("nightlybatch/", views.nightlybatch, name="nightlybatch"),
+ path("wrinstall/", views.wrinstall, name="wrinstall"),
+ path("wrinstallnow/", views.wrinstallnow, name="wrinstallnow"),
+ path("wrfix/", views.wrfix, name="wrfix"),
+ path("reboot/", views.reboot, name="reboot"),
+ path("mockreboot/", views.mockreboot, name="mockreboot"),
+ path("playbookreport/", views.playbookreport, name="playbookreport"),
+ path("subcloud/", views.subcloud, name="subcloud"),
+ # path("fixcreds/", views.fixcreds, name="fixcreds"),
]
diff --git a/src/caas/views.py b/src/caas/views.py
index 91ea44a..049d9e4 100644
--- a/src/caas/views.py
+++ b/src/caas/views.py
@@ -1,3 +1,714 @@
from django.shortcuts import render
+from django.conf import settings
+from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from .forms import CiqUploadForm, CiqUpload2Form, CiqAssignOrphanSubcloudsForm, \
+ PasswordUploadForm, SerialNumberForm, DnsForm, \
+ MacAddressForm, AdminPasswordForm, NicFirmwareVersionForm, PushButtonForm, \
+ PlaybookReportForm, \
+ NicFirmwareUpgradeForm, NicFirmwareUpgradeNowForm, \
+ WrInstallForm, WrInstallNowForm, \
+ AutoremediateForm, WrRemediateForm, \
+ WrRebootForm, MockWrRebootForm, SubcloudForm
+ # QueueCustomForm, \
+from .services.ciqservice import CiqService
+from .services.passwordservice import PasswordService
+from .services.icingaservice import IcingaService
+from .services.bmcservice import BmcService
+from .services.macaddressservice import MacAddressService
+from .services.dnsservice import DnsService
+from .services.firmwareservice import FirmwareService
+from .services.wrservice import WrService
+from .services.patchservice import PatchService
+from .services.playbookreportservice import PlaybookReportService
+from .services.icingapollservice import IcingaPollService
+from .services.nicfirmwareversionservice import NicFirmwareVersionService
+from .services.nicfirmwareupgradenowservice import NicFirmwareUpgradeNowService
+from .services.wrinstallnowservice import WrInstallNowService
+from .services.subcloudservice import SubcloudService
+import logging
-# Create your views here.
+
+logger = logging.getLogger("caas")
+
+
+def index(request):
+ return HttpResponse("Hello world")
+
+
+def count(request, count):
+ data = {
+ "name": "Vitor",
+ "location": "Finland",
+ "is_active": True,
+ "count": count
+ }
+ return JsonResponse(data)
+
+
+@csrf_exempt
+def passwords(request):
+ if request.method == "POST":
+ form = PasswordUploadForm(request.POST, request.FILES)
+ if form.is_valid():
+ PasswordService.import_password_data(request.FILES["filehandle"])
+ return HttpResponseRedirect("success/")
+ else:
+ form = PasswordUploadForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "passwords.xml", context)
+
+
+def passwords_success(request):
+ return render(request, "passwords_success.xml")
+
+
+@csrf_exempt
+def ciq(request):
+ if request.method == "POST":
+ form = CiqUploadForm(request.POST, request.FILES)
+ if form.is_valid():
+ CiqService.import_ciq_data(request.FILES["filehandle"], form.cleaned_data["assign_parent"])
+ # TODO: opstracker for lat, long, etc
+ icinga = IcingaService(settings.ANSIBLE["icinga"]["target"],
+ settings.ANSIBLE["icinga"]["git"],
+ settings.ANSIBLE["icinga"]["branch"],
+ settings.ANSIBLE["icinga"]["playbook"],
+ settings.ANSIBLE["icinga"]["zmq"])
+ icinga.deploy()
+ return HttpResponseRedirect("success/")
+ else:
+ form = CiqUploadForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "ciq.xml", context)
+
+
+@csrf_exempt
+def mockciq(request):
+ if request.method == "POST":
+ form = PushButtonForm(request.POST)
+ if form.is_valid():
+ icinga = IcingaService(settings.ANSIBLE["icinga"]["target"],
+ settings.ANSIBLE["icinga"]["git"],
+ settings.ANSIBLE["icinga"]["branch"],
+ settings.ANSIBLE["icinga"]["playbook"],
+ settings.ANSIBLE["icinga"]["zmq"])
+ icinga.deploy(mockciq = settings.MOCKCIQ)
+ return JsonResponse({"status": "OK"})
+ else:
+ form = PushButtonForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "pushbutton.xml", context)
+
+
+def ciq_success(request):
+ return render(request, "ciq_success.xml")
+
+
+@csrf_exempt
+def serialnumber(request):
+ if request.method == "POST":
+ logger.debug("POST received")
+ form = SerialNumberForm(request.POST)
+ if form.is_valid():
+ logger.debug("%s POST received" % form.cleaned_data["ilo_host_address"])
+ if not PasswordService.is_server_linked_to_blade(ilo_host_address = form.cleaned_data["ilo_host_address"]):
+ if not settings.MOCKCIQ:
+ link_status = PasswordService.link_server_to_blade(vendor_name = form.cleaned_data["vendor"],
+ serial_number = form.cleaned_data["serial_number"],
+ ilo_host_address = form.cleaned_data["ilo_host_address"])
+ if link_status is None:
+ logger.error("%s Error linking server to blade!" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "Error linking server to blade!"})
+
+ PasswordService.create_bmc_admin_password(serial_number = form.cleaned_data["serial_number"])
+ dns = DnsService(form.cleaned_data["ilo_host_address"],
+ settings.ANSIBLE["dns"]["git"],
+ settings.ANSIBLE["dns"]["branch"],
+ settings.ANSIBLE["dns"]["playbook"],
+ settings.ANSIBLE["dns"]["zmq"])
+ rc = dns.deploy()
+ if rc != 0:
+ errmsg = "%s dns deploy failed" % form.cleaned_data["ilo_host_address"]
+ logger.error(errmsg)
+ return JsonResponse({"status": errmsg})
+
+ # don't run bmc automation against central controllers
+ if not PasswordService.central_server_p(form.cleaned_data["ilo_host_address"]):
+ if settings.ENABLE_BMC:
+ bmc = BmcService(form.cleaned_data["ilo_host_address"],
+ settings.ANSIBLE["bmc"]["git"],
+ settings.ANSIBLE["bmc"]["branch"],
+ settings.ANSIBLE["bmc"]["playbook"],
+ settings.ANSIBLE["bmc"]["zmq"])
+ rc = bmc.deploy()
+ if rc != 0:
+ logger.error("%s bmc deploy failed" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "%s bmc deploy failed!" % form.cleaned_data["ilo_host_address"]})
+ else:
+ icingapoll = IcingaPollService(form.cleaned_data["ilo_host_address"],
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "adminpassword")
+ rc = icingapoll.deploy()
+ if rc != 0:
+ logger.error("%s icingapoll deploy failed" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "%s icingapoll deploy failed!" % form.cleaned_data["ilo_host_address"]})
+ else:
+ logger.debug("%s server already linked to blade" % form.cleaned_data["ilo_host_address"])
+
+ return JsonResponse({"status": "OK"})
+ else:
+ errmsg = "%s Error linking server to blade: invalid form" % form.cleaned_data["ilo_host_address"]
+ logger.error(errmsg)
+ raise Exception(errmsg)
+ else:
+ form = SerialNumberForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "serialnumber.xml", context)
+
+
+@csrf_exempt
+def autoremediate(request):
+ if request.method == "POST":
+ form = AutoremediateForm(request.POST)
+ if form.is_valid():
+ ilo_host_address_list = form.cleaned_data["ilo_host_address_list"]
+ for ilo_host_address in list(ilo_host_address_list.split()):
+ linking_data = PasswordService.get_server_link_data(ilo_host_address)
+ if linking_data is None:
+ logger.error("Error PasswordService.get_server_link_data! ilo_host_address=%s" % ilo_host_address)
+ continue
+
+ PasswordService.create_bmc_admin_password(serial_number = linking_data["serial_number"])
+ dns = DnsService(linking_data["ilo_host_address"],
+ settings.ANSIBLE["dns"]["git"],
+ settings.ANSIBLE["dns"]["branch"],
+ settings.ANSIBLE["dns"]["playbook"],
+ settings.ANSIBLE["dns"]["zmq"])
+ rc = dns.deploy()
+ if rc != 0:
+ errmsg = "%s dns deploy failed" % form.cleaned_data["ilo_host_address"]
+ logger.error(errmsg)
+ return JsonResponse({"status": errmsg})
+
+ # don't run bmc automation against central controllers
+ if not PasswordService.central_server_p(ilo_host_address):
+ if settings.ENABLE_BMC:
+ bmc = BmcService(linking_data["ilo_host_address"],
+ settings.ANSIBLE["bmc"]["git"],
+ settings.ANSIBLE["bmc"]["branch"],
+ settings.ANSIBLE["bmc"]["playbook"],
+ settings.ANSIBLE["bmc"]["zmq"])
+ rc = bmc.deploy()
+ if rc != 0:
+ logger.error("%s bmc deploy failed" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "%s bmc deploy failed!" % form.cleaned_data["ilo_host_address"]})
+ else:
+ icingapoll = IcingaPollService(linking_data["ilo_host_address"],
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "adminpassword")
+ rc = icingapoll.deploy()
+ if rc != 0:
+ logger.error("%s dicingapollns deploy failed" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "%s icingapoll deploy failed!" % form.cleaned_data["ilo_host_address"]})
+
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error linking server to blade: invalid form")
+ else:
+ form = AutoremediateForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "autoremediate.xml", context)
+
+
+@csrf_exempt
+def dns(request):
+ if request.method == "POST":
+ form = DnsForm(request.POST)
+ if form.is_valid():
+ dns = DnsService(form.cleaned_data["ilo_host_address"],
+ settings.ANSIBLE["dns"]["git"],
+ settings.ANSIBLE["dns"]["branch"],
+ settings.ANSIBLE["dns"]["playbook"],
+ settings.ANSIBLE["dns"]["zmq"])
+ rc = dns.deploy()
+ if rc != 0:
+ errmsg = "%s dns deploy failed" % form.cleaned_data["ilo_host_address"]
+ logger.error(errmsg)
+ return JsonResponse({"status": errmsg})
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing DNS: form input is invalid.")
+ else:
+ form = DnsForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "dns.xml", context)
+
+
+# @csrf_exempt
+# def queuecustom(request):
+# if request.method == "POST":
+# form = QueueCustomForm(request.POST)
+# if form.is_valid():
+# key = form.cleaned_data["key"]
+# playbook_key = form.cleaned_data["playbook_key"]
+# ansible = AnsibleService(key,
+# form.cleaned_data["target"],
+# settings.ANSIBLE[key]["git"],
+# settings.ANSIBLE[key]["branch"],
+# settings.ANSIBLE[key][playbook_key],
+# settings.ANSIBLE[key]["zmq"])
+# if len(form.cleaned_data["cluster_name"]) > 0:
+# extra_args = {"cluster-name": form.cleaned_data["cluster_name"]}
+# else:
+# extra_args = {}
+# ansible.run_playbook(extra_args = extra_args)
+# return JsonResponse({"status": "OK"})
+# else:
+# raise Exception("Error processing queuecustom: form input is invalid.")
+# else:
+# form = QueueCustomForm()
+# context = {
+# "title": "Far-Edge Ops Middleware",
+# "form": form
+# }
+# return render(request, "queuecustom.xml", context)
+
+
+@csrf_exempt
+def macaddress(request):
+ if request.method == "POST":
+ form = MacAddressForm(request.POST)
+ if form.is_valid():
+ MacAddressService.set_server_mac_address(ilo_host_address = form.cleaned_data["ilo_host_address"],
+ pxe_mac_address = form.cleaned_data["pxe_mac_address"],
+ intel_nic_firmware_version = form.cleaned_data["intel_nic_firmware_version"])
+
+ icinga = IcingaService(settings.ANSIBLE["icinga"]["target"],
+ settings.ANSIBLE["icinga"]["git"],
+ settings.ANSIBLE["icinga"]["branch"],
+ settings.ANSIBLE["icinga"]["playbook"],
+ settings.ANSIBLE["icinga"]["zmq"])
+ rc = icinga.deploy()
+ if rc != 0:
+ logger.error("%s icinga deploy failed" % form.cleaned_data["ilo_host_address"])
+ return JsonResponse({"status": "%s icinga deploy failed!" % form.cleaned_data["ilo_host_address"]})
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing macaddress: form input is invalid.")
+ else:
+ form = MacAddressForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "macaddress.xml", context)
+
+
+@csrf_exempt
+def adminpassword(request):
+ if request.method == "POST":
+ form = AdminPasswordForm(request.POST)
+ if form.is_valid():
+ ilo_host_address = form.cleaned_data["ilo_host_address"]
+ icinga_poll_status = form.cleaned_data["icinga_poll_status"]
+ if icinga_poll_status == "OK":
+ firmware = FirmwareService(ilo_host_address,
+ settings.ANSIBLE["nic"]["git"],
+ settings.ANSIBLE["nic"]["branch"],
+ settings.ANSIBLE["nic"]["playbook"],
+ settings.ANSIBLE["nic"]["zmq"])
+ firmware.schedule_upgrade()
+ nicfirmwareupgradenow = NicFirmwareUpgradeNowService(ilo_host_address,
+ settings.ANSIBLE["nic"]["git"],
+ settings.ANSIBLE["nic"]["branch"],
+ settings.ANSIBLE["nic"]["playbook"],
+ settings.ANSIBLE["nic"]["zmq"])
+ nicfirmwareupgradenow.deploy()
+ wr = WrService(ilo_host_address,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wr.schedule_install()
+ # The firmware pipeline will launch the wr install
+ return JsonResponse({"status": "OK"})
+ else:
+ return JsonResponse({"status": "NO-OP: icinga_poll status NOT_OK"})
+ else:
+ raise Exception("Error changing BMC administrator password: form is invalid")
+ else:
+ form = AdminPasswordForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "adminpassword.xml", context)
+
+
+@csrf_exempt
+def nicfirmwareversion(request):
+ if request.method == "POST":
+ form = NicFirmwareVersionForm(request.POST)
+ if form.is_valid():
+ NicFirmwareVersionService.set_server_nic_firmware_version(ilo_host_address = form.cleaned_data["ilo_host_address"],
+ intel_nic_firmware_version = form.cleaned_data["nic_firmware_version"])
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing macaddress: form input is invalid.")
+ else:
+ form = NicFirmwareVersionForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "nicfirmwareversion.xml", context)
+
+
+@csrf_exempt
+def nicfirmwareupgrade(request):
+ if request.method == "POST":
+ form = NicFirmwareUpgradeForm(request.POST)
+ if form.is_valid():
+ ilo_host_address_list = form.cleaned_data["ilo_host_address_list"]
+ for ilo_host_address in list(ilo_host_address_list.split()):
+ icingapoll = IcingaPollService(ilo_host_address,
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "nicfirmwareupgradenow")
+ icingapoll.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing nicfirmwareupgrade: form input is invalid.")
+ else:
+ form = NicFirmwareUpgradeForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "nicfirmwareupgrade.xml", context)
+
+
+@csrf_exempt
+def nicfirmwareupgradenow(request):
+ if request.method == "POST":
+ form = NicFirmwareUpgradeNowForm(request.POST)
+ if form.is_valid():
+ ilo_host_address = form.cleaned_data["ilo_host_address"]
+ icinga_poll_status = form.cleaned_data["icinga_poll_status"]
+ if icinga_poll_status == "OK":
+ firmware = FirmwareService(ilo_host_address,
+ settings.ANSIBLE["nic"]["git"],
+ settings.ANSIBLE["nic"]["branch"],
+ settings.ANSIBLE["nic"]["playbook"],
+ settings.ANSIBLE["nic"]["zmq"])
+ firmware.schedule_upgrade()
+
+ wr = WrService(ilo_host_address,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wr.schedule_install()
+
+ nicfirmwareupgradenow = NicFirmwareUpgradeNowService(ilo_host_address,
+ settings.ANSIBLE["nic"]["git"],
+ settings.ANSIBLE["nic"]["branch"],
+ settings.ANSIBLE["nic"]["playbook"],
+ settings.ANSIBLE["nic"]["zmq"])
+ nicfirmwareupgradenow.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing nicfirmwareupgradenow: form input is invalid.")
+ else:
+ form = NicFirmwareUpgradeNowForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "nicfirmwareupgradenow.xml", context)
+
+@csrf_exempt
+def wrinstall(request):
+ if request.method == "POST":
+ form = WrInstallForm(request.POST)
+ if form.is_valid():
+ ilo_host_address_list = form.cleaned_data["ilo_host_address_list"]
+ for ilo_host_address in list(ilo_host_address_list.split()):
+ icingapoll = IcingaPollService(ilo_host_address,
+ settings.ANSIBLE["icinga_poll"]["git"],
+ settings.ANSIBLE["icinga_poll"]["branch"],
+ settings.ANSIBLE["icinga_poll"]["playbook"],
+ settings.ANSIBLE["icinga_poll"]["zmq"],
+ "wrinstallnow")
+ icingapoll.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing wrinstall: form input is invalid.")
+ else:
+ form = WrInstallForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "wrinstall.xml", context)
+
+@csrf_exempt
+def wrinstallnow(request):
+ if request.method == "POST":
+ form = WrInstallNowForm(request.POST)
+ if form.is_valid():
+ ilo_host_address = form.cleaned_data["ilo_host_address"]
+ icinga_poll_status = form.cleaned_data["icinga_poll_status"]
+ if icinga_poll_status == "OK":
+ wrinstallnow = WrInstallNowService("wr",
+ ilo_host_address,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wrinstallnow.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error processing wrinstallnow: form input is invalid.")
+ else:
+ form = WrInstallNowForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "wrinstallnow.xml", context)
+
+
+@csrf_exempt
+def nicfirmwareupgradebatch(request):
+ if request.method == "POST":
+ form = PushButtonForm(request.POST)
+ if form.is_valid():
+ firmware = FirmwareService(None,
+ settings.ANSIBLE["nic"]["git"],
+ settings.ANSIBLE["nic"]["branch"],
+ settings.ANSIBLE["nic"]["playbook"],
+ settings.ANSIBLE["nic"]["zmq"])
+ firmware.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running nightly batch: somehow a form that takes no inputs is invalid")
+ else:
+ form = PushButtonForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "pushbutton.xml", context)
+
+
+@csrf_exempt
+def nightlybatch(request):
+ if request.method == "POST":
+ form = PushButtonForm(request.POST)
+ if form.is_valid():
+ wr = WrService(None,
+ settings.ANSIBLE["wr"]["git"],
+ settings.ANSIBLE["wr"]["branch"],
+ settings.ANSIBLE["wr"]["playbook"],
+ settings.ANSIBLE["wr"]["zmq"])
+ wr.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running nightly batch: somehow a form that takes no inputs is invalid")
+ else:
+ form = PushButtonForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "pushbutton.xml", context)
+
+
+@csrf_exempt
+def nightlypatch(request):
+ if request.method == "POST":
+ form = PushButtonForm(request.POST)
+ if form.is_valid():
+ patch = PatchService(None,
+ settings.ANSIBLE["patch"]["git"],
+ settings.ANSIBLE["patch"]["branch"],
+ settings.ANSIBLE["patch"]["playbook"],
+ settings.ANSIBLE["patch"]["zmq"])
+ patch.deploy()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running nightly patch: somehow a form that takes no inputs is invalid")
+ else:
+ form = PushButtonForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "pushbutton.xml", context)
+
+
+@csrf_exempt
+def wrfix(request):
+ if request.method == "POST":
+ form = WrRemediateForm(request.POST)
+ if form.is_valid():
+ playbook = form.cleaned_data["playbook"]
+ cluster_name_list = form.cleaned_data["cluster_name_list"]
+ for cluster_name in list(cluster_name_list.split()):
+ wrinstallnow = WrInstallNowService(playbook,
+ cluster_name,
+ settings.ANSIBLE[playbook]["git"],
+ settings.ANSIBLE[playbook]["branch"],
+ settings.ANSIBLE[playbook]["playbook"],
+ settings.ANSIBLE[playbook]["zmq"])
+ wrinstallnow.remediate()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running playbook: the form is invalid")
+ else:
+ form = WrRemediateForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "wrremediate.xml", context)
+
+
+@csrf_exempt
+def reboot(request):
+ if request.method == "POST":
+ form = WrRebootForm(request.POST)
+ if form.is_valid():
+ namespace_name_list = form.cleaned_data["namespace_name_list"]
+ for namespace_name in list(namespace_name_list.split()):
+ wrinstallnow = WrInstallNowService("wr_reboot",
+ namespace_name,
+ settings.ANSIBLE["wr_reboot"]["git"],
+ settings.ANSIBLE["wr_reboot"]["branch"],
+ settings.ANSIBLE["wr_reboot"]["playbook"],
+ settings.ANSIBLE["wr_reboot"]["zmq"])
+ wrinstallnow.remediate()
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running reboot: the form is invalid")
+ else:
+ form = WrRebootForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "wrremediate.xml", context)
+
+#created Oct30 2020 for Raj reboot testing delete later - Soda
+@csrf_exempt
+def mockreboot(request):
+ if request.method == "POST":
+ form = MockWrRebootForm(request.POST)
+ if form.is_valid():
+ namespace_name_list = form.cleaned_data["namespace_name_list"]
+ for namespace_name in list(namespace_name_list.split()):
+ print(namespace_name)
+ return JsonResponse({"status": "OK"})
+ else:
+ raise Exception("Error running Mock reboot: the form is invalid")
+ else:
+ form = MockWrRebootForm()
+ context = {
+ "title": "Mock Reboot Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "mockwrremediate.xml", context)
+
+@csrf_exempt
+def playbookreport(request):
+ if request.method == "POST":
+ form = PlaybookReportForm(request.POST)
+ if form.is_valid():
+ report_kwargs = {"git": form.cleaned_data["git"],
+ "playbook": form.cleaned_data["playbook"],
+ "target": form.cleaned_data["target"],
+ "failed": form.cleaned_data["failed"]}
+ service = PlaybookReportService(**report_kwargs)
+ status = service.process()
+ return JsonResponse({"status": status})
+ else:
+ raise Exception("Error receiving playbook report: form input is invalid.")
+ else:
+ form = PlaybookReportForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form
+ }
+ return render(request, "playbookreport.xml", context)
+
+
+@csrf_exempt
+def subcloud(request):
+ subclouds = None
+ if request.method == "POST":
+ form = SubcloudForm(request.POST)
+ if form.is_valid():
+ subclouds = SubcloudService.find_subclouds(form.cleaned_data["fuze_id"])
+ else:
+ raise Exception("Error finding subcloud: form input is invalid.")
+ else:
+ form = SubcloudForm()
+ context = {
+ "title": "Far-Edge Ops Middleware",
+ "form": form,
+ "subclouds": subclouds,
+ "fqdn": settings.DNS_DOMAIN
+ }
+ return render(request, "subcloud.xml", context)
+
+
+# @csrf_exempt
+# def fixcreds(request):
+# if request.method == "POST":
+# form = PushButtonForm(request.POST)
+# if form.is_valid():
+# PasswordService.fix_creds()
+# return JsonResponse({"status": "OK"})
+# else:
+# form = PushButtonForm()
+# context = {
+# "title": "Far-Edge Ops Middleware",
+# "form": form
+# }
+# return render(request, "pushbutton.xml", context)
+
+
+# def fixcreds_success(request):
+# return render(request, "ciq_success.xml")
diff --git a/src/dashboard/README.md b/src/dashboard/README.md
new file mode 100644
index 0000000..1e2249e
--- /dev/null
+++ b/src/dashboard/README.md
@@ -0,0 +1,25 @@
+# Dashboard Layout
+
+The dashboard has 2 main sections, the navigation sidebar and the main panel.
+
+The main panel uses a card layout to display different views into the data.
+
+![Dashboard Layout](documentation/images/dashboard_layout.jpg)
+
+## Adding a card
+* Create a new card in the ```/templates/cards``` folder.
+* Include the new card to the ```/templates/dashboard.html``` file.
+
+## Adding a new page
+* Create a new page in the ```/templates``` folder.
+* Follow the exact format as the dashboard.html file.
+* Add new cards in the ```/templates/cards``` folder and include them in the new page.
+* Update the sidebar file ```/templates/includes/sidebar.html``` with a link to the new page.
+
+## Register cronjobs for generating Dashboard reports
+* Register - ```python3 manage.py crontab add```
+* List active cronjbos - ```python3 manage.py crontab show```
+* Remove all active cronjbos - ```python3 manage.py crontab remove```
+* Remove a single active cronjbos - ```python3 manage.py crontab remove <cron id>```
+
+For more info see ![django-crontab](https://github.com/kraiz/django-crontab#readme)
diff --git a/src/dashboard/__init__.py b/src/dashboard/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/dashboard/__init__.py
diff --git a/src/dashboard/apps.py b/src/dashboard/apps.py
new file mode 100644
index 0000000..84fc990
--- /dev/null
+++ b/src/dashboard/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DashboardConfig(AppConfig):
+ name = 'dashboard'
+
diff --git a/src/dashboard/documentation/images/dashboard_layout.jpg b/src/dashboard/documentation/images/dashboard_layout.jpg
new file mode 100644
index 0000000..a949625
--- /dev/null
+++ b/src/dashboard/documentation/images/dashboard_layout.jpg
Binary files differ
diff --git a/src/dashboard/services/caasqueryservice.py b/src/dashboard/services/caasqueryservice.py
new file mode 100644
index 0000000..b367c33
--- /dev/null
+++ b/src/dashboard/services/caasqueryservice.py
@@ -0,0 +1,31 @@
+import json
+from django.apps import apps
+
+
+class CaaSQueryService:
+ def get_clusters(self):
+ wr_install_schedule = apps.get_model('caas', 'WrInstallSchedule')
+ installs = list(wr_install_schedule.objects.select_related('cluster')
+ .values('cluster__cluster_name',
+ 'date_scheduled',
+ 'date_completed',
+ 'date_last_failed',
+ 'kirke_ticket_number',
+ 'kirke_ticket_status',
+ 'kirke_ticket_completed'))
+ return json.dumps(installs, indent=4, sort_keys=True, default=str)
+
+ def get_firmware_version(self):
+ firmware_schedule = apps.get_model('caas', 'FirmwareUpgradeSchedule')
+ query_set = firmware_schedule.objects.select_related('server').order_by('id', 'date_completed')
+ servers = []
+ for server in query_set:
+ servers.append(
+ {'id': server.id,
+ 'oam_hostname': server.server.oam_hostname,
+ 'ilo_host_address': server.server.ilo_host_address,
+ 'intel_nic_firmware_version': server.server.intel_nic_firmware_version,
+ 'date_completed': server.date_completed,
+ 'date_last_failed': server.date_last_failed},
+ )
+ return json.dumps(servers, indent=4, sort_keys=True, default=str)
diff --git a/src/dashboard/static/css/styles.css b/src/dashboard/static/css/styles.css
new file mode 100644
index 0000000..7c4a542
--- /dev/null
+++ b/src/dashboard/static/css/styles.css
@@ -0,0 +1,5242 @@
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@-webkit-keyframes spin {
+ from {
+ -webkit-transform: rotate(0deg);
+ }
+ to {
+ -webkit-transform: rotate(360deg);
+ }
+}
+
+@-moz-keyframes spin {
+ from {
+ -moz-transform: rotate(0deg);
+ }
+ to {
+ -moz-transform: rotate(360deg);
+ }
+}
+
+@-ms-keyframes spin {
+ from {
+ -ms-transform: rotate(0deg);
+ }
+ to {
+ -ms-transform: rotate(360deg);
+ }
+}
+
+
+/* Font Smoothing */
+
+body,
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3,
+h4,
+.h4,
+h5,
+.h5,
+h6,
+.h6,
+p,
+.navbar,
+.brand,
+.btn-simple,
+.alert,
+a,
+.td-name,
+td,
+button.close {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: "Roboto", "Helvetica Neue", Arial, sans-serif;
+ font-weight: 400;
+}
+
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3,
+h4,
+.h4 {
+ font-weight: 300;
+ margin: 30px 0 15px;
+}
+
+h1,
+.h1 {
+ font-size: 52px;
+}
+
+h2,
+.h2 {
+ font-size: 36px;
+}
+
+h3,
+.h3 {
+ font-size: 28px;
+ margin: 20px 0 10px;
+}
+
+h4,
+.h4 {
+ font-size: 22px;
+ line-height: 30px;
+}
+
+h5,
+.h5 {
+ font-size: 16px;
+ margin-bottom: 15px;
+}
+
+h6,
+.h6 {
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+p {
+ font-size: 16px;
+ line-height: 1.5;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+ color: #9A9A9A;
+ font-weight: 300;
+ line-height: 1.5;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h1 .small,
+h2 .small,
+h3 .small {
+ font-size: 60%;
+}
+
+h1 .subtitle {
+ display: block;
+ margin: 0 0 30px;
+}
+
+.text-muted {
+ color: #9A9A9A;
+}
+
+.text-primary,
+.text-primary:hover {
+ color: #1D62F0 !important;
+}
+
+.text-info,
+.text-info:hover {
+ color: #1DC7EA !important;
+}
+
+.text-success,
+.text-success:hover {
+ color: #87CB16 !important;
+}
+
+.text-warning,
+.text-warning:hover {
+ color: #FF9500 !important;
+}
+
+.text-danger,
+.text-danger:hover {
+ color: #FF4A55 !important;
+}
+
+
+/* General overwrite */
+
+body,
+.wrapper {
+ min-height: 100vh;
+ position: relative;
+}
+
+a {
+ color: #1DC7EA;
+}
+
+a:hover,
+a:focus {
+ color: #42d0ed;
+ text-decoration: none;
+}
+
+a:focus,
+a:active,
+button::-moz-focus-inner,
+input::-moz-focus-inner,
+input[type="reset"]::-moz-focus-inner,
+input[type="button"]::-moz-focus-inner,
+input[type="submit"]::-moz-focus-inner,
+select::-moz-focus-inner,
+input[type="file"]>input[type="button"]::-moz-focus-inner {
+ outline: 0;
+}
+
+.ui-slider-handle:focus,
+.navbar-toggle,
+input:focus {
+ outline: 0 !important;
+}
+
+
+/* Animations */
+
+.form-control,
+.input-group-addon,
+.tagsinput,
+.navbar,
+.navbar .alert {
+ -webkit-transition: all 300ms linear;
+ -moz-transition: all 300ms linear;
+ -o-transition: all 300ms linear;
+ -ms-transition: all 300ms linear;
+ transition: all 300ms linear;
+}
+
+.sidebar .nav a,
+.table>tbody>tr .td-actions .btn {
+ -webkit-transition: all 150ms ease-in;
+ -moz-transition: all 150ms ease-in;
+ -o-transition: all 150ms ease-in;
+ -ms-transition: all 150ms ease-in;
+ transition: all 150ms ease-in;
+}
+
+.btn {
+ -webkit-transition: all 100ms ease-in;
+ -moz-transition: all 100ms ease-in;
+ -o-transition: all 100ms ease-in;
+ -ms-transition: all 100ms ease-in;
+ transition: all 100ms ease-in;
+}
+
+.fa {
+ width: 18px;
+ text-align: center;
+}
+
+.margin-top {
+ margin-top: 50px;
+}
+
+.wrapper {
+ position: relative;
+ top: 0;
+ height: 100vh;
+}
+
+.page-header .page-header-image {
+ background-position: center center;
+ background-size: cover;
+ overflow: hidden;
+ width: 100%;
+ z-index: 1;
+}
+
+.page-header .title-container {
+ color: #fff;
+ position: relative;
+ top: 250px;
+ z-index: 3;
+}
+
+.page-header .filter:after {
+ background: transparent linear-gradient(to bottom, #9368e9 0%, #943bea 100%) repeat scroll 0 0/150% 150%;
+ content: "";
+ display: block;
+ height: 100%;
+ left: 0;
+ opacity: 0.77;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ z-index: 2;
+}
+
+.documentation .page-header,
+.documentation .page-header-image,
+.documentation .page-header-image .filter:after {
+ height: 100vh;
+}
+
+.documentation .footer {
+ z-index: 3;
+}
+
+.documentation .wrapper {
+ margin-top: -61px;
+ height: 100vh;
+}
+
+.documentation .navbar {
+ z-index: 21;
+}
+
+.sidebar,
+body>.navbar-collapse {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 200px;
+ display: block;
+ z-index: 1;
+ color: #fff;
+ font-weight: 200;
+ background-size: cover;
+ background-position: center center;
+}
+
+.sidebar .sidebar-wrapper,
+body>.navbar-collapse .sidebar-wrapper {
+ position: relative;
+ max-height: calc(100vh - 75px);
+ min-height: 100%;
+ overflow: auto;
+ width: 200px;
+ z-index: 4;
+ padding-bottom: 100px;
+}
+
+.sidebar .sidebar-background,
+body>.navbar-collapse .sidebar-background {
+ position: absolute;
+ z-index: 1;
+ height: 100%;
+ width: 100%;
+ display: block;
+ top: 0;
+ left: 0;
+ background-size: cover;
+ background-position: center center;
+}
+
+.sidebar .logo,
+body>.navbar-collapse .logo {
+ padding: 10px 15px 9px 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ position: relative;
+ z-index: 4;
+ background: #CD040B;
+}
+
+.sidebar .logo p,
+body>.navbar-collapse .logo p {
+ float: left;
+ font-size: 20px;
+ margin: 10px 10px;
+ color: #FFFFFF;
+ line-height: 20px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+.sidebar .logo .simple-text,
+body>.navbar-collapse .logo .simple-text {
+ text-transform: uppercase;
+ padding: 2px 0px;
+ display: block;
+ font-size: 18px;
+ color: #FFFFFF;
+ text-align: center;
+ font-weight: 400;
+ line-height: 30px;
+ background: #CD040B;
+}
+
+.sidebar .logo-tim,
+body>.navbar-collapse .logo-tim {
+ border-radius: 50%;
+ border: 1px solid #333;
+ display: block;
+ height: 61px;
+ width: 61px;
+ float: left;
+ overflow: hidden;
+}
+
+.sidebar .logo-tim img,
+body>.navbar-collapse .logo-tim img {
+ width: 60px;
+ height: 60px;
+}
+
+.sidebar .nav,
+body>.navbar-collapse .nav {
+ margin-top: 20px;
+ float: none;
+ display: block;
+}
+
+.sidebar .nav li .nav-link,
+body>.navbar-collapse .nav li .nav-link {
+ color: #000000;
+ margin: 5px 15px;
+ opacity: .86;
+ border-radius: 4px;
+ display: block;
+ padding: 10px 15px;
+}
+
+.sidebar .nav li .nav-link:hover,
+body>.navbar-collapse .nav li .nav-link:hover {
+ background: rgba(255, 255, 255, 0.13);
+ opacity: 1;
+}
+
+.sidebar .nav li .nav-link p,
+body>.navbar-collapse .nav li .nav-link p {
+ margin: 0;
+ line-height: 31px;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ display: inline-flex;
+}
+
+.sidebar .nav li .nav-link i,
+body>.navbar-collapse .nav li .nav-link i {
+ font-size: 28px;
+ margin-right: 15px;
+ width: 30px;
+ text-align: center;
+ vertical-align: middle;
+ float: left;
+}
+
+.sidebar .nav li:hover .nav-link,
+body>.navbar-collapse .nav li:hover .nav-link {
+ background: rgba(255, 255, 255, 0.13);
+ opacity: 1;
+}
+
+.sidebar .nav li.active .nav-link,
+body>.navbar-collapse .nav li.active .nav-link {
+ color: #FFFFFF;
+ opacity: 1;
+ background: rgba(255, 255, 255, 0.23);
+}
+
+.sidebar .nav li.separator,
+body>.navbar-collapse .nav li.separator {
+ margin: 15px 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.sidebar .nav li.separator+.nav-item,
+body>.navbar-collapse .nav li.separator+.nav-item {
+ margin-top: 31px;
+}
+
+.sidebar .nav .caret,
+body>.navbar-collapse .nav .caret {
+ margin-top: 13px;
+ position: absolute;
+ right: 30px;
+}
+
+.sidebar .nav .active-pro,
+body>.navbar-collapse .nav .active-pro {
+ position: absolute;
+ width: 100%;
+ bottom: 10px;
+}
+
+.sidebar .nav .active-pro a,
+body>.navbar-collapse .nav .active-pro a {
+ color: #FFFFFF !important;
+}
+
+.sidebar .nav .nav-link,
+body>.navbar-collapse .nav .nav-link {
+ color: #FFFFFF;
+ margin: 5px 15px;
+ opacity: .86;
+ border-radius: 4px;
+ text-transform: uppercase;
+ line-height: 30px;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.sidebar .logo,
+body>.navbar-collapse .logo {
+ padding: 10px 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.sidebar .logo p,
+body>.navbar-collapse .logo p {
+ float: left;
+ font-size: 20px;
+ margin: 10px 10px;
+ color: #FFFFFF;
+ line-height: 20px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+.sidebar .logo .simple-text,
+body>.navbar-collapse .logo .simple-text {
+ text-transform: uppercase;
+ padding: 4px 0px;
+ display: block;
+ font-size: 12px;
+ color: #FFFFFF;
+ text-align: center;
+ font-weight: 500;
+ line-height: 20px;
+}
+
+.sidebar .logo-tim,
+body>.navbar-collapse .logo-tim {
+ border-radius: 50%;
+ border: 1px solid #333;
+ display: block;
+ height: 61px;
+ width: 61px;
+ float: left;
+ overflow: hidden;
+}
+
+.sidebar .logo-tim img,
+body>.navbar-collapse .logo-tim img {
+ width: 60px;
+ height: 60px;
+}
+
+.sidebar:after,
+.sidebar:before,
+body>.navbar-collapse:after,
+body>.navbar-collapse:before {
+ display: block;
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ z-index: 2;
+}
+
+.sidebar:before,
+body>.navbar-collapse:before {
+ opacity: .33;
+ background: #000000;
+}
+
+.sidebar:after,
+body>.navbar-collapse:after {
+ background: #777777;
+ background: -moz-linear-gradient(top, #777777 0%, #777777 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #777777), color-stop(100%, #777777));
+ background: -webkit-linear-gradient(top, #777777 0%, #777777 100%);
+ background: -o-linear-gradient(top, #777777 0%, #777777 100%);
+ background: -ms-linear-gradient(top, #777777 0%, #777777 100%);
+ background: linear-gradient(to bottom, #777777 0%, #777777 100%);
+ background-size: 150% 150%;
+ z-index: 3;
+ opacity: 1;
+}
+
+.sidebar[data-image]:after,
+.sidebar.has-image:after,
+body>.navbar-collapse[data-image]:after,
+body>.navbar-collapse.has-image:after {
+ opacity: .77;
+}
+
+.sidebar[data-color="black"]:after,
+body>.navbar-collapse[data-color="black"]:after {
+ background: #dadada;
+ background: -moz-linear-gradient(top, #dadada 0%, #dadada 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #dadada), color-stop(100%, #dadada));
+ background: -webkit-linear-gradient(top, #dadada 0%, #dadada 100%);
+ background: -o-linear-gradient(top, #dadada 0%, #dadada 100%);
+ background: -ms-linear-gradient(top, #dadada 0%, #dadada 100%);
+ background: linear-gradient(to bottom, #dadada 0%, #dadada 100%);
+ background-size: 150% 150%;
+}
+
+.sidebar[data-color="red"]:after,
+body>.navbar-collapse[data-color="red"]:after {
+ background: #CD040B;
+ background: -moz-linear-gradient(top, #CD040B 0%, #CD040B 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #CD040B), color-stop(100%, #CD040B));
+ background: -webkit-linear-gradient(top, #CD040B 0%, #CD040B 100%);
+ background: -o-linear-gradient(top, #CD040B 0%, #CD040B 100%);
+ background: -ms-linear-gradient(top, #CD040B 0%, #CD040B 100%);
+ background: linear-gradient(to bottom, #CD040B 0%, #CD040B 100%);
+ background-size: 150% 150%;
+}
+
+.sidebar[data-color="white"]:after,
+body>.navbar-collapse[data-color="red"]:after {
+ background: #FFFFFF;
+ background: -moz-linear-gradient(top, #FFFFFF 0%, #FFFFFF 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FFFFFF), color-stop(100%, #FFFFFF));
+ background: -webkit-linear-gradient(top, #FFFFFF 0%, #FFFFFF 100%);
+ background: -o-linear-gradient(top, #FFFFFF 0%, #FFFFFF 100%);
+ background: -ms-linear-gradient(top, #FFFFFF 0%, #FFFFFF 100%);
+ background: linear-gradient(to bottom, #FFFFFF 0%, #FFFFFF 100%);
+ background-size: 150% 150%;
+}
+
+.main-panel {
+ background: rgba(203, 203, 210, 0.15);
+ position: relative;
+ float: right;
+ width: calc(100% - 200px);
+ min-height: 100%;
+}
+
+.main-panel>.content {
+ padding: 30px 15px;
+ min-height: calc(100% - 123px);
+}
+
+.main-panel>.footer {
+ border-top: 1px solid #e7e7e7;
+}
+
+.main-panel .navbar {
+ margin-bottom: 0;
+ background: #CD040B
+}
+
+.sidebar,
+.main-panel {
+ overflow: auto;
+ max-height: 100%;
+ height: 100%;
+ -webkit-transition-property: top, bottom;
+ transition-property: top, bottom;
+ -webkit-transition-duration: .2s, .2s;
+ transition-duration: .2s, .2s;
+ -webkit-transition-timing-function: linear, linear;
+ transition-timing-function: linear, linear;
+ -webkit-overflow-scrolling: touch;
+}
+
+.fixed-plugin .dropdown .dropdown-menu {
+ -webkit-transform: translate3d(0, -5%, 0) !important;
+ -moz-transform: translate3d(0, -5%, 0) !important;
+ -o-transform: translate3d(0, -5%, 0) !important;
+ -ms-transform: translate3d(0, -5%, 0) !important;
+ transform: translate3d(0, -5%, 0) !important;
+ border-radius: 10px;
+}
+
+.fixed-plugin .dropdown .dropdown-menu li.adjustments-line {
+ border-bottom: 1px solid #ddd;
+}
+
+.fixed-plugin .dropdown .dropdown-menu li {
+ padding: 5px 2px !important;
+}
+
+.fixed-plugin .dropdown .dropdown-menu .button-container a {
+ font-size: 14px;
+}
+
+.fixed-plugin .dropdown .dropdown-menu .button-container.show {
+ -webkit-transform: translate3d(0, 0%, 0) !important;
+ -moz-transform: translate3d(0, 0%, 0) !important;
+ -o-transform: translate3d(0, 0%, 0) !important;
+ -ms-transform: translate3d(0, 0%, 0) !important;
+ transform: translate3d(0, 0%, 0) !important;
+ transform-origin: 0 0;
+ left: -303px !important;
+}
+
+.fixed-plugin .dropdown .dropdown-menu {
+ -webkit-transform: translate3d(0, -5%, 0) !important;
+ -moz-transform: translate3d(0, -5%, 0) !important;
+ -o-transform: translate3d(0, -5%, 0) !important;
+ -ms-transform: translate3d(0, -5%, 0) !important;
+ transform: translate3d(0, -5%, 0) !important;
+ top: -40px !important;
+ opacity: 0;
+ left: -303px !important;
+ transform-origin: 0 0;
+}
+
+.fixed-plugin .dropdown.show .dropdown-menu {
+ opacity: 1;
+ -webkit-transform: translate3d(0, 0%, 0) !important;
+ -moz-transform: translate3d(0, 0%, 0) !important;
+ -o-transform: translate3d(0, 0%, 0) !important;
+ -ms-transform: translate3d(0, 0%, 0) !important;
+ transform: translate3d(0, 0%, 0) !important;
+ transform-origin: 0 0;
+ left: -303px !important;
+}
+
+.fixed-plugin .dropdown-menu:before,
+.fixed-plugin .dropdown-menu:after {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ top: 65px;
+ width: 16px;
+ transform: translateY(-50%);
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+}
+
+.fixed-plugin .dropdown-menu:before {
+ border-bottom: 16px solid transparent;
+ border-left: 16px solid rgba(0, 0, 0, 0.2);
+ border-top: 16px solid transparent;
+ right: -16px;
+}
+
+.fixed-plugin .dropdown-menu:after {
+ border-bottom: 16px solid transparent;
+ border-left: 16px solid #fff;
+ border-top: 16px solid transparent;
+ right: -15px;
+}
+
+.modal.show .modal-dialog {
+ -webkit-transform: translate(0, 30%);
+ -o-transform: translate(0, 30%);
+ transform: translate(0, 30%);
+}
+
+.modal.modal-mini .modal-dialog {
+ max-width: 255px;
+ margin: 0 auto;
+}
+
+.modal .modal-content .modal-header {
+ border-bottom: none;
+ padding-top: 24px;
+ padding-right: 24px;
+ padding-bottom: 0;
+ padding-left: 24px;
+}
+
+.modal .modal-content .modal-header .modal-profile {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ text-align: center;
+ line-height: 5.7;
+ box-shadow: 0px 5px 20px 0px rgba(0, 0, 0, 0.3);
+}
+
+.modal .modal-content .modal-header .modal-profile i {
+ font-size: 32px;
+ padding-top: 24px;
+}
+
+.modal .modal-content .modal-body {
+ padding-top: 24px;
+ padding-right: 24px;
+ padding-bottom: 16px;
+ padding-left: 24px;
+ line-height: 1.9;
+}
+
+.modal .modal-content .modal-body+.modal-footer {
+ padding-top: 0;
+}
+
+.modal .modal-content .modal-footer {
+ border-top: none;
+ padding-right: 24px;
+ padding-bottom: 16px;
+ padding-left: 24px;
+ -webkit-justify-content: space-between;
+ justify-content: space-between;
+}
+
+.modal .modal-content .modal-footer .btn {
+ margin: 0;
+ padding-left: 16px;
+ padding-right: 16px;
+ width: auto;
+}
+
+.modal .modal-content .modal-footer .btn:hover,
+.modal .modal-content .modal-footer .btnfocus {
+ text-decoration: none;
+}
+
+.btn {
+ border-width: 2px;
+ background-color: transparent;
+ font-weight: 400;
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+ padding: 8px 16px;
+ border-color: #888888;
+ color: #888888;
+}
+
+.btn:hover,
+.btn:focus,
+.btn:active,
+.btn.active,
+.open>.btn.dropdown-toggle {
+ background-color: transparent;
+ color: #777777;
+ border-color: #777777;
+}
+
+.btn.disabled,
+.btn.disabled:hover,
+.btn.disabled:focus,
+.btn.disabled.focus,
+.btn.disabled:active,
+.btn.disabled.active,
+.btn:disabled,
+.btn:disabled:hover,
+.btn:disabled:focus,
+.btn:disabled.focus,
+.btn:disabled:active,
+.btn:disabled.active,
+.btn[disabled],
+.btn[disabled]:hover,
+.btn[disabled]:focus,
+.btn[disabled].focus,
+.btn[disabled]:active,
+.btn[disabled].active,
+fieldset[disabled] .btn,
+fieldset[disabled] .btn:hover,
+fieldset[disabled] .btn:focus,
+fieldset[disabled] .btn.focus,
+fieldset[disabled] .btn:active,
+fieldset[disabled] .btn.active {
+ background-color: transparent;
+ border-color: #888888;
+}
+
+.btn.btn-fill {
+ color: #FFFFFF;
+ background-color: #888888;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn.btn-fill:hover,
+.btn.btn-fill:focus,
+.btn.btn-fill:active,
+.btn.btn-fill.active,
+.open>.btn.btn-fill.dropdown-toggle {
+ background-color: #777777;
+ color: #FFFFFF;
+}
+
+.btn.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn .caret {
+ border-top-color: #888888;
+}
+
+.btn:hover,
+.btn:focus {
+ opacity: 1;
+ filter: alpha(opacity=100);
+ outline: 0 !important;
+ box-shadow: none;
+}
+
+.btn:active,
+.btn.active,
+.open>.btn.dropdown-toggle {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ outline: 0 !important;
+}
+
+.btn.btn-icon {
+ padding: 8px;
+}
+
+.btn-primary {
+ border-color: #3472F7;
+ color: #3472F7;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open>.btn-primary.dropdown-toggle {
+ background-color: transparent;
+ color: #1D62F0;
+ border-color: #1D62F0;
+}
+
+.btn-primary.disabled,
+.btn-primary.disabled:hover,
+.btn-primary.disabled:focus,
+.btn-primary.disabled.focus,
+.btn-primary.disabled:active,
+.btn-primary.disabled.active,
+.btn-primary:disabled,
+.btn-primary:disabled:hover,
+.btn-primary:disabled:focus,
+.btn-primary:disabled.focus,
+.btn-primary:disabled:active,
+.btn-primary:disabled.active,
+.btn-primary[disabled],
+.btn-primary[disabled]:hover,
+.btn-primary[disabled]:focus,
+.btn-primary[disabled].focus,
+.btn-primary[disabled]:active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary,
+fieldset[disabled] .btn-primary:hover,
+fieldset[disabled] .btn-primary:focus,
+fieldset[disabled] .btn-primary.focus,
+fieldset[disabled] .btn-primary:active,
+fieldset[disabled] .btn-primary.active {
+ background-color: transparent;
+ border-color: #3472F7;
+}
+
+.btn-primary.btn-fill {
+ color: #FFFFFF;
+ background-color: #3472F7;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-primary.btn-fill:hover,
+.btn-primary.btn-fill:focus,
+.btn-primary.btn-fill:active,
+.btn-primary.btn-fill.active,
+.open>.btn-primary.btn-fill.dropdown-toggle {
+ background-color: #1D62F0;
+ color: #FFFFFF;
+}
+
+.btn-primary.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-primary .caret {
+ border-top-color: #3472F7;
+}
+
+.btn-success {
+ border-color: #87CB16;
+ color: #87CB16;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open>.btn-success.dropdown-toggle {
+ background-color: transparent;
+ color: #049F0C;
+ border-color: #049F0C;
+}
+
+.btn-success.disabled,
+.btn-success.disabled:hover,
+.btn-success.disabled:focus,
+.btn-success.disabled.focus,
+.btn-success.disabled:active,
+.btn-success.disabled.active,
+.btn-success:disabled,
+.btn-success:disabled:hover,
+.btn-success:disabled:focus,
+.btn-success:disabled.focus,
+.btn-success:disabled:active,
+.btn-success:disabled.active,
+.btn-success[disabled],
+.btn-success[disabled]:hover,
+.btn-success[disabled]:focus,
+.btn-success[disabled].focus,
+.btn-success[disabled]:active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success,
+fieldset[disabled] .btn-success:hover,
+fieldset[disabled] .btn-success:focus,
+fieldset[disabled] .btn-success.focus,
+fieldset[disabled] .btn-success:active,
+fieldset[disabled] .btn-success.active {
+ background-color: transparent;
+ border-color: #87CB16;
+}
+
+.btn-success.btn-fill {
+ color: #FFFFFF;
+ background-color: #87CB16;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-success.btn-fill:hover,
+.btn-success.btn-fill:focus,
+.btn-success.btn-fill:active,
+.btn-success.btn-fill.active,
+.open>.btn-success.btn-fill.dropdown-toggle {
+ background-color: #049F0C;
+ color: #FFFFFF;
+}
+
+.btn-success.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-success .caret {
+ border-top-color: #87CB16;
+}
+
+.btn-info {
+ border-color: #1DC7EA;
+ color: #1DC7EA;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open>.btn-info.dropdown-toggle {
+ background-color: transparent;
+ color: #42d0ed;
+ border-color: #42d0ed;
+}
+
+.btn-info.disabled,
+.btn-info.disabled:hover,
+.btn-info.disabled:focus,
+.btn-info.disabled.focus,
+.btn-info.disabled:active,
+.btn-info.disabled.active,
+.btn-info:disabled,
+.btn-info:disabled:hover,
+.btn-info:disabled:focus,
+.btn-info:disabled.focus,
+.btn-info:disabled:active,
+.btn-info:disabled.active,
+.btn-info[disabled],
+.btn-info[disabled]:hover,
+.btn-info[disabled]:focus,
+.btn-info[disabled].focus,
+.btn-info[disabled]:active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info,
+fieldset[disabled] .btn-info:hover,
+fieldset[disabled] .btn-info:focus,
+fieldset[disabled] .btn-info.focus,
+fieldset[disabled] .btn-info:active,
+fieldset[disabled] .btn-info.active {
+ background-color: transparent;
+ border-color: #1DC7EA;
+}
+
+.btn-info.btn-fill {
+ color: #FFFFFF;
+ background-color: #1DC7EA;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-info.btn-fill:hover,
+.btn-info.btn-fill:focus,
+.btn-info.btn-fill:active,
+.btn-info.btn-fill.active,
+.open>.btn-info.btn-fill.dropdown-toggle {
+ background-color: #42d0ed;
+ color: #FFFFFF;
+}
+
+.btn-info.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-info .caret {
+ border-top-color: #1DC7EA;
+}
+
+.btn-warning {
+ border-color: #FF9500;
+ color: #FF9500;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open>.btn-warning.dropdown-toggle {
+ background-color: transparent;
+ color: #ED8D00;
+ border-color: #ED8D00;
+}
+
+.btn-warning.disabled,
+.btn-warning.disabled:hover,
+.btn-warning.disabled:focus,
+.btn-warning.disabled.focus,
+.btn-warning.disabled:active,
+.btn-warning.disabled.active,
+.btn-warning:disabled,
+.btn-warning:disabled:hover,
+.btn-warning:disabled:focus,
+.btn-warning:disabled.focus,
+.btn-warning:disabled:active,
+.btn-warning:disabled.active,
+.btn-warning[disabled],
+.btn-warning[disabled]:hover,
+.btn-warning[disabled]:focus,
+.btn-warning[disabled].focus,
+.btn-warning[disabled]:active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning,
+fieldset[disabled] .btn-warning:hover,
+fieldset[disabled] .btn-warning:focus,
+fieldset[disabled] .btn-warning.focus,
+fieldset[disabled] .btn-warning:active,
+fieldset[disabled] .btn-warning.active {
+ background-color: transparent;
+ border-color: #FF9500;
+}
+
+.btn-warning.btn-fill {
+ color: #FFFFFF;
+ background-color: #FF9500;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-warning.btn-fill:hover,
+.btn-warning.btn-fill:focus,
+.btn-warning.btn-fill:active,
+.btn-warning.btn-fill.active,
+.open>.btn-warning.btn-fill.dropdown-toggle {
+ background-color: #ED8D00;
+ color: #FFFFFF;
+}
+
+.btn-warning.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-warning .caret {
+ border-top-color: #FF9500;
+}
+
+.btn-danger {
+ border-color: #FF4A55;
+ color: #FF4A55;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open>.btn-danger.dropdown-toggle {
+ background-color: transparent;
+ color: #EE2D20;
+ border-color: #EE2D20;
+}
+
+.btn-danger.disabled,
+.btn-danger.disabled:hover,
+.btn-danger.disabled:focus,
+.btn-danger.disabled.focus,
+.btn-danger.disabled:active,
+.btn-danger.disabled.active,
+.btn-danger:disabled,
+.btn-danger:disabled:hover,
+.btn-danger:disabled:focus,
+.btn-danger:disabled.focus,
+.btn-danger:disabled:active,
+.btn-danger:disabled.active,
+.btn-danger[disabled],
+.btn-danger[disabled]:hover,
+.btn-danger[disabled]:focus,
+.btn-danger[disabled].focus,
+.btn-danger[disabled]:active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger,
+fieldset[disabled] .btn-danger:hover,
+fieldset[disabled] .btn-danger:focus,
+fieldset[disabled] .btn-danger.focus,
+fieldset[disabled] .btn-danger:active,
+fieldset[disabled] .btn-danger.active {
+ background-color: transparent;
+ border-color: #FF4A55;
+}
+
+.btn-danger.btn-fill {
+ color: #FFFFFF;
+ background-color: #FF4A55;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-danger.btn-fill:hover,
+.btn-danger.btn-fill:focus,
+.btn-danger.btn-fill:active,
+.btn-danger.btn-fill.active,
+.open>.btn-danger.btn-fill.dropdown-toggle {
+ background-color: #EE2D20;
+ color: #FFFFFF;
+}
+
+.btn-danger.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-danger .caret {
+ border-top-color: #FF4A55;
+}
+
+.btn-neutral {
+ border-color: #FFFFFF;
+ color: #FFFFFF;
+}
+
+.btn-neutral:hover,
+.btn-neutral:focus,
+.btn-neutral:active,
+.btn-neutral.active,
+.open>.btn-neutral.dropdown-toggle {
+ background-color: transparent;
+ color: #FFFFFF;
+ border-color: #FFFFFF;
+}
+
+.btn-neutral.disabled,
+.btn-neutral.disabled:hover,
+.btn-neutral.disabled:focus,
+.btn-neutral.disabled.focus,
+.btn-neutral.disabled:active,
+.btn-neutral.disabled.active,
+.btn-neutral:disabled,
+.btn-neutral:disabled:hover,
+.btn-neutral:disabled:focus,
+.btn-neutral:disabled.focus,
+.btn-neutral:disabled:active,
+.btn-neutral:disabled.active,
+.btn-neutral[disabled],
+.btn-neutral[disabled]:hover,
+.btn-neutral[disabled]:focus,
+.btn-neutral[disabled].focus,
+.btn-neutral[disabled]:active,
+.btn-neutral[disabled].active,
+fieldset[disabled] .btn-neutral,
+fieldset[disabled] .btn-neutral:hover,
+fieldset[disabled] .btn-neutral:focus,
+fieldset[disabled] .btn-neutral.focus,
+fieldset[disabled] .btn-neutral:active,
+fieldset[disabled] .btn-neutral.active {
+ background-color: transparent;
+ border-color: #FFFFFF;
+}
+
+.btn-neutral.btn-fill {
+ color: #FFFFFF;
+ background-color: #FFFFFF;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.btn-neutral.btn-fill:hover,
+.btn-neutral.btn-fill:focus,
+.btn-neutral.btn-fill:active,
+.btn-neutral.btn-fill.active,
+.open>.btn-neutral.btn-fill.dropdown-toggle {
+ background-color: #FFFFFF;
+ color: #FFFFFF;
+}
+
+.btn-neutral.btn-fill .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-neutral .caret {
+ border-top-color: #FFFFFF;
+}
+
+.btn-neutral:active,
+.btn-neutral.active,
+.open>.btn-neutral.dropdown-toggle {
+ background-color: #FFFFFF;
+ color: #888888;
+}
+
+.btn-neutral.btn-fill,
+.btn-neutral.btn-fill:hover,
+.btn-neutral.btn-fill:focus {
+ color: #888888;
+}
+
+.btn-neutral.btn-simple:active,
+.btn-neutral.btn-simple.active {
+ background-color: transparent;
+}
+
+.btn:disabled,
+.btn[disabled],
+.btn.disabled {
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.btn-round {
+ border-width: 1px;
+ border-radius: 30px !important;
+ padding: 9px 18px;
+}
+
+.btn-round.btn-icon {
+ padding: 9px;
+}
+
+.btn-simple {
+ border: 0;
+ font-size: 16px;
+ padding: 8px 16px;
+}
+
+.btn-simple.btn-icon {
+ padding: 8px;
+}
+
+.btn-lg {
+ font-size: 18px;
+ border-radius: 6px;
+ padding: 14px 30px;
+ font-weight: 400;
+}
+
+.btn-lg.btn-round {
+ padding: 15px 30px;
+}
+
+.btn-lg.btn-simple {
+ padding: 16px 30px;
+}
+
+.btn-sm {
+ font-size: 12px;
+ border-radius: 3px;
+ padding: 5px 10px;
+}
+
+.btn-sm.btn-round {
+ padding: 6px 10px;
+}
+
+.btn-sm.btn-simple {
+ padding: 7px 10px;
+}
+
+.btn-xs {
+ font-size: 12px;
+ border-radius: 3px;
+ padding: 1px 5px;
+}
+
+.btn-xs.btn-round {
+ padding: 2px 5px;
+}
+
+.btn-xs.btn-simple {
+ padding: 3px 5px;
+}
+
+.btn-wd {
+ min-width: 140px;
+}
+
+.btn-group.select {
+ width: 100%;
+}
+
+.btn-group.select .btn {
+ text-align: left;
+}
+
+.btn-group.select .caret {
+ position: absolute;
+ top: 50%;
+ margin-top: -1px;
+ right: 8px;
+}
+
+.btn-social {
+ opacity: 0.85;
+}
+
+.btn-twitter {
+ border-color: #55acee;
+ color: #55acee;
+}
+
+.btn-twitter:hover {
+ opacity: 1 !important;
+ border-color: #55acee;
+ color: #55acee;
+}
+
+.btn-facebook {
+ border-color: #3b5998;
+ color: #3b5998;
+}
+
+.btn-facebook:hover {
+ opacity: 1 !important;
+ border-color: #3b5998;
+ color: #3b5998;
+}
+
+.form-control::-moz-placeholder {
+ color: #DDDDDD;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.form-control:-moz-placeholder {
+ color: #DDDDDD;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.form-control::-webkit-input-placeholder {
+ color: #DDDDDD;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.form-control:-ms-input-placeholder {
+ color: #DDDDDD;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.form-control {
+ background-color: #FFFFFF;
+ border: 1px solid #E3E3E3;
+ border-radius: 4px;
+ color: #565656;
+ padding: 8px 12px;
+ height: 40px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.form-control:focus {
+ background-color: #FFFFFF;
+ border: 1px solid #AAAAAA;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ outline: 0 !important;
+ color: #333333;
+}
+
+.has-success .form-control,
+.has-error .form-control,
+.has-success .form-control:focus,
+.has-error .form-control:focus {
+ border-color: #E3E3E3;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.has-success .form-control {
+ color: #87CB16;
+}
+
+.has-success .form-control:focus {
+ border-color: #87CB16;
+}
+
+.has-error .form-control {
+ color: #FF4A55;
+}
+
+.has-error .form-control:focus {
+ border-color: #FF4A55;
+}
+
+.form-control+.form-control-feedback {
+ border-radius: 6px;
+ font-size: 14px;
+ margin-top: -7px;
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ vertical-align: middle;
+}
+
+.open .form-control {
+ border-radius: 4px 4px 0 0;
+ border-bottom-color: transparent;
+}
+
+.input-lg {
+ height: 55px;
+ padding: 14px 30px;
+}
+
+.has-error .form-control-feedback {
+ color: #FF4A55;
+}
+
+.has-success .form-control-feedback {
+ color: #87CB16;
+}
+
+.input-group-addon {
+ background-color: #FFFFFF;
+ border: 1px solid #E3E3E3;
+ border-radius: 4px;
+}
+
+.has-success .input-group-addon,
+.has-error .input-group-addon {
+ background-color: #FFFFFF;
+ border: 1px solid #E3E3E3;
+}
+
+.has-error .form-control:focus+.input-group-addon {
+ border-color: #FF4A55;
+ color: #FF4A55;
+}
+
+.has-success .form-control:focus+.input-group-addon {
+ border-color: #87CB16;
+ color: #87CB16;
+}
+
+.form-control:focus+.input-group-addon,
+.form-control:focus~.input-group-addon {
+ background-color: #FFFFFF;
+ border-color: #9A9A9A;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child>.dropdown-toggle,
+.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle) {
+ border-right: 0 none;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child>.dropdown-toggle,
+.input-group-btn:first-child>.btn:not(:first-child) {
+ border-left: 0 none;
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+ background-color: #F5F5F5;
+ color: #888888;
+ cursor: not-allowed;
+}
+
+.input-group-btn .btn {
+ border-width: 1px;
+ padding: 9px 16px;
+}
+
+.input-group-btn .btn-default:not(.btn-fill) {
+ border-color: #DDDDDD;
+}
+
+.input-group-btn:last-child>.btn {
+ margin-left: 0;
+}
+
+.input-group-focus .input-group-addon {
+ border-color: #9A9A9A;
+}
+
+.alert {
+ border: 0;
+ border-radius: 0;
+ color: #FFFFFF;
+ padding: 10px 15px;
+ font-size: 14px;
+}
+
+.container .alert {
+ border-radius: 4px;
+}
+
+.navbar .alert {
+ border-radius: 0;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 85px;
+ width: 100%;
+ z-index: 3;
+}
+
+.navbar:not(.navbar-transparent) .alert {
+ top: 70px;
+}
+
+.alert span[data-notify="icon"] {
+ font-size: 30px;
+ display: block;
+ left: 15px;
+ position: absolute;
+ top: 50%;
+ margin-top: -15px;
+}
+
+.alert i.nc-simple-remove {
+ font-size: 12px !important;
+ font: bold normal normal 14px/1 'nucleo-icons';
+}
+
+.alert button.close {
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ margin-top: -13px;
+ z-index: 1033;
+ background-color: #FFFFFF;
+ display: block;
+ border-radius: 50%;
+ opacity: .4;
+ line-height: 9px;
+ width: 25px;
+ height: 25px;
+ outline: 0 !important;
+ text-align: center;
+ padding: 3px;
+ font-weight: 300;
+}
+
+.alert button.close:hover {
+ opacity: .55;
+}
+
+.alert .close~span {
+ display: block;
+ max-width: 89%;
+}
+
+.alert[data-notify="container"] {
+ padding: 10px 10px 10px 20px;
+ border-radius: 4px;
+}
+
+.alert.alert-with-icon {
+ padding-left: 65px;
+}
+
+.alert-primary {
+ background-color: #4091e2;
+}
+
+.alert-info {
+ background-color: #63d8f1;
+}
+
+.alert-success {
+ background-color: #a1e82c;
+}
+
+.alert-warning {
+ background-color: #ffbc67;
+}
+
+.alert-danger {
+ background-color: #fc727a;
+}
+
+.table .radio,
+.table .checkbox {
+ position: relative;
+ height: 20px;
+ display: block;
+ width: 20px;
+ padding: 0px 0px;
+ margin: 0px 5px;
+ text-align: center;
+}
+
+.table .radio .icons,
+.table .checkbox .icons {
+ left: 5px;
+}
+
+.table>thead>tr>th,
+.table>tbody>tr>th,
+.table>tfoot>tr>th,
+.table>thead>tr>td,
+.table>tbody>tr>td,
+.table>tfoot>tr>td {
+ padding: 12px 8px;
+ vertical-align: middle;
+}
+
+.table>thead>tr>th {
+ border-bottom-width: 1px;
+ font-size: 12px;
+ text-transform: uppercase;
+ color: #9A9A9A;
+ font-weight: 400;
+ padding-bottom: 5px;
+ border-top: none !important;
+ border-bottom: none;
+ text-align: left !important;
+}
+
+.table .td-actions .btn {
+ opacity: 0.36;
+ filter: alpha(opacity=36);
+}
+
+.table .td-actions .btn.btn-xs {
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.table .td-actions {
+ min-width: 90px;
+}
+
+.table>tbody>tr {
+ position: relative;
+}
+
+.table>tbody>tr:hover .td-actions .btn {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.table .btn:focus {
+ box-shadow: none !important;
+}
+
+.table-upgrade .table tr td {
+ width: 100%;
+}
+
+.from-check,
+.form-check-radio {
+ margin-bottom: 12px;
+ position: relative;
+}
+
+.form-check .form-check-label {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ padding-left: 35px;
+ line-height: 26px;
+ margin-bottom: 0;
+}
+
+.form-check .form-check-sign::before,
+.form-check .form-check-sign::after {
+ font-family: 'FontAwesome';
+ content: "\f096";
+ display: inline-block;
+ color: #1DC7EA;
+ position: absolute;
+ width: 19px;
+ height: 19px;
+ margin-top: -12px;
+ margin-left: -23px;
+ font-size: 21px;
+ cursor: pointer;
+ -webkit-transition: opacity 0.3s linear;
+ -moz-transition: opacity 0.3s linear;
+ -o-transition: opacity 0.3s linear;
+ -ms-transition: opacity 0.3s linear;
+ transition: opacity 0.3s linear;
+}
+
+.form-check .form-check-sign::after {
+ font-family: 'FontAwesome';
+ content: "\f046";
+ text-align: center;
+ opacity: 0;
+ color: #1DC7EA;
+ border: 0;
+ background-color: inherit;
+}
+
+.form-check.disabled .form-check-label {
+ color: #9A9A9A;
+ opacity: .5;
+ cursor: not-allowed;
+}
+
+.form-check input[type="checkbox"],
+.form-check-radio input[type="radio"] {
+ opacity: 0;
+ position: absolute;
+ visibility: hidden;
+}
+
+.form-check input[type="checkbox"]:checked+.form-check-sign::after {
+ opacity: 1;
+}
+
+.form-control input[type="checkbox"]:disabled+.form-check-sign::before,
+.checkbox input[type="checkbox"]:disabled+.form-check-sign::after {
+ cursor: not-allowed;
+}
+
+.form-check .form-check-label input[type="checkbox"]:disabled+.form-check-sign,
+.form-check-radio input[type="radio"]:disabled+.form-check-sign {
+ pointer-events: none !important;
+}
+
+.form-check-radio .form-check-label {
+ padding-left: 2rem;
+}
+
+.form-check-radio.disabled .form-check-label {
+ color: #9A9A9A;
+ opacity: .5;
+ cursor: not-allowed;
+}
+
+.form-check-radio .form-check-sign::before {
+ font-family: 'FontAwesome';
+ content: "\f10c";
+ font-size: 22px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ display: inline-block;
+ position: absolute;
+ opacity: .50;
+ left: 5px;
+ top: -5px;
+}
+
+.form-check-radio input[type="radio"]+.form-check-sign:after,
+.form-check-radio input[type="radio"] {
+ opacity: 0;
+ -webkit-transition: opacity 0.3s linear;
+ -moz-transition: opacity 0.3s linear;
+ -o-transition: opacity 0.3s linear;
+ -ms-transition: opacity 0.3s linear;
+ transition: opacity 0.3s linear;
+ content: " ";
+ display: block;
+}
+
+.form-check-radio input[type="radio"]:checked+.form-check-sign::after {
+ font-family: 'FontAwesome';
+ content: "\f192";
+ top: -5px;
+ position: absolute;
+ left: 5px;
+ opacity: 1;
+ font-size: 22px;
+}
+
+.form-check-radio input[type="radio"]:checked+.form-check-sign::after {
+ opacity: 1;
+}
+
+.form-check-radio input[type="radio"]:disabled+.form-check-sign::before,
+.form-check-radio input[type="radio"]:disabled+.form-check-sign::after {
+ color: #9A9A9A;
+}
+
+.nav .nav-item .nav-link:hover,
+.nav .nav-item .nav-link:focus {
+ background-color: transparent;
+}
+
+.navbar {
+ border: 0;
+ font-size: 16px;
+ border-radius: 0;
+ min-height: 49px;
+ max-height: 49px;
+ background-color: rgba(255, 255, 255, 0.96);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.navbar .navbar-brand {
+ font-weight: 400;
+ margin: 5px 0px;
+ font-size: 20px;
+ color: #888888;
+}
+
+.navbar .navbar-brand:hover {
+ color: #5e5e5e;
+}
+
+.navbar .navbar-toggler {
+ width: 37px;
+ height: 27px;
+ vertical-align: middle;
+ outline: 0;
+ cursor: pointer;
+}
+
+.navbar .navbar-toggler.navbar-toggler-left {
+ position: relative;
+ left: 0;
+ padding-left: 0;
+}
+
+.navbar .navbar-toggler.navbar-toggler-right {
+ padding-right: 0;
+ top: 18px;
+}
+
+.navbar .navbar-toggler .navbar-toggler-bar {
+ width: 3px;
+ height: 3px;
+ border-radius: 50%;
+ margin: 0 auto;
+}
+
+.navbar .navbar-toggler .burger-lines {
+ display: block;
+ position: relative;
+ background-color: #888;
+ width: 24px;
+ height: 2px;
+ border-radius: 1px;
+ margin: 4px auto;
+}
+
+.navbar .navbar-nav .nav-item .nav-link {
+ color: #FFFFFF;
+ padding: 10px 15px;
+ margin: 10px 3px;
+ position: relative;
+ display: inline-flex;
+ line-height: 40px;
+}
+
+.navbar .navbar-nav .nav-item .nav-link.btn {
+ margin: 15px 3px;
+ padding: 8px 16px;
+}
+
+.navbar .navbar-nav .nav-item .nav-link.btn-round {
+ margin: 16px 3px;
+}
+
+.navbar .navbar-nav .nav-item .nav-link [class^="fa"] {
+ font-size: 19px;
+ position: relative;
+ line-height: 40px;
+ top: 1px;
+}
+
+.navbar .navbar-nav .nav-item .nav-link:hover {
+ color: #1DC7EA;
+}
+
+.navbar .navbar-nav .nav-item .dropdown-menu {
+ border-radius: 10px;
+ margin-top: -5px;
+}
+
+.navbar .navbar-nav .nav-item .dropdown-menu .dropdown-item:first-child {
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+}
+
+.navbar .navbar-nav .nav-item .dropdown-menu .dropdown-item:last-child {
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+}
+
+.navbar .navbar-nav .nav-item .dropdown-menu .divider {
+ height: 1px;
+ margin: 5px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+
+.navbar .navbar-nav .notification {
+ position: absolute;
+ background-color: #FB404B;
+ text-align: center;
+ border-radius: 10px;
+ min-width: 18px;
+ padding: 0 5px;
+ height: 18px;
+ font-size: 12px;
+ color: #FFFFFF;
+ font-weight: bold;
+ line-height: 18px;
+ top: 10px;
+ left: 7px;
+}
+
+.navbar .navbar-nav .dropdown-toggle:after {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 5px;
+ margin-top: 20px;
+ vertical-align: middle;
+ border-top: 4px dashed;
+ border-top: 4px solid\9;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+}
+
+.navbar .btn {
+ margin: 15px 3px;
+ font-size: 14px;
+}
+
+.navbar .btn-simple {
+ font-size: 16px;
+}
+
+.navbar.fixed {
+ width: calc(100% - $sidebar-width);
+ right: 0;
+ left: auto;
+ border-radius: 0;
+}
+
+.navbar .nc-icon {
+ font-weight: 700;
+ margin-top: 10px;
+}
+
+.navbar-transparent .navbar-brand,
+[class*="navbar-ct"] .navbar-brand {
+ color: #FFFFFF;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.navbar-transparent .navbar-brand:focus,
+.navbar-transparent .navbar-brand:hover,
+[class*="navbar-ct"] .navbar-brand:focus,
+[class*="navbar-ct"] .navbar-brand:hover {
+ background-color: transparent;
+ opacity: 1;
+ filter: alpha(opacity=100);
+ color: #FFFFFF;
+}
+
+.navbar-transparent .navbar-nav .nav-item .nav-link:not(.btn),
+[class*="navbar-ct"] .navbar-nav .nav-item .nav-link:not(.btn) {
+ color: #FFFFFF;
+ border-color: #FFFFFF;
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.navbar-transparent .navbar-nav .active .nav-link:not(.btn),
+.navbar-transparent .navbar-nav .active .nav-link:hover:not(.btn),
+.navbar-transparent .navbar-nav .active .nav-link:focus:not(.btn),
+.navbar-transparent .navbar-nav .nav-item .nav-link:not(.btn),
+.navbar-transparent .navbar-nav .nav-item .nav-link:hover:not(.btn),
+.navbar-transparent .navbar-nav .nav-item .nav-link:focus:not(.btn),
+[class*="navbar-ct"] .navbar-nav .active .nav-link:not(.btn),
+[class*="navbar-ct"] .navbar-nav .active .nav-link:hover:not(.btn),
+[class*="navbar-ct"] .navbar-nav .active .nav-link:focus:not(.btn),
+[class*="navbar-ct"] .navbar-nav .nav-item .nav-link:not(.btn),
+[class*="navbar-ct"] .navbar-nav .nav-item .nav-link:hover:not(.btn),
+[class*="navbar-ct"] .navbar-nav .nav-item .nav-link:focus:not(.btn) {
+ background-color: transparent;
+ border-radius: 3px;
+ color: #FFFFFF;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.navbar-transparent .navbar-nav .nav .nav-item .nav-link.btn:hover,
+[class*="navbar-ct"] .navbar-nav .nav .nav-item .nav-link.btn:hover {
+ background-color: transparent;
+}
+
+.navbar-transparent .navbar-nav .show .nav-link,
+.navbar-transparent .navbar-nav .show .nav-link:hover,
+.navbar-transparent .navbar-nav .show .nav-link:focus,
+[class*="navbar-ct"] .navbar-nav .show .nav-link,
+[class*="navbar-ct"] .navbar-nav .show .nav-link:hover,
+[class*="navbar-ct"] .navbar-nav .show .nav-link:focus {
+ background-color: transparent;
+ color: #FFFFFF;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.navbar-transparent .btn-default,
+[class*="navbar-ct"] .btn-default {
+ color: #FFFFFF;
+ border-color: #FFFFFF;
+}
+
+.navbar-transparent .btn-default.btn-fill,
+[class*="navbar-ct"] .btn-default.btn-fill {
+ color: #9A9A9A;
+ background-color: #FFFFFF;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.navbar-transparent .btn-default.btn-fill:hover,
+.navbar-transparent .btn-default.btn-fill:focus,
+.navbar-transparent .btn-default.btn-fill:active,
+.navbar-transparent .btn-default.btn-fill.active,
+.navbar-transparent .show .dropdown-toggle.btn-fill.btn-default,
+[class*="navbar-ct"] .btn-default.btn-fill:hover,
+[class*="navbar-ct"] .btn-default.btn-fill:focus,
+[class*="navbar-ct"] .btn-default.btn-fill:active,
+[class*="navbar-ct"] .btn-default.btn-fill.active,
+[class*="navbar-ct"] .show .dropdown-toggle.btn-fill.btn-default {
+ border-color: #FFFFFF;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.navbar-transparent .dropdown-menu .divider {
+ background-color: rgba(255, 255, 255, 0.2);
+}
+
+.navbar-default {
+ background-color: rgba(255, 255, 255, 0.96);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.navbar-default .navbar-nav .nav-item .nav-link:not(.btn) {
+ color: #9A9A9A;
+}
+
+.navbar-default .navbar-nav .active .nav-link,
+.navbar-default .navbar-nav .active .nav-link:not(.btn):hover,
+.navbar-default .navbar-nav .active .nav-link:not(.btn):focus,
+.navbar-default .navbar-nav .nav-item .nav-link:not(.btn):hover,
+.navbar-default .navbar-nav .nav-item .nav-link:not(.btn):focus {
+ background-color: transparent;
+ border-radius: 3px;
+ color: #1DC7EA;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.navbar-default .navbar-nav .show .nav-link,
+.navbar-default .navbar-nav .show .nav-link:hover,
+.navbar-default .navbar-nav .show .nav-link:focus {
+ background-color: transparent;
+ color: #1DC7EA;
+}
+
+.navbar-default .navbar-nav .navbar-toggle:hover,
+.navbar-default .navbar-nav .navbar-toggle:focus {
+ background-color: transparent;
+}
+
+.navbar-default:not(.navbar-transparent) .btn-default:hover {
+ color: #1DC7EA;
+ border-color: #1DC7EA;
+}
+
+.navbar-default:not(.navbar-transparent) .btn-neutral,
+.navbar-default:not(.navbar-transparent) .btn-neutral:hover,
+.navbar-default:not(.navbar-transparent) .btn-neutral:active {
+ color: #9A9A9A;
+}
+
+
+/* Navbar with icons */
+
+.navbar-icons.navbar .navbar-brand {
+ margin-top: 12px;
+ margin-bottom: 12px;
+}
+
+.navbar-icons .navbar-nav .nav-item .nav-link {
+ text-align: center;
+ padding: 6px 15px;
+ margin: 6px 3px;
+}
+
+.navbar-icons .navbar-nav [class^="pe"] {
+ font-size: 30px;
+ position: relative;
+}
+
+.navbar-icons .navbar-nav p {
+ margin: 3px 0 0;
+}
+
+.navbar-form {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.navbar-form .form-control {
+ border-radius: 0;
+ border: 0;
+ padding: 0;
+ background-color: transparent;
+ height: 22px;
+ font-size: 16px;
+ line-height: 1.5;
+ color: #E3E3E3;
+}
+
+.navbar-transparent .navbar-form .form-control,
+[class*="navbar-ct"] .navbar-form .form-control {
+ color: #FFFFFF;
+ border: 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.6);
+}
+
+.navbar-ct-blue {
+ background-color: #4091e2;
+}
+
+.navbar-ct-red {
+ background-color: #fc727a;
+}
+
+.navbar-transparent {
+ padding-top: 15px;
+ background-color: transparent;
+ border-bottom: 1px solid transparent;
+}
+
+.navbar-toggle {
+ margin-top: 19px;
+ margin-bottom: 19px;
+ border: 0;
+}
+
+.navbar-toggle .icon-bar {
+ background-color: #FFFFFF;
+}
+
+.navbar-toggle .navbar-collapse,
+.navbar-toggle .navbar-form {
+ border-color: transparent;
+}
+
+.navbar-toggle.navbar-default .navbar-toggle:hover,
+.navbar-toggle.navbar-default .navbar-toggle:focus {
+ background-color: transparent;
+}
+
+.footer {
+ background-color: #FFFFFF;
+}
+
+.footer .footer-menu {
+ height: 41px;
+}
+
+.footer nav>ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+}
+
+.footer nav>ul a:not(.btn) {
+ color: #9A9A9A;
+ display: block;
+ margin-bottom: 3px;
+}
+
+.footer nav>ul a:not(.btn):hover,
+.footer nav>ul a:not(.btn):focus {
+ color: #777777;
+}
+
+.footer .social-area {
+ padding: 15px 0;
+}
+
+.footer .social-area h5 {
+ padding-bottom: 15px;
+}
+
+.footer .social-area>a:not(.btn) {
+ color: #9A9A9A;
+ display: inline-block;
+ vertical-align: top;
+ padding: 10px 5px;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 20px;
+ text-align: center;
+}
+
+.footer .social-area>a:not(.btn):hover,
+.footer .social-area>a:not(.btn):focus {
+ color: #777777;
+}
+
+.footer .copyright {
+ color: #777777;
+ padding: 10px 15px;
+ margin: 10px 3px;
+ line-height: 20px;
+ font-size: 14px;
+}
+
+.footer hr {
+ border-color: #DDDDDD;
+}
+
+.footer .title {
+ color: #777777;
+}
+
+.footer-default {
+ background-color: #F5F5F5;
+}
+
+.footer:not(.footer-big) nav>ul {
+ font-size: 14px;
+}
+
+.footer:not(.footer-big) nav>ul li {
+ margin-left: 20px;
+ float: left;
+}
+
+.footer:not(.footer-big) nav>ul a {
+ padding: 10px 0px;
+ margin: 10px 10px 10px 0px;
+}
+
+
+/*!
+Animate.css - http://daneden.me/animate
+Licensed under the MIT license - http://opensource.org/licenses/MIT
+
+Copyright (c) 2015 Daniel Eden
+*/
+
+.animated {
+ -webkit-animation-duration: 1s;
+ animation-duration: 1s;
+ -webkit-animation-fill-mode: both;
+ animation-fill-mode: both;
+}
+
+.animated.infinite {
+ -webkit-animation-iteration-count: infinite;
+ animation-iteration-count: infinite;
+}
+
+.animated.hinge {
+ -webkit-animation-duration: 2s;
+ animation-duration: 2s;
+}
+
+.animated.bounceIn,
+.animated.bounceOut {
+ -webkit-animation-duration: .75s;
+ animation-duration: .75s;
+}
+
+.animated.flipOutX,
+.animated.flipOutY {
+ -webkit-animation-duration: .75s;
+ animation-duration: .75s;
+}
+
+@-webkit-keyframes shake {
+ from,
+ to {
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+ 10%,
+ 30%,
+ 50%,
+ 70%,
+ 90% {
+ -webkit-transform: translate3d(-10px, 0, 0);
+ transform: translate3d(-10px, 0, 0);
+ }
+ 20%,
+ 40%,
+ 60%,
+ 80% {
+ -webkit-transform: translate3d(10px, 0, 0);
+ transform: translate3d(10px, 0, 0);
+ }
+}
+
+@keyframes shake {
+ from,
+ to {
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+ 10%,
+ 30%,
+ 50%,
+ 70%,
+ 90% {
+ -webkit-transform: translate3d(-10px, 0, 0);
+ transform: translate3d(-10px, 0, 0);
+ }
+ 20%,
+ 40%,
+ 60%,
+ 80% {
+ -webkit-transform: translate3d(10px, 0, 0);
+ transform: translate3d(10px, 0, 0);
+ }
+}
+
+.shake {
+ -webkit-animation-name: shake;
+ animation-name: shake;
+}
+
+@-webkit-keyframes fadeInDown {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+@keyframes fadeInDown {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+.fadeInDown {
+ -webkit-animation-name: fadeInDown;
+ animation-name: fadeInDown;
+}
+
+@-webkit-keyframes fadeOut {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+@keyframes fadeOut {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+.fadeOut {
+ -webkit-animation-name: fadeOut;
+ animation-name: fadeOut;
+}
+
+@-webkit-keyframes fadeOutDown {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+}
+
+@keyframes fadeOutDown {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+}
+
+.fadeOutDown {
+ -webkit-animation-name: fadeOutDown;
+ animation-name: fadeOutDown;
+}
+
+@-webkit-keyframes fadeOutUp {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+}
+
+@keyframes fadeOutUp {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+}
+
+.fadeOutUp {
+ -webkit-animation-name: fadeOutUp;
+ animation-name: fadeOutUp;
+}
+
+.dropdown-menu {
+ visibility: hidden;
+ margin: 0;
+ padding: 0;
+ border-radius: 10px;
+ display: block;
+ z-index: 9000;
+ position: absolute;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ -webkit-box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.125);
+ box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.125);
+}
+
+.show .dropdown-menu {
+ opacity: 1;
+ filter: alpha(opacity=100);
+ visibility: visible;
+}
+
+.select .dropdown-menu {
+ border-radius: 0 0 10px 10px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-transform-origin: 50% -40px;
+ -moz-transform-origin: 50% -40px;
+ -o-transform-origin: 50% -40px;
+ -ms-transform-origin: 50% -40px;
+ transform-origin: 50% -40px;
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ -o-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ -webkit-transition: all 150ms linear;
+ -moz-transition: all 150ms linear;
+ -o-transition: all 150ms linear;
+ -ms-transition: all 150ms linear;
+ transition: all 150ms linear;
+ margin-top: -20px;
+}
+
+.select.show .dropdown-menu {
+ margin-top: -1px;
+}
+
+.dropdown-menu .dropdown-item {
+ padding: 8px 16px;
+ color: #333333;
+}
+
+.dropdown-menu .dropdown-item img {
+ margin-top: -3px;
+}
+
+.dropdown-menu .dropdown-item:focus {
+ outline: 0 !important;
+}
+
+.btn-group.select .dropdown-menu {
+ min-width: 100%;
+}
+
+.dropdown-menu>li:first-child>a {
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+}
+
+.dropdown-menu>li:last-child>a {
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+}
+
+.select .dropdown-menu>li:first-child>a {
+ border-radius: 0;
+ border-bottom: 0 none;
+}
+
+.dropdown-menu .dropdown-item:hover,
+.dropdown-menu .dropdown-item:focus {
+ background-color: #F5F5F5;
+ color: #333333;
+ opacity: 1;
+ text-decoration: none;
+}
+
+.dropdown-menu.dropdown-blue>li>a:hover,
+.dropdown-menu.dropdown-blue>li>a:focus {
+ background-color: rgba(52, 114, 247, 0.2);
+}
+
+.dropdown-menu.dropdown-red>li>a:hover,
+.dropdown-menu.dropdown-red>li>a:focus {
+ background-color: rgba(255, 74, 85, 0.2);
+}
+
+.dropdown-menu .dropdown-item i[class*="nc-icon"] {
+ font-size: 18px;
+ text-align: center;
+ line-height: 25px;
+ float: left;
+ padding-right: 10px;
+}
+
+.dropdown-menu.dropdown-menu-right:before,
+.dropdown-menu.dropdown-menu-right:after {
+ right: 12px !important;
+ left: auto !important;
+}
+
+.dropdown-with-icons>li>a {
+ padding-left: 0px;
+ line-height: 28px;
+}
+
+.dropdown-with-icons i {
+ text-align: center;
+ line-height: 28px;
+ float: left;
+}
+
+.dropdown-with-icons i[class^="pe-"] {
+ font-size: 14px;
+ width: 46px;
+}
+
+.dropdown-with-icons i[class^="fa"] {
+ font-size: 14px;
+ width: 38px;
+}
+
+.btn-group.select {
+ overflow: hidden;
+}
+
+.btn-group.select.show {
+ overflow: visible;
+}
+
+.card {
+ border-radius: 4px;
+ background-color: #FFFFFF;
+ margin-bottom: 30px;
+}
+
+.card .card-image {
+ width: 100%;
+ overflow: hidden;
+ height: 260px;
+ border-radius: 4px 4px 0 0;
+ position: relative;
+ -webkit-transform-style: preserve-3d;
+ -moz-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+}
+
+.card .card-image img {
+ width: 100%;
+}
+
+.card .filter {
+ position: absolute;
+ z-index: 2;
+ background-color: rgba(0, 0, 0, 0.68);
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ opacity: 0;
+ filter: alpha(opacity=0);
+}
+
+.card .filter .btn {
+ position: relative;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+.card:hover .filter {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.card .btn-hover {
+ opacity: 0;
+ filter: alpha(opacity=0);
+}
+
+.card:hover .btn-hover {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.card .card-body {
+ padding: 15px 15px 10px 15px;
+}
+
+.card .card-body .icon { font-size: 45px; color: #9aa0ac; }
+
+.card .card-header {
+ padding: 15px 15px 0;
+ background-color: #FFFFFF;
+ border-bottom: none !important;
+}
+
+.card .card-category,
+.card label {
+ font-size: 14px;
+ font-weight: 400;
+ color: #9A9A9A;
+ margin-bottom: 0px;
+}
+
+.card .card-category i,
+.card label i {
+ font-size: 16px;
+}
+
+.card label {
+ font-size: 12px;
+ margin-bottom: 5px;
+ text-transform: uppercase;
+}
+
+.card .card-title {
+ margin: 0;
+ color: #333333;
+ font-weight: 300;
+}
+
+.card .avatar {
+ width: 30px;
+ height: 30px;
+ overflow: hidden;
+ border-radius: 50%;
+ margin-right: 5px;
+}
+
+.card .description {
+ font-size: 14px;
+ color: #333;
+}
+
+.card .card-footer {
+ padding-top: 0;
+ background-color: transparent;
+ line-height: 30px;
+ border-top: none !important;
+ font-size: 14px;
+}
+
+.card .card-footer .legend {
+ padding: 5px 0;
+}
+
+.card .card-footer hr {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.card .stats {
+ color: #a9a9a9;
+}
+
+.card .card-footer div {
+ display: inline-block;
+}
+
+.card .author {
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.card .author i {
+ font-size: 14px;
+}
+
+.card h6 {
+ font-size: 10px;
+ margin: 15;
+}
+
+.card.card-separator:after {
+ height: 100%;
+ right: -15px;
+ top: 0;
+ width: 1px;
+ background-color: #DDDDDD;
+ card-body: "";
+ position: absolute;
+}
+
+.card .ct-chart {
+ margin: 30px 0 30px;
+ height: 245px;
+}
+
+.card .ct-label {
+ font-size: 1rem !important;
+}
+
+.card .table tbody td:first-child,
+.card .table thead th:first-child {
+ padding-left: 15px;
+}
+
+.card .table tbody td:last-child,
+.card .table thead th:last-child {
+ padding-right: 15px;
+ display: inline-flex;
+}
+
+.card .alert {
+ border-radius: 4px;
+ position: relative;
+}
+
+.card .alert.alert-with-icon {
+ padding-left: 65px;
+}
+
+.card-stats .card-body {
+ padding: 15px 15px 0px;
+}
+
+.card-stats .card-body .numbers {
+ font-size: 1.8rem;
+ text-align: right;
+}
+
+.card-stats .card-body .numbers p {
+ margin-bottom: 0;
+}
+
+.card-stats .card-footer {
+ padding: 0px 15px 10px 15px;
+}
+
+.card-stats .icon-big {
+ font-size: 3em;
+ min-height: 64px;
+}
+
+.card-stats .icon-big i {
+ font-weight: 700;
+ line-height: 59px;
+}
+
+.card-user .card-image {
+ height: 110px;
+}
+
+.card-user .card-image-plain {
+ height: 0;
+ margin-top: 110px;
+}
+
+.card-user .author {
+ text-align: center;
+ text-transform: none;
+ margin-top: -70px;
+}
+
+.card-user .avatar {
+ width: 124px;
+ height: 124px;
+ border: 5px solid #FFFFFF;
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.card-user .avatar.border-gray {
+ border-color: #EEEEEE;
+}
+
+.card-user .title {
+ line-height: 24px;
+}
+
+.card-user .card-body {
+ min-height: 240px;
+}
+
+.card-user .card-footer,
+.card-price .card-footer {
+ padding: 5px 15px 10px;
+}
+
+.card-user hr,
+.card-price hr {
+ margin: 5px 15px;
+}
+
+.card-plain {
+ background-color: transparent;
+ box-shadow: none;
+ border-radius: 0;
+}
+
+.card-plain .card-image {
+ border-radius: 4px;
+}
+
+.card.card-plain {
+ border: none !important;
+}
+
+.card.card-plain .card-header {
+ background-color: transparent !important;
+}
+
+.ct-label {
+ fill: rgba(0, 0, 0, 0.4);
+ color: rgba(0, 0, 0, 0.4);
+ font-size: 1.3rem;
+ line-height: 1;
+}
+
+.ct-chart-line .ct-label,
+.ct-chart-bar .ct-label {
+ display: block;
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+}
+
+.ct-label.ct-horizontal.ct-start {
+ -webkit-box-align: flex-end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: flex-end;
+ align-items: flex-end;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: start;
+}
+
+.ct-label.ct-horizontal.ct-end {
+ -webkit-box-align: flex-start;
+ -webkit-align-items: flex-start;
+ -ms-flex-align: flex-start;
+ align-items: flex-start;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: start;
+}
+
+.ct-label.ct-vertical.ct-start {
+ -webkit-box-align: flex-end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: flex-end;
+ align-items: flex-end;
+ -webkit-box-pack: flex-end;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: flex-end;
+ justify-content: flex-end;
+ text-align: right;
+ text-anchor: end;
+}
+
+.ct-label.ct-vertical.ct-end {
+ -webkit-box-align: flex-end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: flex-end;
+ align-items: flex-end;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: start;
+}
+
+.ct-chart-bar .ct-label.ct-horizontal.ct-start {
+ -webkit-box-align: flex-end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: flex-end;
+ align-items: flex-end;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ text-align: center;
+ text-anchor: start;
+}
+
+.ct-chart-bar .ct-label.ct-horizontal.ct-end {
+ -webkit-box-align: flex-start;
+ -webkit-align-items: flex-start;
+ -ms-flex-align: flex-start;
+ align-items: flex-start;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ text-align: center;
+ text-anchor: start;
+}
+
+.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start {
+ -webkit-box-align: flex-end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: flex-end;
+ align-items: flex-end;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: start;
+}
+
+.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end {
+ -webkit-box-align: flex-start;
+ -webkit-align-items: flex-start;
+ -ms-flex-align: flex-start;
+ align-items: flex-start;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: start;
+}
+
+.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start {
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: flex-end;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: flex-end;
+ justify-content: flex-end;
+ text-align: right;
+ text-anchor: end;
+}
+
+.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end {
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: flex-start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ text-anchor: end;
+}
+
+.ct-grid {
+ stroke: rgba(0, 0, 0, 0.2);
+ stroke-width: 1px;
+ stroke-dasharray: 2px;
+}
+
+.ct-point {
+ stroke-width: 8px;
+ stroke-linecap: round;
+}
+
+.ct-line {
+ fill: none;
+ stroke-width: 3px;
+}
+
+.ct-area {
+ stroke: none;
+ fill-opacity: 0.8;
+}
+
+.ct-bar {
+ fill: none;
+ stroke-width: 10px;
+}
+
+.ct-slice-donut {
+ fill: none;
+ stroke-width: 60px;
+}
+
+.ct-series-a .ct-point,
+.ct-series-a .ct-line,
+.ct-series-a .ct-bar,
+.ct-series-a .ct-slice-donut {
+ stroke: #1DC7EA;
+}
+
+.ct-series-a .ct-slice-pie,
+.ct-series-a .ct-area {
+ fill: #1DC7EA;
+}
+
+.ct-series-b .ct-point,
+.ct-series-b .ct-line,
+.ct-series-b .ct-bar,
+.ct-series-b .ct-slice-donut {
+ stroke: #FB404B;
+}
+
+.ct-series-b .ct-slice-pie,
+.ct-series-b .ct-area {
+ fill: #FB404B;
+}
+
+.ct-series-c .ct-point,
+.ct-series-c .ct-line,
+.ct-series-c .ct-bar,
+.ct-series-c .ct-slice-donut {
+ stroke: #FFA534;
+}
+
+.ct-series-c .ct-slice-pie,
+.ct-series-c .ct-area {
+ fill: #FFA534;
+}
+
+.ct-series-d .ct-point,
+.ct-series-d .ct-line,
+.ct-series-d .ct-bar,
+.ct-series-d .ct-slice-donut {
+ stroke: #9368E9;
+}
+
+.ct-series-d .ct-slice-pie,
+.ct-series-d .ct-area {
+ fill: #9368E9;
+}
+
+.ct-series-e .ct-point,
+.ct-series-e .ct-line,
+.ct-series-e .ct-bar,
+.ct-series-e .ct-slice-donut {
+ stroke: #87CB16;
+}
+
+.ct-series-e .ct-slice-pie,
+.ct-series-e .ct-area {
+ fill: #87CB16;
+}
+
+.ct-series-f .ct-point,
+.ct-series-f .ct-line,
+.ct-series-f .ct-bar,
+.ct-series-f .ct-slice-donut {
+ stroke: #1F77D0;
+}
+
+.ct-series-f .ct-slice-pie,
+.ct-series-f .ct-area {
+ fill: #1F77D0;
+}
+
+.ct-series-g .ct-point,
+.ct-series-g .ct-line,
+.ct-series-g .ct-bar,
+.ct-series-g .ct-slice-donut {
+ stroke: #5e5e5e;
+}
+
+.ct-series-g .ct-slice-pie,
+.ct-series-g .ct-area {
+ fill: #5e5e5e;
+}
+
+.ct-series-h .ct-point,
+.ct-series-h .ct-line,
+.ct-series-h .ct-bar,
+.ct-series-h .ct-slice-donut {
+ stroke: #dd4b39;
+}
+
+.ct-series-h .ct-slice-pie,
+.ct-series-h .ct-area {
+ fill: #dd4b39;
+}
+
+.ct-series-i .ct-point,
+.ct-series-i .ct-line,
+.ct-series-i .ct-bar,
+.ct-series-i .ct-slice-donut {
+ stroke: #35465c;
+}
+
+.ct-series-i .ct-slice-pie,
+.ct-series-i .ct-area {
+ fill: #35465c;
+}
+
+.ct-series-j .ct-point,
+.ct-series-j .ct-line,
+.ct-series-j .ct-bar,
+.ct-series-j .ct-slice-donut {
+ stroke: #e52d27;
+}
+
+.ct-series-j .ct-slice-pie,
+.ct-series-j .ct-area {
+ fill: #e52d27;
+}
+
+.ct-series-k .ct-point,
+.ct-series-k .ct-line,
+.ct-series-k .ct-bar,
+.ct-series-k .ct-slice-donut {
+ stroke: #55acee;
+}
+
+.ct-series-k .ct-slice-pie,
+.ct-series-k .ct-area {
+ fill: #55acee;
+}
+
+.ct-series-l .ct-point,
+.ct-series-l .ct-line,
+.ct-series-l .ct-bar,
+.ct-series-l .ct-slice-donut {
+ stroke: #cc2127;
+}
+
+.ct-series-l .ct-slice-pie,
+.ct-series-l .ct-area {
+ fill: #cc2127;
+}
+
+.ct-series-m .ct-point,
+.ct-series-m .ct-line,
+.ct-series-m .ct-bar,
+.ct-series-m .ct-slice-donut {
+ stroke: #1769ff;
+}
+
+.ct-series-m .ct-slice-pie,
+.ct-series-m .ct-area {
+ fill: #1769ff;
+}
+
+.ct-series-n .ct-point,
+.ct-series-n .ct-line,
+.ct-series-n .ct-bar,
+.ct-series-n .ct-slice-donut {
+ stroke: #6188e2;
+}
+
+.ct-series-n .ct-slice-pie,
+.ct-series-n .ct-area {
+ fill: #6188e2;
+}
+
+.ct-series-o .ct-point,
+.ct-series-o .ct-line,
+.ct-series-o .ct-bar,
+.ct-series-o .ct-slice-donut {
+ stroke: #a748ca;
+}
+
+.ct-series-o .ct-slice-pie,
+.ct-series-o .ct-area {
+ fill: #a748ca;
+}
+
+.ct-square {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-square:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 100%;
+}
+
+.ct-square:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-square>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-minor-second {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-minor-second:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 93.75%;
+}
+
+.ct-minor-second:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-minor-second>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-second {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-second:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 88.88889%;
+}
+
+.ct-major-second:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-second>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-minor-third {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-minor-third:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 83.33333%;
+}
+
+.ct-minor-third:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-minor-third>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-third {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-third:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 80%;
+}
+
+.ct-major-third:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-third>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-perfect-fourth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-perfect-fourth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 75%;
+}
+
+.ct-perfect-fourth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-perfect-fourth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-perfect-fifth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-perfect-fifth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 66.66667%;
+}
+
+.ct-perfect-fifth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-perfect-fifth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-minor-sixth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-minor-sixth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 62.5%;
+}
+
+.ct-minor-sixth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-minor-sixth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-golden-section {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-golden-section:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 61.8047%;
+}
+
+.ct-golden-section:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-golden-section>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-sixth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-sixth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 60%;
+}
+
+.ct-major-sixth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-sixth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-minor-seventh {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-minor-seventh:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 56.25%;
+}
+
+.ct-minor-seventh:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-minor-seventh>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-seventh {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-seventh:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 53.33333%;
+}
+
+.ct-major-seventh:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-seventh>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-octave {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-octave:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 50%;
+}
+
+.ct-octave:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-octave>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-tenth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-tenth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 40%;
+}
+
+.ct-major-tenth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-tenth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-eleventh {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-eleventh:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 37.5%;
+}
+
+.ct-major-eleventh:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-eleventh>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-major-twelfth {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-major-twelfth:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 33.33333%;
+}
+
+.ct-major-twelfth:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-major-twelfth>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ct-double-octave {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+.ct-double-octave:before {
+ display: block;
+ float: left;
+ content: "";
+ width: 0;
+ height: 0;
+ padding-bottom: 25%;
+}
+
+.ct-double-octave:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.ct-double-octave>svg {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+@media (min-width: 992px) {
+ .navbar-form {
+ margin-top: 21px;
+ margin-bottom: 21px;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ .navbar-nav .nav-item .dropdown-menu,
+ .dropdown .dropdown-menu {
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ -o-transform: scale(0);
+ -ms-transform: scale(0);
+ transform: scale(0);
+ -webkit-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
+ -moz-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
+ -o-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
+ -ms-transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
+ transition: all 370ms cubic-bezier(0.34, 1.61, 0.7, 1);
+ }
+ .navbar-nav .nav-item.show .dropdown-menu,
+ .dropdown.show .dropdown-menu {
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ -o-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 29px -50px;
+ -moz-transform-origin: 29px -50px;
+ -o-transform-origin: 29px -50px;
+ -ms-transform-origin: 29px -50px;
+ transform-origin: 29px -50px;
+ }
+ .footer {
+ height: 60px;
+ }
+ .footer .footer-menu {
+ float: left;
+ }
+ .footer .copyright {
+ float: right;
+ }
+ .navbar-nav .nav-item .dropdown-menu:before {
+ border-bottom: 11px solid rgba(0, 0, 0, 0.2);
+ border-left: 11px solid transparent;
+ border-right: 11px solid transparent;
+ content: "";
+ display: inline-block;
+ position: absolute;
+ left: 12px;
+ top: -11px;
+ }
+ .navbar-nav .nav-item .dropdown-menu:after {
+ border-bottom: 11px solid #FFFFFF;
+ border-left: 11px solid transparent;
+ border-right: 11px solid transparent;
+ content: "";
+ display: inline-block;
+ position: absolute;
+ left: 12px;
+ top: -10px;
+ }
+ .navbar-nav.navbar-right .nav-item .dropdown-menu:before {
+ left: auto;
+ right: 12px;
+ }
+ .navbar-nav.navbar-right .nav-item .dropdown-menu:after {
+ left: auto;
+ right: 12px;
+ }
+ .footer:not(.footer-big) nav>ul li:first-child {
+ margin-left: 0;
+ }
+ .card form [class*="col-"] {
+ padding: 6px;
+ }
+ .card form [class*="col-"]:first-child {
+ padding-left: 15px;
+ }
+ .card form [class*="col-"]:last-child {
+ padding-right: 15px;
+ }
+}
+
+
+/* Changes for small display */
+
+@media (max-width: 991px) {
+ .sidebar {
+ right: 0 !important;
+ left: auto;
+ position: absolute;
+ -webkit-transform: translate3d(262px, 0, 0);
+ -moz-transform: translate3d(262px, 0, 0);
+ -o-transform: translate3d(262px, 0, 0);
+ -ms-transform: translate3d(262px, 0, 0);
+ transform: translate3d(262px, 0, 0) !important;
+ -webkit-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ .nav-open .main-panel {
+ position: absolute;
+ left: 0;
+ -webkit-transform: translate3d(-250px, 0, 0);
+ -moz-transform: translate3d(-250px, 0, 0);
+ -o-transform: translate3d(-250px, 0, 0);
+ -ms-transform: translate3d(-250px, 0, 0);
+ transform: translate3d(-250px, 0, 0) !important;
+ -webkit-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ .nav-open .sidebar {
+ -webkit-transform: translate3d(10px, 0, 0);
+ -moz-transform: translate3d(10px, 0, 0);
+ -o-transform: translate3d(10px, 0, 0);
+ -ms-transform: translate3d(10px, 0, 0);
+ transform: translate3d(10px, 0, 0) !important;
+ -webkit-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ .main-panel {
+ -webkit-transform: translate3d(0px, 0, 0);
+ -moz-transform: translate3d(0px, 0, 0);
+ -o-transform: translate3d(0px, 0, 0);
+ -ms-transform: translate3d(0px, 0, 0);
+ transform: translate3d(0px, 0, 0) !important;
+ -webkit-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ .nav-item.active-pro {
+ position: relative !important;
+ }
+ .nav-mobile-menu {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ margin-bottom: 15px;
+ padding-bottom: 15px;
+ padding-top: 5px;
+ }
+ .nav-mobile-menu .dropdown .dropdown-menu {
+ position: static !important;
+ float: none;
+ width: auto;
+ color: #FFFFFF;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.5s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ .nav-mobile-menu .dropdown .dropdown-menu .dropdown-item {
+ margin: 5px 15px 0px 40px;
+ border-radius: 4px;
+ color: #FFFFFF;
+ opacity: .86;
+ padding: 8px 50px;
+ }
+ .nav-mobile-menu .dropdown .dropdown-menu .dropdown-item:hover {
+ background-color: rgba(255, 255, 255, 0.23);
+ }
+ .nav-mobile-menu .nav-item .nav-link span {
+ display: inline-block !important;
+ }
+ .nav-mobile-menu .nav-item .nav-link .no-icon {
+ padding-left: 50px;
+ }
+ .main-panel {
+ width: 100%;
+ }
+ .navbar-brand {
+ padding: 15px 15px;
+ }
+ .navbar-transparent {
+ padding-top: 15px;
+ background-color: rgba(0, 0, 0, 0.45);
+ }
+ body {
+ position: relative;
+ }
+ .wrapper {
+ left: 0;
+ background-color: white;
+ }
+ .navbar .container {
+ left: 15px;
+ width: 100%;
+ position: relative;
+ top: -10px;
+ }
+ .navbar-nav .nav-item {
+ float: none;
+ position: relative;
+ display: block;
+ }
+ body>.navbar-collapse {
+ position: fixed;
+ display: block;
+ top: 0;
+ height: 100%;
+ right: 0;
+ left: auto;
+ z-index: 1032;
+ visibility: visible;
+ background-color: #999;
+ overflow-y: visible;
+ border-top: none;
+ text-align: left;
+ padding: 0;
+ -webkit-transform: translate3d(260px, 0, 0);
+ -moz-transform: translate3d(260px, 0, 0);
+ -o-transform: translate3d(260px, 0, 0);
+ -ms-transform: translate3d(260px, 0, 0);
+ transform: translate3d(260px, 0, 0);
+ -webkit-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -moz-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -o-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ -ms-transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ transition: all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1);
+ }
+ body>.navbar-collapse>ul {
+ position: relative;
+ z-index: 4;
+ overflow-y: scroll;
+ height: calc(100vh - 61px);
+ width: 100%;
+ }
+ body>.navbar-collapse::before {
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ background-color: #282828;
+ display: block;
+ content: "";
+ z-index: 1;
+ }
+ body>.navbar-collapse .logo {
+ position: relative;
+ z-index: 4;
+ }
+ body>.navbar-collapse .nav li>a {
+ padding: 10px 15px;
+ }
+ .nav-show .navbar-collapse {
+ -webkit-transform: translate3d(0px, 0, 0);
+ -moz-transform: translate3d(0px, 0, 0);
+ -o-transform: translate3d(0px, 0, 0);
+ -ms-transform: translate3d(0px, 0, 0);
+ transform: translate3d(0px, 0, 0);
+ }
+ .nav-show .navbar .container {
+ left: -250px;
+ }
+ .nav-show .wrapper {
+ left: 0;
+ -webkit-transform: translate3d(-260px, 0, 0);
+ -moz-transform: translate3d(-260px, 0, 0);
+ -o-transform: translate3d(-260px, 0, 0);
+ -ms-transform: translate3d(-260px, 0, 0);
+ transform: translate3d(-260px, 0, 0);
+ }
+ .navbar-toggle .icon-bar {
+ display: block;
+ position: relative;
+ background: #fff;
+ width: 24px;
+ height: 2px;
+ border-radius: 1px;
+ margin: 0 auto;
+ }
+ .navbar-header .navbar-toggle {
+ margin: 10px 15px 10px 0;
+ width: 40px;
+ height: 40px;
+ }
+ .bar1,
+ .bar2,
+ .bar3 {
+ outline: 1px solid transparent;
+ }
+ .bar1 {
+ top: 0px;
+ -webkit-animation: topbar-back 500ms linear 0s;
+ -moz-animation: topbar-back 500ms linear 0s;
+ animation: topbar-back 500ms 0s;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ }
+ .bar2 {
+ opacity: 1;
+ }
+ .bar3 {
+ bottom: 0px;
+ -webkit-animation: bottombar-back 500ms linear 0s;
+ -moz-animation: bottombar-back 500ms linear 0s;
+ animation: bottombar-back 500ms 0s;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ }
+ .toggled .bar1 {
+ top: 6px;
+ -webkit-animation: topbar-x 500ms linear 0s;
+ -moz-animation: topbar-x 500ms linear 0s;
+ animation: topbar-x 500ms 0s;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ }
+ .toggled .bar2 {
+ opacity: 0;
+ }
+ .toggled .bar3 {
+ bottom: 6px;
+ -webkit-animation: bottombar-x 500ms linear 0s;
+ -moz-animation: bottombar-x 500ms linear 0s;
+ animation: bottombar-x 500ms 0s;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ }
+ @keyframes topbar-x {
+ 0% {
+ top: 0px;
+ transform: rotate(0deg);
+ }
+ 45% {
+ top: 6px;
+ transform: rotate(145deg);
+ }
+ 75% {
+ transform: rotate(130deg);
+ }
+ 100% {
+ transform: rotate(135deg);
+ }
+ }
+ @-webkit-keyframes topbar-x {
+ 0% {
+ top: 0px;
+ -webkit-transform: rotate(0deg);
+ }
+ 45% {
+ top: 6px;
+ -webkit-transform: rotate(145deg);
+ }
+ 75% {
+ -webkit-transform: rotate(130deg);
+ }
+ 100% {
+ -webkit-transform: rotate(135deg);
+ }
+ }
+ @-moz-keyframes topbar-x {
+ 0% {
+ top: 0px;
+ -moz-transform: rotate(0deg);
+ }
+ 45% {
+ top: 6px;
+ -moz-transform: rotate(145deg);
+ }
+ 75% {
+ -moz-transform: rotate(130deg);
+ }
+ 100% {
+ -moz-transform: rotate(135deg);
+ }
+ }
+ @keyframes topbar-back {
+ 0% {
+ top: 6px;
+ transform: rotate(135deg);
+ }
+ 45% {
+ transform: rotate(-10deg);
+ }
+ 75% {
+ transform: rotate(5deg);
+ }
+ 100% {
+ top: 0px;
+ transform: rotate(0);
+ }
+ }
+ @-webkit-keyframes topbar-back {
+ 0% {
+ top: 6px;
+ -webkit-transform: rotate(135deg);
+ }
+ 45% {
+ -webkit-transform: rotate(-10deg);
+ }
+ 75% {
+ -webkit-transform: rotate(5deg);
+ }
+ 100% {
+ top: 0px;
+ -webkit-transform: rotate(0);
+ }
+ }
+ @-moz-keyframes topbar-back {
+ 0% {
+ top: 6px;
+ -moz-transform: rotate(135deg);
+ }
+ 45% {
+ -moz-transform: rotate(-10deg);
+ }
+ 75% {
+ -moz-transform: rotate(5deg);
+ }
+ 100% {
+ top: 0px;
+ -moz-transform: rotate(0);
+ }
+ }
+ @keyframes bottombar-x {
+ 0% {
+ bottom: 0px;
+ transform: rotate(0deg);
+ }
+ 45% {
+ bottom: 6px;
+ transform: rotate(-145deg);
+ }
+ 75% {
+ transform: rotate(-130deg);
+ }
+ 100% {
+ transform: rotate(-135deg);
+ }
+ }
+ @-webkit-keyframes bottombar-x {
+ 0% {
+ bottom: 0px;
+ -webkit-transform: rotate(0deg);
+ }
+ 45% {
+ bottom: 6px;
+ -webkit-transform: rotate(-145deg);
+ }
+ 75% {
+ -webkit-transform: rotate(-130deg);
+ }
+ 100% {
+ -webkit-transform: rotate(-135deg);
+ }
+ }
+ @-moz-keyframes bottombar-x {
+ 0% {
+ bottom: 0px;
+ -moz-transform: rotate(0deg);
+ }
+ 45% {
+ bottom: 6px;
+ -moz-transform: rotate(-145deg);
+ }
+ 75% {
+ -moz-transform: rotate(-130deg);
+ }
+ 100% {
+ -moz-transform: rotate(-135deg);
+ }
+ }
+ @keyframes bottombar-back {
+ 0% {
+ bottom: 6px;
+ transform: rotate(-135deg);
+ }
+ 45% {
+ transform: rotate(10deg);
+ }
+ 75% {
+ transform: rotate(-5deg);
+ }
+ 100% {
+ bottom: 0px;
+ transform: rotate(0);
+ }
+ }
+ @-webkit-keyframes bottombar-back {
+ 0% {
+ bottom: 6px;
+ -webkit-transform: rotate(-135deg);
+ }
+ 45% {
+ -webkit-transform: rotate(10deg);
+ }
+ 75% {
+ -webkit-transform: rotate(-5deg);
+ }
+ 100% {
+ bottom: 0px;
+ -webkit-transform: rotate(0);
+ }
+ }
+ @-moz-keyframes bottombar-back {
+ 0% {
+ bottom: 6px;
+ -moz-transform: rotate(-135deg);
+ }
+ 45% {
+ -moz-transform: rotate(10deg);
+ }
+ 75% {
+ -moz-transform: rotate(-5deg);
+ }
+ 100% {
+ bottom: 0px;
+ -moz-transform: rotate(0);
+ }
+ }
+ @-webkit-keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ @-moz-keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ @keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ .dropdown-menu .divider {
+ background-color: rgba(229, 229, 229, 0.15);
+ }
+ .navbar-nav {
+ margin: 1px 0;
+ }
+ .navbar-nav .show .dropdown-menu .nav-item .nav-link {
+ padding: 10px 15px 10px 60px;
+ }
+ [class*="navbar-"] .navbar-nav>li>a,
+ [class*="navbar-"] .navbar-nav>li>a:hover,
+ [class*="navbar-"] .navbar-nav>li>a:focus,
+ [class*="navbar-"] .navbar-nav .active>a,
+ [class*="navbar-"] .navbar-nav .active>a:hover,
+ [class*="navbar-"] .navbar-nav .active>a:focus,
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a,
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a:hover,
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a:focus,
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a:active {
+ color: white;
+ }
+ [class*="navbar-"] .navbar-nav>li>a,
+ [class*="navbar-"] .navbar-nav>li>a:hover,
+ [class*="navbar-"] .navbar-nav>li>a:focus {
+ opacity: .7;
+ background-color: transparent;
+ outline: none;
+ }
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a:hover,
+ [class*="navbar-"] .navbar-nav .show .dropdown-menu>li>a:focus {
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+ [class*="navbar-"] .navbar-nav.navbar-nav .show .dropdown-menu>li>a:active {
+ opacity: 1;
+ }
+ [class*="navbar-"] .navbar-nav .dropdown>a:hover .caret {
+ border-bottom-color: #fff;
+ border-top-color: #fff;
+ }
+ [class*="navbar-"] .navbar-nav .dropdown>a:active .caret {
+ border-bottom-color: white;
+ border-top-color: white;
+ }
+ .dropdown-menu {
+ display: none;
+ }
+ .navbar-fixed-top {
+ -webkit-backface-visibility: hidden;
+ }
+ #bodyClick {
+ height: 100%;
+ width: 100%;
+ position: fixed;
+ opacity: 0;
+ top: 0;
+ left: auto;
+ right: 250px;
+ content: "";
+ z-index: 9999;
+ overflow-x: hidden;
+ }
+ .social-line .btn {
+ margin: 0 0 10px 0;
+ }
+ .subscribe-line .form-control {
+ margin: 0 0 10px 0;
+ }
+ .social-line.pull-right {
+ float: none;
+ }
+ .social-area.pull-right {
+ float: none !important;
+ }
+ .form-control+.form-control-feedback {
+ margin-top: -8px;
+ }
+ .navbar-toggle:hover,
+ .navbar-toggle:focus {
+ background-color: transparent !important;
+ }
+ .btn.dropdown-toggle {
+ margin-bottom: 0;
+ }
+ .media-post .author {
+ width: 20%;
+ float: none !important;
+ display: block;
+ margin: 0 auto 10px;
+ }
+ .media-post .media-body {
+ width: 100%;
+ }
+ .navbar-collapse.collapse {
+ height: 100% !important;
+ }
+ .navbar-collapse.collapse.in {
+ display: block;
+ }
+ .navbar-header .collapse,
+ .navbar-toggle {
+ display: block !important;
+ }
+ .navbar-header {
+ float: none;
+ }
+ .navbar-nav .show .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+ .navbar-collapse .navbar-nav p {
+ line-height: 40px !important;
+ margin: 0;
+ }
+ .navbar-collapse [class^="pe-7s-"] {
+ float: left;
+ font-size: 20px;
+ margin-right: 10px;
+ }
+}
+
+@media (min-width: 992px) {
+ .table-full-width {
+ margin-left: -15px;
+ margin-right: -15px;
+ }
+ .table-responsive {
+ overflow: visible;
+ }
+}
+
+@media (max-width: 991px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-x: scroll;
+ overflow-y: hidden;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+
+.bootstrap-switch {
+ display: inline-block;
+ direction: ltr;
+ cursor: pointer;
+ border-radius: 30px;
+ border: 0;
+ position: relative;
+ text-align: left;
+ overflow: hidden;
+ margin-bottom: 5px;
+ margin-left: 66px;
+ line-height: 8px;
+ width: 61px !important;
+ height: 26px;
+ outline: none;
+ z-index: 0;
+ margin-right: 1px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ vertical-align: middle;
+ -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+
+.bootstrap-switch .bootstrap-switch-container {
+ display: inline-flex;
+ top: 0;
+ height: 26px;
+ border-radius: 4px;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ width: 100px !important;
+}
+
+.bootstrap-switch .bootstrap-switch-handle-on,
+.bootstrap-switch .bootstrap-switch-handle-off,
+.bootstrap-switch .bootstrap-switch-label {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ cursor: pointer;
+ display: inline-block !important;
+ height: 100%;
+ color: #fff;
+ padding: 6px 10px;
+ font-size: 11px;
+ text-indent: -5px;
+ line-height: 15px;
+ -webkit-transition: 0.25s ease-out;
+ transition: 0.25s ease-out;
+}
+
+.bootstrap-switch .bootstrap-switch-handle-on i,
+.bootstrap-switch .bootstrap-switch-handle-off i,
+.bootstrap-switch .bootstrap-switch-label i {
+ font-size: 12px;
+ line-height: 14px;
+}
+
+.bootstrap-switch .bootstrap-switch-handle-on,
+.bootstrap-switch .bootstrap-switch-handle-off {
+ text-align: center;
+ z-index: 1;
+ float: left;
+ width: 50% !important;
+ background-color: #1DC7EA;
+}
+
+.bootstrap-switch .bootstrap-switch-label {
+ text-align: center;
+ z-index: 100;
+ color: #333333;
+ background: #ffffff;
+ width: 22px !important;
+ height: 22px;
+ margin: 2px -11px;
+ border-radius: 12px;
+ position: relative;
+ float: left;
+ padding: 0;
+ background-color: #FFFFFF;
+ box-shadow: 0 1px 1px #FFFFFF inset, 0 1px 1px rgba(0, 0, 0, 0.25);
+}
+
+.bootstrap-switch .bootstrap-switch-handle-on {
+ border-bottom-left-radius: 3px;
+ border-top-left-radius: 3px;
+}
+
+.bootstrap-switch .bootstrap-switch-handle-off {
+ text-indent: 6px;
+}
+
+.bootstrap-switch input[type='radio'],
+.bootstrap-switch input[type='checkbox'] {
+ position: absolute !important;
+ top: 0;
+ left: 0;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ z-index: -1;
+}
+
+.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container {
+ -webkit-transition: margin-left 0.5s;
+ transition: margin-left 0.5s;
+}
+
+.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-container {
+ margin-left: -2px !important;
+}
+
+.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-container {
+ margin-left: -37px !important;
+}
+
+.bootstrap-switch.bootstrap-switch-on:hover .bootstrap-switch-label {
+ width: 26px !important;
+ margin: 2px -15px;
+}
+
+.bootstrap-switch.bootstrap-switch-off:hover .bootstrap-switch-label {
+ width: 26px !important;
+ margin: 2px -15px -13px -11px;
+}
+
+
+/*--------------------------------
+
+nucleo-icons Web Font - built using nucleoapp.com
+License - nucleoapp.com/license/
+
+-------------------------------- */
+
+@font-face {
+ font-family: 'nucleo-icons';
+ src: url("../fonts/nucleo-icons.eot");
+ src: url("../fonts/nucleo-icons.eot") format("embedded-opentype"), url("../fonts/nucleo-icons.woff2") format("woff2"), url("../fonts/nucleo-icons.woff") format("woff"), url("../fonts/nucleo-icons.ttf") format("truetype"), url("../fonts/nucleo-icons.svg") format("svg");
+ font-weight: normal;
+ font-style: normal;
+}
+
+
+/*------------------------
+ base class definition
+-------------------------*/
+
+.nc-icon {
+ display: inline-block;
+ font: normal normal normal 14px/1 'nucleo-icons';
+ font-size: inherit;
+ speak: none;
+ text-transform: none;
+ /* Better Font Rendering */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+
+/*------------------------
+ change icon size
+-------------------------*/
+
+.nc-icon.lg {
+ font-size: 1.33333333em;
+ vertical-align: -16%;
+}
+
+.nc-icon.x2 {
+ font-size: 2em;
+}
+
+.nc-icon.x3 {
+ font-size: 3em;
+}
+
+
+/*----------------------------------
+ add a square/circle background
+-----------------------------------*/
+
+.nc-icon.square,
+.nc-icon.circle {
+ padding: 0.33333333em;
+ vertical-align: -16%;
+ background-color: #eee;
+}
+
+.nc-icon.circle {
+ border-radius: 50%;
+}
+
+
+/*------------------------
+ list icons
+-------------------------*/
+
+.nc-icon-ul {
+ padding-left: 0;
+ margin-left: 2.14285714em;
+ list-style-type: none;
+}
+
+.nc-icon-ul>li {
+ position: relative;
+}
+
+.nc-icon-ul>li>.nc-icon {
+ position: absolute;
+ left: -1.57142857em;
+ top: 0.14285714em;
+ text-align: center;
+}
+
+.nc-icon-ul>li>.nc-icon.lg {
+ top: 0;
+ left: -1.35714286em;
+}
+
+.nc-icon-ul>li>.nc-icon.circle,
+.nc-icon-ul>li>.nc-icon.square {
+ top: -0.19047619em;
+ left: -1.9047619em;
+}
+
+.all-icons .font-icon-list .font-icon-detail i {
+ font-size: 32px;
+}
+
+
+/*------------------------
+ spinning icons
+-------------------------*/
+
+.nc-icon.spin {
+ -webkit-animation: nc-icon-spin 2s infinite linear;
+ -moz-animation: nc-icon-spin 2s infinite linear;
+ animation: nc-icon-spin 2s infinite linear;
+}
+
+@-webkit-keyframes nc-icon-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
+}
+
+@-moz-keyframes nc-icon-spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ }
+ 100% {
+ -moz-transform: rotate(360deg);
+ }
+}
+
+@keyframes nc-icon-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -ms-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+
+/*------------------------
+ rotated/flipped icons
+-------------------------*/
+
+.nc-icon.rotate-90 {
+ filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=1);
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ -o-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+
+.nc-icon.rotate-180 {
+ filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=2);
+ -webkit-transform: rotate(180deg);
+ -moz-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ -o-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+
+.nc-icon.rotate-270 {
+ filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=3);
+ -webkit-transform: rotate(270deg);
+ -moz-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ -o-transform: rotate(270deg);
+ transform: rotate(270deg);
+}
+
+.nc-icon.flip-y {
+ filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=0);
+ -webkit-transform: scale(-1, 1);
+ -moz-transform: scale(-1, 1);
+ -ms-transform: scale(-1, 1);
+ -o-transform: scale(-1, 1);
+ transform: scale(-1, 1);
+}
+
+.nc-icon.flip-x {
+ filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=2);
+ -webkit-transform: scale(1, -1);
+ -moz-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ -o-transform: scale(1, -1);
+ transform: scale(1, -1);
+}
+
+
+/*------------------------
+ font icons
+-------------------------*/
+
+.nc-air-baloon::before {
+ content: "\ea01";
+}
+
+.nc-album-2::before {
+ content: "\ea02";
+}
+
+.nc-alien-33::before {
+ content: "\ea03";
+}
+
+.nc-align-center::before {
+ content: "\ea04";
+}
+
+.nc-align-left-2::before {
+ content: "\ea05";
+}
+
+.nc-ambulance::before {
+ content: "\ea06";
+}
+
+.nc-android::before {
+ content: "\ea07";
+}
+
+.nc-app::before {
+ content: "\ea08";
+}
+
+.nc-apple::before {
+ content: "\ea09";
+}
+
+.nc-atom::before {
+ content: "\ea0a";
+}
+
+.nc-attach-87::before {
+ content: "\ea0b";
+}
+
+.nc-audio-92::before {
+ content: "\ea0c";
+}
+
+.nc-backpack::before {
+ content: "\ea0d";
+}
+
+.nc-badge::before {
+ content: "\ea0e";
+}
+
+.nc-bag::before {
+ content: "\ea0f";
+}
+
+.nc-bank::before {
+ content: "\ea10";
+}
+
+.nc-battery-81::before {
+ content: "\ea11";
+}
+
+.nc-bell-55::before {
+ content: "\ea12";
+}
+
+.nc-bold::before {
+ content: "\ea13";
+}
+
+.nc-bulb-63::before {
+ content: "\ea14";
+}
+
+.nc-bullet-list-67::before {
+ content: "\ea15";
+}
+
+.nc-bus-front-12::before {
+ content: "\ea16";
+}
+
+.nc-button-pause::before {
+ content: "\ea17";
+}
+
+.nc-button-play::before {
+ content: "\ea18";
+}
+
+.nc-button-power::before {
+ content: "\ea19";
+}
+
+.nc-camera-20::before {
+ content: "\ea1a";
+}
+
+.nc-caps-small::before {
+ content: "\ea1b";
+}
+
+.nc-cart-simple::before {
+ content: "\ea1c";
+}
+
+.nc-cctv::before {
+ content: "\ea1d";
+}
+
+.nc-chart-bar-32::before {
+ content: "\ea1e";
+}
+
+.nc-chart-pie-35::before {
+ content: "\ea1f";
+}
+
+.nc-chart-pie-36::before {
+ content: "\ea20";
+}
+
+.nc-chart::before {
+ content: "\ea21";
+}
+
+.nc-chat-round::before {
+ content: "\ea22";
+}
+
+.nc-check-2::before {
+ content: "\ea23";
+}
+
+.nc-circle-09::before {
+ content: "\ea24";
+}
+
+.nc-circle::before {
+ content: "\ea25";
+}
+
+.nc-cloud-download-93::before {
+ content: "\ea26";
+}
+
+.nc-cloud-upload-94::before {
+ content: "\ea27";
+}
+
+.nc-compass-05::before {
+ content: "\ea28";
+}
+
+.nc-controller-modern::before {
+ content: "\ea29";
+}
+
+.nc-credit-card::before {
+ content: "\ea2a";
+}
+
+.nc-delivery-fast::before {
+ content: "\ea2b";
+}
+
+.nc-email-83::before {
+ content: "\ea2c";
+}
+
+.nc-email-85::before {
+ content: "\ea2d";
+}
+
+.nc-explore-2::before {
+ content: "\ea2e";
+}
+
+.nc-fav-remove::before {
+ content: "\ea2f";
+}
+
+.nc-favourite-28::before {
+ content: "\ea30";
+}
+
+.nc-globe-2::before {
+ content: "\ea31";
+}
+
+.nc-grid-45::before {
+ content: "\ea32";
+}
+
+.nc-headphones-2::before {
+ content: "\ea33";
+}
+
+.nc-html5::before {
+ content: "\ea34";
+}
+
+.nc-istanbul::before {
+ content: "\ea35";
+}
+
+.nc-key-25::before {
+ content: "\ea36";
+}
+
+.nc-layers-3::before {
+ content: "\ea37";
+}
+
+.nc-light-3::before {
+ content: "\ea38";
+}
+
+.nc-lock-circle-open::before {
+ content: "\ea39";
+}
+
+.nc-map-big::before {
+ content: "\ea3a";
+}
+
+.nc-mobile::before {
+ content: "\ea3c";
+}
+
+.nc-money-coins::before {
+ content: "\ea3b";
+}
+
+.nc-note-03::before {
+ content: "\ea3d";
+}
+
+.nc-notes::before {
+ content: "\ea3e";
+}
+
+.nc-notification-70::before {
+ content: "\ea3f";
+}
+
+.nc-palette::before {
+ content: "\ea40";
+}
+
+.nc-paper-2::before {
+ content: "\ea41";
+}
+
+.nc-pin-3::before {
+ content: "\ea42";
+}
+
+.nc-planet::before {
+ content: "\ea43";
+}
+
+.nc-preferences-circle-rotate::before {
+ content: "\ea44";
+}
+
+.nc-puzzle-10::before {
+ content: "\ea45";
+}
+
+.nc-quote::before {
+ content: "\ea46";
+}
+
+.nc-refresh-02::before {
+ content: "\ea47";
+}
+
+.nc-ruler-pencil::before {
+ content: "\ea48";
+}
+
+.nc-satisfied::before {
+ content: "\ea49";
+}
+
+.nc-scissors::before {
+ content: "\ea4a";
+}
+
+.nc-send::before {
+ content: "\ea4b";
+}
+
+.nc-settings-90::before {
+ content: "\ea4c";
+}
+
+.nc-settings-gear-64::before {
+ content: "\ea4d";
+}
+
+.nc-settings-tool-66::before {
+ content: "\ea4e";
+}
+
+.nc-simple-add::before {
+ content: "\ea4f";
+}
+
+.nc-simple-delete::before {
+ content: "\ea50";
+}
+
+.nc-simple-remove::before {
+ content: "\ea51";
+}
+
+.nc-single-02::before {
+ content: "\ea52";
+}
+
+.nc-single-copy-04::before {
+ content: "\ea53";
+}
+
+.nc-spaceship::before {
+ content: "\ea54";
+}
+
+.nc-square-pin::before {
+ content: "\ea55";
+}
+
+.nc-stre-down::before {
+ content: "\ea56";
+}
+
+.nc-stre-left::before {
+ content: "\ea57";
+}
+
+.nc-stre-right::before {
+ content: "\ea58";
+}
+
+.nc-stre-up::before {
+ content: "\ea59";
+}
+
+.nc-sun-fog-29::before {
+ content: "\ea5a";
+}
+
+.nc-support-17::before {
+ content: "\ea5b";
+}
+
+.nc-tablet-2::before {
+ content: "\ea5c";
+}
+
+.nc-tag-content::before {
+ content: "\ea5d";
+}
+
+.nc-tap-01::before {
+ content: "\ea5e";
+}
+
+.nc-time-alarm::before {
+ content: "\ea5f";
+}
+
+.nc-tv-2::before {
+ content: "\ea60";
+}
+
+.nc-umbrella-13::before {
+ content: "\ea61";
+}
+
+.nc-vector::before {
+ content: "\ea62";
+}
+
+.nc-watch-time::before {
+ content: "\ea63";
+}
+
+.nc-zoom-split::before {
+ content: "\ea64";
+}
+
+
+/* all icon font classes list here */ \ No newline at end of file
diff --git a/src/dashboard/static/js/dashboard.js b/src/dashboard/static/js/dashboard.js
new file mode 100644
index 0000000..e171f59
--- /dev/null
+++ b/src/dashboard/static/js/dashboard.js
@@ -0,0 +1,76 @@
+// Setup dxGrid with data from Django
+function initInstallGrid(installs) {
+ $("#gridContainer").dxDataGrid({
+ dataSource: installs,
+ showColumnLines: false,
+ showRowLines: true,
+ rowAlternationEnabled: true,
+ columnAutoWidth: true,
+ showBorders: true,
+ searchPanel: {
+ visible: true,
+ width: 240,
+ placeholder: "Search..."
+ },
+ groupPanel: {
+ visible: true
+ },
+ paging: {
+ pageSize: 50
+ },
+ pager: {
+ showPageSizeSelector: true,
+ allowedPageSizes: [5, 10, 20],
+ showInfo: true
+ },
+ columns: [
+ {
+ dataField: "cluster__cluster_name",
+ dataType: "string",
+ caption: "Cluster Name"
+ },
+ {
+ dataField: "date_completed",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Completed"
+ },
+ {
+ dataField: "date_last_failed",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Last Failed"
+ },
+ {
+ dataField: "date_scheduled",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Scheduled"
+ },
+ {
+ dataField: "kirke_ticket_completed",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Kirke Ticket Completed"
+ },
+ {
+ dataField: "kirke_ticket_number",
+ dataType: "int",
+ caption: "Kirke Ticket Number",
+ cellTemplate: function (container, options) {
+ $('<a/>').addClass('dx-link')
+ .text(options.data.kirke_ticket_number)
+ .on('dxclick', function () {
+ window.open('https://kirke.verizon.com/changeRequest/' + options.data.kirke_ticket_number)
+ })
+ .appendTo(container);
+ }
+ },
+ {
+ dataField: "kirke_ticket_status",
+ dataType: "string",
+ caption: "Kirke Ticket Status"
+ }
+ ]
+ });
+}; \ No newline at end of file
diff --git a/src/dashboard/static/js/firmware_version.js b/src/dashboard/static/js/firmware_version.js
new file mode 100644
index 0000000..e050f57
--- /dev/null
+++ b/src/dashboard/static/js/firmware_version.js
@@ -0,0 +1,60 @@
+// Setup dxGrid with data from Django
+function FirmwareVersionGrid(firmware_versions) {
+ $("#FirmwareGridContainer").dxDataGrid({
+ dataSource: firmware_versions,
+ showColumnLines: false,
+ showRowLines: true,
+ rowAlternationEnabled: true,
+ columnAutoWidth: true,
+ showBorders: true,
+ searchPanel: {
+ visible: true,
+ width: 240,
+ placeholder: "Search..."
+ },
+ groupPanel: {
+ visible: true
+ },
+ paging: {
+ pageSize: 50
+ },
+ pager: {
+ showPageSizeSelector: true,
+ allowedPageSizes: [5, 10, 20],
+ showInfo: true
+ },
+ columns: [{
+ dataField: "id",
+ dataType: "integer",
+ caption: "Server ID"
+ },
+ {
+ dataField: "oam_hostname",
+ dataType: "string",
+ caption: "OAM Hostname"
+ },
+ {
+ dataField: "ilo_host_address",
+ dataType: "string",
+ caption: "ilo Host Address"
+ },
+ {
+ dataField: "intel_nic_firmware_version",
+ dataType: "string",
+ caption: "Firmware Version"
+ },
+ {
+ dataField: "date_completed",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Completed"
+ },
+ {
+ dataField: "date_last_failed",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Last failed on"
+ }
+ ]
+ });
+}; \ No newline at end of file
diff --git a/src/dashboard/static/js/orchestration_workflow.js b/src/dashboard/static/js/orchestration_workflow.js
new file mode 100644
index 0000000..b432cb9
--- /dev/null
+++ b/src/dashboard/static/js/orchestration_workflow.js
@@ -0,0 +1,55 @@
+// Setup dxGrid with data from Django
+function OrchestrationGrid(workflows) {
+ console.log(workflows)
+ $("#WorkflowGridContainer").dxDataGrid({
+ dataSource: workflows,
+ showColumnLines: false,
+ showRowLines: true,
+ rowAlternationEnabled: true,
+ columnAutoWidth: true,
+ showBorders: true,
+ searchPanel: {
+ visible: true,
+ width: 240,
+ placeholder: "Search..."
+ },
+ groupPanel: {
+ visible: true
+ },
+ paging: {
+ pageSize: 50
+ },
+ pager: {
+ showPageSizeSelector: true,
+ allowedPageSizes: [5, 10, 20],
+ showInfo: true
+ },
+ columns: [{
+ dataField: "pk",
+ dataType: "integer",
+ caption: "Cluster"
+ },
+ {
+ dataField: "fields.online",
+ dataType: "string",
+ caption: "Online"
+ },
+ {
+ dataField: "fields.bmc",
+ dataType: "boolean",
+ caption: "BMC"
+ },
+ {
+ dataField: "fields.wr_installed",
+ dataType: "boolean",
+ caption: "WR installed"
+ },
+ {
+ dataField: "fields.created_at",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Updated"
+ }
+ ]
+ });
+}; \ No newline at end of file
diff --git a/src/dashboard/static/js/playbook_status.js b/src/dashboard/static/js/playbook_status.js
new file mode 100644
index 0000000..9f4d051
--- /dev/null
+++ b/src/dashboard/static/js/playbook_status.js
@@ -0,0 +1,75 @@
+// Setup dxGrid with data from Django
+function PlaybookStatusGrid(query_result) {
+ $("#PlaybookGridContainer").dxDataGrid({
+ dataSource: query_result,
+ showColumnLines: false,
+ showRowLines: true,
+ rowAlternationEnabled: true,
+ columnAutoWidth: true,
+ showBorders: true,
+ searchPanel: {
+ visible: true,
+ width: 240,
+ placeholder: "Search..."
+ },
+ groupPanel: {
+ visible: true
+ },
+ paging: {
+ pageSize: 50
+ },
+ pager: {
+ showPageSizeSelector: true,
+ allowedPageSizes: [5, 10, 20],
+ showInfo: true
+ },
+ columns: [{
+ dataField: "fields.fuze_spm_site_name",
+ dataType: "string",
+ caption: "Fuze Site Name"
+ },
+ {
+ dataField: "fields.fuze_spm_site_id",
+ dataType: "datetime",
+ caption: "Fuze Site Id"
+ },
+ {
+ dataField: "fields.cluster_name",
+ dataType: "datetime",
+ caption: "Cluster Name",
+ cellTemplate: function(container, options) {
+ $('<a/>').addClass('dx-link')
+ .text(options.data.fields.cluster_name)
+ .on('dxclick', function() {
+ window.open('/dashboard/cluster/' + options.data.fields.cluster_name)
+ })
+ .appendTo(container);
+ }
+ },
+ {
+ dataField: "fields.playbook_name",
+ dataType: "datetime",
+ caption: "Playbook",
+ cellTemplate: function(container, options) {
+ $('<a/>').addClass('dx-link')
+ .text(options.data.fields.playbook_name)
+ .on('dxclick', function() {
+ window.open('/dashboard/playbook/' + options.data.fields.playbook_name)
+ })
+ .appendTo(container);
+ }
+ },
+ {
+ dataField: "fields.status",
+ dataType: "datetime",
+ caption: "Status"
+ },
+ {
+ dataField: "fields.created_at",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Update at"
+ }
+ ]
+ });
+}; \ No newline at end of file
diff --git a/src/dashboard/static/js/utils.js b/src/dashboard/static/js/utils.js
new file mode 100644
index 0000000..a5ef815
--- /dev/null
+++ b/src/dashboard/static/js/utils.js
@@ -0,0 +1,14 @@
+function set_navbar_focus(selected_tab){
+ console.log("in set_navbar_focus")
+ console.log(selected_tab)
+ var nav = document.getElementById("nav");
+ var nav_tabs = nav.childNodes;
+
+ for (var i = 0; i < nav_tabs.length; i++) {
+ if (nav_tabs[i].id == selected_tab) {
+ nav_tabs[i].classList.add("active");
+ } else {
+ nav_tabs[i].classList.remove("active");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/dashboard/static/js/workflow_status.js b/src/dashboard/static/js/workflow_status.js
new file mode 100644
index 0000000..b2224ee
--- /dev/null
+++ b/src/dashboard/static/js/workflow_status.js
@@ -0,0 +1,55 @@
+// Setup dxGrid with data from Django
+function WorkflowGrid(workflows) {
+ console.log(workflows)
+ $("#WorkflowGridContainer").dxDataGrid({
+ dataSource: workflows,
+ showColumnLines: false,
+ showRowLines: true,
+ rowAlternationEnabled: true,
+ columnAutoWidth: true,
+ showBorders: true,
+ searchPanel: {
+ visible: true,
+ width: 240,
+ placeholder: "Search..."
+ },
+ groupPanel: {
+ visible: true
+ },
+ paging: {
+ pageSize: 50
+ },
+ pager: {
+ showPageSizeSelector: true,
+ allowedPageSizes: [5, 10, 20],
+ showInfo: true
+ },
+ columns: [{
+ dataField: "pk",
+ dataType: "integer",
+ caption: "Cluster"
+ },
+ {
+ dataField: "fields.online",
+ dataType: "string",
+ caption: "Online"
+ },
+ {
+ dataField: "fields.bmc",
+ dataType: "boolean",
+ caption: "BMC"
+ },
+ {
+ dataField: "fields.wr_installed",
+ dataType: "boolean",
+ caption: "WR installed"
+ },
+ {
+ dataField: "fields.created_at",
+ dataType: "datetime",
+ format: "M/d/yyyy, HH:mm",
+ caption: "Date Updated"
+ }
+ ]
+ });
+}; \ No newline at end of file
diff --git a/src/dashboard/templates/by_cluster_name.html b/src/dashboard/templates/by_cluster_name.html
new file mode 100644
index 0000000..6715052
--- /dev/null
+++ b/src/dashboard/templates/by_cluster_name.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+{% load static %}
+<html lang="en">
+
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+ <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
+
+ <!-- Core JS Files -->
+ <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
+ {% block javascript %}
+ <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ==" crossorigin="anonymous"></script>
+ {% endblock %}
+
+ <!-- Fonts and icons -->
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet" />
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" />
+
+ <!-- CSS Files -->
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+ <link rel="stylesheet" type="text/css" href="../../static/css/styles.css?v=2.0.0 " />
+
+ {% block stylesheet %}
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet"
+ integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous"/>
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet"
+ integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous"/>
+ {% endblock %}
+
+ <title>Far-Edge Ops Middleware</title>
+</head>
+
+<body>
+ <div class="wrapper">
+ {% block content %}
+ <div class="row">
+ {% include 'cards/clusters_by_name.html' %}
+ </div>
+ {% endblock %}
+ </div>
+</body>
+
+</html>
+
+
diff --git a/src/dashboard/templates/by_playbook_name.html b/src/dashboard/templates/by_playbook_name.html
new file mode 100644
index 0000000..d8da7c6
--- /dev/null
+++ b/src/dashboard/templates/by_playbook_name.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+{% load static %}
+<html lang="en">
+
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+ <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
+
+ <!-- Core JS Files -->
+ <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
+ {% block javascript %}
+ <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ==" crossorigin="anonymous"></script>
+ {% endblock %}
+
+ <!-- Fonts and icons -->
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet" />
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" />
+
+ <!-- CSS Files -->
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+ <link rel="stylesheet" type="text/css" href="../../static/css/styles.css?v=2.0.0 " />
+
+ {% block stylesheet %}
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet"
+ integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous"/>
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet"
+ integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous"/>
+ {% endblock %}
+
+ <title>Far-Edge Ops Middleware</title>
+</head>
+
+<body>
+ <div class="wrapper">
+ {% block content %}
+ <div class="row">
+ {% include 'cards/playbook_by_name.html' %}
+ </div>
+ {% endblock %}
+ </div>
+</body>
+
+</html>
+
+
diff --git a/src/dashboard/templates/cards/clusters.html b/src/dashboard/templates/cards/clusters.html
new file mode 100644
index 0000000..ea23b71
--- /dev/null
+++ b/src/dashboard/templates/cards/clusters.html
@@ -0,0 +1,38 @@
+{% load static %}
+
+{% block clusters-libraries %}
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet" integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous" />
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet" integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous" /> {% endblock %} {% block javascript %}
+ <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ=="
+ crossorigin="anonymous"></script>
+ <script type="text/javascript" src="{% static " js/dashboard.js " %}"></script>
+ <script>
+ $(function() {
+ initInstallGrid({{installs|safe }})
+ })
+ </script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h4 class="card-title">Cluster List</h4>
+ <p class="card-category">Comprehensive list</p>
+ </div>
+ <div class="card-body ">
+ <div class="progress">
+ <div class="progress-bar" role="progressbar" style="width: 15%" aria-valuenow="15" aria-valuemin="0" aria-valuemax="100">Started</div>
+ <div class="progress-bar bg-success" role="progressbar" style="width: 40%" aria-valuenow="30" aria-valuemin="0" aria-valuemax="100">Done</div>
+ <div class="progress-bar bg-info" role="progressbar" style="width: 30%" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">Pending</div>
+ <div class="progress-bar bg-danger" role="progressbar" style="width: 51%" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">Failed</div>
+ </div>
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="gridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/src/dashboard/templates/cards/clusters_by_name.html b/src/dashboard/templates/cards/clusters_by_name.html
new file mode 100644
index 0000000..0350daf
--- /dev/null
+++ b/src/dashboard/templates/cards/clusters_by_name.html
@@ -0,0 +1,26 @@
+{% load static %}
+
+{% block javascript %}
+ <script type="text/javascript" src="{% static "js/playbook_status.js" %}"></script>
+ <script>
+ $(function() {
+ PlaybookStatusGrid({{ query_result|safe }})
+ })
+ </script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h5 class="card-title" style="color:#326690;">Cluster Name: {{ cluster_name }}</h5>
+ </div>
+ <div class="card-body ">
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="PlaybookGridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
diff --git a/src/dashboard/templates/cards/firmware_version.html b/src/dashboard/templates/cards/firmware_version.html
new file mode 100644
index 0000000..6ac5ddb
--- /dev/null
+++ b/src/dashboard/templates/cards/firmware_version.html
@@ -0,0 +1,32 @@
+{% load static %}
+
+{% block libraries %}
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet" integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous" />
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet" integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous" /> {% endblock %} {% block javascript %}
+ <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ=="
+ crossorigin="anonymous"></script>
+ <script type="text/javascript" src="{% static "js/firmware_version.js" %}"></script>
+ <script>
+ $(function() {
+ FirmwareVersionGrid({{ firmware_versions|safe }})
+ })
+ </script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h4 class="card-title">Firmware Versions</h4>
+ <p class="card-category">Latest firmware versions per server</p>
+ </div>
+ <div class="card-body ">
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="FirmwareGridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/src/dashboard/templates/cards/map.html b/src/dashboard/templates/cards/map.html
new file mode 100644
index 0000000..3f12b59
--- /dev/null
+++ b/src/dashboard/templates/cards/map.html
@@ -0,0 +1,16 @@
+<div class="col-md-6">
+ <div class="card ">
+ <div class="card-header ">
+ <h4 class="card-title">Deployment list</h4>
+ </div>
+ <div class="card-body ">
+ <ul class="list-group list-group-flush">
+ <li class="list-group-item">Cluster 1</li>
+ <li class="list-group-item">Cluster 2</li>
+ <li class="list-group-item">Cluster 3</li>
+ <li class="list-group-item">Cluster 4</li>
+ <li class="list-group-item">Cluster 5</li>
+ </ul>
+ </div>
+ </div>
+</div>
diff --git a/src/dashboard/templates/cards/playbook.html b/src/dashboard/templates/cards/playbook.html
new file mode 100644
index 0000000..c888937
--- /dev/null
+++ b/src/dashboard/templates/cards/playbook.html
@@ -0,0 +1,33 @@
+{% load static %}
+
+{% block playbook-libraries %}
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet" integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous" />
+ <link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet" integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous" /> {% endblock %} {% block javascript %}
+ <script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ=="
+ crossorigin="anonymous"></script>
+ <script type="text/javascript" src="{% static "js/playbook_status.js" %}"></script>
+ <script>
+ $(function() {
+ PlaybookStatusGrid({{ clusters|safe }})
+ })
+ </script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h4 class="card-title">Deployment Workflow</h4>
+ <p class="card-category">Latest status update received for each cluster</p>
+ </div>
+ <div class="card-body ">
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="PlaybookGridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
diff --git a/src/dashboard/templates/cards/playbook_by_name.html b/src/dashboard/templates/cards/playbook_by_name.html
new file mode 100644
index 0000000..0bc4b20
--- /dev/null
+++ b/src/dashboard/templates/cards/playbook_by_name.html
@@ -0,0 +1,26 @@
+{% load static %}
+
+{% block javascript %}
+ <script type="text/javascript" src="{% static "js/playbook_status.js" %}"></script>
+ <script>
+ $(function() {
+ PlaybookStatusGrid({{ query_result|safe }})
+ })
+ </script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h5 class="card-title" style="color:#326690;">Playbook: {{ playbook_name }}</h5>
+ </div>
+ <div class="card-body ">
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="PlaybookGridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
diff --git a/src/dashboard/templates/cards/summary.html b/src/dashboard/templates/cards/summary.html
new file mode 100644
index 0000000..454b6fd
--- /dev/null
+++ b/src/dashboard/templates/cards/summary.html
@@ -0,0 +1,315 @@
+{% load static %}
+
+<div class="col-md-3">
+ <div class="card ">
+ <div class="card-body">
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Clusters</h6>
+ <h2>{{ summary.clusters }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-server" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M1.333 2.667C1.333 1.194 4.318 0 8 0s6.667 1.194 6.667 2.667V4C14.665 5.474 11.68 6.667 8 6.667 4.318 6.667 1.333 5.473 1.333 4V2.667zm0 3.667v3C1.333 10.805 4.318 12 8 12c3.68 0 6.665-1.193 6.667-2.665V6.334c-.43.32-.931.58-1.458.79C11.81 7.684 9.967 8 8 8c-1.967 0-3.81-.317-5.21-.876a6.508 6.508 0 0 1-1.457-.79zm13.334 5.334c-.43.319-.931.578-1.458.789-1.4.56-3.242.876-5.209.876-1.967 0-3.81-.316-5.21-.876a6.51 6.51 0 0 1-1.457-.79v1.666C1.333 14.806 4.318 16 8 16s6.667-1.194 6.667-2.667v-1.665z"/>
+ </svg>
+ </div>
+ </div>
+ <small class="text-small mt-10 d-block">Last updated at {{ summary.created_at }}</small>
+ </div>
+
+ </div>
+</div>
+<div class="col-md-3">
+ <div class="card ">
+ <div class="card-body">
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Completed Installs</h6>
+ <h2>{{ summary.completed }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check2-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M15.354 2.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L8 9.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
+ <path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 0 0 3 14.5h10a1.5 1.5 0 0 0 1.5-1.5V8a.5.5 0 0 0-1 0v5a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V3a.5.5 0 0 1 .5-.5h8a.5.5 0 0 0 0-1H3A1.5 1.5 0 0 0 1.5 3v10z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-success" role="progressbar" style="width: {{ completed_percent }}%;">
+ {{ completed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="col-md-3">
+ <div class="card ">
+ <div class="card-body">
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>In Progress</h6>
+ <h2>{{ summary.in_progress }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-hourglass-split" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0c0 .701.478 1.236 1.011 1.492A3.5 3.5 0 0 1 11.5 13s-.866-1.299-3-1.48V8.35z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-warning" role="progressbar" style="width: {{ in_progress_percent }}%;">
+ {{ in_progress_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="col-md-3">
+ <div class="card ">
+ <div class="card-body">
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Failed</h6>
+ <h2>{{ summary.failed }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1.0625em" height="1em" viewBox="0 0 17 16" class="bi bi-exclamation-triangle" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M7.938 2.016a.146.146 0 0 0-.054.057L1.027 13.74a.176.176 0 0 0-.002.183c.016.03.037.05.054.06.015.01.034.017.066.017h13.713a.12.12 0 0 0 .066-.017.163.163 0 0 0 .055-.06.176.176 0 0 0-.003-.183L8.12 2.073a.146.146 0 0 0-.054-.057A.13.13 0 0 0 8.002 2a.13.13 0 0 0-.064.016zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"/>
+ <path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Break down CaaS workflow -->
+
+<div class="col-md-2">
+ <div class="card">
+ <div class="card-body text-decoration-none">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Ingested</h6>
+ <h2>{{ summary.ingested }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-plus" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-2">
+ <div class="card ">
+ <div class="card-body">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}?filter=online"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Online</h6>
+ <h2>{{ summary.online }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-share" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.499 2.499 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5zm-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-2">
+ <div class="card ">
+ <div class="card-body">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}?filter=firmware_scheduled"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Firmware Scheduled</h6>
+ <h2>{{ summary.firmware_scheduled }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-checklist" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
+ <path fill-rule="evenodd" d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-2">
+ <div class="card ">
+ <div class="card-body">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}?filter=firmware_upgraded"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Firmware updated</h6>
+ <h2>{{ summary.firmware_upgraded }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check2-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M15.354 2.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L8 9.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
+ <path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 0 0 3 14.5h10a1.5 1.5 0 0 0 1.5-1.5V8a.5.5 0 0 0-1 0v5a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V3a.5.5 0 0 1 .5-.5h8a.5.5 0 0 0 0-1H3A1.5 1.5 0 0 0 1.5 3v10z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-2">
+ <div class="card ">
+ <div class="card-body">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}?filter=wr_scheduled"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>WindRiver Scheduled</h6>
+ <h2>{{ summary.wr_scheduled }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-checklist" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
+ <path fill-rule="evenodd" d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-2">
+ <div class="card ">
+ <div class="card-body">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}?filter=wr_installed"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>WindRiver Installed</h6>
+ <h2>{{ summary.wr_installed }}</h2>
+ </div>
+ <div class="icon">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check2-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M15.354 2.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L8 9.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
+ <path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 0 0 3 14.5h10a1.5 1.5 0 0 0 1.5-1.5V8a.5.5 0 0 0-1 0v5a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V3a.5.5 0 0 1 .5-.5h8a.5.5 0 0 0 0-1H3A1.5 1.5 0 0 0 1.5 3v10z"/>
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="progress progress-sm">
+ <div class="progress-bar bg-danger" role="progressbar" style="width: {{ failed_percent }}%;">
+ {{ failed_percent }}%
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Break down Orchestration workflow -->
+
+<div class="col-md-2">
+ <div class="card">
+ <div class="card-body text-decoration-none">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Namespaces Created</h6>
+ <h2>{{ orchestration.namespaces }}</h2>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-3">
+ <div class="card">
+ <div class="card-body text-decoration-none">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Service Accounts</h6>
+ Orchestration: {{ orchestration.orch_service_account }} <br /> Edge Engineering: {{ orchestration.edge_eng_service_account }} <br /> Samsung:{{ orchestration.samsung_service_account }} <br />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="col-md-3">
+ <div class="card">
+ <div class="card-body text-decoration-none">
+ <a class="card-block stretched-link " href="{% url 'dashboard' %}"></a>
+ <div class="d-flex justify-content-between align-items-center">
+ <div class="state">
+ <h6>Kubeconfig's Created</h6>
+ Orchestration: {{ orchestration.orch_kubeconfig }} <br /> Edge Engineering: {{ orchestration.edge_eng_kubeconfig }} <br /> Samsung: {{ orchestration.samsung_kubeconfig }}
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+
+
+{% block playbook-libraries %}
+<link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.common.css" rel="stylesheet" integrity="sha384-omn7qdmCOCi0LwoUGkpBh85uDEJ+5RKyoNkmGSa1G9Lmf7GdOoLV52Ajtgh+GRhu sha512-F0kqJ4z+Ki/fqxnlO3NvK3XI/JN74WbAtpC1US8gkwjDt43on9lEMpTwiNoKTvvEYfjzd5rI4udXr/6PJfN42w=="
+ crossorigin="anonymous" />
+<link href="https://cdn3.devexpress.com/jslib/20.1.6/css/dx.light.css" rel="stylesheet" integrity="sha384-R65dqZGbgZfpXHFmVOTHN1JO/CnqFI3B6EeBllGP37UxWuR24s2e17IPcLTEsQc7 sha512-QeIvMjb9zMmdUtgqveA8L74c6z1+QxJmmexDcs0FBagRfiHNdrPHmw5JlX8iWqftAK2gds/wVerJkyeWKBTpWA=="
+ crossorigin="anonymous" /> {% endblock %} {% block javascript %}
+<script type="text/javascript" src="https://cdn3.devexpress.com/jslib/20.1.6/js/dx.all.js" integrity="sha384-w44LtjCWJWHKxAXiYG97WL0a94M75mb3WwENxD/YFYYmnbQWMwS2CTj2yRKAQ0Da sha512-hCh3HwHjNw5eALy0w0p4z3DzbMuCj8ErcBMpLj+8RpSakFpCt/FL8arG2gnoYKjF6o/bslHXHqaDwO8TfYc9kQ=="
+ crossorigin="anonymous"></script>
+<script type="text/javascript" src="{% static " js/workflow_status.js " %}"></script>
+<script>
+ $(function() {
+ WorkflowGrid({
+ {
+ workflow | safe
+ }
+ })
+ })
+</script>
+{% endblock %}
+
+<div class="col-md-12">
+ <div class="card ">
+ <div class="card-header ">
+ <h4 class="card-title">Deployment Workflow</h4>
+ <p class="card-category">Latest status update received for each cluster</p>
+ </div>
+ <div class="card-body ">
+ <div class="col-md-12 col-xl-12" style="margin-top:20px; padding: 5px;">
+ <div class="demo-container">
+ <div id="WorkflowGridContainer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/src/dashboard/templates/dashboard.html b/src/dashboard/templates/dashboard.html
new file mode 100644
index 0000000..c7d7e39
--- /dev/null
+++ b/src/dashboard/templates/dashboard.html
@@ -0,0 +1,7 @@
+{% extends 'includes/main-panel.html' %}
+
+{% block content %}
+<div class="row">
+ {% include 'cards/summary.html' %}
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/src/dashboard/templates/deployment.html b/src/dashboard/templates/deployment.html
new file mode 100644
index 0000000..a63556d
--- /dev/null
+++ b/src/dashboard/templates/deployment.html
@@ -0,0 +1,7 @@
+{% extends 'includes/main-panel.html' %}
+
+{% block content %}
+<div class="row">
+ {% include 'cards/playbook.html' %}
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/src/dashboard/templates/hardware.html b/src/dashboard/templates/hardware.html
new file mode 100644
index 0000000..6d1f992
--- /dev/null
+++ b/src/dashboard/templates/hardware.html
@@ -0,0 +1,7 @@
+{% extends 'includes/main-panel.html' %}
+
+{% block content %}
+<div class="row">
+ {% include 'cards/firmware_version.html' %}
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/src/dashboard/templates/includes/base.html b/src/dashboard/templates/includes/base.html
new file mode 100644
index 0000000..751853b
--- /dev/null
+++ b/src/dashboard/templates/includes/base.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+{% load static %}
+<html lang="en">
+
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+ <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
+
+ <!-- Core JS Files -->
+ <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
+ {% block javascript %}{% endblock %}
+
+ <!-- Fonts and icons -->
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet" />
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" />
+
+ <!-- CSS Files -->
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+ <link rel="stylesheet" href="../../static/css/styles.css?v=2.0.0 " />
+ {% block stylesheet %}{% endblock %}
+
+ <title>Far-Edge Ops Middleware</title>
+</head>
+
+<body>
+ <div class="wrapper">
+ {% include 'includes/sidebar.html' %}
+ {% block main_panel %} {% endblock %}
+ </div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/src/dashboard/templates/includes/main-panel.html b/src/dashboard/templates/includes/main-panel.html
new file mode 100644
index 0000000..5e9aad7
--- /dev/null
+++ b/src/dashboard/templates/includes/main-panel.html
@@ -0,0 +1,17 @@
+{% extends 'includes/base.html' %}
+
+{% block main_panel %}
+<div class="main-panel">
+ <!-- Navbar -->
+ {% include 'includes/top_navbar.html' %}
+ <!-- End Navbar -->
+
+ <!-- Content Section -->
+ <div class="content">
+ <div class="container-fluid">
+ {% block content %} No Content Added Yet {% endblock %}
+ </div>
+ </div>
+ <!-- End Content Section -->
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/src/dashboard/templates/includes/sidebar.html b/src/dashboard/templates/includes/sidebar.html
new file mode 100644
index 0000000..5997f61
--- /dev/null
+++ b/src/dashboard/templates/includes/sidebar.html
@@ -0,0 +1,26 @@
+<div class="sidebar" data-color="black">
+ <div class="sidebar-wrapper">
+ <div class="logo">
+ <a class="simple-text">
+ VCP Far Edge Dashboard
+ </a>
+ </div>
+ <ul id="nav" class="nav">
+ <li id="dashboard" class="nav-item ">
+ <a class="nav-link" href="{% url 'dashboard' %}">
+ <p>Dashboard</p>
+ </a>
+ </li>
+ <li id="deployment" class="nav-item ">
+ <a class="nav-link" href="{% url 'deployment' %}">
+ <p>Deployment</p>
+ </a>
+ </li>
+ <li id="hardware" class="nav-item ">
+ <a class="nav-link" href="{% url 'hardware' %}">
+ <p>Hardware</p>
+ </a>
+ </li>
+ </ul>
+ </div>
+</div> \ No newline at end of file
diff --git a/src/dashboard/templates/includes/top_navbar.html b/src/dashboard/templates/includes/top_navbar.html
new file mode 100644
index 0000000..5765358
--- /dev/null
+++ b/src/dashboard/templates/includes/top_navbar.html
@@ -0,0 +1,13 @@
+<nav class="navbar navbar-expand-lg " color-on-scroll="500">
+ <div class="container-fluid">
+ <div class="collapse navbar-collapse justify-content-end" id="navigation">
+ <ul class="navbar-nav ml-auto">
+ <li class="nav-item">
+ <a class="nav-link" href="#pablo">
+ <span class="no-icon">Log out</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+</nav> \ No newline at end of file
diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py
new file mode 100644
index 0000000..d77419a
--- /dev/null
+++ b/src/dashboard/urls.py
@@ -0,0 +1,11 @@
+from django.urls import path
+from . import views
+
+urlpatterns = [
+ path("dashboard/", view=views.dashboard, name="dashboard"),
+ path("dashboard/deployment/", views.deployment, name="deployment"),
+ path("dashboard/hardware/", views.hardware, name="hardware"),
+ path("dashboard/playbook/<str:playbook_name>/", views.by_playbook_name, name="by_playbook_name"),
+ path("dashboard/cluster/<str:cluster_name>/", views.by_cluster_name, name="by_cluster_name")
+ ]
+
diff --git a/src/dashboard/views.py b/src/dashboard/views.py
new file mode 100644
index 0000000..ca74afc
--- /dev/null
+++ b/src/dashboard/views.py
@@ -0,0 +1,80 @@
+import json
+from django.shortcuts import render
+from automationstatus.models import AutomationStatus, Summary
+from automationstatus.models import DeploymentWorkflow, OrchestrationWorkflow
+from django.core import serializers
+from .services.caasqueryservice import CaaSQueryService
+
+def filter_by(filter):
+ workflow = DeploymentWorkflow.objects.select_related('cluster')
+
+ if (filter == None):
+ return workflow.all()
+ elif (filter == 'online'):
+ return workflow.filter(online=True).all()
+ elif (filter == 'firmware_scheduled'):
+ return workflow.filter(firmware_scheduled=True).filter(firmware_upgraded=False).all()
+ elif (filter == 'firmware_upgraded'):
+ return workflow.filter(firmware_upgraded = True)
+ elif (filter == 'wr_scheduled'):
+ return workflow.filter(wr_scheduled=True).filter(wr_installed=False).all()
+ elif (filter == 'wr_installed'):
+ return workflow.filter(wr_installed = True)
+
+ return workflow.all()
+
+
+def dashboard(request):
+ print("in dashboard with no filter")
+ filter = request.GET.get('filter', None)
+ query_result = Summary.objects.latest('created_at')
+ workflows = filter_by(filter)
+ orchestration = OrchestrationWorkflow.objects.latest('created_at')
+
+
+ context = {
+ "summary": query_result,
+ "completed_percent" : int((query_result.completed * 100)/query_result.clusters),
+ "in_progress_percent" : int((query_result.in_progress * 100)/query_result.clusters),
+ "failed_percent" : int((query_result.failed * 100)/query_result.clusters),
+ "workflow": serializers.serialize("json", workflows),
+ "orchestration": orchestration
+ }
+ return render(request, "dashboard.html", context)
+
+def deployment(request):
+ # Look up playbook status by distinch cluster names
+ query_result = AutomationStatus.objects.all().distinct('cluster_name').order_by('cluster_name', '-created_at')
+ clusters = serializers.serialize("json", query_result)
+
+ # Get full list of clusters
+ # caasqueryservice = CaaSQueryService()
+ context = {
+ "clusters": clusters
+ }
+ return render(request, "deployment.html", context)
+
+def hardware(request):
+ # Get firmware versions
+ caasqueryservice = CaaSQueryService()
+ context = {
+ "firmware_versions": caasqueryservice.get_firmware_version()
+ }
+ return render(request, "hardware.html", context)
+
+def by_playbook_name(request, playbook_name):
+ query_result = AutomationStatus.objects.filter(playbook_name=playbook_name).order_by('-created_at')
+ context = {
+ "playbook_name": playbook_name,
+ "query_result": serializers.serialize("json", query_result)
+ }
+ return render(request, "by_playbook_name.html", context)
+
+def by_cluster_name(request, cluster_name):
+ query_result = AutomationStatus.objects.filter(cluster_name=cluster_name).order_by('-created_at')
+ context = {
+ "cluster_name": cluster_name,
+ "query_result": serializers.serialize("json", query_result)
+ }
+ return render(request, "by_cluster_name.html", context)
+
diff --git a/src/far_edge_ops_api/.gitignore b/src/far_edge_ops_api/.gitignore
index fce19e4..55abd48 100644
--- a/src/far_edge_ops_api/.gitignore
+++ b/src/far_edge_ops_api/.gitignore
@@ -1 +1,3 @@
settings.py
+settings.py.prod
+settings.py.wclab
diff --git a/src/far_edge_ops_api/settings.py.example b/src/far_edge_ops_api/settings.py.example
index e64066c..82f3c49 100644
--- a/src/far_edge_ops_api/settings.py.example
+++ b/src/far_edge_ops_api/settings.py.example
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
+from pathlib import Path
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -31,22 +32,29 @@ ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin',
+ 'caas.apps.CaasConfig',
+ 'orchestration.apps.OrchestrationConfig',
+ 'dashboard.apps.DashboardConfig',
+ 'automationstatus.apps.AutomationStatusConfig',
+ 'rest_framework',
+ 'django_crontab',
+ # 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'django_db_views',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware'
]
ROOT_URLCONF = 'far_edge_ops_api.urls'
@@ -60,11 +68,21 @@ TEMPLATES = [
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
+ # 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages'
+ ]
+ }
+ }
+]
+
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [],
+ 'DEFAULT_PERMISSION_CLASSES': [],
+}
+
+CRONJOBS = [
+ ('20 * * * *', 'automationstatus.crons.summary', '>> /var/log/far-edge-ops-api/dashboard-cron.log'),
+ ('25 * * * *', 'automationstatus.crons.orchestration_summary', '>> /var/log/far-edge-ops-api/orchestration_summary-cron.log'),
]
WSGI_APPLICATION = 'far_edge_ops_api.wsgi.application'
@@ -75,10 +93,10 @@ WSGI_APPLICATION = 'far_edge_ops_api.wsgi.application'
DATABASES = {
'default': {
- 'ENGINE': 'django.db.backends.mysql',
+ 'ENGINE': 'django.db.backends.postgresql',
'NAME': 'faredge',
'HOST': 'localhost',
- 'PORT': '3306',
+ 'PORT': '5432',
'USER': 'faredge',
'PASSWORD': 'password'
}
@@ -122,3 +140,178 @@ USE_TZ = True
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
+STATIC_ROOT = "%s/static/" % BASE_DIR
+
+
+# caas section
+# ansible-queue services
+VAULT_PASSWORD_FILE = "/etc/far-edge-ops-api/.ansible_pass.far_edge"
+ANSIBLE = {
+ "icinga": {
+ "target": "build.hqplan.lab",
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/icinga.git",
+ "branch": "master",
+ "playbook": "icinga.yaml",
+ "zmq": "tcp://127.0.0.1:5555"
+ },
+ "bmc": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/bmc.git",
+ "branch": "master",
+ "playbook": "bmc.yaml",
+ "playbook_adminpassword": "adminpassword.yaml",
+ "zmq": "tcp://127.0.0.1:5556"
+ },
+ "nic": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/wr-installer.git",
+ "branch": "master",
+ "playbook": "firmware_upgrade.yaml",
+ "zmq": "tcp://127.0.0.1:5557"
+ },
+ "wr": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/wr-installer.git",
+ "branch": "master",
+ "playbook": "wr_remote.yaml",
+ "playbook_central": "wr_central.yaml",
+ "zmq": "tcp://127.0.0.1:5558"
+ },
+ "dns": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/dns.git",
+ "branch": "master",
+ "playbook": "dns.yaml",
+ "zmq": "tcp://127.0.0.1:5559"
+ },
+ "patch": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/wr-installer.git",
+ "branch": "master",
+ "playbook": "patch.yaml",
+ "zmq": "tcp://127.0.0.1:5560"
+ },
+ "icinga_poll": {
+ "git": "git@gitlab.verizon.com:vcp/webscale/far-edge/caas/icinga_poll.git",
+ "brach": "master",
+ "playbook": "icinga_poll.yaml",
+ "zmq": "tcp://127.0.0.1:5561"
+ }
+}
+
+MOCKCIQ = False
+ENABLE_BMC = True
+ZEROMQ_IPV6 = False
+GIT_SHALLOW = True
+SYSLOG_SERVER = "vcp-faredge-syslog.mon.vzwops.com"
+SYSLOG_SERVER_PORT = 5140
+WR_VERSION = "20.06"
+WR_MAX_CHILD_CLUSTERS = 200
+INTEL_NIC_FIRMWARE_VERSION = "1.2585.0"
+CAAS_MIDDLEWARE_ENDPOINT = "http://middleware.vcpfe.vzwops.com"
+CAAS_MIDDLEWARE_USERNAME = "middlewareuser"
+CAAS_MIDDLEWARE_PASSWORD = "middlewarepassword"
+ICINGA_API_URL = "https://icinga.vcpfe.vzwops.com:5665/v1/objects/services"
+ICINGA_API_USERNAME = "someuser"
+ICINGA_API_PASSWORD = "somepass"
+BMC_USERNAME = "k8sctl"
+DNS_DOMAIN = "vcpfe.vzwops.com"
+INFOBLOX_USERNAME = "someuser"
+INFOBLOX_PASSWORD = "somepass"
+FIRMWARE_ISO = "intel-firmware-upgrade-1.2585.0.iso"
+DNS_SERVERS = ["2001:4888:a00:f::103:0:1", "2001:4888:a00:f::103:0:2"]
+NTP_SERVERS = ["pool.ntp.org"]
+WR_BMC_USERNAME = "someuser"
+WR_BMC_PASSWORD = "somepass"
+WR_REGISTRY_SERVER = "vnf.vzwnet.com/far-edge-infra"
+WR_REGISTRY_USERNAME = "admin"
+WR_REGISTRY_PASSWORD = "somepass"
+WR_WEBDAV_SERVER = "https://vnf.vzwnet.com/far-edge-image-infra"
+#WR_WEBDAV_SERVER = "http://mirror.meter.vzwops.com/faredge/webdav/"
+WR_WEBDAV_USERNAME = "someuser"
+WR_WEBDAV_PASSWORD = "somepass"
+WR_DOCKER_HTTP_PROXY = None
+WR_DOCKER_HTTPS_PROXY = None
+WR_DOCKER_NO_PROXY = None
+WR_SSL_CA_CERT = """PEM block here"""
+
+
+# orchestration section
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'formatters': {
+ 'verbose': {
+ 'format': '{levelname} {asctime} {filename} {funcName} {lineno} {message}',
+ 'style': '{',
+ },
+ },
+ 'handlers': {
+ 'orchestration': {
+ 'level':'DEBUG',
+ 'class':'logging.handlers.RotatingFileHandler',
+ 'filename': '/var/log/far-edge-ops-api/orchestration.log',
+ 'maxBytes': 10000,
+ 'backupCount': 10,
+ 'formatter': 'verbose',
+ },
+ },
+ 'loggers': {
+ 'orchestration': {
+ 'handlers': ['orchestration'],
+ 'level': 'DEBUG',
+ },
+ 'caas': {
+ 'handlers': ['caas'],
+ 'level': 'DEBUG',
+ },
+ },
+}
+
+ORCH_TEMP_FILE_LOCATION = "/var/log/far-edge-ops-api/"
+SCP_TIMEOUT = 30
+SETTINGS = False
+
+MIDDLEWARE_ENDPOINT = "http://middleware.hqplan.lab"
+
+VMB = {
+ 'SERVICE_URL': 'pulsar+ssl://txslvmbda12v.nss.vzwnet.com:6651/',
+ 'CERTS_PATH': '/etc/ssl/certs',
+ 'TLS_CERT_FILE': 'faredge-vmb-i1wv.cert.pem',
+ 'TLS_KEY_FILE': 'faredge-vmb-i1wv.key-pk8.pem',
+ 'TLS_TRUST_CERTS_FILE_PATH': 'faredge-vmb-ca.cert.pem',
+ 'VMB_TOPICS': {
+ 'TOPIC_CLUSTER_STATUS': 'persistent://i1wv/vcpfe-cluster-status/VCPFarEdgeAutomation-VCPFE-Cluster-Status-On-Demand',
+ 'TOPIC_NAMESPACE_CREATION': 'persistent://i1wv/vcpfe-namespace-creation/VCPFarEdgeAutomation-VCPFE-Namespace-Creation-On-Demand',
+ 'TOPIC_KUBECONFIG_TOKEN': 'persistent://i1wv/vcpfe-kubeconfig-token/VCPFarEdgeAutomation-VCPFE-Kubeconfig-Token-On-Demand',
+ 'TOPIC_IMAGE_STATUS': 'persistent://i1wv/vcpfe-image-status/VCPFarEdgeAutomation-VCPFE-Image-Status-On-Demand',
+ },
+}
+
+TIMEOUT = {
+ 'central': 600,
+ 'remote': 3600,
+}
+
+ARTIFACTORY_CREDS = {
+ 'username': '',
+ 'password': '',
+}
+
+HOST_CREDS = {
+ 'username': '',
+ 'password': '',
+}
+
+CENTRAL_DR_CREDS = {
+ 'username': '',
+ 'password': '',
+}
+
+LDAP_USER_CREDS = {
+ 'edge_eng': '',
+ 'orchestration': '',
+}
+
+FE_LDAP_SVC_CREDS = {
+ 'username': '',
+ 'password': '',
+}
+
+KUBECONFIG_SRC = 'SA' # Two options: 'SA'/'LDAP'
+
diff --git a/src/far_edge_ops_api/urls.py b/src/far_edge_ops_api/urls.py
index 298a4a7..13bd80d 100644
--- a/src/far_edge_ops_api/urls.py
+++ b/src/far_edge_ops_api/urls.py
@@ -13,10 +13,13 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
-from django.contrib import admin
+# from django.contrib import admin
from django.urls import include, path
urlpatterns = [
+ path('caas/', include('caas.urls')),
path('orchestration/', include('orchestration.urls')),
- path('admin/', admin.site.urls)
+ path('', include('dashboard.urls')),
+ path('automationstatus/', include('automationstatus.urls'))
+ # path('admin/', admin.site.urls)
]
diff --git a/src/far_edge_ops_api/wsgi.py b/src/far_edge_ops_api/wsgi.py
index 3b5d6a6..7f9b679 100644
--- a/src/far_edge_ops_api/wsgi.py
+++ b/src/far_edge_ops_api/wsgi.py
@@ -6,11 +6,27 @@ It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
-
import os
-
+import sys
from django.core.wsgi import get_wsgi_application
+from django.conf import settings
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(BASE_DIR)
+sys.path.append("%s../static/" % BASE_DIR)
+sys.path.append("%s/../../venv/lib/python3.6/site-packages" % BASE_DIR)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'far_edge_ops_api.settings')
-application = get_wsgi_application()
+if settings.DEBUG:
+ application = get_wsgi_application()
+else:
+ import time
+ import traceback
+ import signal
+ try:
+ application = get_wsgi_application()
+ except Exception:
+ if "mod_wsgi" in sys.modules:
+ traceback.print_exc()
+ os.kill(os.getpid(), signal.SIGINT)
+ time.sleep(2.5)
diff --git a/src/orchestration/cluster-setup-files/application-ldap.yaml b/src/orchestration/cluster-setup-files/application-ldap.yaml
new file mode 100644
index 0000000..2155f3a
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/application-ldap.yaml
@@ -0,0 +1,69 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: application-svc-edge-eng
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: edit
+subjects:
+ - kind: User
+ name: SVC-Edge-Eng
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{crd_cluster_role}}-for-app-team
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{crd_cluster_role}}-application
+subjects:
+ - kind: User
+ name: SVC-Edge-Eng
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{crd_cluster_role}}-application
+rules:
+ - apiGroups: ["{{crd_api_group}}"]
+ resources: ["{{crd_api_resources}}"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: crd-rb-app
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: crd-view
+subjects:
+ - kind: User
+ name: SVC-Edge-Eng
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: vdu-upgrade-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["", "apps"]
+ resources: ["configmaps", "services", "secrets", "persistentvolumeclaims", "deployments"]
+ verbs: ["get","list","watch","delete","update"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: vdu-upgrade-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: vdu-upgrade-role
+subjects:
+ - kind: User
+ name: SVC-Edge-Eng
+---
diff --git a/src/orchestration/cluster-setup-files/application-sa.yaml b/src/orchestration/cluster-setup-files/application-sa.yaml
new file mode 100644
index 0000000..9d87a80
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/application-sa.yaml
@@ -0,0 +1,79 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{application_sa}}-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: edit
+subjects:
+ - kind: ServiceAccount
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{crd_cluster_role}}-for-app-team
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ crd_cluster_role }}-application
+subjects:
+ - kind: ServiceAccount
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ crd_cluster_role }}-application
+rules:
+ - apiGroups: ["{{ crd_api_group }}"]
+ resources: ["{{ crd_api_resources }}"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: crd-rb-app
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: crd-view
+subjects:
+ - kind: ServiceAccount
+ name: {{ application_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: vdu-upgrade-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["", "apps"]
+ resources: ["configmaps", "services", "secrets", "persistentvolumeclaims", "deployments"]
+ verbs: ["get","list","watch","delete","update"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: vdu-upgrade-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: vdu-upgrade-role
+subjects:
+ - kind: ServiceAccount
+ name: {{ application_sa }}
+ namespace: default
+---
diff --git a/src/orchestration/cluster-setup-files/crd-role.yaml b/src/orchestration/cluster-setup-files/crd-role.yaml
new file mode 100644
index 0000000..b500e6d
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/crd-role.yaml
@@ -0,0 +1,18 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ crd_cluster_role }}
+rules:
+ - apiGroups: ["{{ crd_api_group }}"]
+ resources: ["{{ crd_api_resources }}"]
+ verbs: ["{{ crd_api_verbs }}"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: crd-view
+rules:
+ - apiGroups: ["apiextensions.k8s.io"]
+ resources: ["crds","customresourcedefinitions"]
+ verbs: ["*"]
+---
diff --git a/src/orchestration/cluster-setup-files/helm.yaml b/src/orchestration/cluster-setup-files/helm.yaml
new file mode 100644
index 0000000..f0ec72c
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/helm.yaml
@@ -0,0 +1,75 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ creationTimestamp: null
+ labels:
+ app: helm
+ name: tiller
+ name: tiller-deploy
+ namespace: {{ namespace }}
+spec:
+ replicas: 1
+ selector: {"matchLabels": {"app": "helm", "name": "tiller"}}
+ strategy: {}
+ template:
+ metadata:
+ creationTimestamp: null
+ labels:
+ app: helm
+ name: tiller
+ spec:
+ automountServiceAccountToken: true
+ containers:
+ - env:
+ - name: TILLER_NAMESPACE
+ value: {{ namespace }}
+ - name: TILLER_HISTORY_MAX
+ value: "0"
+ image: registry.local:9001/gcr.io/kubernetes-helm/tiller:v2.13.1
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ httpGet:
+ path: /liveness
+ port: 44135
+ initialDelaySeconds: 1
+ timeoutSeconds: 1
+ name: tiller
+ ports:
+ - containerPort: 44134
+ name: tiller
+ - containerPort: 44135
+ name: http
+ readinessProbe:
+ httpGet:
+ path: /readiness
+ port: 44135
+ initialDelaySeconds: 1
+ timeoutSeconds: 1
+ resources: {}
+ serviceAccountName: vdu-tiller
+status: {}
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ creationTimestamp: null
+ labels:
+ app: helm
+ name: tiller
+ name: tiller-deploy
+ namespace: {{ namespace }}
+spec:
+ ports:
+ - name: tiller
+ port: 44134
+ targetPort: tiller
+ selector:
+ app: helm
+ name: tiller
+ type: ClusterIP
+status:
+ loadBalancer: {}
+
+...
diff --git a/src/orchestration/cluster-setup-files/orchestration-ldap.yaml b/src/orchestration/cluster-setup-files/orchestration-ldap.yaml
new file mode 100644
index 0000000..ee03ba3
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/orchestration-ldap.yaml
@@ -0,0 +1,166 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: orchestration-user-crb
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: orchestration-user-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: edit
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{crd_cluster_role}}-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ crd_cluster_role }}
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: configmap-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: configmap-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: configmap-role
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: service-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: service-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: service-role
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: deployment-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["apps"]
+ resources: ["deployments"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: deployment-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: deployment-role
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: secret-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: secret-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: secret-role
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: pvc-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["persistentvolumeclaims"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: pvc-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: pvc
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: crd-rb-orch
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: crd-view
+subjects:
+ - kind: User
+ name: SVC-FE-Atlas
+---
+
diff --git a/src/orchestration/cluster-setup-files/orchestration-sa.yaml b/src/orchestration/cluster-setup-files/orchestration-sa.yaml
new file mode 100644
index 0000000..5531431
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/orchestration-sa.yaml
@@ -0,0 +1,181 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{orchestration_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{orchestration_sa}}-crb
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: {{orchestration_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{orchestration_sa}}-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: edit
+subjects:
+ - kind: ServiceAccount
+ name: {{orchestration_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{crd_cluster_role}}-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ crd_cluster_role }}
+subjects:
+ - kind: ServiceAccount
+ name: {{orchestration_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: configmap-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: configmap-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: configmap-role
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: service-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: service-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: service-role
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: deployment-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["apps"]
+ resources: ["deployments"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: deployment-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: deployment-role
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: secret-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: secret-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: secret-role
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: pvc-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: [""]
+ resources: ["persistentvolumeclaims"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: pvc-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: pvc
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: crd-rb-orch
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: crd-view
+subjects:
+ - kind: ServiceAccount
+ name: {{ orchestration_sa }}
+ namespace: default
+---
+
diff --git a/src/orchestration/cluster-setup-files/rbd.yaml b/src/orchestration/cluster-setup-files/rbd.yaml
new file mode 100644
index 0000000..45d497b
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/rbd.yaml
@@ -0,0 +1,9 @@
+classes:
+- additionalNamespaces: [default, kube-public, {{ namespace }} ]
+ chunk_size: 64
+ crush_rule_name: storage_tier_ruleset
+ name: general
+ pool_name: kube-rbdkube-system
+ replication: 1
+ userId: ceph-pool-kube-rbd
+ userSecretName: ceph-pool-kube-rbd
diff --git a/src/orchestration/cluster-setup-files/samsung-sa.yaml b/src/orchestration/cluster-setup-files/samsung-sa.yaml
new file mode 100644
index 0000000..2525e8b
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/samsung-sa.yaml
@@ -0,0 +1,55 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{application_sa}}-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: edit
+subjects:
+ - kind: ServiceAccount
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{crd_cluster_role}}-for-app-team
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ crd_cluster_role }}-application
+subjects:
+ - kind: ServiceAccount
+ name: {{application_sa}}
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ crd_cluster_role }}-application
+rules:
+ - apiGroups: ["{{ crd_api_group }}"]
+ resources: ["{{ crd_api_resources }}"]
+ verbs: ["get", "list", "watch"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: crd-rb-app
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: crd-view
+subjects:
+ - kind: ServiceAccount
+ name: {{ application_sa }}
+ namespace: default
+---
diff --git a/src/orchestration/cluster-setup-files/tiller-sa.yaml b/src/orchestration/cluster-setup-files/tiller-sa.yaml
new file mode 100644
index 0000000..62cc860
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/tiller-sa.yaml
@@ -0,0 +1,42 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: vdu-tiller
+ namespace: {{ namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: vdu-tiller-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["*"]
+ resources: ["*"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: vdu-tiller-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: vdu-tiller-role
+subjects:
+ - kind: ServiceAccount
+ name: vdu-tiller
+ namespace: {{ namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: vdu-tiller-cluster-crd-crb
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{crd_cluster_role}}
+subjects:
+ - kind: ServiceAccount
+ name: vdu-tiller
+ namespace: {{ namespace }}
diff --git a/src/orchestration/cluster-setup-files/vdu_tiller_sa.yaml b/src/orchestration/cluster-setup-files/vdu_tiller_sa.yaml
new file mode 100644
index 0000000..3644856
--- /dev/null
+++ b/src/orchestration/cluster-setup-files/vdu_tiller_sa.yaml
@@ -0,0 +1,29 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: vdu-tiller
+ namespace: {{ namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: vdu-tiller-role
+ namespace: {{ namespace }}
+rules:
+ - apiGroups: ["*"]
+ resources: ["*"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: vdu-tiller-rb
+ namespace: {{ namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: vdu-tiller-role
+subjects:
+ - kind: ServiceAccount
+ name: vdu-tiller
+ namespace: {{ namespace }}
diff --git a/src/orchestration/data.json b/src/orchestration/data.json
new file mode 100644
index 0000000..927707c
--- /dev/null
+++ b/src/orchestration/data.json
@@ -0,0 +1,4 @@
+{
+ "images": ["i1", "i2", "i5"],
+ "remoteRegions": ["r1", "r2", "r3"]
+} \ No newline at end of file
diff --git a/src/orchestration/db_updater.py b/src/orchestration/db_updater.py
new file mode 100644
index 0000000..6837dd3
--- /dev/null
+++ b/src/orchestration/db_updater.py
@@ -0,0 +1,83 @@
+import django
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+from django.db import transaction
+import logging
+from logging.handlers import QueueHandler
+
+try:
+ django.setup()
+ from .models import ImageSync, CentralToRemoteMap, RemoteRegionSetup
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class DBUpdater():
+
+ def __init__(self, loggerQueue, dbQueue, coordinateQueue):
+ self.loggerQueue = loggerQueue
+ self.dbQueue = dbQueue
+ self.coordinateQueue = coordinateQueue
+
+ def run(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.logger.info("DBUpdater started...")
+ while True:
+ #self.logger.info("--------------------------")
+ item = self.dbQueue.get()
+ msg_type = item['type']
+ self.logger.info("Message type:" + msg_type)
+ if msg_type == 'kubeconfig':
+ self._update_kubeconfig(item)
+ if msg_type == 'image':
+ self._update_image_status(item)
+
+ def _update_image_status(self, item):
+ self.logger.info("Inside _update_image_status")
+ image = item['image']
+ region = item['region']
+ message = item['message']
+ status = item['status']
+ transaction_id = item['transaction_id']
+ upload_end_time = item['upload_end_time']
+
+ #ImageSync.objects.filter(remote_region_name=region,
+ # transaction_id=transaction_id,
+ # docker_image=image).update(upload_message=message,
+ # upload_status=status,
+ # upload_end_time=upload_end_time)
+
+ ImageSync.objects.filter(
+ transaction_id=transaction_id,
+ docker_image=image).update(upload_message=message,
+ upload_status=status,
+ upload_end_time=upload_end_time)
+
+ done = item['done']
+ if done:
+ self.coordinateQueue.put("Done")
+ return
+
+ def _update_kubeconfig(self, item):
+ transaction_id = item['transaction_id']
+ message = item['message']
+ status = item['status']
+ saName = item['saName']
+ namespace = item['namespace']
+ region = item['region']
+ kubeconfig = item['kubeconfig']
+ RemoteRegionSetup.objects.filter(transaction_id=transaction_id).update(message=message,
+ status=status,
+ serviceaccount=saName,
+ kubernetes_namespace=namespace,
+ remote_region_name=region,
+ kubeconfig=kubeconfig)
+ done = item['done']
+ if done:
+ self.coordinateQueue.put("Done")
+ return
+
diff --git a/src/orchestration/imagesynchandler.py b/src/orchestration/imagesynchandler.py
new file mode 100644
index 0000000..cddf1ac
--- /dev/null
+++ b/src/orchestration/imagesynchandler.py
@@ -0,0 +1,622 @@
+import django
+import threading
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+
+from multiprocessing import Process, Pool, Queue
+import pexpect
+import traceback
+import logging
+import multiprocessing
+from logging.handlers import QueueHandler
+import re
+import sys
+import time
+import datetime
+from .utils import *
+
+import os
+import signal, psutil
+
+from .db_updater import DBUpdater
+from .vmb_messages import ImageStatus, ImagesStatusMessage
+from .remoteregionhandler import RemoteRegionWorker
+
+try:
+ django.setup()
+ from .models import ImageSync, CentralToRemoteMap, RemoteRegionSetup
+ from caas.models import Cluster
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class ImageSyncWorker(RemoteRegionWorker):
+
+ def __init__(self, loggerQueue, requestQueue, dbQueue, vmbQueue, dbCoordinationQueue, vmbCoordinationQueue, doneQueue):
+ self.loggerQueue = loggerQueue
+ self.requestQueue = requestQueue
+ self.dbQueue = dbQueue
+ self.vmbQueue = vmbQueue
+ self.doneQueue = doneQueue
+ self.dbCoordinationQueue = dbCoordinationQueue
+ self.vmbCoordinationQueue = vmbCoordinationQueue
+ #self.dbQueue = Queue()
+ #self.db_updater = DBUpdater(loggerQueue, self.dbQueue)
+ #self.db_updater_p = Process(target=self.db_updater.run)
+ #self.db_updater_p.start()
+
+ # https://stackoverflow.com/questions/3332043/obtaining-pid-of-child-process
+ # Current not used; Leaving it here if needed in the future
+ def _kill_child_processes(self, parent_pid, sig=signal.SIGTERM):
+ try:
+ parent = psutil.Process(parent_pid)
+ except psutil.NoSuchProcess:
+ return
+ children = parent.children(recursive=True)
+ for process in children:
+ #if not process.is_alive():
+ process.send_signal(sig)
+
+ def _kill_child_process(self, pid):
+ os.kill(pid, signal.SIGKILL)
+
+ def run(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.logger.info("ImageSyncWorker started...")
+
+ while True:
+ try:
+ item = self.requestQueue.get(block=True)
+ if item:
+ self.logger.info(item)
+ self._handle_request(item)
+ except:
+ pass
+ time.sleep(1)
+
+ def _handle_request(self, request):
+ command = request['command']
+ if command == 'upload':
+ self._handle_request_image_upload(request)
+ if command == 'delete':
+ self._handle_request_image_delete(request)
+ if command == 'cancel':
+ self._handle_request_image_upload_cancel(request)
+
+ def _handle_request_image_delete(self, request):
+ pass
+
+ def _handle_request_image_upload_cancel(self, request):
+ pass
+
+ def _setup_logger(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.INFO)
+
+ def get_image_tags(self, remote_region, image_name, logger):
+ remote_region_oam_ip = self._get_remote_region_oam_ip(remote_region, "-1", logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ env_string = self._read_remote_openrc(remote_region_oam_ip, logger)
+ basecmd = env_string
+
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system registry-image-tags " + image_name
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ tags = []
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger)
+ #logger.info("Returned output lines:")
+ #logger.info(output_lines)
+ print(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ #logger.info("Line:" + line)
+ print("Line:" + line)
+ if 'Connection to' not in line and 'Authorization failed' not in line:
+ parts = line.split(" ")
+ if len(parts) > 1:
+ if parts[1] != 'Image':
+ print("Line:" + line)
+ tags.append(parts[1])
+ if 'Authorization failed' in line:
+ tags.append(line)
+ logger.info("Tags:" + str(tags))
+ print("Tags:" + str(tags))
+ return tags
+
+ def delete_image_tag(self, remote_region, image_name, image_tag, logger):
+ remote_region_oam_ip = self._get_remote_region_oam_ip(remote_region, "-1", logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ env_string = self._read_remote_openrc(remote_region_oam_ip, logger)
+ basecmd = env_string
+
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system registry-image-delete " + image_name + ":" + image_tag
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ tags = []
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger)
+
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system registry-garbage-collect "
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ tags = []
+ output_lines1 = self._run_command_get_all_lines(cmds, host_password, logger)
+ logger.info("Output lines1:")
+ logger.info(output_lines1)
+
+ lines_to_return = []
+ for line in output_lines.split('\n'):
+ if 'Connection to' not in line:
+ lines_to_return.append(line)
+
+ for line in output_lines1.split('\n'):
+ if 'Connection to' not in line:
+ lines_to_return.append(line)
+
+ logger.info("Output lines:")
+ logger.info(lines_to_return)
+ return lines_to_return
+
+ def _handle_request_image_upload(self, request):
+ transaction_id = request['transaction_id']
+ self.logger.info("Transction ID:" + str(transaction_id))
+ imageList = request['images']
+ remoteRegions = request['remoteRegions']
+ centralRegions = []
+ for region in remoteRegions:
+ centralRegionList = self._get_central_region_oam_ip(region, transaction_id)
+ for centralRegion in centralRegionList:
+ if centralRegion not in centralRegions:
+ centralRegions.append(centralRegion)
+ remoteregion_oam_ip = self._get_remote_region_oam_ip(region, transaction_id, self.logger)
+ self.logger.info("Remote region Name:" + region + " Remote region OAM IP:" + remoteregion_oam_ip)
+ self.logger.info(str(transaction_id) + " Downloading images to Central regions.." + ','.join(centralRegions))
+ successful_image_list, failed_image_list = self._handle_central_regions(centralRegions, remoteRegions, imageList, transaction_id, region_type='central')
+
+ if len(successful_image_list) > 0:
+ self.logger.info(str(transaction_id) + " Downloading images to Remote regions.." + ','.join(remoteRegions) + " " + ','.join(successful_image_list))
+ self._handle_regions(remoteRegions, successful_image_list, transaction_id, region_type='remote')
+ else:
+ self.logger.info(str(transaction_id) + " Could not download images to Central region..So not progressing to Remote region")
+ self._wait_and_done(imageList, remoteRegions, transaction_id)
+ return
+
+ def _handle_central_regions(self, regionList, remoteRegionList, imageList, transaction_id, region_type=''):
+ workers = []
+ imageRegionList = get_pairs(imageList, regionList)
+ self.logger.info(str(transaction_id) + " Handling central region")
+ self.logger.info(str(transaction_id) + " ImageList:" + ','.join(imageList))
+ self.logger.info(str(transaction_id) + " RegionList:" + ','.join(regionList))
+ self.logger.info(str(transaction_id) + " ImageRegionList:" + str(imageRegionList))
+ for region in regionList:
+ self.logger.info(str(transaction_id) + " Region:" + region)
+ successful_image_list, failed_image_list = self._handle_image_list(region, imageList, self.logger, region_type, transaction_id)
+ # We want to download from Artifactory in its own process so that the parent can terminate the process if needed;
+ # Without doing this in its own process, the parent will block foreever and we won't be able to process subsequent calls.
+ #worker = multiprocessing.Process(target=self._handle_image_list,
+ # args=(region, imageList, self.logger, region_type, transaction_id))
+ #workers.append(worker)
+ #worker.start()
+ #self.logger.info("Central controller handling process pid:" + str(worker.pid))
+
+ #https://stackoverflow.com/questions/26063877/python-multiprocessing-module-join-processes-with-timeout
+ #TIMEOUT = 300 # 5 minutes
+ #start = time.time()
+ #while time.time() - start <= TIMEOUT:
+ # if not any(p.is_alive() for p in workers):
+ # # All the processes are done, break now.
+ # break
+ # time.sleep(1) # Just to avoid hogging the CPU
+ #else:
+ # # We only enter this if we didn't 'break' above.
+ # print("Central downloader timed out, terminating the process...")
+ # for w in workers:
+ # self.logger.info("Terminating process handling central region download - pid:" + str(w.pid))
+ # self._kill_child_process(w.pid)
+ # return download_to_central_done
+
+ #for w in workers:
+ # w.join()
+
+ return successful_image_list, failed_image_list
+
+ def _handle_regions(self, regionList, imageList, transaction_id, region_type=''):
+ self.logger.info(str(transaction_id) + " Handling remote regions")
+ workers = []
+ imageRegionList = get_pairs(imageList, regionList)
+ self.logger.info(str(transaction_id) + " ImageList:" + ','.join(imageList))
+ self.logger.info(str(transaction_id) + " RegionList:" + ','.join(regionList))
+ self.logger.info(str(transaction_id) + " ImageRegionList:" + str(imageRegionList))
+ for item in imageRegionList:
+ self.logger.info(str(transaction_id) + " Item:" + str(item))
+ worker = multiprocessing.Process(target=self._worker_process,
+ args=(self.logger, self.loggerQueue, self._worker_configurer, item, region_type, transaction_id))
+ workers.append(worker)
+ worker.start()
+ time.sleep(3) # Stagger the requests
+
+ #https://stackoverflow.com/questions/26063877/python-multiprocessing-module-join-processes-with-timeout
+ TIMEOUT = 3600 # 1 hour
+ start = time.time()
+ while time.time() - start <= TIMEOUT:
+ if not any(p.is_alive() for p in workers):
+ # All the processes are done, break now.
+ break
+ time.sleep(1) # Just to avoid hogging the CPU
+ else:
+ # We only enter this if we didn't 'break' above.
+ print("timed out, killing all processes")
+ for w in workers:
+ self.logger.info(str(transaction_id) + " Terminating process handling remote region download - pid:" + str(w.pid))
+ self._kill_child_process(w.pid)
+ return
+
+ # No need to wait for processes to finish; otherwise we won't be able to pick-up any new incoming requests for an hour.
+ #for w in workers:
+ # w.join()
+
+ def _wait_and_done(self, imageList, remoteRegions, transaction_id):
+ # Ensure that VMB messages have been sent
+ self.logger.info(str(transaction_id) + " About to be done..waiting for cleanup")
+ num_of_images = len(imageList)
+ num_of_regions = len(remoteRegions)
+ self.logger.info(str(transaction_id) + " Number of images.." + str(num_of_images))
+ self.logger.info(str(transaction_id) + " Number of regions.." + str(num_of_regions))
+ count = 0
+ while count < num_of_images * num_of_regions:
+ vmb_coordination = self.vmbCoordinationQueue.get()
+ self.logger.info(str(transaction_id) + " VMB coordination message:" + vmb_coordination)
+ count = count + 1
+
+ # Ensure that all DB messages have been processed
+ db_coordination = self.dbCoordinationQueue.get()
+ self.logger.info(str(transaction_id) + " DB coordination message:" + db_coordination)
+
+ self.logger.info(str(transaction_id) + " Done")
+ time.sleep(10)
+ self.doneQueue.put("Done")
+
+ def _worker_process(self, lg, queue, configurer, item, region_type, transaction_id):
+ #configurer(queue)
+ name = multiprocessing.current_process().name
+ self._handle_image(item, lg, region_type, transaction_id)
+
+ # Currently not used; left here if we want to configure the loggers to add any extra logging (formats, etc.)
+ def _worker_configurer(self, queue):
+ h = logging.handlers.QueueHandler(queue)
+ root = logging.getLogger()
+ root.addHandler(h)
+ root.setLevel(logging.INFO)
+
+ def _handle_image(self, imageRegionPair, logger, region_type, transaction_id):
+ image = imageRegionPair["image"]
+ regionname = imageRegionPair["region"]
+ region = self._get_remote_region_oam_ip(regionname, transaction_id, logger)
+ name = multiprocessing.current_process().name
+ logger.info(str(transaction_id) + " Process:" + name + " " + image + " " + region)
+
+ logger.info(str(transaction_id) + " Checking if image exist")
+ imageexists = self._check_image_exists(image, region)
+ logger.info(str(transaction_id) + " Image exists status:" + str(imageexists))
+ if not imageexists:
+ message = 'Starting Image download from Central Controller'
+ status = 'STARTED'
+ self._update_status_async(image, region, message, status, transaction_id, logger)
+ success, err = self._download_image(image, region, logger, region_type, transaction_id)
+ if success:
+ really_there = False
+ dest_image_name_and_tag = self._get_dest_image_name(image)
+ parts = dest_image_name_and_tag.split(":")
+ dest_image_name = ''
+ if len(parts) > 0:
+ dest_image_name = parts[0]
+ logger.info("Destination image name:" + dest_image_name)
+ dest_image_tags = self.get_image_tags(regionname, dest_image_name, logger)
+ really_there = self._is_it_really_there(image, dest_image_tags, transaction_id, logger)
+ if really_there:
+ message = 'Image download to Remote Region complete.'
+ status = 'COMPLETED'
+ else:
+ message = 'Image download to Remote Region failed. Reason: Could not find image on remote region.'
+ status = 'FAILED'
+ else:
+ message = 'Image download to Remote Region failed. Reason:' + err
+ status = 'FAILED'
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time, done=True)
+
+ self._notify_vmb(region, regionname, image, status, message, image_upload_completion_time, transaction_id, logger)
+
+ def _is_it_really_there(self, image, dest_image_tags, transaction_id, logger):
+ logger.info(str(transaction_id) + " Comparing image tags..")
+ logger.info(str(transaction_id) + " Image:" + image)
+ logger.info(str(transaction_id) + " Destination Image tags:" + str(dest_image_tags))
+ present = False
+ for tag in dest_image_tags:
+ logger.info(str(transaction_id) + " Tag:" + tag)
+ logger.info(str(transaction_id) + " Image:" + image)
+ if tag in image:
+ present = True
+ break
+ return present
+
+ def _notify_vmb(self, region, regionname, image, status, message, image_upload_completion_time, transaction_id, logger):
+
+ # Notify VMB
+ image_status1 = ImageStatus(
+ cluster=regionname,
+ image=image,
+ action='UPLOAD',
+ status=status,
+ message=message,
+ created_at=str(image_upload_completion_time),
+ )
+
+ logger.info(str(transaction_id) + " Image status::" + str(image_status1))
+
+ site_name, site_location = self.get_fuze_spm_site_details(regionname, logger)
+ logger.info("Site name:" + site_name)
+ logger.info("Site location:" + site_location)
+ reportDescription = 'Image upload status : ' + site_name
+
+ images_status_list = []
+ images_status_message = ImagesStatusMessage(reportName='vcp_fe_imagestatus',
+ transactionId=transaction_id,
+ reportDescription=reportDescription,
+ reportGeneratedOn=str(datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")))
+ images_status_list.append(image_status1)
+ images_status_message.rowCount = 1
+ images_status_message.reportDataRows = images_status_list
+
+ logger.info(str(transaction_id) + " Image status message::" + str(images_status_message))
+
+ item = {}
+ item['message'] = 'ImageStatus'
+ item['payload'] = images_status_message
+ self.vmbQueue.put(item)
+
+ def _get_central_region_oam_ip(self, remoteregion, transaction_id):
+ remoteclusterObj = Cluster.objects.filter(cluster_name=remoteregion)
+ parent_cluster_id = remoteclusterObj[0].parent_cluster_id
+ self.logger.info(str(transaction_id) + " Parent cluster id.." + str(parent_cluster_id))
+ centralclusterObj = Cluster.objects.filter(id=parent_cluster_id)
+ self.logger.info(str(transaction_id) + " " + str(centralclusterObj))
+ central_region_list = []
+ for central_region in centralclusterObj:
+ central_region_list.append(central_region.oam_vip_address)
+ self.logger.info(str(transaction_id) + " Central Region List:" + ','.join(central_region_list))
+ return central_region_list
+
+ def _get_remote_region_oam_ip(self, regionname, transaction_id, logger):
+ remoteclusterObj = Cluster.objects.filter(cluster_name=regionname)
+ oam_ip = remoteclusterObj[0].oam_vip_address
+ logger.info(str(transaction_id) + " Remote region:" + regionname + " OAM IP:" + str(oam_ip))
+ return oam_ip
+
+ def _get_central_region_prev(self, remoteregion, transaction_id):
+ centralToRemoteMapObj = CentralToRemoteMap.objects.filter(remote_region_name=remoteregion)
+ self.logger.info(str(transaction_id) + " Inside _get_central_region...")
+ self.logger.info(str(transaction_id) + " " + str(centralToRemoteMapObj))
+ central_region_list = []
+ for central_region in centralToRemoteMapObj:
+ central_region_list.append(central_region.central_region_name)
+ #central_region_name = centralToRemoteMapObj[0].central_region_name
+ self.logger.info(str(transaction_id) + " Central Region List:" + ','.join(central_region_list))
+ return central_region_list
+
+ def _check_image_exists(self, image, region):
+ # TODO: Query database for a quick check; Query region for accurate check
+ return False
+
+ def _handle_image_list(self, region, imageList, logger, region_type, transaction_id):
+ logger.info(str(transaction_id) + " Handling image list")
+ name = multiprocessing.current_process().name
+ logger.info(str(transaction_id) + " Process:" + name + " " + region)
+
+ central_region_oam_ip = region
+ logger.info("1a")
+ central_region_name = self._get_central_region_name(central_region_oam_ip, logger)
+ logger.info("Central region name:" + central_region_name)
+
+ host_username = settings.HOST_CREDS['username']
+ host_password = settings.HOST_CREDS['password']
+ art_username = settings.ARTIFACTORY_CREDS['username']
+ art_password = settings.ARTIFACTORY_CREDS['password']
+ dr_username = settings.CENTRAL_DR_CREDS['username']
+ dr_password = settings.CENTRAL_DR_CREDS['password']
+
+ failed_image_list = []
+ successful_image_list = []
+ for image in imageList:
+ image_parts = image.split("/")
+ # Image: http://vnf-twb.vzwnet.com/docker-images/samsung_vdu_vdu_svr20aa5vvzwg06a3_r05_20.a.0-0101_6.0.0/vzw-adpf-rmp:svr20aa5vvzwg06a3_r05
+ artifactory_host = image_parts[0] # ' vsp.vici.verizon.com:7443'
+ logger.info("Artifactory host:" + artifactory_host)
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + region
+ login_to_artifactory = []
+ login_to_artifactory.append(ssh_prefix + ' sudo docker login --username ' + art_username + ' --password ' + art_password + ' ' + artifactory_host)
+ artifactory_login_status, cmd_err = self._run_commands(login_to_artifactory, host_password, logger, transaction_id)
+ if not artifactory_login_status:
+ message = 'Could not download Image from Artifactory. Reason:' + cmd_err
+ status = 'FAILED'
+ failed_image_list.append(image)
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+ logger.info(str(transaction_id) + " Notifying VMB")
+ self._notify_vmb(central_region_name, central_region_name, image, status, message, image_upload_completion_time, transaction_id, logger)
+ else:
+ message = 'Starting Image download from Artifactory.'
+ status = 'STARTED'
+ self._update_status_async(image, region, message, status, transaction_id, logger)
+
+ image_name = self._get_image_name(image)
+ download_from_artifactory = []
+ download_from_artifactory.append(ssh_prefix + ' sudo docker pull ' + image)
+ logger.info(str(transaction_id) + " Downloading " + image)
+ image_download_status, cmd_err1 = self._run_commands(download_from_artifactory, host_password, logger, transaction_id, cmd_timeout=300) # 5 minutes
+
+ if image_download_status:
+ message = 'Image download from Artifactory complete.'
+ status = 'COMPLETED'
+ successful_image_list.append(image)
+ else:
+ message = 'Could not download Image from Artifactory. Reason:' + cmd_err1
+ status = 'FAILED'
+ failed_image_list.append(image)
+ logger.info(str(transaction_id) + " Notifying VMB")
+ self._notify_vmb(central_region_name, central_region_name, image, status, message, image_upload_completion_time, transaction_id, logger)
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+
+ logger.info("**********")
+ logger.info("Successful Image list:" + str(len(successful_image_list)))
+ logger.info("----------")
+ logger.info("Failed Image list:" + str(len(failed_image_list)))
+ if len(successful_image_list) > 0:
+ login_to_dr_central = []
+ login_to_dr_central.append(ssh_prefix + ' sudo docker login --username ' + dr_username + ' --password ' + dr_password + ' registry.local:9001')
+ self._run_commands(login_to_dr_central, host_password, logger, transaction_id)
+
+ for image in successful_image_list:
+ message = 'Pushing Image to Central Controller Docker Registry.'
+ status = 'STARTED'
+ self._update_status_async(image, region, message, status, transaction_id, logger)
+
+ image_name = self._get_image_name(image)
+ dest_image = 'registry.local:9001/' + image_name
+ push_to_local_dr_central = []
+ push_to_local_dr_central.append(ssh_prefix + ' sudo docker tag ' + image + ' ' + dest_image)
+ push_to_local_dr_central.append(ssh_prefix + ' sudo docker push ' + dest_image)
+ logger.info(str(transaction_id) + " Pushing image to Central Docker Registry " + dest_image)
+ self._run_commands(push_to_local_dr_central, host_password, logger, transaction_id)
+
+ message = 'Pushing Image to Central Controller Docker Registry complete.'
+ status = 'COMPLETED'
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+
+ return successful_image_list, failed_image_list
+
+ # TODO: Try two times
+ # https://gitlab.verizon.com/vcp/webscale/far-edge/caas/orchestration/-/blob/master/docker_image_sync_steps.txt
+ def _download_image(self, source_image, region, logger, region_type, transaction_id):
+ image_name = self._get_image_name(source_image)
+ central_image = 'registry.central:9001/' + image_name
+ dest_image_name = self._get_dest_image_name(image_name)
+ dest_image = 'registry.local:9001/' + dest_image_name
+ logger.info("Destination Image name:" + dest_image)
+ host_username = settings.HOST_CREDS['username']
+ host_password = settings.HOST_CREDS['password']
+ dr_username = settings.CENTRAL_DR_CREDS['username']
+ dr_password = settings.CENTRAL_DR_CREDS['password']
+
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + region
+ download_from_central_dr = []
+ download_from_central_dr.append(ssh_prefix + ' sudo docker login --username ' + dr_username + ' --password ' + dr_password + ' registry.central:9001')
+ download_from_central_dr.append(ssh_prefix + ' sudo docker pull ' + central_image)
+ push_to_local_dr_remote = []
+ push_to_local_dr_remote.append(ssh_prefix + ' sudo docker tag ' + central_image + ' ' + dest_image)
+ push_to_local_dr_remote.append(ssh_prefix + ' sudo docker login --username ' + dr_username + ' --password ' + dr_password + ' registry.local:9001')
+ push_to_local_dr_remote.append(ssh_prefix + ' sudo docker push ' + dest_image)
+
+ image_download_complete = False
+ if region_type == 'remote':
+ logger.info(str(transaction_id) + " Downloading image from central region")
+ message = 'Downloading Image from Central Controller Docker Registry.'
+ status = 'STARTED'
+ self._update_status_async(central_image, region, message, status, transaction_id, logger)
+ image_download_complete, cmd_err = self._run_commands(download_from_central_dr, host_password, logger, transaction_id, cmd_timeout=3600) # 1 hour
+ if image_download_complete:
+ message = 'Downloading Image from Central Controller Docker Registry.'
+ status = 'COMPLETED'
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(central_image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+
+ message = 'Pushing Image to Remote region Docker Registry.'
+ status = 'STARTED'
+ self._update_status_async(central_image, region, message, status, transaction_id, logger)
+ logger.info(str(transaction_id) + " Push to local docker registry")
+ self._run_commands(push_to_local_dr_remote, host_password, logger, transaction_id)
+ message = 'Pushing Image to Remote region Docker Registry complete.'
+ status = 'COMPLETED'
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(central_image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+ else:
+ message = 'Downloading Image from Central Controller Docker Registry Failed. Reason:' + cmd_err
+ status = 'FAILED'
+ image_upload_completion_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ self._update_status_async(central_image, region, message, status, transaction_id, logger, upload_end_time=image_upload_completion_time)
+ return image_download_complete, cmd_err
+
+ def _run_commands(self, commands, host_password, logger, transaction_id, cmd_timeout=None):
+ #logger.info(str(transaction_id) + " " + str(commands))
+ image_download_complete = False
+ image_download_error = ''
+ for command in commands:
+ logger.info(command)
+ child = pexpect.spawn(command)
+ #logger.info("-- Child PID:" + str(child.pid))
+ child.timeout=cmd_timeout
+ try:
+ #child.expect(['password: '], timeout=cmd_timeout)
+ #child.expect('\r\n\r\n\w+')#, timeout=cmd_timeout)
+ #child.expect('\w+\r\n')
+ child.expect_exact('password: ', timeout=cmd_timeout)
+ child.sendline(host_password)
+ #child.expect(['Password: '], timeout=cmd_timeout)
+ #child.expect('\w+\r\n')
+ child.expect_exact('Password: ', timeout=cmd_timeout)
+ child.sendline(host_password)
+ #child.sendline("\r\n")
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ #logger.info(all_lines)
+ image_download_complete = True # Tentative
+ for line in all_lines.split("\n"):
+ logger.info(str(transaction_id) + " " + line)
+ if re.search('error', line, re.IGNORECASE) or 'tag does not exist' in line: #or 'Error response from daemon' in line:
+ image_download_complete = False
+ image_download_error = line
+ except:
+ logger.info(str(transaction_id) + " " + str(child))
+ return image_download_complete, image_download_error
+
+ def _get_image_name(self, image):
+ i = image.find("/")
+ image_name = image[i+1:]
+ return image_name
+
+ def _get_dest_image_name(self, image):
+ i = image.rfind("/")
+ image_name = image[i+1:]
+ return image_name
+
+ def _update_status_async(self, image, region, message, status, transaction_id, logger, upload_end_time=None, done=False):
+ item = {}
+ item['type'] = 'image'
+ item['image'] = image
+ item['region'] = region
+ item['message'] = message
+ item['status'] = status
+ item['transaction_id'] = transaction_id
+ item['upload_end_time'] = upload_end_time
+ item['done'] = done # done flag indicates when we are done using db_handler
+ self.dbQueue.put(item)
+
+ def _update_status_sync(self, image, region, message, status, transaction_id, logger):
+ ImageSync.objects.filter(
+ transaction_id=transaction_id,
+ docker_image=image).update(upload_message=message,
+ upload_status=status)
+ return
diff --git a/src/orchestration/kubeconfighandler.py b/src/orchestration/kubeconfighandler.py
new file mode 100644
index 0000000..d7318b2
--- /dev/null
+++ b/src/orchestration/kubeconfighandler.py
@@ -0,0 +1,926 @@
+import django
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+from django.utils import timezone
+
+from multiprocessing import Process, Pool, Queue
+import pexpect
+import logging
+import multiprocessing
+from logging.handlers import QueueHandler
+import sys
+import time
+import datetime
+from .utils import *
+
+import os
+import signal, psutil
+import json
+
+from jinja2 import Environment, FileSystemLoader
+
+from .vmb_messages import Kubeconfig, KubeconfigMessage
+from .remoteregionhandler import RemoteRegionWorker
+from .vendorhandler import Vendor
+
+try:
+ django.setup()
+ from .models import RemoteRegionSetup
+ from caas.models import Cluster
+ from caas.models import Namespace
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class KubeconfigGenerator(RemoteRegionWorker):
+
+ ORCHESTRATION_TEAM = 'orchestration-team'
+ APPLICATION_TEAM = 'application-team'
+ VENDOR_TEAM_SAMSUNG = 'samsung-team'
+ OPS_TEAM = 'ops-team'
+
+ def __init__(self, loggerQueue, requestQueue, dbQueue, vmbQueue, dbCoordinationQueue, vmbCoordinationQueue, doneQueue):
+ self.loggerQueue = loggerQueue
+ self.requestQueue = requestQueue
+ self.dbQueue = dbQueue
+ self.vmbQueue = vmbQueue
+ self.dbCoordinationQueue = dbCoordinationQueue
+ self.vmbCoordinationQueue = vmbCoordinationQueue
+ self.doneQueue = doneQueue
+
+ def run(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.logger.info("KubeconfigGenerator started...")
+
+ while True:
+ try:
+ item = self.requestQueue.get(block=True)
+ if item:
+ self.logger.info(item)
+ self.get_kubeconfig(item)
+ self.logger.info("Done generating kubeconfig")
+ self.logger.info("Performing vendor setup...")
+ vendor_provisioner = Vendor(self.logger, self.dbCoordinationQueue, self.vmbCoordinationQueue, self.doneQueue)
+ vendor_provisioner.perform_vendor_setup(item)
+ except:
+ pass
+ time.sleep(1)
+
+ def get_kubeconfig(self, request):
+ transaction_id = request['transaction_id']
+ region = request['remote_region']
+ kubeconfig_for = request['kubeconfig_for']
+ kubeconfig_approach = request['kubeconfig_approach']
+ namespace = self._get_namespace(region, self.logger)
+ if namespace != "":
+ self.logger.info(" Transaction:" + transaction_id)
+ self.logger.info(" Region:" + region)
+ self.logger.info(" Namespace:" + namespace)
+ kubeconfig = self._create_kubeconfig(kubeconfig_for, region, namespace, kubeconfig_approach, transaction_id)
+ return kubeconfig
+ else:
+ message = " Could not find Namespace for region " + region
+ self.logger.info(message)
+ status = "FAILED"
+ self._update_status_async(region, namespace, '', message, status, transaction_id, self.logger, kubeconfig='', done=True)
+ return json.dumps({})
+
+ def _get_serviceaccount_name(self, team, transaction_id):
+ if team == KubeconfigGenerator.ORCHESTRATION_TEAM:
+ return "orchestration-sa"
+ if team == KubeconfigGenerator.APPLICATION_TEAM:
+ return "application-sa-" + str(transaction_id)
+ if team == KubeconfigGenerator.OPS_TEAM:
+ return "ops-sa"
+ if team == KubeconfigGenerator.VENDOR_TEAM_SAMSUNG:
+ return "samsung-sa"
+
+ def _get_user_name(self, team):
+ if team == KubeconfigGenerator.ORCHESTRATION_TEAM:
+ return "SVC-FE-Atlas"
+ if team == KubeconfigGenerator.APPLICATION_TEAM:
+ return "SVC-Edge-Eng"
+
+ def _apply_application_rbac_policies_sa(self, region, remote_region_oam_ip, app_sa_namespace, namespace, saName, saFilePath, logger):
+ logger.info("Inside _apply_application_rbac_policies")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render Application RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ app_config = {}
+ app_config["application_sa"] = saName
+ app_config["namespace"] = namespace
+ app_config["crd_cluster_role"] = crd_cluster_role
+ app_config["crd_api_group"] = crd_api_group
+ app_config["crd_api_resources"] = crd_api_resources
+ app_rbac_template = env.get_template('application-sa.yaml')
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-application-sa.yaml", "w")
+ fp.write(app_rbac_template.render(app_config))
+ fp.close()
+
+ logger.info("About to copy rendered-application-rbac.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-application-sa.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ logger.info("About to apply rendered-application-sa.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-application-sa.yaml')
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ def _apply_application_rbac_policies_ldap(self, region, remote_region_oam_ip, namespace, logger):
+ logger.info("Inside _apply_application_rbac_policies ldap")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render Application RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ self.logger.info(" RBAC File Path:" + str(saFilePath))
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ orch_config = {}
+ orch_config["namespace"] = namespace
+ orch_config["crd_cluster_role"] = crd_cluster_role
+ orch_config["crd_api_group"] = crd_api_group
+ orch_config["crd_api_resources"] = crd_api_resources
+ orch_rbac_template = env.get_template('application-ldap.yaml')
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-application-ldap.yaml", "w")
+ fp.write(orch_rbac_template.render(orch_config))
+ fp.close()
+
+ logger.info("About to copy rendered-application-ldap.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-application-ldap.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ logger.info("About to apply rendered-application-ldap.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-application-ldap.yaml')
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ def _apply_vendor_samsung_rbac_policies(self, region, remote_region_oam_ip, app_sa_namespace, namespace, saName, saFilePath, logger):
+ logger.info("Inside _apply_vendor_samsung_rbac_policies")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render Vendor Samsung RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ app_config = {}
+ app_config["application_sa"] = saName
+ app_config["namespace"] = namespace
+ app_config["crd_cluster_role"] = crd_cluster_role
+ app_config["crd_api_group"] = crd_api_group
+ app_config["crd_api_resources"] = crd_api_resources
+ app_rbac_template = env.get_template('samsung-sa.yaml')
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-samsung-sa.yaml", "w")
+ fp.write(app_rbac_template.render(app_config))
+ fp.close()
+
+ logging.info("About to copy rendered-samsung-rbac.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-samsung-sa.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ logger.info("About to apply rendered-samsung-sa.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-samsung-sa.yaml')
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ def _apply_orchestration_rbac_policies_ldap(self, region, remote_region_oam_ip, namespace, logger):
+ logger.info("Inside _apply_orchestration_rbac_policies ldap")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render Orchestration RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ self.logger.info(" RBAC File Path:" + str(saFilePath))
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ orch_config = {}
+ orch_config["namespace"] = namespace
+ orch_config["crd_cluster_role"] = crd_cluster_role
+ orch_rbac_template = env.get_template('orchestration-ldap.yaml')
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-orchestration-ldap.yaml", "w")
+ fp.write(orch_rbac_template.render(orch_config))
+ fp.close()
+
+ logger.info("About to copy rendered-orchestration-ldap.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-orchestration-ldap.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ logger.info("About to apply rendered-orchestration-ldap.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-orchestration-ldap.yaml')
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ def _apply_orchestration_rbac_policies_sa(self, region, remote_region_oam_ip, orch_sa_namespace, namespace, saName, saFilePath, logger):
+ logger.info("Inside _apply_orchestration_rbac_policies sa")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render Orchestration RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ orch_config = {}
+ orch_config["orchestration_sa"] = saName
+ orch_config["namespace"] = namespace
+ orch_config["crd_cluster_role"] = crd_cluster_role
+ orch_rbac_template = env.get_template('orchestration-sa.yaml')
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-orchestration-sa.yaml", "w")
+ fp.write(orch_rbac_template.render(orch_config))
+ fp.close()
+
+ logger.info("About to copy rendered-orchestration-rbac.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-orchestration-sa.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ logger.info("About to apply rendered-orchestration-sa.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-orchestration-sa.yaml')
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ def _create_rbac_files_clusteradmin(self, region, namespace, saName, saFilePath, logger):
+ logger.info("Inside _create_rbac_files")
+ if not os.path.exists(saFilePath):
+ os.makedirs(saFilePath)
+
+ sa_metadata = {}
+ sa_metadata["namespace"] = namespace
+ sa_metadata["name"] = saName
+
+ subjects_list = []
+ subjects = {}
+ subjects["kind"] = "ServiceAccount"
+ subjects["name"] = saName
+ subjects["namespace"] = namespace
+ subjects_list.append(subjects)
+
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/sa-role.json", "w")
+ sa_role = {}
+ sa_role["apiVersion"] = "rbac.authorization.k8s.io/v1"
+ sa_role["kind"] = "Role"
+ sa_role["metadata"] = sa_metadata
+ sa_rules_list = []
+ sa_rules = {}
+ sa_rules["apiGroups"] = ["*"]
+ sa_rules["resources"] = ["*"]
+ sa_rules["verbs"] = ["*"]
+ sa_rules_list.append(sa_rules)
+ sa_role["rules"] = sa_rules_list
+ sa_role_json = json.dumps(sa_role)
+ logger.info("sa_role_json:" + str(sa_role_json))
+ fp.write(sa_role_json)
+
+ fp = open(policy_file_location + "/sa-rolebinding.json", "w")
+ sa_role_binding = {}
+ sa_role_binding["apiVersion"] = "rbac.authorization.k8s.io/v1"
+ sa_role_binding["kind"] = "RoleBinding"
+ sa_role_binding["metadata"] = sa_metadata
+ sa_role_binding["subjects"] = subjects_list
+ role_ref = {}
+ role_ref["kind"] = "Role"
+ role_ref["name"] = saName
+ role_ref["apiGroup"] = "rbac.authorization.k8s.io"
+ sa_role_binding["roleRef"] = role_ref
+ sa_role_binding_json = json.dumps(sa_role_binding)
+ fp.write(sa_role_binding_json)
+
+ fp = open(temp_file_location + "/sa-clusterrole.json", "w")
+ sa_clusterrole = {}
+ sa_clusterrole["apiVersion"] = "rbac.authorization.k8s.io/v1"
+ sa_clusterrole["kind"] = "ClusterRole"
+ sa_clusterrole["metadata"] = sa_metadata
+ sa_rules_list = []
+ sa_rules = {}
+ sa_rules["apiGroups"] = [""]
+ sa_rules["resources"] = ["*"]
+ sa_rules["verbs"] = ["*"]
+ sa_rules_list.append(sa_rules)
+ sa_clusterrole["rules"] = sa_rules_list
+ sa_clusterrole_json = json.dumps(sa_clusterrole)
+ fp.write(sa_clusterrole_json)
+
+ fp = open(temp_file_location + "/sa-clusterrolebinding.json", "w")
+ sa_clusterrole_binding = {}
+ sa_clusterrole_binding["apiVersion"] = "rbac.authorization.k8s.io/v1"
+ sa_clusterrole_binding["kind"] = "ClusterRoleBinding"
+ sa_clusterrole_binding["metadata"] = sa_metadata
+ sa_clusterrole_binding["subjects"] = subjects_list
+ clusterrole_ref = {}
+ clusterrole_ref["kind"] = "ClusterRole"
+ clusterrole_ref["name"] = saName
+ clusterrole_ref["apiGroup"] = "rbac.authorization.k8s.io"
+ sa_clusterrole_binding["roleRef"] = clusterrole_ref
+ sa_clusterrole_binding_json = json.dumps(sa_clusterrole_binding)
+ fp.write(sa_clusterrole_binding_json)
+ fp.close()
+
+ def _wait_for_oidc_app(self, region, namespace):
+ self.logger.info("Inside checking _wait_for_oidc_app...")
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ env_string = self._read_remote_openrc(remote_region_oam_ip, self.logger)
+ basecmd = env_string
+
+ status_applied = False
+ while not status_applied:
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system application-show oidc-auth-apps"
+ self.logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ output_lines = self._run_command_get_all_lines(cmds, host_password, self.logger)
+ self.logger.info("Returned output lines:")
+ self.logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ self.logger.info("Line:" + line)
+ if 'status' in line and ('applied' in line or 'apply-failed' in line):
+ self.logger.info("oidc-auth-apps applied: " + line)
+ if 'applied' in line:
+ status_applied = True
+ if 'apply-failed' in line:
+ cmds1 = []
+ cmd1 = ssh_prefix + " " + basecmd + " system application-remove oidc-auth-apps"
+ self.logger.info("cmd:" + cmd1)
+ cmd2 = ssh_prefix + " " + basecmd + " system application-apply oidc-auth-apps"
+ self.logger.info("cmd:" + cmd2)
+ cmds1.append(cmd1)
+ cmds1.append(cmd2)
+ output_lines1 = self._run_command_get_all_lines(cmds1 , host_password, self.logger)
+ self.logger.info("Returned output lines:")
+ self.logger.info(output_lines1)
+ time.sleep(3)
+
+ def _create_kubeconfig(self, kubeconfig_for, region, namespace, kubeconfig_approach, transaction_id):
+ self.logger.info("Kubeconfig for:" + kubeconfig_for)
+ kubeconfig_src = kubeconfig_approach
+ if kubeconfig_for == KubeconfigGenerator.ORCHESTRATION_TEAM:
+ if kubeconfig_src == 'SA':
+ self._create_kubeconfig_orchestration_sa(region, namespace, transaction_id)
+ if kubeconfig_src == 'LDAP':
+ #self._wait_for_oidc_app(region, namespace)
+ self._create_kubeconfig_orchestration_ldap(region, namespace, transaction_id)
+ elif kubeconfig_for == KubeconfigGenerator.APPLICATION_TEAM:
+ if kubeconfig_src == 'SA':
+ self._create_kubeconfig_application_sa(region, namespace, transaction_id)
+ if kubeconfig_src == 'LDAP':
+ self._create_kubeconfig_application_ldap(region, namespace, transaction_id)
+ self.vmbCoordinationQueue.put("Done")
+ elif kubeconfig_for == KubeconfigGenerator.OPS_TEAM:
+ self._create_kubeconfig_ops(region, namespace, transaction_id)
+ elif kubeconfig_for == KubeconfigGenerator.VENDOR_TEAM_SAMSUNG:
+ self._create_kubeconfig_samsung(region, namespace, transaction_id)
+ self.vmbCoordinationQueue.put("Done")
+
+ def _create_kubeconfig_application_sa(self, region, namespace, transaction_id):
+ self.logger.info("Inside ..kubeconfig application")
+ saName = self._get_serviceaccount_name(KubeconfigGenerator.APPLICATION_TEAM, transaction_id)
+ self.logger.info("Service Account name:" + saName)
+
+ message = 'Starting kubeconfig creation. '
+ status = 'STARTING'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ app_sa_namespace = 'default' # default ns is better as it already exists
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+
+ message = 'Applying RBAC to Service Account'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Service Account File Path:" + str(saFilePath))
+
+ self._apply_application_rbac_policies_sa(region, remote_region_oam_ip, app_sa_namespace, namespace, saName, saFilePath, self.logger)
+
+ cmds_token_name = []
+ cmds_token_name.append(ssh_prefix + " kubectl describe serviceaccount --kubeconfig=/etc/kubernetes/admin.conf -n " + app_sa_namespace + " " + saName + "| grep Tokens ")
+ all_lines = self._run_commands(cmds_token_name, host_password, self.logger, block=True)
+ secretname = self._parse_token_name(all_lines)
+
+ message = 'Parsing token'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Secret name:" + secretname)
+ if secretname != None:
+ cmds1 = []
+ cmds1.append(ssh_prefix + " kubectl describe secret --kubeconfig=/etc/kubernetes/admin.conf -n " + app_sa_namespace + " " + secretname + " | grep token:")
+ all_lines = self._run_commands(cmds1, host_password, self.logger, block=True)
+ token = self._parse_token(all_lines)
+ #self.logger.info("TOKEN TO USE:" + token)
+ token = token.strip()
+ self.logger.info(" Token:[" + str(token) + "]")
+
+ # Generate kubeconfig
+ kubeconfig_value = self._generate_kubeconfig(region, namespace, saName, token, transaction_id, self.logger)
+
+ # Update database
+ message = 'kubeconfig creation done. '
+ status = 'COMPLETE'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig=kubeconfig_value, done=True)
+
+ def _create_kubeconfig_application_ldap(self, region, namespace, transaction_id):
+ self.logger.info("Inside kubeconfig application ldap...")
+ message = 'Starting kubeconfig creation. '
+ status = 'STARTING'
+ self.logger.info(message + ' ' + status)
+ app_user = self._get_user_name(KubeconfigGenerator.APPLICATION_TEAM)
+ self.logger.info("Application User:" + app_user)
+ self._update_status_async(region, namespace, app_user, message, status, transaction_id, self.logger, kubeconfig='')
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ message = 'Applying RBAC to User Account'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, app_user, message, status, transaction_id, self.logger, kubeconfig='')
+
+ self.logger.info("Applying RBAC policies to the Application User...")
+ self._apply_application_rbac_policies_ldap(region, remote_region_oam_ip, namespace, self.logger)
+
+ ldap_account = 'edge_eng'
+ token = self._get_ldap_token(host_password, ssh_prefix, app_user, ldap_account, remote_region_oam_ip)
+
+ # Generate kubeconfig
+ kubeconfig_value = self._generate_kubeconfig(region, namespace, app_user, token, transaction_id, self.logger)
+
+ # Update database
+ message = 'kubeconfig creation done. '
+ status = 'COMPLETE'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, app_user, message, status, transaction_id, self.logger, kubeconfig=kubeconfig_value, done=True)
+
+ #mv /home/sysadmin/.kube/config /home/sysadmin/.kube/config-for-orchestration
+ self.logger.info("Moving /home/sysadmin/.kube/config to /home/sysadmin/.kube/config-for-edge-eng...")
+ set_kubeconfig_cmd = []
+ set_kubeconfig_cmd.append(ssh_prefix + ' mv /home/sysadmin/.kube/config /home/sysadmin/.kube/config-for-edge-eng')
+ self._run_commands(set_kubeconfig_cmd, host_password, self.logger, block=True)
+
+ #export KUBECONFIG=/etc/kubernetes/admin.conf
+ self.logger.info("Resetting KUBECONFIG...")
+ set_kubeconfig_cmd = []
+ set_kubeconfig_cmd.append(ssh_prefix + ' export KUBECONFIG=/etc/kubernetes/admin.conf ')
+ self._run_commands(set_kubeconfig_cmd, host_password, self.logger, block=True)
+ return kubeconfig_value
+
+ def _create_kubeconfig_samsung(self, region, namespace, transaction_id):
+ self.logger.info("Inside ..kubeconfig samsung")
+ saName = self._get_serviceaccount_name(KubeconfigGenerator.VENDOR_TEAM_SAMSUNG, transaction_id)
+ self.logger.info("Service Account name:" + saName)
+
+ message = 'Starting kubeconfig creation. '
+ status = 'STARTING'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ app_sa_namespace = 'default' # default ns is better as it already exists
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+
+ message = 'Applying RBAC to Service Account'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Service Account File Path:" + str(saFilePath))
+
+ self._apply_vendor_samsung_rbac_policies(region, remote_region_oam_ip, app_sa_namespace, namespace, saName, saFilePath, self.logger)
+
+ cmds_token_name = []
+ cmds_token_name.append(ssh_prefix + " kubectl describe serviceaccount --kubeconfig=/etc/kubernetes/admin.conf -n " + app_sa_namespace + " " + saName + "| grep Tokens ")
+ all_lines = self._run_commands(cmds_token_name, host_password, self.logger, block=True)
+ secretname = self._parse_token_name(all_lines)
+
+ message = 'Parsing token'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Secret name:" + secretname)
+ if secretname != None:
+ cmds1 = []
+ cmds1.append(ssh_prefix + " kubectl describe secret --kubeconfig=/etc/kubernetes/admin.conf -n " + app_sa_namespace + " " + secretname + " | grep token:")
+ all_lines = self._run_commands(cmds1, host_password, self.logger, block=True)
+ token = self._parse_token(all_lines)
+ #self.logger.info("TOKEN TO USE:" + token)
+ token = token.strip()
+ self.logger.info(" Token:[" + str(token) + "]")
+
+ # Generate kubeconfig
+ kubeconfig_value = self._generate_kubeconfig(region, namespace, saName, token, transaction_id, self.logger)
+
+ # Update database
+ message = 'kubeconfig creation done. '
+ status = 'COMPLETE'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig=kubeconfig_value, done=True)
+
+
+ def _create_kubeconfig_ops(self, region, namespace, transaction_id):
+ pass
+
+ def _create_kubeconfig_orchestration_ldap(self, region, namespace, transaction_id):
+ self.logger.info("Inside kubeconfig orchestration ldap...")
+ message = 'Starting kubeconfig creation. '
+ status = 'STARTING'
+ self.logger.info(message + ' ' + status)
+ orch_user = self._get_user_name(KubeconfigGenerator.ORCHESTRATION_TEAM)
+ self.logger.info("Orchestration User:" + orch_user)
+ self._update_status_async(region, namespace, orch_user, message, status, transaction_id, self.logger, kubeconfig='')
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ message = 'Applying RBAC to User Account'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, orch_user, message, status, transaction_id, self.logger, kubeconfig='')
+
+ self.logger.info("Applying RBAC policies to the Orchestration User...")
+ self._apply_orchestration_rbac_policies_ldap(region, remote_region_oam_ip, namespace, self.logger)
+
+ ldap_account = 'orchestration'
+ token = self._get_ldap_token(host_password, ssh_prefix, orch_user, ldap_account, remote_region_oam_ip)
+
+ # Generate kubeconfig
+ kubeconfig_value = self._generate_kubeconfig(region, namespace, orch_user, token, transaction_id, self.logger)
+
+ # Update database
+ message = 'kubeconfig creation done. '
+ status = 'COMPLETE'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, orch_user, message, status, transaction_id, self.logger, kubeconfig=kubeconfig_value, done=True)
+
+ #mv /home/sysadmin/.kube/config /home/sysadmin/.kube/config-for-orchestration
+ self.logger.info("Moving /home/sysadmin/.kube/config to /home/sysadmin/.kube/config-for-orchestration...")
+ set_kubeconfig_cmd = []
+ set_kubeconfig_cmd.append(ssh_prefix + ' mv /home/sysadmin/.kube/config /home/sysadmin/.kube/config-for-orchestration')
+ self._run_commands(set_kubeconfig_cmd, host_password, self.logger, block=True)
+
+ #export KUBECONFIG=/etc/kubernetes/admin.conf
+ self.logger.info("Resetting KUBECONFIG...")
+ set_kubeconfig_cmd = []
+ set_kubeconfig_cmd.append(ssh_prefix + ' export KUBECONFIG=/etc/kubernetes/admin.conf ')
+ self._run_commands(set_kubeconfig_cmd, host_password, self.logger, block=True)
+
+ # Send notification on VMB
+ self._send_vmb_notification(region, namespace, transaction_id, kubeconfig_value)
+ return kubeconfig_value
+
+ def _get_ldap_token(self, host_password, ssh_prefix, user, ldap_account, remote_region_oam_ip):
+ #cp /etc/kubernetes/admin.conf /home/sysadmin/.kube/config
+ self.logger.info("Starting token generation...")
+ cp_cmd = []
+ cp_cmd.append(ssh_prefix + ' cp /etc/kubernetes/admin.conf /home/sysadmin/.kube/config')
+ self._run_commands(cp_cmd, host_password, self.logger, block=True)
+
+ #export KUBECONFIG=/home/sysadmin/.kube/config
+ self.logger.info("Setting KUBECONFIG...")
+ set_kubeconfig_cmd = []
+ set_kubeconfig_cmd.append(ssh_prefix + ' export KUBECONFIG=/home/sysadmin/.kube/config ')
+ self._run_commands(set_kubeconfig_cmd, host_password, self.logger, block=True)
+
+ #kubectl config set-context --kubeconfig=/home/sysadmin/.kube/config SVC-Edge-Eng@kubernetes --cluster=kubernetes --user=SVC-Edge-Eng
+ self.logger.info("Performing set-context...")
+ set_context = ' kubectl config set-context --kubeconfig=/home/sysadmin/.kube/config ' + user + '@kubernetes --cluster=kubernetes --user=' + user
+ self.logger.info("Set context cmd:" + set_context)
+ set_context_cmd = []
+ set_context_cmd.append(ssh_prefix + set_context)
+ self._run_commands(set_context_cmd, host_password, self.logger, block=True)
+
+ #oidc-auth -c <OAM-IP> -u SVC-Edge-Eng -p 322C6v22acuhAGdyce22S3w282
+ self.logger.info("Executing oidc-auth...")
+ orch_password = settings.LDAP_USER_CREDS[ldap_account]
+ oidc_auth = ' oidc-auth -c ' + remote_region_oam_ip + ' -u ' + user + ' -p ' + orch_password
+ self.logger.info("OIDC Auth..:" + oidc_auth)
+ oidc_auth_cmd = []
+ oidc_auth_cmd.append(ssh_prefix + oidc_auth)
+ self._run_commands(oidc_auth_cmd, host_password, self.logger, block=True)
+
+ #grep token /home/sysadmin/.kube/config
+ self.logger.info("Retrieving token...")
+ cmds1 = []
+ cmds1.append(ssh_prefix + " grep token /home/sysadmin/.kube/config ")
+ all_lines = self._run_commands(cmds1, host_password, self.logger, block=True)
+ token = self._parse_token(all_lines)
+ #self.logger.info("TOKEN TO USE:" + token)
+ token = token.strip()
+ self.logger.info(" Token:[" + str(token) + "]")
+ return token
+
+ def _create_kubeconfig_orchestration_sa(self, region, namespace, transaction_id):
+ # For orchestration SA, we use either the 'orchestration' namespace or the 'default' namespace
+ self.logger.info("Inside ..kubeconfig orchestration sa")
+ saName = self._get_serviceaccount_name(KubeconfigGenerator.ORCHESTRATION_TEAM, transaction_id)
+ self.logger.info("Service Account name:" + saName)
+
+ message = 'Starting kubeconfig creation. '
+ status = 'STARTING'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, self.logger)
+ orch_sa_namespace = 'default' # default ns is better as it already exists
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ #cmds_sa = []
+ #cmds_sa.append(ssh_prefix + ' kubectl create serviceaccount --kubeconfig=/etc/kubernetes/admin.conf ' + saName + ' -n ' + orch_sa_namespace)
+ #self._run_commands(cmds_sa, host_password, self.logger, block=True)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+
+ message = 'Applying RBAC to Service Account'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Service Account File Path:" + str(saFilePath))
+
+ self._apply_orchestration_rbac_policies_sa(region, remote_region_oam_ip, orch_sa_namespace, namespace, saName, saFilePath, self.logger)
+
+ cmds_token_name = []
+ secret_name_found = False
+ cmds_token_name.append(ssh_prefix + " kubectl describe serviceaccount --kubeconfig=/etc/kubernetes/admin.conf -n " + orch_sa_namespace + " " + saName + "| grep Tokens ")
+ while not secret_name_found:
+ all_lines = self._run_commands(cmds_token_name, host_password, self.logger, block=True)
+ # Check if secretname != <none>; if so, repeat the command
+ ## kubeconfighandler.py _create_kubeconfig_orchestration 351 Secret name:<none>
+ secretname = self._parse_token_name(all_lines)
+ if secretname != "<none>":
+ secret_name_found = True
+ else:
+ time.sleep(60)
+
+ message = 'Parsing token'
+ status = 'CREATING'
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig='')
+ self.logger.info(" Secret name:" + secretname)
+ if secretname != None:
+ #self.logger.info("ABC")
+ cmds1 = []
+ cmds1.append(ssh_prefix + " kubectl describe secret --kubeconfig=/etc/kubernetes/admin.conf -n " + orch_sa_namespace + " " + secretname + " | grep token:")
+ all_lines = self._run_commands(cmds1, host_password, self.logger, block=True)
+ token = self._parse_token(all_lines)
+ #self.logger.info("TOKEN TO USE:" + token)
+ token = token.strip()
+ self.logger.info(" Token:[" + str(token) + "]")
+
+ # Generate kubeconfig
+ kubeconfig_value = self._generate_kubeconfig(region, namespace, saName, token, transaction_id, self.logger)
+
+ # Update database
+ message = 'kubeconfig creation done. '
+ status = 'COMPLETE'
+ self.logger.info(message + ' ' + status)
+ self._update_status_async(region, namespace, saName, message, status, transaction_id, self.logger, kubeconfig=kubeconfig_value, done=True)
+
+ # Send notification on VMB
+ self._send_vmb_notification(region, namespace, transaction_id, kubeconfig_value)
+ return kubeconfig_value
+
+ def _send_vmb_notification(self, region, namespace, transaction_id, kubeconfig_value):
+ created_at_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+
+ site_name, site_location = self.get_fuze_spm_site_details(region, self.logger)
+ self.logger.info("Site name:" + site_name)
+ self.logger.info("Site location:" + site_location)
+
+ # Send VMB Notification
+ kubeconfig = Kubeconfig(
+ cluster=region,
+ namespace=namespace,
+ location=site_location,
+ kubeconfig='Example kubeconfig',
+ created_at=str(created_at_time),
+ updated_at=str(created_at_time),
+ transactionId=str(transaction_id)
+ )
+
+ kubeconfig.kubeconfig = kubeconfig_value
+
+ reportDescription = 'kubeconfig file: ' + site_name
+ kubeconfig_message = KubeconfigMessage(
+ reportName='vcp_fe_kubeconfig',
+ reportDescription=reportDescription,
+ reportGeneratedOn=str(created_at_time),
+ rowCount = 1,
+ reportDataRows=[kubeconfig],
+ )
+
+ item = {}
+ item['payload'] = kubeconfig_message
+ item['message'] = 'Kubeconfig'
+ self.logger.info("Sending to VMB")
+ self.logger.info(kubeconfig_message)
+ self.logger.info(kubeconfig)
+ self.vmbQueue.put(item)
+
+ def _generate_kubeconfig(self, region, namespace, saName, token, transaction_id, logger):
+ logger.info(" Inside _generate_kubeconfig")
+
+ user_list = []
+ tokendata = {}
+ tokendata['token'] = token
+ userdata = {}
+ userdata['name'] = saName
+ userdata['user'] = tokendata
+ user_list.append(userdata)
+
+ context_list = []
+ contextdatawrapper = {}
+ contextdata = {}
+ contextdata['cluster'] = region
+ contextdata['user'] = saName
+ contextdata['namespace'] = namespace
+ contextdatawrapper['context'] = contextdata
+ contextdatawrapper['name'] = region
+ context_list.append(contextdatawrapper)
+
+ cluster_list = []
+ clusterdata = {}
+ clusterdata['name'] = region
+ cluster_detail = {}
+ cluster_detail['insecure-skip-tls-verify'] = True
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, logger)
+ cluster_detail['server'] = 'https://[' + remote_region_oam_ip + ']:6443'
+ clusterdata["cluster"] = cluster_detail
+ cluster_list.append(clusterdata)
+
+ outer_dict = {}
+ outer_dict['apiVersion'] = 'v1'
+ outer_dict['kind'] = 'Config'
+ outer_dict['current-context'] = region
+ outer_dict['users'] = user_list
+ outer_dict['contexts'] = context_list
+ outer_dict['clusters'] = cluster_list
+
+ kubeconfig_json = json.dumps(outer_dict)
+ logger.info(str(transaction_id) + " Kubeconfig:" + kubeconfig_json)
+ return kubeconfig_json
+
+ def _parse_token_name(self, all_lines):
+ for line in all_lines.split("\n"):
+ if 'Tokens' in line:
+ parts = line.split(":")
+ tokenName = parts[1].rstrip().lstrip()
+ return tokenName
+
+ def _parse_token(self, all_lines):
+ for line in all_lines.split("\n"):
+ if 'token:' in line:
+ parts = line.split(":")
+ token = parts[1].rstrip().lstrip()
+ return token
+
+ def _run_commands_scp(self, commands, host_password, logger, block=False):
+ logger.info("Inside _run_commands_scp..:")
+ status = run_commands_scp(commands, host_password, logger, block=False)
+ return status
+
+ def _run_commands(self, commands, host_password, logger, block=False):
+ #logger.info(commands)
+ all_lines1 = []
+ for command in commands:
+ logger.info(" Executing.." + str(command))
+ child = pexpect.spawn(command)
+ try:
+ if block:
+ child.timeout=None
+ child.expect(['password: '], timeout=None)
+ child.sendline(host_password)
+ all_lines1 = child.read()
+ all_lines1 = all_lines1.rstrip().lstrip()
+ all_lines1 = all_lines1.decode('utf-8').replace('\r\n', '\n')
+ logger.info(all_lines1)
+ return all_lines1
+ else:
+ child.expect(['(yes/no)? '])
+ child.sendline('yes')
+ child.expect(['password: '])
+ child.sendline(host_password)
+ #child.interact()
+ #child.close()
+ child.expect(pexpect.EOF, timeout=5)
+ except:
+ pass
+ return all_lines1
+
+ def _update_status_async(self, region, namespace, saName, message, status, transaction_id, logger, kubeconfig='', done=False):
+ item = {}
+ item['type'] = 'kubeconfig'
+ item['region'] = region
+ item['namespace'] = namespace
+ item['saName'] = saName
+ item['message'] = message
+ item['status'] = status
+ item['transaction_id'] = transaction_id
+ item['kubeconfig'] = kubeconfig
+ item['done'] = done
+ self.dbQueue.put(item)
+
+ def _update_status(self, region, namespace, saName, message, status, transaction_id, logger, kubeconfig=''):
+ RemoteRegionSetup.objects.filter(transaction_id=transaction_id).update(message=message,
+ status=status,
+ serviceaccount=saName,
+ kubernetes_namespace=namespace,
+ remote_region_name=region,
+ kubeconfig=kubeconfig)
+ return
+
diff --git a/src/orchestration/migrations/0008_auto_20200514_1951.py b/src/orchestration/migrations/0008_auto_20200514_1951.py
new file mode 100644
index 0000000..d85773d
--- /dev/null
+++ b/src/orchestration/migrations/0008_auto_20200514_1951.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.0.6 on 2020-05-15 01:51
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0007_auto_20200429_1837'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='ImageSync',
+ ),
+ migrations.DeleteModel(
+ name='RemoteRegionSetup',
+ ),
+ ]
diff --git a/src/orchestration/migrations/0008_imagesync_transaction_id.py b/src/orchestration/migrations/0008_imagesync_transaction_id.py
new file mode 100644
index 0000000..af58c73
--- /dev/null
+++ b/src/orchestration/migrations/0008_imagesync_transaction_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.5 on 2020-05-29 16:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0007_auto_20200429_1837'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='imagesync',
+ name='transaction_id',
+ field=models.CharField(default='-1', max_length=20),
+ ),
+ ]
diff --git a/src/orchestration/migrations/0009_auto_20200529_1929.py b/src/orchestration/migrations/0009_auto_20200529_1929.py
new file mode 100644
index 0000000..c5d5aa1
--- /dev/null
+++ b/src/orchestration/migrations/0009_auto_20200529_1929.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.5 on 2020-05-29 19:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0008_imagesync_transaction_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='imagesync',
+ name='transaction_id',
+ field=models.CharField(default='-1', max_length=39),
+ ),
+ ]
diff --git a/src/orchestration/migrations/0010_centralregiontoremoteregionmap.py b/src/orchestration/migrations/0010_centralregiontoremoteregionmap.py
new file mode 100644
index 0000000..905f71c
--- /dev/null
+++ b/src/orchestration/migrations/0010_centralregiontoremoteregionmap.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.5 on 2020-05-29 19:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0009_auto_20200529_1929'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CentralRegionToRemoteRegionMap',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('central_region_name', models.CharField(max_length=64)),
+ ('remote_region_name', models.CharField(max_length=64)),
+ ],
+ ),
+ ]
diff --git a/src/orchestration/migrations/0011_auto_20200529_1944.py b/src/orchestration/migrations/0011_auto_20200529_1944.py
new file mode 100644
index 0000000..0d44d36
--- /dev/null
+++ b/src/orchestration/migrations/0011_auto_20200529_1944.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.0.5 on 2020-05-29 19:44
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0010_centralregiontoremoteregionmap'),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name='CentralRegionToRemoteRegionMap',
+ new_name='CentralToRemoteMap',
+ ),
+ ]
diff --git a/src/orchestration/migrations/0012_merge_20200702_0053.py b/src/orchestration/migrations/0012_merge_20200702_0053.py
new file mode 100644
index 0000000..f31df44
--- /dev/null
+++ b/src/orchestration/migrations/0012_merge_20200702_0053.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.8 on 2020-07-02 00:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0008_auto_20200514_1951'),
+ ('orchestration', '0011_auto_20200529_1944'),
+ ]
+
+ operations = [
+ ]
diff --git a/src/orchestration/migrations/0013_imagesync_remoteregionsetup.py b/src/orchestration/migrations/0013_imagesync_remoteregionsetup.py
new file mode 100644
index 0000000..987851e
--- /dev/null
+++ b/src/orchestration/migrations/0013_imagesync_remoteregionsetup.py
@@ -0,0 +1,39 @@
+# Generated by Django 3.0.8 on 2020-07-02 03:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0012_merge_20200702_0053'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ImageSync',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('transaction_id', models.CharField(default='-1', max_length=39)),
+ ('remote_region_name', models.CharField(max_length=64)),
+ ('docker_image', models.TextField(max_length=65535)),
+ ('upload_start_time', models.DateTimeField()),
+ ('upload_end_time', models.DateTimeField(null=True)),
+ ('upload_status', models.CharField(choices=[('STARTED', 'STARTED'), ('UPLOADING', 'UPLOADING'), ('COMPLETE', 'COMPLETE'), ('FAILED', 'FAILED')], max_length=9)),
+ ('upload_message', models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='RemoteRegionSetup',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('caas_vendor_version', models.CharField(max_length=20)),
+ ('kubernetes_version', models.CharField(max_length=20)),
+ ('central_region_name', models.CharField(max_length=64)),
+ ('remote_region_name', models.CharField(max_length=64)),
+ ('kubernetes_namespace', models.CharField(max_length=64)),
+ ('serviceaccount', models.CharField(max_length=64)),
+ ('kubeconfig', models.TextField(blank=True, max_length=16777215, null=True)),
+ ],
+ ),
+ ]
diff --git a/src/orchestration/migrations/0014_remoteregionsetup_transaction_id.py b/src/orchestration/migrations/0014_remoteregionsetup_transaction_id.py
new file mode 100644
index 0000000..2555269
--- /dev/null
+++ b/src/orchestration/migrations/0014_remoteregionsetup_transaction_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.8 on 2020-07-07 20:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0013_imagesync_remoteregionsetup'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='remoteregionsetup',
+ name='transaction_id',
+ field=models.CharField(default='-1', max_length=39),
+ ),
+ ]
diff --git a/src/orchestration/migrations/0015_auto_20200707_2034.py b/src/orchestration/migrations/0015_auto_20200707_2034.py
new file mode 100644
index 0000000..7288f9a
--- /dev/null
+++ b/src/orchestration/migrations/0015_auto_20200707_2034.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.0.8 on 2020-07-07 20:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orchestration', '0014_remoteregionsetup_transaction_id'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='remoteregionsetup',
+ name='message',
+ field=models.CharField(max_length=255, null=True),
+ ),
+ migrations.AddField(
+ model_name='remoteregionsetup',
+ name='status',
+ field=models.CharField(choices=[('STARTED', 'STARTED'), ('CREATING', 'CREATING'), ('COMPLETE', 'COMPLETE'), ('FAILED', 'FAILED')], max_length=9, null=True),
+ ),
+ ]
diff --git a/src/orchestration/models.py b/src/orchestration/models.py
index 8e79d8d..fac240c 100644
--- a/src/orchestration/models.py
+++ b/src/orchestration/models.py
@@ -2,6 +2,7 @@ from django.db import models
class ImageSync(models.Model):
+ transaction_id = models.CharField(max_length=39,default="-1")
remote_region_name = models.CharField(max_length=64)
docker_image = models.TextField(max_length=65535)
upload_start_time = models.DateTimeField()
@@ -10,10 +11,11 @@ class ImageSync(models.Model):
upload_message = models.CharField(max_length=255)
def __str__(self):
- return "%s : %s" % (self.remote_region_name, self.docker_image)
+ return "%s : %s : %s" % (self.transaction_id, self.remote_region_name, self.docker_image)
class RemoteRegionSetup(models.Model):
+ transaction_id = models.CharField(max_length=39,default="-1")
caas_vendor_version = models.CharField(max_length=20)
kubernetes_version = models.CharField(max_length=20)
central_region_name = models.CharField(max_length=64)
@@ -21,6 +23,13 @@ class RemoteRegionSetup(models.Model):
kubernetes_namespace = models.CharField(max_length=64)
serviceaccount = models.CharField(max_length=64)
kubeconfig = models.TextField(max_length=16777215, null=True, blank=True)
+ status = models.CharField(max_length=9, null=True, choices=[('STARTED', 'STARTED'), ('CREATING', 'CREATING'), ('COMPLETE', 'COMPLETE'), ('FAILED', 'FAILED')])
+ message = models.CharField(max_length=255, null=True)
def __str__(self):
return "%s : %s" % (self.remote_region_name, self.kubernetes_namespace)
+
+
+class CentralToRemoteMap(models.Model):
+ central_region_name = models.CharField(max_length=64)
+ remote_region_name = models.CharField(max_length=64)
diff --git a/src/orchestration/namespacehandler.py b/src/orchestration/namespacehandler.py
new file mode 100644
index 0000000..59597ac
--- /dev/null
+++ b/src/orchestration/namespacehandler.py
@@ -0,0 +1,431 @@
+import django
+import threading
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+from django.utils import timezone
+
+from multiprocessing import Process, Pool, Queue
+import pexpect
+import traceback
+import logging
+import multiprocessing
+from logging.handlers import QueueHandler
+import sys
+import time
+import datetime
+from .utils import *
+
+import os
+import signal, psutil
+
+from jinja2 import Environment, FileSystemLoader
+
+from .vmb_messages import RemoteRegion, NameSpaceMessage
+from .remoteregionhandler import RemoteRegionWorker
+from .vendorhandler import Samsung
+
+try:
+ django.setup()
+ from .models import ImageSync
+ from caas.models import Cluster
+ from caas.models import Namespace
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class NamespaceWorker(RemoteRegionWorker):
+
+ def __init__(self, loggerQueue, requestQueue, kubeconfigQueue, dbQueue, vmbQueue, doneQueue):
+ self.loggerQueue = loggerQueue
+ self.requestQueue = requestQueue
+ self.kubeconfigQueue = kubeconfigQueue
+ self.dbQueue = dbQueue
+ self.vmbQueue = vmbQueue
+ self.doneQueue = doneQueue
+
+ def run(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.logger.info("NamespaceWorker started...")
+
+ while True:
+ try:
+ item = self.requestQueue.get(block=True)
+ if item:
+ self.logger.info(item)
+ self._handle_request(item)
+ except:
+ pass
+ time.sleep(1)
+
+ def _handle_request(self, request):
+ self.logger.info("Inside handle request")
+
+ remoteRegions = []
+ self.logger.info(request)
+ regions_list = request['cluster_status']
+ network_setup = request['network_setup']
+ self.logger.info(regions_list)
+ for region in regions_list:
+ self.logger.info("region " + region['name'])
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region['name'], self.logger)
+ self.logger.info(" _handle_request: " + remote_region_oam_ip)
+ remoteRegions.append(remote_region_oam_ip)
+ self._create_namespace(region['name'], remote_region_oam_ip, network_setup, self.logger)
+
+ def _create_namespace(self, region, region_oam_ip, network_setup, logger):
+ logger.info("Inside _create_namespace " + region)
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(region_oam_ip)
+
+ namespace = self._get_namespace(region, logger)
+ logger.info("_create_namespace " + namespace + " region: " + region + " oam_ip:" + region_oam_ip)
+
+ namespace_creation_command = []
+ namespace_creation_command.append(ssh_prefix + ' kubectl create namespace --kubeconfig=/etc/kubernetes/admin.conf ' + namespace)
+
+ namespace_exist = self._check_namespaces(region_oam_ip, namespace, logger)
+ if not namespace_exist:
+ message = 'Starting namespace creation. '
+ status = 'STARTING'
+ logger.info(message + ' ' + status)
+ #self._update_status(central_image, region, message, status, transaction_id, logger)
+ namespace_created = self._run_commands(namespace_creation_command, host_password, logger, block=True)
+
+ if namespace_created:
+ self._create_crd_rbac(namespace, region, region_oam_ip, logger)
+ self._create_rbd_provisioner(namespace, region, region_oam_ip, logger)
+
+ message = 'Namespace creation done. '
+ status = 'COMPLETE'
+ logger.info(message + ' ' + status)
+ #self._update_status1(central_image, region, message, status, transaction_id, logger)
+ else:
+ logger.info("Namespace creation failed.")
+
+ if namespace_exist or namespace_created:
+ created_at_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+
+ site_name, site_location = self.get_fuze_spm_site_details(region, logger)
+ logger.info("Site name:" + site_name)
+ logger.info("Site location:" + site_location)
+
+ # Send VMB Notification
+ remote_region = RemoteRegion(cluster=region,
+ namespace=namespace,
+ location=site_location,
+ created_at=str(created_at_time),
+ updated_at=str(created_at_time))
+
+ namespace_message = NameSpaceMessage(reportName='vcp_fe_namespace',
+ reportDescription=site_name,
+ reportGeneratedOn=str(created_at_time),
+ rowCount=1,
+ reportDataRows=[remote_region])
+
+ item = {}
+ item['payload'] = namespace_message
+ item['message'] = 'Namespace'
+ self.vmbQueue.put(item)
+
+ # Trigger Kubeconfig generation
+ kubeconfig_request = {}
+ kubeconfig_request['remote_region'] = region
+ kubeconfig_request['transaction_id'] = "-1"
+ kubeconfig_request['kubeconfig_for'] = "orchestration-team"
+ kubeconfig_request['kubeconfig_approach'] = settings.KUBECONFIG_SRC
+ kubeconfig_request['namespace'] = namespace
+ kubeconfig_request['region_oam_ip'] = region_oam_ip
+ kubeconfig_request['network_setup'] = network_setup
+ self.kubeconfigQueue.put(kubeconfig_request)
+
+ def _create_rbd_provisioner(self, namespace, region, remote_region_oam_ip, logger):
+ logger.info("Creating RBD provisioner in Namespace:" + namespace)
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, self.logger, block=True)
+
+ logger.info("About to render rbd files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+ config_data = {}
+ config_data["namespace"] = namespace
+
+ rbd_template = env.get_template('rbd.yaml')
+ logger.info(rbd_template)
+ temp_file_location = get_temp_file_location()
+
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+ fp = open(policy_file_location + "/rendered-rbd.yaml", "w")
+
+ fp.write(rbd_template.render(config_data))
+ fp.close()
+
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-rbd.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, self.logger, block=True)
+
+ cmds = []
+
+ env_string = self._read_remote_openrc(remote_region_oam_ip, logger)
+ basecmd = env_string
+
+ logger.info("basecmd:" + basecmd)
+ cmd = ssh_prefix + " " + basecmd + " system helm-override-update --values ./" + folder + "/rendered-rbd.yaml platform-integ-apps rbd-provisioner kube-system"
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system application-apply platform-integ-apps"
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ self._run_commands(cmds, host_password, self.logger, block=True)
+
+ # Wait for the platform-integ-apps to status to become 'applied'
+ cmds = []
+ cmd = ssh_prefix + " " + basecmd + " system application-show platform-integ-apps"
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ status_applied = False
+ while not status_applied:
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ logger.info("Line:" + line)
+ if 'status' in line and 'applied' in line:
+ logger.info("platform-integ-apps application applied.")
+ status_applied = True
+ time.sleep(3)
+
+ # Check if the rbd secret has been created or not; If not create it
+ cmds = []
+ cmd = ssh_prefix + " kubectl get secrets --kubeconfig=/etc/kubernetes/admin.conf -n " + namespace
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ found = False
+ count = 0
+ # Will wait for 30 seconds to ensure that the ceph rbd secret is created.
+ while not found and count < 10:
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ logger.info("Line:" + line)
+ if 'ceph-pool-kube-rbd' in line:
+ logger.info("ceph-pool-kube-rbd secret created in the namespace:" + namespace)
+ found = True
+ count = count + 1
+ time.sleep(3)
+
+ if not found:
+ logger.info("ceph-pool-rbd-secret not found in the namespace..creating one")
+ cmds = []
+ rbd_secret_cmd = " kubectl --kubeconfig=/etc/kubernetes/admin.conf get secret ceph-pool-kube-rbd -n default -o yaml "
+ #rbd_secret_cmd = rbd_secret_cmd + " | sed 's/namespace: default/namespace: " + namespace + "/' | "
+ #rbd_secret_cmd = rbd_secret_cmd + " sed 's/namespaces\/default/namespaces\/" + namespace + "/'"
+ #rbd_secret_cmd = rbd_secret_cmd + " kubectl --kubeconfig=/etc/kubernetes/admin.conf apply -n " + namespace + " -f - "
+ cmd = ssh_prefix + rbd_secret_cmd
+ logger.info("cmd:" + cmd)
+ cmds.append(cmd)
+ all_lines = self._run_command_get_all_lines(cmds, host_password, logger)
+ temp_file_location = get_temp_file_location()
+ policy_file_location = temp_file_location + "/" + region
+
+ fp = open(policy_file_location + "/rendered-rbd-secret.yaml", "w")
+ for line in all_lines.split("\n"):
+ if 'Connection to' not in line:
+ if 'default' not in line:
+ fp.write(line + "\n")
+ elif 'namespace: default' in line:
+ fp.write(' namespace: ' + namespace + "\n")
+ else:
+ fp.write(' selfLink: /api/v1/namespaces/' + namespace + '/secrets/ceph-pool-kube-rbd' + "\n")
+ fp.close()
+
+ logger.info("About to copy rendered-rbd-secret.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-rbd-secret.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, logger, block=True)
+
+ logger.info("About to apply rendered-rbd-secret.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-rbd-secret.yaml')
+ self._run_commands(cmds, host_password, logger, block=True)
+
+
+ def _create_crd_rbac(self, namespace, region, remote_region_oam_ip, logger):
+ logger.info("Applyin CRD RBAC policies")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, logger, block=True)
+
+ logger.info("About to render CRD RBAC files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ logger.info("Getting crd details")
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ logger.info(crd_cluster_role + ' ' + crd_api_group + ' ' + crd_api_resources + ' ' + crd_api_verbs)
+ crd_config = {}
+ crd_config["crd_cluster_role"] = crd_cluster_role
+ crd_config["crd_api_group"] = crd_api_group
+ crd_config["crd_api_resources"] = crd_api_resources
+ crd_config["crd_api_verbs"] = crd_api_verbs
+ logger.info(crd_config)
+ crd_role_template = env.get_template('crd-role.yaml')
+ logger.info(crd_role_template)
+ temp_file_location = get_temp_file_location()
+
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+
+ fp = open(policy_file_location + "/rendered-crd-role.yaml", "w")
+ logger.info("ABC")
+ try:
+ rendered_crd_template = crd_role_template.render(crd_config)
+ logger.info(rendered_crd_template)
+ except Exception as e:
+ logger.info(e)
+ fp.write(rendered_crd_template)
+ logger.info("DEF")
+ fp.close()
+
+ logger.info("About to copy rendered-crd-role.yaml")
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-crd-role.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, logger, block=True)
+
+ logger.info("About to apply rendered-crd-role.yaml")
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-crd-role.yaml')
+ self._run_commands(cmds, host_password, logger, block=True)
+
+ def _deploy_helm(self, namespace, region, remote_region_oam_ip, logger):
+ logger.info("Instantiating Tiller in Namespace:" + namespace)
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ sa_files_dir = []
+ folder = 'cluster-setup-files'
+ sa_files_dir.append(ssh_prefix + ' mkdir -p /home/sysadmin/' + folder)
+ self._run_commands(sa_files_dir, host_password, logger, block=True)
+
+ logger.info("About to render Tiller files")
+ saFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ logger.info("SA File Path:" + saFilePath)
+ env = Environment(loader = FileSystemLoader(saFilePath), trim_blocks=True, lstrip_blocks=True)
+ logger.info(env)
+
+ # Create Tiller Service Account and RBAC
+ logger.info("Getting crd details")
+ crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs = self._get_crd_details()
+ logger.info(crd_cluster_role + ' ' + crd_api_group + ' ' + crd_api_resources + ' ' + crd_api_verbs)
+ tiller_config = {}
+ tiller_config["namespace"] = namespace
+ tiller_config["crd_cluster_role"] = crd_cluster_role
+ vdu_tiller_sa_template = env.get_template('tiller-sa.yaml')
+ temp_file_location = get_temp_file_location()
+
+ policy_file_location = temp_file_location + "/" + region
+ logger.info("Policy file location:" + policy_file_location)
+ if not os.path.exists(policy_file_location):
+ os.makedirs(policy_file_location)
+
+ fp = open(policy_file_location + "/rendered-tiller-sa.yaml", "w")
+ fp.write(vdu_tiller_sa_template.render(tiller_config))
+ fp.close()
+
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-tiller-sa.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, logger, block=True)
+
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-tiller-sa.yaml')
+ self._run_commands(cmds, host_password, logger, block=True)
+
+ # Instantiate Tiller Pod
+ logger.info("About to render Helm files")
+ config_data = {}
+ config_data["namespace"] = namespace
+
+ helm_template = env.get_template('helm.yaml')
+ logger.info(helm_template)
+ fp = open(policy_file_location + "/rendered-helm.yaml", "w")
+ fp.write(helm_template.render(config_data))
+ fp.close()
+ logger.info("ABC")
+
+ cmds_scp = []
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + policy_file_location + '/rendered-helm.yaml ' + host_username + '@[' + remote_region_oam_ip + ']:~/' + folder + '/.')
+ self._run_commands_scp(cmds_scp, host_password, logger, block=True)
+
+ cmds = []
+ cmds.append(ssh_prefix + ' kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f ./' + folder + '/rendered-helm.yaml')
+ self._run_commands(cmds, host_password, logger, block=True)
+
+ def _run_commands(self, commands, host_password, logger, block=False, timeout=None):
+ logger.info("Inside _run_commands..timeout:" + str(timeout))
+ status = run_commands(commands, host_password, logger, block=False, timeout=timeout)
+ return status
+
+ def _run_commands_scp(self, commands, host_password, logger, block=False):
+ logger.info("Inside _run_commands_scp..")
+ status = run_commands_scp(commands, host_password, logger, block=False)
+ return status
+
+ def _update_status(self, image, region, message, status, transaction_id, logger, upload_end_time=None):
+ item = {}
+ item['image'] = image
+ item['region'] = region
+ item['message'] = message
+ item['status'] = status
+ item['transaction_id'] = transaction_id
+ item['upload_end_time'] = upload_end_time
+ self.dbQueue.put(item)
+
+ def _update_status1(self, image, region, message, status, transaction_id, logger):
+ ImageSync.objects.filter(
+ transaction_id=transaction_id,
+ docker_image=image).update(upload_message=message,
+ upload_status=status)
+
+ def _check_namespaces(self, remote_region_oam_ip, namespace, logger):
+
+ namespace_exist = False
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get namespaces --kubeconfig=/etc/kubernetes/admin.conf ")
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+
+ for line in output_lines.split("\n"):
+ if namespace in line:
+ print(namespace + ' already exists')
+ namespace_exist = True
+
+ return namespace_exist
+
diff --git a/src/orchestration/remoteregionhandler.py b/src/orchestration/remoteregionhandler.py
new file mode 100644
index 0000000..6754672
--- /dev/null
+++ b/src/orchestration/remoteregionhandler.py
@@ -0,0 +1,375 @@
+import django
+import os
+import threading
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+from django.utils import timezone
+
+from .utils import *
+
+try:
+ django.setup()
+ from .models import ImageSync
+ from caas.models import Cluster
+ from caas.models import Location
+ from caas.models import Namespace
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class RemoteRegionWorker:
+
+ def __init__(self):
+ pass
+
+ def _get_crd_details(self):
+ crd_cluster_role = "nad"
+ crd_api_group = "k8s.cni.cncf.io"
+ crd_api_resources = "network-attachment-definitions"
+ crd_api_verbs = "*"
+ return crd_cluster_role, crd_api_group, crd_api_resources, crd_api_verbs
+
+ def _read_remote_openrc(self, remote_region_oam_ip, logger):
+ #cmd = "scp @[fd00:4888:2000:120c::290]:/etc/platform/openrc .
+
+ logger.info("Inside _read_remote_openrc")
+ host_username = settings.HOST_CREDS['username']
+ host_password = settings.HOST_CREDS['password']
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + remote_region_oam_ip
+ env_string = ""
+ cmds_scp = []
+ folder = get_temp_file_location()
+ logger.info("Tmp file location:" + folder)
+ #tmpFilePath = os.path.join(os.path.dirname(__file__), "./" + folder)
+ tmpFileName = 'openrc_' + str(remote_region_oam_ip)
+ cmds_scp.append('scp -o StrictHostKeyChecking=no ' + host_username + '@[' + remote_region_oam_ip + ']:/etc/platform/openrc ' + folder + '/' + tmpFileName)
+ logger.info(cmds_scp)
+ run_commands_scp(cmds_scp, host_password, logger, block=True)
+ logger.info("About to create env string")
+
+ fp = open(folder + '/' + tmpFileName)
+ lines = fp.readlines()
+ logger.info(lines)
+ password_line = ''
+ for line in lines:
+ line = line.lstrip().rstrip()
+ logger.info(line)
+ parts = line.split(' ')
+ if len(parts) == 2:
+ if parts[0] == 'export':
+ if parts[1]:
+ env_var = parts[1].lstrip().rstrip()
+ logger.info(env_var)
+ if 'OS_PASSWORD' not in env_var:
+ env_string = env_string + " " + env_var
+ if len(parts) >= 2 and 'OS_PASSWORD=' in parts[1]:
+ logger.info("Parsing password")
+ password_parts = line.split('PASSWORD=')
+ password_command_parts = password_parts[1].split(' ')
+ password_line = password_command_parts[1].rstrip().lstrip()
+ logger.info("Password line:" + password_line)
+
+ logger.info("Deleting openrc file ")
+
+ # Delete file
+ if os.path.exists(folder + '/' + tmpFileName):
+ os.remove(folder + '/' + tmpFileName)
+
+ logger.info("Looking for password")
+ # Get password
+ password_cmd = []
+ #cmd = 'TERM=linux /opt/platform/.keyring/20.06/.CREDENTIAL 2>/dev/null'
+ cmd_to_run = ssh_prefix + " " + password_line
+ logger.info(cmd_to_run)
+ successful, value = self._run_command_get_output([cmd_to_run], host_password, logger)
+ password_val = host_password
+ #if successful:
+ # if value != '':
+ # password_val = value
+ os_password = "OS_PASSWORD=" + password_val
+ env_string = env_string + " " + os_password
+
+ logger.info("Env string:" + env_string)
+ return env_string
+
+ def _run_command_get_all_lines(self, commands, host_password, logger, block=False, timeout=None):
+ logger.info("Inside _run_command_get_all_lines")
+ all_lines = []
+ for command in commands:
+ logger.info(" Executing.." + str(command))
+ child = pexpect.spawn(command)
+ child.timeout=timeout
+ try:
+ i = child.expect(['password: ','Connection refused\r\r\n'], timeout=timeout)
+ if i == 0:
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ logger.info(all_lines)
+ if i == 1:
+ logger.info("Connection refused")
+ except:
+ logger.info(str(child))
+ return all_lines
+
+ def _run_command_get_output(self, commands, host_password, logger, block=False, timeout=None):
+ successful = False
+ value_to_return = ''
+ logger.info("Inside _run_command_get_output")
+ for command in commands:
+ logger.info(" Executing.." + str(command))
+ child = pexpect.spawn(command)
+ child.timeout=timeout
+ try:
+ child.expect(['password: '], timeout=timeout)
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ successful = True # Tentative
+ for line in all_lines.split("\n"):
+ logger.info(line)
+ if value_to_return == '':
+ value_to_return = line.strip()
+ if re.search('error', line, re.IGNORECASE):
+ successful = False
+ if re.search('unable', line, re.IGNORECASE):
+ successful = False
+ except:
+ logger.info(str(child))
+ logger.info("Status:" + str(successful) + " value_to_return:" + value_to_return)
+ return successful, value_to_return
+
+ def _get_remote_region_oam_ip(self, cluster_name, logger):
+ remoteclusterObj = Cluster.objects.filter(cluster_name=cluster_name)
+ oam_ip = remoteclusterObj[0].oam_vip_address
+ logger.info(" Remote region:" + cluster_name + " OAM IP:" + str(oam_ip))
+ return oam_ip
+
+ def _get_central_region_name(self, oam_vip_address, logger):
+ logger.info("1")
+ remoteclusterObj = Cluster.objects.filter(oam_vip_address=oam_vip_address)
+ logger.info("2")
+ logger.info(remoteclusterObj)
+ cluster_name = remoteclusterObj[0].cluster_name
+ logger.info(" Remote region:" + str(oam_vip_address) + " Cluster Name:" + str(cluster_name))
+ return cluster_name
+
+ def _get_namespace(self, region, logger):
+ # Lookup database and findout namespace given region
+ logger.info(" Inside _get_namespace")
+ remoteclusterObj = Cluster.objects.filter(cluster_name=region)
+ if len(remoteclusterObj) > 0:
+ logger.info(" RemoteClusterObj:" + str(remoteclusterObj))
+ namespace_id = remoteclusterObj[0].namespace_id
+ logger.info(" Namespace id:" + str(namespace_id))
+ namespaceObj = Namespace.objects.filter(id=namespace_id)
+ logger.info(" NamespaceObj:" + str(namespaceObj))
+ namespace_name = namespaceObj[0].namespace_name
+ logger.info(" Remote region:" + region + " Namespace:" + namespace_name)
+ return namespace_name
+ else:
+ return ""
+
+ def _get_central_region_oam_ip(self, remoteregion, transaction_id):
+ remoteclusterObj = Cluster.objects.filter(cluster_name=remoteregion)
+ parent_cluster_id = remoteclusterObj[0].parent_cluster_id
+ self.logger.info(str(transaction_id) + " Parent cluster id.." + str(parent_cluster_id))
+ centralclusterObj = Cluster.objects.filter(id=parent_cluster_id)
+ self.logger.info(str(transaction_id) + " " + str(centralclusterObj))
+ central_region_list = []
+ for central_region in centralclusterObj:
+ central_region_list.append(central_region.oam_vip_address)
+ self.logger.info(str(transaction_id) + " Central Region List:" + ','.join(central_region_list))
+ return central_region_list
+
+ def get_fuze_spm_site_details(self, cluster_name, logger):
+ fuze_spm_site_name = ''
+ fuze_spm_site_id = ''
+ logger.info(" Inside _get_fuze_spm_site_name " + str(cluster_name))
+ remoteclusterObj = Cluster.objects.filter(cluster_name=cluster_name)
+ if len(remoteclusterObj) > 0:
+ fuze_id = remoteclusterObj[0].location_id
+ locationObj = Location.objects.filter(id=fuze_id)
+ if len(locationObj) > 0:
+ fuze_spm_site_name = locationObj[0].fuze_spm_site_name
+ fuze_spm_site_id = locationObj[0].fuze_spm_site_id
+ logger.info(" Fuze site name:" + fuze_spm_site_name)
+ logger.info(" Fuze site id:" + fuze_spm_site_id)
+ return fuze_spm_site_name, fuze_spm_site_id
+
+ def check_namespaces(self, remote_region_oam_ip, logger):
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get namespaces --kubeconfig=/etc/kubernetes/admin.conf ")
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ #logger.info("Returned output lines:")
+ #logger.info(output_lines)
+
+ new_op_lines = []
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines.append(line)
+
+ return new_op_lines
+
+ def check_namespace_secrets(self, region, remote_region_oam_ip, logger):
+ namespace = self._get_namespace(region, logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get secrets --kubeconfig=/etc/kubernetes/admin.conf -n " + namespace)
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ #logger.info("Returned output lines:")
+ #logger.info(output_lines)
+
+ new_op_lines = []
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines.append(line)
+
+ return new_op_lines
+
+ def check_namespace_serviceaccounts(self, region, remote_region_oam_ip, logger):
+ namespace = self._get_namespace(region, logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get serviceaccounts --kubeconfig=/etc/kubernetes/admin.conf -n " + namespace)
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ #logger.info("Returned output lines:")
+ #logger.info(output_lines)
+
+ new_op_lines = []
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines.append(line)
+
+ return new_op_lines
+
+ def check_online_status(self, region, remote_region_oam_ip, logger):
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ central_region_list = self._get_central_region_oam_ip(region, "-1")
+ region_online_status = []
+ new_op_lines = []
+ for central_region_ip in central_region_list:
+ cmds = []
+ cmd = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + central_region_ip + ' ' + '"source /etc/platform/openrc; dcmanager subcloud list | grep ' + region + '"'
+ cmds.append(cmd)
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ logger.info(output_lines)
+
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines.append(line)
+ return new_op_lines
+
+ def create_host_network(self, remote_region_oam_ip, logger):
+ logger.info("Inside _create_host_network")
+
+ #host_username = settings.HOST_CREDS['username']
+ #host_password = settings.HOST_CREDS['password']
+ #ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + remote_region_oam_ip
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ env_string = self._read_remote_openrc(remote_region_oam_ip, logger)
+
+ #basecmd = " OS_ENDPOINT_TYPE=internalURL CINDER_ENDPOINT_TYPE=internalURL OS_USERNAME=admin"
+ #basecmd = basecmd + " OS_PASSWORD=`TERM=linux /opt/platform/.keyring/20.06/.CREDENTIAL 2>/dev/null`"
+ #basecmd = basecmd + " OS_AUTH_TYPE=password OS_AUTH_URL=http://[fd00:4888:2000:120b::220]:5000/v3"
+ #basecmd = basecmd + " OS_PROJECT_NAME=admin OS_USER_DOMAIN_NAME=Default OS_PROJECT_DOMAIN_NAME=Default"
+ #basecmd = basecmd + " OS_IDENTITY_API_VERSION=3 OS_REGION_NAME=subcloud2 OS_INTERFACE=internal"
+
+ basecmd = env_string
+
+ cmds = ["system host-lock controller-0",
+ "system host-if-modify -n f1u -c pci-sriov --num-vfs 8 controller-0 ens3f1 --vf-driver=vfio",
+ "system host-if-add -c pci-sriov controller-0 f1c vf f1u --num-vfs 4 --vf-driver=netdevice",
+ "system host-if-modify controller-0 f1u --imtu=1956",
+ "system host-if-modify controller-0 f1c --imtu=1956",
+ "system datanetwork-add f1u vlan --mtu=1956",
+ "system datanetwork-add f1c vlan --mtu=1956",
+ "system interface-datanetwork-assign controller-0 f1u f1u",
+ "system interface-datanetwork-assign controller-0 f1c f1c",
+ "system host-if-add -c pci-sriov controller-0 fh0m vf fh0 --num-vfs 4 --vf-driver=netdevice",
+ "system host-if-modify controller-0 fh0m --imtu=9000",
+ "system datanetwork-add fh0m flat",
+ "system interface-datanetwork-assign controller-0 fh0m fh0m",
+ "system host-if-modify -n fh1 -c pci-sriov --num-vfs 8 controller-0 enp181s0f0 --vf-driver=vfio",
+ "system host-if-modify controller-0 fh1 --imtu=9000",
+ "system datanetwork-add fh1 vlan --mtu=9000",
+ "system interface-datanetwork-assign controller-0 fh1 fh1"]
+
+ # New steps proposed by Eddy - These do not seem to create fh0m so commenting out.
+ #cmds = ["system host-lock controller-0",
+ # "system host-if-modify -n f1c -c pci-sriov --num-vfs 8 controller-0 ens3f1 --vf-driver=netdevice",
+ # "system host-if-add -c pci-sriov controller-0 f1u vf f1c --num-vfs 4 --vf-driver=vfio",
+ # "system host-if-modify controller-0 f1u --imtu=1956",
+ # "system host-if-modify controller-0 f1c --imtu=1956",
+ # "system datanetwork-add f1u vlan --mtu=1956",
+ # "system datanetwork-add f1c vlan --mtu=1956",
+ # "system interface-datanetwork-assign controller-0 f1u f1u",
+ # "system interface-datanetwork-assign controller-0 f1c f1c",
+ # # this one is wrong as well but for some reason I think the fh0 is setup in Carlos deployment config. So we have to delete the fh0 and recreate both.
+ # "system host-if-modify controller0 fh0 -nc none"
+ # "system host-if-modify -n fh0m -c pci-sriov --num-vfs 8 controller-0 ens179s0f0 --vf-driver=netdevice",
+ # "system host-if-add -c pci-sriov controller-0 fh0 vf fh0m --num-vfs 4 --vf-driver=vfio",
+ # "system host-if-modify controller-0 fh0m --imtu=9000",
+ # "system datanetwork-add fh0m flat",
+ # "system interface-datanetwork-assign controller-0 fh0m fh0m",
+ # "system host-if-modify -n fh1 -c pci-sriov --num-vfs 8 controller-0 enp181s0f0 --vf-driver=vfio",
+ # "system host-if-modify controller-0 fh1 --imtu=9000",
+ # "system datanetwork-add fh1 vlan --mtu=9000",
+ # "system interface-datanetwork-assign controller-0 fh1 fh1"]
+
+ for cmd in cmds:
+ cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+ logger.info(cmd_to_run)
+ run_commands([cmd_to_run], host_password, logger, block=True)
+
+# host_network_configured = False
+# while not host_network_configured:
+# host_network_configured = self._verify_host_network(ssh_prefix, basecmd, host_password, logger)
+# if not host_network_configured:
+# cmds = ["system host-lock controller-0"]
+# for cmd in cmds:
+# cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+# logger.info(cmd_to_run)
+# output_lines = self._run_command_get_all_lines([cmd_to_run], host_password, logger, block=True)
+# logger.info("Returned output lines:")
+# logger.info(output_lines)
+# time.sleep(3)
+
+ cmds = ["system host-unlock controller-0"]
+ while True:
+ unlock_wait = False
+ for cmd in cmds:
+ cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+ logger.info(cmd_to_run)
+ output_lines = self._run_command_get_all_lines([cmd_to_run], host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ logger.info("Line:" + line)
+ if not unlock_wait:
+ if 'retry host-unlock' in line or 'Rejected' in line:
+ logger.info("Need to wait to call host-unlock ##### ")
+ unlock_wait = True
+ break
+ if unlock_wait:
+ time.sleep(60)
+ else:
+ break
+
+# for cmd in cmds:
+# cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+# logger.info(cmd_to_run)
+# output_lines = self._run_command_get_all_lines([cmd_to_run], host_password, logger, block=True)
+# logger.info("Returned output lines:")
+# logger.info(output_lines)
+ logger.info("Done setting up host network")
diff --git a/src/orchestration/tests/cluster_status.json b/src/orchestration/tests/cluster_status.json
new file mode 100644
index 0000000..db2821f
--- /dev/null
+++ b/src/orchestration/tests/cluster_status.json
@@ -0,0 +1,15 @@
+{
+ "reportName": "vcp_fe_cluster_status",
+ "reportDescription": "caas deployment status",
+ "reportGeneratedOn": "2020-06-28T09:06:32.1962088-04:00",
+ "reportDataRows": [{
+ "name": "waeomagj-d654321-001",
+ "description": "NE CONCORD 8_NH",
+ "location": "654322",
+ "software_version": "19.12",
+ "availability": "online",
+ "deploy_status": "complete",
+ "created_at": "2020-06-17 04:16:15.743617",
+ "updated_at": "2020-06-17 06:03:10.854598"
+ }]
+} \ No newline at end of file
diff --git a/src/orchestration/tests/image_status.json b/src/orchestration/tests/image_status.json
new file mode 100644
index 0000000..adc1714
--- /dev/null
+++ b/src/orchestration/tests/image_status.json
@@ -0,0 +1,22 @@
+{
+ "reportName": "vcp_fe_imagestatus",
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 2,
+ "reportDataRows": [{
+ "cluster": "wsbomagj-d654321-001",
+ "image": "i1",
+ "action": "UPLOAD",
+ "status": "SUCCESS",
+ "message":"Image Successfully Deleted",
+ "created_at": "2020-01-07 04:16:15.743617"
+ },
+ {
+ "cluster": "wsbomagj-d654321-001",
+ "image": "i2",
+ "action": "UPLOAD",
+ "status": "FAILED",
+ "message":"Image Not Present in Artifactory",
+ "created_at": "2020-01-07 04:16:15.743617"
+ }]
+} \ No newline at end of file
diff --git a/src/orchestration/tests/kubeconfig.yaml b/src/orchestration/tests/kubeconfig.yaml
new file mode 100644
index 0000000..eba9a96
--- /dev/null
+++ b/src/orchestration/tests/kubeconfig.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Config
+users:
+- name: ldap-user
+ user:
+ token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJub2tpYSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJzYTEtdG9rZW4tbW5mMmoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2ExIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTA5YTkwMDItMTg0Zi0xMWVhLWE5NzYtMDgwMDI3YTFjODc3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Om5va2lhOnNhMSJ9.e9uD5kMmAT0HRboSTAbH5xlkETkltLclVQ2GedvoeUmH76WB6G5kGWQrhJjkjpMtPDKxWp6wTzZdEXXwGYGdB6aXbaxcAmau1qid5NGz725BtaoRbSVS2Uk6XrOSNfycFzqc8Z7GTX81VtKKSPYnjeMo47W6FqHw6qk0NEpLLxbpGfJHz8w2KZQiuvI-JRQXtA3PHW3tEaWq3ME3XnYgHNSRJmoiKA99bWjN-HKoOsBDMjhX7kw_VtycRYJ1gbqXmVsSl7BuAQjoplnLN_stRt7ZgpsV4aqmZueqJqHaglB91XOeqe_JcD5unLMb3B5VXpEXPp3V6tjLIyVD8F8XbA
+clusters:
+- cluster:
+ server: https://[fd00:4888:2000:120e::290]:8443
+ name: wsbomagj-d654321-001
+contexts:
+- context:
+ cluster: wsbomagj-d654321-001
+ user: ldap-user
+ namespace: WSBOMAGJ-441352VZWcVDU-Y-SM-x-001
+ name: ldap-user
+current-context: ldab-user
diff --git a/src/orchestration/tests/namespace.json b/src/orchestration/tests/namespace.json
new file mode 100644
index 0000000..be11eb6
--- /dev/null
+++ b/src/orchestration/tests/namespace.json
@@ -0,0 +1,12 @@
+{
+ "reportName": "vcp_fe_namespace",
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ "cluster": "wsbomagj-d654321-001",
+ "namespace": "WSBOMAGJ-441352VZWcVDU-Y-SM-x-001",
+ "location": "654321",
+ "created_at": "2020-01-07 04:16:15.743617"
+ }]
+} \ No newline at end of file
diff --git a/src/orchestration/urls.py b/src/orchestration/urls.py
index 6b7dbbc..78c6d3e 100644
--- a/src/orchestration/urls.py
+++ b/src/orchestration/urls.py
@@ -4,5 +4,15 @@ from . import views
urlpatterns = [
path('', views.index, name='index'),
path('count/<int:count>/', views.count, name='count'),
- path('imagesync/', views.imagesync, name='imagesync')
+ path('image-upload', views.imagesync, name='images'),
+ path('image-upload/<str:transaction_id>', views.get_status_by_transaction_id, name='get_status_by_transaction_id'),
+ path('remote-regions/<str:remote_region>/status', views.get_status_by_remote_region, name='get_status_by_remote_region'),
+ path('remote-regions/<str:remote_region>/images/<str:image_name>/status', views.get_status_by_image_name_and_remote_region, name='get_status_by_image_name_and_remote_region'),
+ path('remote-regions/<str:remote_region>/images/<str:image_name>/tags/<str:image_tag>', views.delete_image_tag_remote_region, name='delete_image_tag_remote_region'),
+ path('caas-status', views.caas_status, name='caas-status'),
+ path('setup-cluster/<str:cluster_name>', views.setup_cluster, name='setup_cluster'),
+ path('remote-regions/<str:remote_region>/site-details', views.get_setup_details_by_remote_region, name='get_setup_details_by_remote_region'),
+ path('remote-regions/<str:remote_region>/setup-network', views.setup_network_for_remote_region, name='setup_network_for_remote_region'),
+ path('remote-regions/<str:remote_region>/connection-details', views.get_kubeconfig_by_remote_region, name='get_kubeconfig_by_remote_region'),
+ path('remote-regions/<str:remote_region>/connection-details/<str:transaction_id>', views.get_kubeconfig_by_transaction_id, name='get_kubeconfig_by_transaction_id'),
]
diff --git a/src/orchestration/utils.py b/src/orchestration/utils.py
new file mode 100644
index 0000000..24f6b9c
--- /dev/null
+++ b/src/orchestration/utils.py
@@ -0,0 +1,110 @@
+import pexpect
+import re
+import django
+from django.core.exceptions import AppRegistryNotReady
+
+from django.conf import settings
+
+try:
+ django.setup()
+ from caas.models import Cluster
+ from caas.models import Namespace
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+process_started = False
+
+def set_process_started():
+ global process_started
+ if not process_started:
+ process_started = True
+ return process_started
+
+def get_pairs(imageList, remoteRegions):
+ pairList = []
+ for image in imageList:
+ for region in remoteRegions:
+ pair = {"image": image, "region": region}
+ pairList.append(pair)
+ return pairList
+
+def get_docker_reg_connection_details():
+ dr_username = settings.CENTRAL_DR_CREDS['username']
+ dr_password = settings.CENTRAL_DR_CREDS['password']
+ return dr_username, dr_password
+
+def get_temp_file_location():
+ file_loc = settings.ORCH_TEMP_FILE_LOCATION
+ return file_loc
+
+def get_host_connection_details(region_oam_ip):
+ host_username = settings.HOST_CREDS['username']
+ host_password = settings.HOST_CREDS['password']
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + region_oam_ip
+ return host_username, host_password, ssh_prefix
+
+def run_commands(commands, host_password, logger, block=False, timeout=None):
+ successful = False
+ logger.info("Inside run_commands")
+ for command in commands:
+ logger.info(" Executing.." + str(command))
+ child = pexpect.spawn(command)
+ child.timeout=timeout
+ try:
+ child.expect(['password: '], timeout=timeout)
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ successful = True # Tentative
+ for line in all_lines.split("\n"):
+ logger.info(line)
+ if re.search('error', line, re.IGNORECASE):
+ successful = False
+ if re.search('unable', line, re.IGNORECASE):
+ successful = False
+ except:
+ logger.info(str(child))
+ logger.info("Status:" + str(successful))
+ return successful
+
+def run_commands_scp(commands, host_password, logger, block=False, timeout=settings.SCP_TIMEOUT):
+ successful = False
+ logger.info("Inside run_commands")
+ logger.info("settings.RUNSERVER:" + str(settings.RUNSERVER))
+ for command in commands:
+ logger.info(" Executing.." + str(command))
+ try:
+ child = pexpect.spawn(str(command))
+ except:
+ logger.info(str(child))
+ #child.timeout=timeout
+ child.timeout = None
+ try:
+ #child.expect(['(yes/no)? '], timeout=timeout)
+ #child.expect(['Are you sure you want to continue connecting (yes/no)? '])
+ #child.expect(['.+'], timeout=timeout)
+ #child.expect([':b5:9c:21:14:6f:fa:45:20:c7:ba:49:3a:aa:31.\r\nAre you sure you want to continue connecting (yes/no)? '], timeout=timeout)
+ if not settings.RUNSERVER:
+ i = child.expect([b'', 'Are you .*(yes/no)? ', pexpect.EOF], timeout=None)
+ if i == 0:
+ child.sendline('')
+ if i == 1:
+ child.sendline('yes')
+ child.expect(['password: '], timeout=timeout)
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ successful = True # Tentative
+ for line in all_lines.split("\n"):
+ logger.info(line)
+ if re.search('error', line, re.IGNORECASE):
+ successful = False
+ if re.search('unable', line, re.IGNORECASE):
+ successful = False
+ except:
+ logger.info(str(child))
+ logger.info("Status:" + str(successful))
+ return successful
+
diff --git a/src/orchestration/vendorhandler.py b/src/orchestration/vendorhandler.py
new file mode 100644
index 0000000..0285441
--- /dev/null
+++ b/src/orchestration/vendorhandler.py
@@ -0,0 +1,308 @@
+from django.conf import settings
+
+import os
+import pexpect
+import re
+import time
+import json
+
+from .utils import *
+from .remoteregionhandler import RemoteRegionWorker
+
+class Vendor(RemoteRegionWorker):
+
+ def __init__(self, logger, dbCoordinationQueue, vmbCoordinationQueue, doneQueue):
+ self.logger = logger
+ self.doneQueue = doneQueue
+ self.dbCoordinationQueue = dbCoordinationQueue
+ self.vmbCoordinationQueue = vmbCoordinationQueue
+ self.logger.info(".. created Vendor handler")
+
+ def perform_vendor_setup(self, data):
+ self.logger.info("Checking if any vendor setup needs to be done")
+ if 'namespace' in data:
+ namespace = data['namespace']
+ region_oam_ip = data['region_oam_ip']
+ network_setup = data['network_setup']
+
+ vendor = self._get_vendor(namespace, self.logger)
+ self.logger.info("Vendor:" + vendor)
+ self.logger.info("Network setup:" + network_setup)
+ self._perform_vendor_specific_actions(vendor, namespace, region_oam_ip, network_setup, self.logger)
+ self.logger.info("Done configuring vendor related things on the remote region")
+ else:
+ self.logger.info("Vendor setup not needed for this call.")
+ self._wait_and_done()
+
+ def _wait_and_done(self):
+ if self.dbCoordinationQueue != '' and self.vmbCoordinationQueue != '' and self.doneQueue != '':
+ self.logger.info("About to be done..waiting for cleanup")
+ db_coordination = self.dbCoordinationQueue.get()
+ self.logger.info(" DB coordination message:" + db_coordination)
+
+ vmb_coordination = self.vmbCoordinationQueue.get()
+ self.logger.info(" VMB coordination message:" + vmb_coordination)
+ self.doneQueue.put("Done")
+ self.logger.info("Done")
+
+ def _get_vendor(self, namespace, logger):
+ logger.info(" Inside _get_vendor")
+ parts = namespace.split('-')
+ logger.info("parts:" + str(parts))
+ vendor = "unknown"
+ if len(parts) >= 5:
+ if parts[3] == "ss" or parts[4] == "ss":
+ #vendor_shortform = parts[3]
+ #if vendor_shortform == "ss":
+ vendor = "Samsung"
+ return vendor
+
+ def _perform_vendor_specific_actions(self, vendor, namespace, region_oam_ip, network_setup, logger):
+ logger.info(" Inside _perform_vendor_specific_actions...")
+
+ if vendor == "Samsung":
+ logger.info(" Handling Samsung...")
+ samsung_provisioner = Samsung(logger)
+ samsung_provisioner.setup(namespace, region_oam_ip, network_setup)
+
+ def setup_network(self, region, logger):
+ logger.info(" Setting up network...")
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, logger)
+ logger.info(" Received OAM IP: " + region + " " + remote_region_oam_ip)
+ self.create_host_network(remote_region_oam_ip, logger)
+
+ def check_site(self, region, logger):
+ logger.info(" Checking network...")
+ remote_region_oam_ip = self._get_remote_region_oam_ip(region, logger)
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl describe nodes controller-0 --kubeconfig=/etc/kubernetes/admin.conf ")
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ #logger.info("Returned output lines:")
+ #logger.info(output_lines)
+ new_op_lines = []
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ if 'Connection to' not in line:
+ if 'Capacity:' in line or 'Allocatable:' in line or 'Allocated' in line or 'intel.com/pci_sriov_net' in line:
+ logger.info("LINE:" + line)
+ new_op_lines.append(line)
+
+ namespaces = self.check_namespaces(remote_region_oam_ip, logger)
+
+ #new_op_lines.append("----------")
+ #for namespace_line in namespaces.split("\n"):
+ # new_op_lines.append(namespace_line)
+
+ namespace_secrets = self.check_namespace_secrets(region, remote_region_oam_ip, logger)
+
+ namespace_service_accounts = self.check_namespace_serviceaccounts(region, remote_region_oam_ip, logger)
+
+ online_status = self.check_online_status(region, remote_region_oam_ip, logger)
+
+ return new_op_lines, namespaces, namespace_secrets, namespace_service_accounts, online_status
+
+class Samsung(RemoteRegionWorker):
+
+ def __init__(self, logger):
+ self.logger = logger
+ self.logger.info(".. created Samsung handler")
+
+ def setup(self, namespace, remote_region_oam_ip, network_setup):
+ self.logger.info(" Inside Samsung setup")
+ self._create_docker_reg_secret(namespace, remote_region_oam_ip, self.logger)
+ self._create_serviceaccount(namespace, remote_region_oam_ip, self.logger)
+ self._add_pac_crd_annotation(remote_region_oam_ip, self.logger)
+ self.logger.info(" Network setup:" + network_setup)
+ if network_setup == 'true':
+ self.create_host_network(remote_region_oam_ip, self.logger)
+
+ def _add_pac_crd_annotation(self, remote_region_oam_ip, logger):
+ logger.info("Inside _add_pac_crd_annotation")
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ cmd = " kubectl annotate --kubeconfig=/etc/kubernetes/admin.conf --overwrite crd network-attachment-definitions.k8s.cni.cncf.io "
+ cmd = cmd + " resource/annotation-relationship=\"on:Pod,key:k8s.v1.cni.cncf.io/networks,value:[{name:INSTANCE.metadata.name}]\""
+ cmd = ssh_prefix + cmd
+ logger.info("Annotation cmd:" + cmd)
+
+ cmds_annotate = []
+ cmds_annotate.append(cmd)
+ run_commands(cmds_annotate, host_password, logger, block=True)
+
+ def _create_host_network1(self, remote_region_oam_ip, logger):
+ logger.info("Inside _create_host_network")
+
+ #host_username = settings.HOST_CREDS['username']
+ #host_password = settings.HOST_CREDS['password']
+ #ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + remote_region_oam_ip
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get nodes controller-0 --kubeconfig=/etc/kubernetes/admin.conf -o json")
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ new_op_lines = ""
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines = new_op_lines + line + "\n"
+
+ allocatable_found = False
+ capacity_found = False
+ hugepg1G = "hugepages-1Gi"
+ hugepg2M = "hugepages-2Mi"
+ logger.info("New o/p lines:" + new_op_lines)
+ if len(new_op_lines) > 0:
+ json_op = json.loads(new_op_lines)
+ logger.info("JSON O/P:" + str(json_op))
+ status = json_op["status"]
+ logger.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")
+ logger.info("Status:" + str(status))
+ addresses = status["addresses"]
+ logger.info("###############################\n")
+ logger.info("Addresses:" + str(addresses))
+ allocatable = status["allocatable"]
+ logger.info("********************************\n")
+ logger.info("Allocatable:" + str(allocatable))
+ if hugepg1G in allocatable and hugepg2M in allocatable:
+ logger.info("Allocatable found..")
+ allocatable_found = True
+ capacity = status["capacity"]
+ if hugepg1G in capacity and hugepg2M in capacity:
+ logger.info("Capacity found..")
+ capacity_found = True
+
+ result = allocatable_found and capacity_found
+ logger.info("Create host network result:" + str(result))
+ return result
+
+ def _verify_host_network(self, ssh_prefix, basecmd, host_password, logger):
+ cmds = ["system host-unlock controller-0"]
+ while True:
+ unlock_wait = False
+ for cmd in cmds:
+ cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+ logger.info(cmd_to_run)
+ output_lines = self._run_command_get_all_lines([cmd_to_run], host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ logger.info("Line:" + line)
+ if not unlock_wait:
+ if 'retry host-unlock' in line or 'Rejected' in line:
+ logger.info("Need to wait to call host-unlock ##### ")
+ unlock_wait = True
+ break
+ if unlock_wait:
+ time.sleep(60)
+ else:
+ break
+
+ cmds = ["system host-show controller-0"]
+ system_available = False
+ while True:
+ for cmd in cmds:
+ cmd_to_run = ssh_prefix + " " + basecmd + " " + cmd
+ logger.info(cmd_to_run)
+ output_lines = self._run_command_get_all_lines([cmd_to_run], host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ if len(output_lines) > 0:
+ for line in output_lines.split("\n"):
+ logger.info("Line:" + line)
+ if 'available' in line:
+ logger.info("Found available #######")
+ system_available = True
+ break
+ if not system_available:
+ time.sleep(5)
+ else:
+ break
+
+ cmds = []
+ cmds.append(ssh_prefix + " kubectl get nodes controller-0 --kubeconfig=/etc/kubernetes/admin.conf -o json")
+
+ output_lines = self._run_command_get_all_lines(cmds, host_password, logger, block=True)
+ logger.info("Returned output lines:")
+ logger.info(output_lines)
+ new_op_lines = ""
+ for line in output_lines.split("\n"):
+ if not 'Connection to' in line:
+ new_op_lines = new_op_lines + line + "\n"
+
+ allocatable_found = False
+ capacity_found = False
+
+ f1c = 'intel.com/pci_sriov_net_f1c'
+ f1u = 'intel.com/pci_sriov_net_f1u'
+ fh0 = 'intel.com/pci_sriov_net_fh0'
+ fh0m = 'intel.com/pci_sriov_net_fh0m'
+ fh1 = 'intel.com/pci_sriov_net_fh1'
+ logger.info("New o/p lines:" + new_op_lines)
+ if len(new_op_lines) > 0:
+ json_op = json.loads(new_op_lines)
+ logger.info("JSON O/P:" + str(json_op))
+ status = json_op["status"]
+ logger.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")
+ logger.info("Status:" + str(status))
+ addresses = status["addresses"]
+ logger.info("###############################\n")
+ logger.info("Addresses:" + str(addresses))
+ allocatable = status["allocatable"]
+ logger.info("********************************\n")
+ logger.info("Allocatable:" + str(allocatable))
+ if f1c in allocatable and f1u in allocatable and fh0 in allocatable and fh0m in allocatable and fh1 in allocatable:
+ logger.info("Allocatable found..")
+ allocatable_found = True
+ capacity = status["capacity"]
+ if f1c in capacity and f1u in capacity and fh0 in capacity and fh0m in capacity and fh1 in capacity:
+ logger.info("Capacity found..")
+ capacity_found = True
+
+ result = allocatable_found and capacity_found
+ logger.info("Create host network result:" + str(result))
+ return result
+
+ def _create_serviceaccount(self, namespace, remote_region_oam_ip, logger):
+ logger.info("Inside _create_serviceaccount")
+
+ #host_username = settings.HOST_CREDS['username']
+ #host_password = settings.HOST_CREDS['password']
+ #ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + remote_region_oam_ip
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+
+ saName = 'vran-serviceaccount'
+ cmds_sa = []
+ cmds_sa.append(ssh_prefix + ' kubectl create serviceaccount --kubeconfig=/etc/kubernetes/admin.conf ' + saName + ' -n ' + namespace)
+ run_commands(cmds_sa, host_password, logger, block=True)
+
+ def _create_docker_reg_secret(self, namespace, remote_region_oam_ip, logger):
+ logger.info("Creating Docker registry secret in Namespace:" + namespace)
+
+ #host_username = settings.HOST_CREDS['username']
+ #host_password = settings.HOST_CREDS['password']
+ #ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t ' + host_username + '@' + remote_region_oam_ip
+
+ host_username, host_password, ssh_prefix = get_host_connection_details(remote_region_oam_ip)
+ dr_username, dr_password = get_docker_reg_connection_details()
+
+ #dr_username = settings.CENTRAL_DR_CREDS['username']
+ #dr_password = settings.CENTRAL_DR_CREDS['password']
+
+ secret_name = 'admin-registry-secret'
+ cmd = ssh_prefix + " kubectl create secret --kubeconfig=/etc/kubernetes/admin.conf docker-registry " + secret_name
+ cmd = cmd + " --docker-server=registry.local:9001 --docker-username=" + dr_username + " --docker-password=" + dr_password
+ cmd = cmd + " -n " + namespace
+
+ cmds = []
+ cmds.append(cmd)
+ run_commands(cmds, host_password, logger, block=True)
+
diff --git a/src/orchestration/views.py b/src/orchestration/views.py
index 4c3910c..cc15b3b 100644
--- a/src/orchestration/views.py
+++ b/src/orchestration/views.py
@@ -1,13 +1,198 @@
+import sys
+
+import django
+import django.db
from django.shortcuts import render
+from django.db.models import F
from django.core import serializers
from django.http import HttpResponse, JsonResponse
+from django.conf import settings
+from django.utils import timezone
+from django.views.decorators.csrf import csrf_exempt
+
+import json
+import threading
+import traceback
+import threading
+import logging
+import uuid
+import multiprocessing
+from multiprocessing import Process, Queue, SimpleQueue
+from http import HTTPStatus
+from pulsar.schema import *
+
from .models import ImageSync, RemoteRegionSetup
+from .imagesynchandler import ImageSyncWorker
+from .namespacehandler import NamespaceWorker
+from .remoteregionhandler import RemoteRegionWorker
+from .kubeconfighandler import KubeconfigGenerator
+from .vmb_producer import VMBProducer
+from .vmb_messages import ClusterStatus, ClusterStatusMessage
+from .db_updater import DBUpdater
+from .vmb_handler import VMBHandler
+from .vendorhandler import Vendor
+import datetime
+from .utils import *
+
+import os
+import signal
+
+
+def cleaner_thread(q, pid_list):
+ while True:
+ record = q.get()
+ if record == "Done":
+ for pid in pid_list:
+ print("Terminating process " + str(pid))
+ os.kill(pid, signal.SIGKILL)
+
+def logger_thread(q):
+ logger = logging.getLogger("orchestration")
+ level = logging.DEBUG
+ seen = []
+ while True:
+ record = q.get()
+ if record is None:
+ break
+ else:
+ logger.handle(record)
+
+def create_logger_and_start_thread():
+ logger = logging.getLogger("orchestration")
+ logger.propagate = False
+ return logger
+
+def setup_machinery(image_handling=False, namespace_handling=False, kubeconfig_handling=False):
+ kubeconfigQueue = None
+ namespaceQueue = None
+ requestQueue = None
+ doneQueue = None
+ pid_list = []
+ try:
+
+ doneQueue = Queue()
+
+ django.db.close_old_connections()
+ print("Hello::")
+ loggerQueue = Queue()
+ qh = logging.handlers.QueueHandler(loggerQueue)
+ logger = logging.getLogger(__name__)
+ logger.propagate = False
+ logger.addHandler(qh)
+
+ dbCoordinationQ = Queue()
+ dbQueue = Queue()
+ db_updater = DBUpdater(loggerQueue, dbQueue, dbCoordinationQ)
+ db_updater_p = Process(target=db_updater.run)
+ db_updater_p.start()
+ db_updater_pid = db_updater_p.pid
+ print("DB Updater PID:" + str(db_updater_pid))
+ pid_list.append(db_updater_pid)
+
+ vmbCoordinationQ = Queue()
+ vmbQueue = Queue()
+ vm_handler = VMBHandler(loggerQueue, vmbQueue, vmbCoordinationQ)
+ vm_handler_p = Process(target=vm_handler.run)
+ vm_handler_p.start()
+ vmb_handler_pid = vm_handler_p.pid
+ print("VMB Handler PID:" + str(vmb_handler_pid))
+ pid_list.append(vmb_handler_pid)
+
+ if image_handling:
+ requestQueue = Queue()
+ image_sync_handler = ImageSyncWorker(loggerQueue, requestQueue, dbQueue, vmbQueue, dbCoordinationQ, vmbCoordinationQ, doneQueue)
+ image_sync_handler_p = Process(target=image_sync_handler.run)
+ image_sync_handler_p.start()
+ image_sync_handler_pid = image_sync_handler_p.pid
+ print("Imagesync Handler PID:" + str(image_sync_handler_pid))
+ pid_list.append(image_sync_handler_pid)
+
+ if kubeconfig_handling:
+ kubeconfigQueue = Queue()
+ kubeconfig_handler = KubeconfigGenerator(loggerQueue, kubeconfigQueue, dbQueue, vmbQueue, dbCoordinationQ, vmbCoordinationQ, doneQueue)
+ kubeconfig_handler_p = Process(target=kubeconfig_handler.run)
+ kubeconfig_handler_p.start()
+ kubeconfig_handler_pid = kubeconfig_handler_p.pid
+ print("Kubeconfig Handler PID:" + str(kubeconfig_handler_pid))
+ pid_list.append(kubeconfig_handler_pid)
+
+ if namespace_handling:
+ namespaceQueue = Queue()
+ namespace_handler = NamespaceWorker(loggerQueue, namespaceQueue, kubeconfigQueue, dbQueue, vmbQueue, doneQueue)
+ namespace_handler_p = Process(target=namespace_handler.run)
+ namespace_handler_p.start()
+ namespace_handler_pid = namespace_handler_p.pid
+ print("Namespace Handler PID:" + str(namespace_handler_pid))
+ pid_list.append(namespace_handler_pid)
+
+ ct = threading.Thread(target=cleaner_thread, args=(doneQueue,pid_list,))
+ ct.start()
+
+ lp = threading.Thread(target=logger_thread, args=(loggerQueue,))
+ lp.start()
+ return logger, requestQueue, vmbQueue, kubeconfigQueue, namespaceQueue
+ except KeyboardInterrupt:
+ os.kill(db_updater_p.pid, signal.SIGKILL)
+ os.kill(image_sync_handler_p.pid, signal.SIGKILL)
+ os.kill(namespace_handler_p.pid, signal.SIGKILL)
+ os.kill(kubeconfig_handler_p.pid, signal.SIGKILL)
+ os.exit()
+# print(sys.argv)
+# if len(sys.argv) > 1 and (sys.argv[1] == "migrate" or sys.argv[1] == "makemigrations" or sys.argv[1] == "collectstatic"):
+# pass
+# else:
+# if ((len(sys.argv) > 1 and sys.argv[1] == "runserver") or not process_started):
+# if len(sys.argv) > 1 and sys.argv[1] == "runserver":
+# settings.RUNSERVER = True
+# print("settings.RUNSERVER: " + str(settings.RUNSERVER))
+# print("utils.process_started: " + str(process_started))
+# set_process_started()
+# try:
+# django.db.close_old_connections()
+# print("Hello::")
+# loggerQueue = Queue()
+# qh = logging.handlers.QueueHandler(loggerQueue)
+# logger = logging.getLogger(__name__)
+# logger.addHandler(qh)
+
+# dbQueue = Queue()
+# db_updater = DBUpdater(loggerQueue, dbQueue)
+# db_updater_p = Process(target=db_updater.run)
+# db_updater_p.start()
+
+# vmbQueue = Queue()
+# vm_handler = VMBHandler(loggerQueue, vmbQueue)
+# vm_handler_p = Process(target=vm_handler.run)
+# vm_handler_p.start()
+
+# requestQueue = Queue()
+# image_sync_handler = ImageSyncWorker(loggerQueue, requestQueue, dbQueue, vmbQueue)
+# image_sync_handler_p = Process(target=image_sync_handler.run)
+# image_sync_handler_p.start()
+
+# kubeconfigQueue = Queue()
+# kubeconfig_handler = KubeconfigGenerator(loggerQueue, kubeconfigQueue, dbQueue, vmbQueue)
+# kubeconfig_handler_p = Process(target=kubeconfig_handler.run)
+# kubeconfig_handler_p.start()
+
+# namespaceQueue = Queue()
+# namespace_handler = NamespaceWorker(loggerQueue, namespaceQueue, kubeconfigQueue, dbQueue, vmbQueue)
+# namespace_handler_p = Process(target=namespace_handler.run)
+# namespace_handler_p.start()
+
+# lp = threading.Thread(target=logger_thread, args=(loggerQueue,))
+# lp.start()
+# except KeyboardInterrupt:
+# os.kill(db_updater_p.pid, signal.SIGKILL)
+# os.kill(image_sync_handler_p.pid, signal.SIGKILL)
+# os.kill(namespace_handler_p.pid, signal.SIGKILL)
+# os.kill(kubeconfig_handler_p.pid, signal.SIGKILL)
+# os.exit()
def index(request):
return HttpResponse("Hello world")
-
def count(request, count):
data = {
'name': 'Vitor',
@@ -17,8 +202,387 @@ def count(request, count):
}
return JsonResponse(data)
-
+@csrf_exempt
def imagesync(request):
- rec = ImageSync.objects.order_by('-remote_region_name')
- return JsonResponse(serializers.serialize('json', rec), safe=False)
+ requestQueue = None
+ try:
+ logger.info("Inside imagesync")
+ except UnboundLocalError as error:
+ print(error)
+ logger, requestQueue, _, _, _ = setup_machinery(image_handling=True)
+ logger.info("Inside imagesync")
+
+ if request.method == 'GET':
+ rec = ImageSync.objects.order_by('-remote_region_name')
+ resp = serializers.serialize('json', rec)
+ data = json.loads(resp)
+ fields = data[0]['fields']
+ item = None # TODO: query database
+ fields['data'] = item
+ return JsonResponse(fields)
+ elif request.method == 'POST':
+ body_unicode = request.body.decode('utf-8')
+ body_data = json.loads(body_unicode)
+ transaction_id = str(uuid.uuid4()).replace("-", "")
+ imageList = body_data['images']
+ regionList = body_data['remoteRegions']
+ regionImagePairList = get_pairs(imageList, regionList)
+ for rI in regionImagePairList:
+ region = rI['region']
+ image = rI['image']
+ imageSyncReq = ImageSync(remote_region_name=region,
+ docker_image=image,
+ transaction_id=str(transaction_id),
+ upload_start_time=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ )
+ imageSyncReq.save()
+ body_data['transaction_id'] = transaction_id
+ body_data['command'] = 'upload'
+ requestQueue.put(body_data)
+
+ data = {
+ 'transaction_id': transaction_id
+ }
+ return JsonResponse(data, status=HTTPStatus.ACCEPTED)
+
+def get_status_by_transaction_id(request, transaction_id):
+ """
+ To-do: add error handling and logging
+ """
+ if request.method == 'GET':
+ value_list = ImageSync.objects.filter(transaction_id=transaction_id).values_list('remote_region_name',flat=True).distinct()
+ remote_regions = []
+ for value in value_list:
+ remote_region = {}
+ remote_region['remoteRegion'] = value
+ remote_region['images'] = list(ImageSync.objects.filter(
+ transaction_id=transaction_id, remote_region_name=value).values(image=F('docker_image'), status=F('upload_status'), message=F('upload_message'),
+ start_time=F('upload_start_time'), end_time=F('upload_end_time')))
+ remote_regions.append(remote_region)
+
+ return_json = {}
+ return_json['transaction_id'] = transaction_id
+ return_json['imagesStatus'] = remote_regions
+ return JsonResponse(return_json, status=HTTPStatus.OK)
+
+def get_status_by_remote_region(request, remote_region):
+ """
+ /regions/{remoteRegionId}/status Get remote region image status for a given region id
+ """
+ if request.method == 'GET':
+ value_list = ImageSync.objects.filter(remote_region_name=remote_region).values_list('docker_image',flat=True).distinct()
+ image_list = []
+ for value in value_list:
+ q_list = list(ImageSync.objects.filter(remote_region_name=remote_region, docker_image=value).order_by('-upload_start_time')[:1].values(image=F('docker_image'), status=F('upload_status'), message=F('upload_message'), start_time=F('upload_start_time'), end_time=F('upload_end_time')))
+ image_list += q_list
+
+ return_json = {}
+ return_json['remoteRegion'] = remote_region
+ return_json['images'] = image_list
+ return JsonResponse(return_json, status=HTTPStatus.OK)
+
+def get_status_by_image_name_and_remote_region(request, image_name, remote_region):
+ """
+ /regions/{remoteRegionId}/images/{imageName}/status Get remote region image status for a given image name
+ """
+ logger = create_logger_and_start_thread()
+ if request.method == 'GET':
+ #image_status = list(ImageSync.objects.filter(remote_region_name=remote_region, docker_image=image_name).order_by('-upload_start_time')[:1].values(imageName=F('docker_image'), imageUploadStatus=F('upload_status'), imageUploadMessage=F('upload_message'),imageUploadTime=F('upload_start_time'), remoteRegionName=F('remote_region_name')))
+ image_sync_handler = ImageSyncWorker('', '', '', '', '', '', '')
+ logger.info("Finding image tags on a sub-cloud..." + image_name + " " + remote_region)
+ image_tags_list = image_sync_handler.get_image_tags(remote_region, image_name, logger)
+ return_json = {}
+ return_json['remoteRegion'] = remote_region
+ return_json['image'] = image_name
+ return_json['tags'] = image_tags_list
+ return JsonResponse(return_json, safe=False, status=HTTPStatus.OK)
+
+@csrf_exempt
+def delete_image_tag_remote_region(request, image_name, image_tag, remote_region):
+ """
+ /regions/{remoteRegionId}/images/{imageName}/{imageTag} Get remote region image status for a given image name
+ """
+ logger = create_logger_and_start_thread()
+ if request.method == 'DELETE':
+ image_sync_handler = ImageSyncWorker('', '', '', '', '', '', '')
+ logger.info("Deleting image tag on a sub-cloud..." + image_name + " " + image_tag + " " + remote_region)
+ output = image_sync_handler.delete_image_tag(remote_region, image_name, image_tag, logger)
+ logger.info("O/P:")
+ logger.info(output)
+ return_json = {}
+ return_json['remoteRegion'] = remote_region
+ return_json['image'] = image_name
+ return_json['tag'] = image_tag
+ return_json['output'] = output
+ logger.info(return_json)
+ return JsonResponse(return_json, safe=False, status=HTTPStatus.OK)
+
+@csrf_exempt
+def setup_cluster(request, cluster_name):
+ namespaceQueue = None
+ vmbQueue = None
+ try:
+ logger.info("Inside setup_cluster")
+ except UnboundLocalError as error:
+ print(error)
+ logger, _, vmbQueue, kubeconfigQueue, namespaceQueue = setup_machinery(kubeconfig_handling=True, namespace_handling=True)
+ logger.info("Inside setup_cluster. Cluster name:" + cluster_name)
+ remote_region_worker = RemoteRegionWorker()
+ site_name, site_location = remote_region_worker.get_fuze_spm_site_details(cluster_name, logger)
+
+ # WR CAAS version; CAAS availability; deploy_status
+ caasversion = '20.06'
+ if 'caasversion' in request.GET:
+ caasversion = request.GET['caasversion']
+
+ availability = 'ONLINE'
+ if 'availability' in request.GET:
+ availability = request.GET['availability']
+
+ deploystatus = 'COMPLETE'
+ if 'deploystatus' in request.GET:
+ deploystatus = request.GET['deploystatus']
+
+ created_at = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ if 'created_at' in request.GET:
+ created_at = request.GET['created_at']
+
+ updated_at = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ logger.info("CaaS Version:" + caasversion + " Availability:" + availability + " DeployStatus:" + deploystatus)
+ logger.info("Created at:" + str(created_at) + " Updated at:" + str(updated_at))
+
+ network_setup = 'true'
+ if 'network_setup' in request.GET:
+ network_setup = request.GET['network_setup']
+
+ message = {}
+ cluster_status = {}
+ cluster_status['name'] = cluster_name
+ cluster_status['description'] = site_name
+ cluster_status['location'] = site_location
+ cluster_status['software_version'] = caasversion
+ cluster_status['availability'] = availability
+ cluster_status['deploy_status'] = deploystatus
+ cluster_status['created_at'] = created_at
+ cluster_status['updated_at'] = updated_at
+ message['cluster_status'] = [cluster_status]
+ message['network_setup'] = network_setup
+ logger.info(message)
+ namespaceQueue.put(message)
+ logger.info("Done putting message on Namespace Queue")
+
+ # Create vmb object
+ vmbProducer = VMBProducer()
+
+ # Dump request obj to vmb object
+ cluster_status_message = ClusterStatusMessage()
+ cluster_status_message.reportName = 'vcp_fe_cluster_status'
+ cluster_status_message.reportGeneratedOn = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ cluster_status_message.reportDescription = site_name
+ cluster_status_list = []
+ cluster_status_vmb = ClusterStatus()
+ cluster_status_vmb.name = cluster_name
+ cluster_status_vmb.description = site_name
+ cluster_status_vmb.location = site_location
+ cluster_status_vmb.software_version = caasversion
+ cluster_status_vmb.availability = availability
+ cluster_status_vmb.deploy_status = deploystatus
+ cluster_status_vmb.created_at = created_at
+ cluster_status_vmb.updated_at = updated_at
+ cluster_status_list.append(cluster_status_vmb)
+ cluster_status_message.reportDataRows = cluster_status_list
+ cluster_status_message.rowCount = 1
+
+ # Send to VMB
+ logger.info("Putting message on VMB Queue")
+ item = {}
+ item['message'] = 'ClusterStatus'
+ item['payload'] = cluster_status_message
+ vmbQueue.put(item)
+ logger.info("Done putting message on VMB Queue")
+
+ return_json = {}
+ return_json['cluster_name'] = cluster_name
+ return JsonResponse(return_json, status=HTTPStatus.OK)
+
+@csrf_exempt
+def caas_status(request):
+ '''
+ /caas-status Send CaaS readiness message to VMB
+
+ expected: request payload:
+
+ {"cluster_status": [
+ {
+ "name": "wsbomagj-d654321-001",
+ "description": "NE CONCORD 8_N",
+ "location": 654321,
+ "software_version": "20.06",
+ "availability": "ONLINE",
+ "deploy_status": "COMPLETE",
+ "created_at": "2020-01-07 04:16:15.743617",
+ "updated_at": "2020-01-07 04:16:15.743617"
+ }
+ ]
+ }
+ '''
+ namespaceQueue = None
+ vmbQueue = None
+ try:
+ logger.info("Inside caas_status")
+ except UnboundLocalError as error:
+ print(error)
+ logger, _, vmbQueue, kubeconfigQueue, namespaceQueue = setup_machinery(kubeconfig_handling=True, namespace_handling=True)
+ if request.method == 'POST':
+ message_json = json.loads(request.body)
+ logger.info("Inside caas_status")
+ logger.info(message_json)
+
+ # Trigger Namespace creation
+ message_json['network_setup'] = 'true'
+ namespaceQueue.put(message_json)
+
+ remote_region_worker = RemoteRegionWorker()
+
+ # 1. create vmb object
+ vmbProducer = VMBProducer()
+
+ # 2. dump request obj to vmb object
+ cluster_status_message = ClusterStatusMessage()
+ cluster_status_message.reportName = 'vcp_fe_cluster_status'
+ cluster_status_message.reportGeneratedOn = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+ try:
+ regions_list = message_json['cluster_status']
+ cluster_status_message.rowCount = len(regions_list)
+ cluster_status_list = []
+ for i, region in enumerate(regions_list):
+ cluster_status = ClusterStatus()
+ for k in region:
+ if k == 'name':
+ cluster_status.name = region[k]
+ site_name, site_location = remote_region_worker.get_fuze_spm_site_details(cluster_status.name, logger)
+ if k == 'software_version':
+ cluster_status.software_version = region[k]
+ if k == 'availability':
+ cluster_status.availability = region[k]
+ if k == 'deploy_status':
+ cluster_status.deploy_status = region[k]
+ if k == 'created_at':
+ cluster_status.created_at = region[k]
+ if k == 'updated_at':
+ cluster_status.updated_at = region[k]
+
+ cluster_status.description = site_name
+ cluster_status_message.reportDescription = site_name
+ cluster_status.location = site_location
+ cluster_status_list.append(cluster_status)
+ cluster_status_message.reportDataRows = cluster_status_list
+ except KeyError:
+ return HttpResponse("Malformed data!", HTTPStatus.BAD_REQUEST)
+
+ # 3. send to VMB
+ item = {}
+ item['message'] = 'ClusterStatus'
+ item['payload'] = cluster_status_message
+ vmbQueue.put(item)
+
+ return HttpResponse(status=HTTPStatus.OK)
+
+def get_kubeconfig_by_remote_region(request, remote_region):
+ '''
+ /regions/{remoteRegionId}/connection-details get remote region kubeconfig by remote region id
+
+ '''
+ namespaceQueue = None
+ vmbQueue = None
+ try:
+ logger.info("Inside get_kubeconfig_by_remote_region")
+ except UnboundLocalError as error:
+ print(error)
+ logger, _, vmbQueue, kubeconfigQueue, namespaceQueue = setup_machinery(kubeconfig_handling=True)
+ if request.method == 'GET':
+ logger.info("Inside get_kubeconfig_by_remote_region")
+ request_data = {}
+ transaction_id = str(uuid.uuid4()).replace("-", "")
+
+ kubeconfigReq = RemoteRegionSetup(remote_region_name=remote_region,
+ transaction_id=str(transaction_id))
+ kubeconfigReq.save()
+
+ #team = 'orchestration' # default
+ team = '' #default
+ if 'team' in request.GET:
+ team = request.GET['team']
+ #print("TEAM:" + team)
+ team = team + "-team"
+ print("Kubeconfig for TEAM:" + team)
+
+ kubeconfig_approach = 'SA'
+ if 'kubeconfig_approach' in request.GET:
+ kubeconfig_approach = request.GET['kubeconfig_approach']
+
+ print("Kubeconfig approach:" + kubeconfig_approach)
+ logger.info("Kubeconfig for:" + team + " using approach:" + kubeconfig_approach)
+
+ request_data['remote_region'] = remote_region
+ request_data['transaction_id'] = transaction_id
+ request_data['kubeconfig_for'] = team
+ request_data['kubeconfig_approach'] = kubeconfig_approach
+ kubeconfigQueue.put(request_data)
+
+ data = {
+ 'transaction_id': transaction_id
+ }
+ return JsonResponse(data, status=HTTPStatus.ACCEPTED)
+
+
+def get_kubeconfig_by_transaction_id(request, remote_region, transaction_id):
+ """
+ /regions/{remoteRegionId}/connection-details/{transactionId} get remote region kubeconfig by transactionId
+ """
+ if request.method == 'GET':
+ remote_region_setup_list = list(RemoteRegionSetup.objects.filter(transaction_id=transaction_id).values(kube_config=F('kubeconfig'), namespace=F('kubernetes_namespace'), service_account=F('serviceaccount'), kubeconfig_gen_status=F('status'), kubeconfig_gen_message=F('message')))
+
+ return_json = {}
+ return_json['transaction_id'] = transaction_id
+ return_json['remote_region'] = remote_region
+ return_json['remote_region_setup'] = remote_region_setup_list
+ return JsonResponse(return_json, status=HTTPStatus.OK)
+
+def get_setup_details_by_remote_region(request, remote_region):
+ '''
+ /regions/{remoteRegionId}/setup-details get remote region info by remote region id
+ '''
+ logger = create_logger_and_start_thread()
+ if request.method == 'GET':
+ logger.info("Finding network status for " + remote_region)
+ vendor_provisioner = Vendor(logger, '', '', '')
+ network_details, namespaces, ns_secrets, ns_service_accounts, online_status = vendor_provisioner.check_site(remote_region, logger)
+
+ setup_details = {}
+ setup_details['network_details'] = network_details
+ setup_details['namespaces'] = namespaces
+ setup_details['ns_secrets'] = ns_secrets
+ setup_details['ns_service_accounts'] = ns_service_accounts
+ setup_details['online_status'] = online_status
+
+ return_json = {}
+ return_json['remoteRegion'] = remote_region
+ return_json['setup_details'] = setup_details
+ return_json['caasVendorVersion'] = '20.06'
+ return_json['kubernetesVersion'] = '1.16'
+ return_json['helmVersion'] = '3.0'
+ return JsonResponse(return_json, status=HTTPStatus.OK)
+
+@csrf_exempt
+def setup_network_for_remote_region(request, remote_region):
+ logger = create_logger_and_start_thread()
+ if request.method == 'PUT':
+ logger.info("Setting up network for " + remote_region)
+ vendor_provisioner = Vendor(logger, '', '', '')
+ vendor_provisioner.setup_network(remote_region, logger)
+ data = {
+ 'remote_region': remote_region,
+ }
+ return JsonResponse(data, status=HTTPStatus.ACCEPTED)
diff --git a/src/orchestration/vmb_consumer.py b/src/orchestration/vmb_consumer.py
new file mode 100644
index 0000000..3d6ebe6
--- /dev/null
+++ b/src/orchestration/vmb_consumer.py
@@ -0,0 +1,88 @@
+import os
+import pprint
+
+from django.conf import settings
+from pulsar import Client, AuthenticationTLS
+from .vmb_messages import *
+
+class VMBConsumer(object):
+
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'src.far_edge_ops_api.settings')
+
+ TLS_CERT_FILE = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_CERT_FILE"])
+ TLS_KEY_FILE = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_KEY_FILE"])
+ TLS_TRUST_CERTS_FILE_PATH = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_TRUST_CERTS_FILE_PATH"])
+
+ # Each environment(Rocklin, Branchburg, AWS Non Prod/PLE/STG/PROD has its own service url
+ SERVICE_URL = settings.VMB['SERVICE_URL']
+ #SERVICE_URL = "pulsar://localhost:6650/"
+
+ auth = AuthenticationTLS(TLS_CERT_FILE,TLS_KEY_FILE)
+
+ def get_topic_message(self, topic, topic_schema):
+
+ client = Client(self.SERVICE_URL, tls_trust_certs_file_path=self.TLS_TRUST_CERTS_FILE_PATH,
+ tls_allow_insecure_connection=False, authentication=self.auth)
+ #client = Client(SERVICE_URL)
+
+ topic = settings.VMB["VMB_TOPICS"][topic]
+ consumer = client.subscribe(topic=topic, subscription_name='sub-2', schema=JsonSchema(eval(topic_schema)))
+ pp = pprint.PrettyPrinter(indent=4)
+ try:
+ while True:
+ msg = consumer.receive()
+ ex = msg.value()
+ try:
+ print("Received message {}".format(ex))
+ consumer.acknowledge(msg)
+ except:
+ # Message failed to be processed
+ consumer.negative_acknowledge(msg)
+ except KeyboardInterrupt:
+ pass
+
+ client.close()
+
+import sys, getopt
+
+def main(argv):
+ try:
+ opts, args = getopt.getopt(argv,"ht:",["topic="])
+ except getopt.GetoptError:
+ print('vmb_consumer.py -t <topic>')
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print('vmb_consumer.py -t [cluster_status|namespace|kubeconfig|image_status>')
+ sys.exit()
+ elif opt in ("-t", "--topic"):
+ topic = arg
+ print('topic is ' + topic)
+
+ vmbConsumer = VMBConsumer()
+
+
+ if (topic == 'namespace'):
+ # 1. Namespace Creation
+ print('subscribe to namespace topic ...')
+ vmbConsumer.get_topic_message(topic='TOPIC_NAMESPACE_CREATION', topic_schema='NameSpaceMessage')
+
+ elif(topic == 'cluster_status'):
+ # 2. Cluster Status
+ print('subscribe to cluster_status topic ...')
+ vmbConsumer.get_topic_message(topic='TOPIC_CLUSTER_STATUS', topic_schema='ClusterStatusMessage')
+
+ elif(topic == 'kubeconfig'):
+ # 3. Kubeconfig Token
+ print('subscribe to kubeconfig topic ...')
+ vmbConsumer.get_topic_message(topic='TOPIC_KUBECONFIG_TOKEN', topic_schema='KubeconfigMessage')
+
+ elif(topic == 'image_status'):
+ # 4. Images Status
+ print('subscribe to image_status topic ...')
+ vmbConsumer.get_topic_message(topic='TOPIC_IMAGE_STATUS', topic_schema='ImagesStatusMessage')
+ else:
+ print('unsupport topic: ' + topic)
+
+if __name__ == "__main__":
+ main(sys.argv[1:]) \ No newline at end of file
diff --git a/src/orchestration/vmb_handler.py b/src/orchestration/vmb_handler.py
new file mode 100644
index 0000000..fc003f2
--- /dev/null
+++ b/src/orchestration/vmb_handler.py
@@ -0,0 +1,52 @@
+import django
+
+from django.conf import settings
+from django.core.exceptions import AppRegistryNotReady
+from django.db import transaction
+import logging
+from logging.handlers import QueueHandler
+
+from .vmb_producer import VMBProducer
+
+try:
+ django.setup()
+ from .models import ImageSync, CentralToRemoteMap, RemoteRegionSetup
+except django.core.exceptions.AppRegistryNotReady as exp:
+ pass
+
+class VMBHandler():
+
+ def __init__(self, loggerQueue, vmbQueue, coordinateQueue):
+ self.loggerQueue = loggerQueue
+ self.vmbQueue = vmbQueue
+ self.coordinateQueue = coordinateQueue
+
+ def run(self):
+ qh = QueueHandler(self.loggerQueue)
+ self.logger = logging.getLogger()
+ self.logger.addHandler(qh)
+ self.logger.setLevel(logging.DEBUG)
+ self.vmbProducer = VMBProducer()
+
+ self.logger.info("VMBHandler started...")
+ while True:
+ #self.logger.info("--------------------------")
+ item = self.vmbQueue.get()
+ self._send_message(item)
+
+ def _send_message(self, item):
+ message = item['message']
+ if message == 'ImageStatus':
+ images_status_message = item['payload']
+ self.vmbProducer.images_status(images_status_message)
+ self.coordinateQueue.put("Done")
+ if message == 'Kubeconfig':
+ kubeconfig_message = item['payload']
+ self.vmbProducer.kubeconfig(kubeconfig_message)
+ if message == 'Namespace':
+ namespace_message = item['payload']
+ self.vmbProducer.namespace_creation(namespace_message)
+ if message == 'ClusterStatus':
+ cluster_status_message = item['payload']
+ self.vmbProducer.cluster_status(cluster_status_message)
+ return
diff --git a/src/orchestration/vmb_messages.py b/src/orchestration/vmb_messages.py
new file mode 100644
index 0000000..515dc03
--- /dev/null
+++ b/src/orchestration/vmb_messages.py
@@ -0,0 +1,174 @@
+# -*- coding: UTF-8 -*-
+from pulsar.schema import *
+
+'''
+Topic - VCP Far Edge Namespace Provisioning
+Payload
+Format: JSON
+Example Content:
+{
+ "reportName": “vcp_fe_namespace”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ "cluster”: "wsbomagj-d654321-001",
+ “namespace”: “WSBOMAGJ-441352VZWcVDU-Y-SM-x-001”
+ “location”: 654321
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+}
+'''
+class RemoteRegion(Record):
+ cluster = String()
+ namespace = String()
+ location = String()
+ created_at = String()
+
+class NameSpaceMessage(Record):
+ reportName = String()
+ reportDescription = String()
+ reportGeneratedOn = String()
+ rowCount = Integer()
+ reportDataRows = Array(RemoteRegion())
+
+'''
+Topic - VCP Far Edge Cluster Status
+Payload
+Format: JSON
+Example Content:
+{
+ "reportName": “vcp_fe_cluster_status”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ “name”: "wsbomagj-d654321-001",
+ “description”: NE CONCORD 8_NH
+ “location”: 654321
+ “software version”: 19.12
+ “availability”: online
+ “deploy_status”: complete
+ “created_at”: 2020-01-07 04:16:15.743617
+ “updated_at”: 2020-01-07 06:03:10.854598
+ }]
+}
+'''
+class ClusterStatus(Record):
+ name = String()
+ description = String()
+ software_version = String()
+ location = String()
+ availability = String()
+ deploy_status = String()
+ created_at = String()
+ updated_at = String()
+
+class ClusterStatusMessage(Record):
+ reportName = String()
+ reportDescription = String()
+ reportGeneratedOn = String()
+ rowCount = Integer()
+ reportDataRows = Array(ClusterStatus())
+
+'''
+Topic - VCP Far Edge Kubeconfig Token
+Payload
+Format: JSON
+Example Content:
+{
+ "reportName": “vcp_fe_kubeconfig”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ “cluster”: "wsbomagj-d654321-001",
+ “namespace”: “WSBOMAGJ-441352VZWcVDU-Y-SM-x-001”
+ “location”: 654321
+ “kubeconfig”: <Example kubeconfig>
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+}
+---
+Example kubeconfig:
+
+apiVersion: v1
+kind: Config
+users:
+- name: ldap-user
+ user:
+ token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJub2tpYSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJzYTEtdG9rZW4tbW5mMmoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2ExIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTA5YTkwMDItMTg0Zi0xMWVhLWE5NzYtMDgwMDI3YTFjODc3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Om5va2lhOnNhMSJ9.e9uD5kMmAT0HRboSTAbH5xlkETkltLclVQ2GedvoeUmH76WB6G5kGWQrhJjkjpMtPDKxWp6wTzZdEXXwGYGdB6aXbaxcAmau1qid5NGz725BtaoRbSVS2Uk6XrOSNfycFzqc8Z7GTX81VtKKSPYnjeMo47W6FqHw6qk0NEpLLxbpGfJHz8w2KZQiuvI-JRQXtA3PHW3tEaWq3ME3XnYgHNSRJmoiKA99bWjN-HKoOsBDMjhX7kw_VtycRYJ1gbqXmVsSl7BuAQjoplnLN_stRt7ZgpsV4aqmZueqJqHaglB91XOeqe_JcD5unLMb3B5VXpEXPp3V6tjLIyVD8F8XbA
+clusters:
+- cluster:
+ server: https://<IPv6>:8443
+ name: wsbomagj-d654321-001
+contexts:
+- context:
+ cluster: wsbomagj-d654321-001
+ user: ldap-user
+ namespace: WSBOMAGJ-441352VZWcVDU-Y-SM-x-001
+ name: ldap-user
+current-context: ldab-user
+'''
+class Kubeconfig(Record):
+ transactionId = String()
+ cluster = String()
+ namespace = String()
+ location = String()
+ kubeconfig = String()
+ created_at = String()
+ updated_at = String()
+
+class KubeconfigMessage(Record):
+ reportName = String()
+ reportDescription = String()
+ reportGeneratedOn = String()
+ rowCount = Integer()
+ reportDataRows = Array(Kubeconfig())
+
+'''
+Topic - VCP Far Edge Image Status
+Payload
+Format: JSON
+Example Content:
+
+Image Upload Example
+{
+ "reportName": “vcp_fe_imagestatus”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 2,
+ "reportDataRows": [{
+ “cluster”: "wsbomagj-d654321-001",
+ “image”: “i1”
+ “action”: “UPLOAD”
+ “status”: “SUCCESS”
+ “message”:”Image Successfully Deleted”
+ “created_at”: 2020-01-07 04:16:15.743617
+ }
+ {
+ “cluster”: "wsbomagj-d654321-001",
+ “image”: “i2”
+ “action”: “UPLOAD”
+ “status”: “FAILED”
+ “message”:”Image Not Present in Artifactory”
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+}
+'''
+class ImageStatus(Record):
+ cluster = String()
+ image = String()
+ action = String()
+ status = String()
+ message = String()
+ created_at = String()
+
+class ImagesStatusMessage(Record):
+ reportName = String()
+ transactionId = String()
+ reportDescription = String()
+ reportGeneratedOn = String()
+ rowCount = Integer()
+ reportDataRows = Array(ImageStatus())
+
diff --git a/src/orchestration/vmb_producer.py b/src/orchestration/vmb_producer.py
new file mode 100644
index 0000000..709b193
--- /dev/null
+++ b/src/orchestration/vmb_producer.py
@@ -0,0 +1,329 @@
+import sys, getopt
+import yaml
+import os
+
+from django.conf import settings
+from .vmb_messages import *
+from pulsar import Client, AuthenticationTLS
+
+class VMBProducer(object):
+
+ #os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'src.far_edge_ops_api.settings')
+
+ TLS_CERT_FILE = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_CERT_FILE"])
+ TLS_KEY_FILE = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_KEY_FILE"])
+ TLS_TRUST_CERTS_FILE_PATH = os.path.join(settings.VMB["CERTS_PATH"], settings.VMB["TLS_TRUST_CERTS_FILE_PATH"])
+
+ #TLS_CERT_FILE = os.path.join(os.path.dirname(__file__), settings.VMB["TLS_CERT_FILE"])
+ #TLS_KEY_FILE = os.path.join(os.path.dirname(__file__), settings.VMB["TLS_KEY_FILE"])
+ #TLS_TRUST_CERTS_FILE_PATH = os.path.join(os.path.dirname(__file__), settings.VMB["TLS_TRUST_CERTS_FILE_PATH"])
+
+ # Each environment(Rocklin, Branchburg, AWS Non Prod/PLE/STG/PROD has its own service url
+ SERVICE_URL = settings.VMB['SERVICE_URL']
+ #SERVICE_URL = "pulsar://localhost:6650/"
+
+ auth = AuthenticationTLS(TLS_CERT_FILE,TLS_KEY_FILE)
+
+ def vmb_client(self, topic, schema_name, message):
+ client = Client(self.SERVICE_URL, tls_trust_certs_file_path=self.TLS_TRUST_CERTS_FILE_PATH, tls_allow_insecure_connection=False, authentication=self.auth)
+
+ topic = settings.VMB["VMB_TOPICS"][topic]
+ print("topic: " + topic)
+ producer = client.create_producer(topic=topic, schema=JsonSchema(eval(schema_name)))
+ consumer = client.subscribe(topic=topic, subscription_name='sub-fe', schema=JsonSchema(eval(schema_name)))
+ producer.send(message)
+
+ msg = consumer.receive()
+
+ try:
+ print("VMB-received message: %s" % (msg.value()))
+ consumer.acknowledge(msg)
+ except:
+ # Message failed to be processed
+ consumer.negative_acknowledge(msg)
+
+ producer.close()
+ client.close()
+
+
+ def namespace_creation(self, message):
+ '''
+ Payload
+ Format: JSON
+ Example Content:
+ {
+ "reportName": “vcp_fe_namespace”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ “cluster”: "wsbomagj-d654321-001",
+ “namespace”: “WSBOMAGJ-441352VZWcVDU-Y-SM-x-001”
+ “location”: 654321
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+ }
+ '''
+
+ self.vmb_client("TOPIC_NAMESPACE_CREATION", "NameSpaceMessage", message)
+
+ def cluster_status(self, message):
+ '''
+ Format: JSON
+ Example Content:
+
+ {
+ "reportName": “vcp_fe_cluster_status”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [
+ {
+ “name”: "wsbomagj-d654321-001",
+ “description”: NE CONCORD 8_NH
+ “location”: 654321
+ “software version”: 19.12
+ “availability”: online
+ “deploy_status”: complete
+ “created_at”: 2020-01-07 04:16:15.743617
+ “updated_at”: 2020-01-07 06:03:10.854598
+ }
+ ]
+ '''
+
+ self.vmb_client("TOPIC_CLUSTER_STATUS", "ClusterStatusMessage", message)
+
+ def kubeconfig(self, message):
+ '''
+ Topic - VCP Far Edge Kubeconfig Token
+ Payload
+ Format: JSON
+ Example Content:
+ {
+ "reportName": “vcp_fe_kubeconfig”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 1,
+ "reportDataRows": [{
+ “cluster”: "wsbomagj-d654321-001",
+ “namespace”: “WSBOMAGJ-441352VZWcVDU-Y-SM-x-001”
+ “location”: 654321
+ “kubeconfig”: <Example kubeconfig>
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+ }
+ ---
+ Example kubeconfig:
+
+ apiVersion: v1
+ kind: Config
+ users:
+ - name: ldap-user
+ user:
+ token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJub2tpYSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJzYTEtdG9rZW4tbW5mMmoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2ExIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTA5YTkwMDItMTg0Zi0xMWVhLWE5NzYtMDgwMDI3YTFjODc3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Om5va2lhOnNhMSJ9.e9uD5kMmAT0HRboSTAbH5xlkETkltLclVQ2GedvoeUmH76WB6G5kGWQrhJjkjpMtPDKxWp6wTzZdEXXwGYGdB6aXbaxcAmau1qid5NGz725BtaoRbSVS2Uk6XrOSNfycFzqc8Z7GTX81VtKKSPYnjeMo47W6FqHw6qk0NEpLLxbpGfJHz8w2KZQiuvI-JRQXtA3PHW3tEaWq3ME3XnYgHNSRJmoiKA99bWjN-HKoOsBDMjhX7kw_VtycRYJ1gbqXmVsSl7BuAQjoplnLN_stRt7ZgpsV4aqmZueqJqHaglB91XOeqe_JcD5unLMb3B5VXpEXPp3V6tjLIyVD8F8XbA
+ clusters:
+ - cluster:
+ server: https://<IPv6>:8443
+ name: wsbomagj-d654321-001
+ contexts:
+ - context:
+ cluster: wsbomagj-d654321-001
+ user: ldap-user
+ namespace: WSBOMAGJ-441352VZWcVDU-Y-SM-x-001
+ name: ldap-user
+ current-context: ldab-user
+ '''
+ self.vmb_client("TOPIC_KUBECONFIG_TOKEN", "KubeconfigMessage", message)
+
+ def images_status(self, message):
+ '''
+ Topic - VCP Far Edge Image Status
+ Payload
+ Format: JSON
+ Example Content:
+
+ Image Upload Example
+ {
+ "reportName": “vcp_fe_imagestatus”,
+ "reportDescription": null,
+ "reportGeneratedOn": "2020-04-28T09:06:32.1962088-04:00",
+ "rowCount": 2,
+ "reportDataRows": [{
+ “cluster”: "wsbomagj-d654321-001",
+ “image”: “i1”
+ “action”: “UPLOAD”
+ “status”: “SUCCESS”
+ “message”:”Image Successfully Deleted”
+ “created_at”: 2020-01-07 04:16:15.743617
+ }
+ {
+ “cluster”: "wsbomagj-d654321-001",
+ “image”: “i2”
+ “action”: “UPLOAD”
+ “status”: “FAILED”
+ “message”:”Image Not Present in Artifactory”
+ “created_at”: 2020-01-07 04:16:15.743617
+ }]
+ }
+ '''
+
+ self.vmb_client("TOPIC_IMAGE_STATUS", "ImagesStatusMessage", message)
+
+def main(argv):
+ try:
+ opts, args = getopt.getopt(argv,"ht:i:",["topic=","ifile="])
+ except getopt.GetoptError:
+ print('vmb_producer.py -t <topic> -i <inputfile>')
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print('vmb_producer.py -t [cluster_status|namespace|kubeconfig|image_status> -i <inputfile>')
+ sys.exit()
+ elif opt in ("-t", "--topic"):
+ topic = arg
+ print('topic is ' + topic)
+ elif opt in ("-i", "--ifile"):
+ inputfile = arg
+ print('Input file is ' + inputfile)
+
+
+ vmbProducer = VMBProducer()
+
+ f = open (inputfile, "r")
+ message_json = json.loads(f.read())
+ print(message_json)
+
+ if (topic == 'namespace'):
+ # 1. Namespace Creation
+ print('return namespace to vmb ...')
+ remote_region = RemoteRegion(cluster='wsbomagj',
+ namespace='WSBOMAGcluster',
+ location='CILI654321',
+ created_at='2020-01-07 04:16:15.743617')
+ remote_region1 = RemoteRegion(cluster='wsbomagj1',
+ namespace='WSBOMAGcluster1',
+ location='123456',
+ created_at='2020-06-07 04:16:15.743617')
+ namespace_message = NameSpaceMessage(reportName='vcp_fe_namespace',
+ reportDescription='dRAN namespace',
+ reportGeneratedOn='2020-06-07 04:16:15.743617',
+ rowCount= 2,
+ reportDataRows=[remote_region, remote_region1])
+
+ vmbProducer.namespace_creation(namespace_message)
+
+ elif(topic == 'cluster_status'):
+ # 2. Cluster Status
+ print('return cluster status to vmb ...')
+
+ cluster_status_message = ClusterStatusMessage()
+ for k in message_json:
+ if k == 'reportName':
+ cluster_status_message.reportName = message_json[k]
+ if k == 'reportDescription':
+ cluster_status_message.reportDescription = message_json[k]
+ if k == 'reportGeneratedOn':
+ cluster_status_message.reportGeneratedOn = message_json[k]
+ if k == 'reportDataRows':
+ regions_list = message_json['reportDataRows']
+ for region in regions_list:
+ cluster_status = ClusterStatus()
+ for k in region:
+ if k == 'name':
+ cluster_status.name = region[k]
+ if k == 'description':
+ cluster_status.description = region[k]
+ if k == 'location':
+ cluster_status.location = region[k]
+ if k == 'software_version':
+ cluster_status.software_version = region[k]
+ if k == 'availability':
+ cluster_status.availability = region[k]
+ if k == 'deploy_status':
+ cluster_status.deploy_status = region[k]
+ if k == 'created_at':
+ cluster_status.created_at = region[k]
+ if k == 'updated_at':
+ cluster_status.updated_at = region[k]
+ cluster_status_message.reportDataRows = [cluster_status]
+ cluster_status_message.rowCount = 1
+
+
+ #cluster_status = ClusterStatus(name= "wsbomagj-d654321-001",
+ # description='NE CONCORD 8_NH',
+ # location='654321',
+ # software_version='19.12',
+ # availability='online',
+ # deploy_status='complete',
+ # created_at='2020-01-07 04:16:15.743617',
+ # updated_at='2020-01-07 06:03:10.854598')
+
+ #cluster_status_message = ClusterStatusMessage(reportName='vcp_fe_cluster_status',
+ # reportDescription='cluster status',
+ # reportGeneratedOn='2020-04-28T09:06:32.1962088-04:00',
+ # reportDataRows=cluster_status
+ #)
+
+ vmbProducer.cluster_status(cluster_status_message)
+
+ elif(topic == 'kubeconfig'):
+ # 3. Kubeconfig Token
+ print('return kubeconfig to vmb ...')
+ kubeconfig = Kubeconfig(
+ cluster='wsbomagj-d654321-001',
+ namespace='WSBOMAGJ-441352VZWcVDU-Y-SM-x-001',
+ location='654321',
+ kubeconfig='Example kubeconfig',
+ created_at='2020-01-07 04:16:15.743617'
+ )
+
+
+ with open('tests/kubeconfig.yaml') as f:
+ #kubeconfig_value = json.dumps(yaml.load(f,Loader=yaml.FullLoader), indent=2)
+ kubeconfig_value = json.dumps(yaml.load(f,Loader=yaml.FullLoader))
+ print(kubeconfig_value)
+ kubeconfig.kubeconfig = kubeconfig_value
+
+ kubeconfig_message = KubeconfigMessage(
+ reportName='vcp_fe_kubeconfig',
+ reportDescription='kubeconfig file',
+ reportGeneratedOn='2020-04-28T09:06:32.1962088-04:00',
+ rowCount = 1,
+ reportDataRows=[kubeconfig]
+ )
+
+ vmbProducer.kubeconfig(kubeconfig_message)
+
+ elif(topic == 'image_status'):
+ # 4. Images Status
+ print('return image status to vmb ...')
+ image_status1 = ImageStatus(
+ cluster='wsbomagj-d654321-001',
+ image='i1',
+ action='UPLOAD',
+ status='SUCCESS',
+ message='Image Successfully uploaded',
+ created_at='2020-01-07 04:16:15.743617',
+ )
+
+ image_status2 = ImageStatus(
+ cluster='wsbomagj-d654321-002',
+ image='i1',
+ action='UPLOAD',
+ status='FAILED',
+ message='Image uploading failed',
+ created_at='2020-01-07 04:16:15.743617',
+ )
+ images_status_message = ImagesStatusMessage(reportName='vcp_fe_imagestatus',
+ transactionId='13e11612a7494cd6a7f69ea46e3c0537',
+ reportDescription='cluster status',
+ reportGeneratedOn='2020-04-28T09:06:32.1962088-04:00',
+ rowCount = 2,
+ reportDataRows=[image_status1,image_status2])
+
+ vmbProducer.images_status(images_status_message)
+ else:
+ print('unsupport topic: ' + topic)
+
+if __name__ == "__main__":
+ main(sys.argv[1:]) \ No newline at end of file
diff --git a/src/static/.gitignore b/src/static/.gitignore
new file mode 100644
index 0000000..77f120f
--- /dev/null
+++ b/src/static/.gitignore
@@ -0,0 +1,3 @@
+admin
+caas
+orchestration
diff --git a/steps.txt b/steps.txt
new file mode 100644
index 0000000..4ebeb6e
--- /dev/null
+++ b/steps.txt
@@ -0,0 +1,123 @@
+brew install mariadb@10.4
+mysql.server start
+sudo mysql -u root
+ - Create user faredge with password:password_changeme
+mysql --user=faredge --password=password_changeme
+
+mysql --user=root --password=password_changeme < sql/schema/create-database.sql
+cp src/far_edge_ops_api/setting.py.example src/far_edge_ops_api/settings.py
+
+Fill values in settings.py by contacting one of the following folks:
+- Carlos, Dev, Subin
+
+pip3 install virtualenv
+virtualenv -p python3.6 venv
+source venv/bin/activate
+pip install -r requirements.txt --target=./venv/lib/python3.6/site-packages/
+
+python src/manage.py makemigrations
+python src/manage.py migrate
+
+mysql --user=faredge --password=password_changeme < sql/schema/orchestration.testdata
+
+python src/manage.py runserver
+
+#sudo mysql --user=root < faredge.sql.example
+
+Login to database as faredge user and run following command:
+
+su - postgres
+createuser -P faredge
+dropdb faredge
+createdb faredge --owner=faredge
+psql -d faredge -U faredge
+\dt;
+
+insert into caas_cluster(cluster_name, oam_vip_address) values ('wsbomagj-d1566492-001', '2001:4888:2a14:502e:1b1:40a:0:f400');
+insert into caas_cluster(cluster_name, oam_vip_address) values ('wsbomagj-p0000000-001', 'fd00:4888:2000:07f7::290');
+insert into caas_cluster(cluster_name, oam_vip_address) values ('wsbomagj-d0000000-001', 'fd00:4888:2000:07fd::290');
+
+insert into caas_namespace(namespace_name) values ('wsbomagj-1566492vzwcvdu-y-ss-x-00000000000');
+insert into caas_namespace(namespace_name) values ('wsbomagj-1257637vzwcvdu-y-ss-x-002');
+
+insert into caas_location(fuze_spm_site_id, fuze_spm_site_name, fuze_spm_market, fuze_spm_submarket, fuze_spm_site_type) values (9999999, 'Walnut Creek CA', 'WEST', 'WEST COAST', 'CRAN');
+insert into caas_cluster(cluster_name, oam_vip_address, parent_cluster_id, namespace_id, location_id) values ('wsbomagj-d1257637-002', 'fd00:4888:2000:120a::a', (select id from caas_cluster where cluster_name='wsbomagj-d0000000-001'), (select id from caas_namespace where namespace_name='wsbomagj-1257637vzwcvdu-y-ss-x-002'), (select id from caas_location where fuze_spm_site_name='Walnut Creek CA'));
+insert into caas_cluster(cluster_name, oam_vip_address, parent_cluster_id, namespace_id, location_id) values ('wsbomagj-p1257637-002', 'fd00:4888:2000:120c::290', (select id from caas_cluster where cluster_name='wsbomagj-p0000000-001'), (select id from caas_namespace where namespace_name='wsbomagj-1257637vzwcvdu-y-ss-x-002'), (select id from caas_location where fuze_spm_site_name='Walnut Creek CA'));
+
+insert into orchestration_centraltoremotemap (central_region_name, remote_region_name) values ('fd00:4888:2000:07fd::290', 'fd00:4888:2000:120a::a');
+insert into orchestration_centraltoremotemap (central_region_name, remote_region_name) values ('fd00:4888:2000:07fd::290', 'fd00:4888:2000:120a::a');
+insert into orchestration_centraltoremotemap (central_region_name, remote_region_name) values ('fd00:4888:2000:07fd::290', 'fd00:4888:2000:120a::a');
+insert into orchestration_centraltoremotemap (central_region_name, remote_region_name) values ('fd00:4888:2000:07fd::290', 'fd00:4888:2000:120a::a');
+
+mkdir -p /var/log/far-edge-ops-api/
+chown apache -R /var/log/far-edge-ops-api/
+chgrp apache -R /var/log/far-edge-ops-api/
+
+system registry-garbage-collect
+
+curl -v http://localhost:8000/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data3.json
+curl -v http://localhost:8000/orchestration/caas-status -X POST -H "Content-Type: application/json" -d@cluster-status.json
+curl -v "http://localhost:8000/orchestration/remote-regions/wsbomagj-p1257637-002/connection-details?team=orchestration" -X GET
+curl -v "http://localhost:8000/orchestration/remote-regions/wsbomagj-p1257637-002/connection-details?team=application" -X GET
+curl http://localhost:8000/orchestration/remote-regions/wsbomagj-p1257637-002/status -X GET | python -m json.tool
+
+sed 's/\\//'g testconfig1.conf > testconfig2.conf
+
+---
+
+Walnut Creek Lab testing:
+
+sudo systemctl start httpd
+sudo systemctl stop httpd
+tail -f /var/log/apache2/middleware_error_log
+tail -f /var/log/far-edge-ops-api/orchestration.log
+
+Planning's cluster:
+curl -v "http://192.168.31.20/orchestration/remote-regions/wsbomagj-p1257637-002/connection-details?team=orchestration" -X GET
+
+---
+
+docker build . -t far-edge-ops-apis
+docker run --network host -v /var/log/far-edge-ops-api/:/var/log/far-edge-ops-api/ far-edge-ops-apis:latest
+
+How to deploy far_edge_ops_api code changes to middleware production server:
+
+1. cd /opt/far-edge-ops-api/src/
+2. git pull
+3. systemctl restart far-edge-ops-api.service
+
+
+--- Samsung Log collection ---
+
+source venv/bin/activate
+pip3 install -r logcollection-requirements.txt
+
+1. Search IP address details about a sub-cloud:
+ - ssh vcpfe-database-1-prod-birmingham.vcpfe.vzwops.com
+ su - postgres
+ psql -U faredge faredge
+ faredge=> select cluster_name, fuze_spm_site_id, fuze_spm_site_name, oam_vip_address from caas_cluster join caas_location on caas_cluster.location_id=caas_location.id where fuze_spm_site_name ilike 'allen%';
+
+2. python3 collect-logs.py
+ Example:
+ python3 collect-logs.py longmeadow9 2001:4888:2a10:e0ad:122:40a::f400 dummy-password
+
+(Ask: Dev, Subin, Murali, Victor for actual password)
+
+How to test caas_setup on wcLab
+-------------------------------
+1. Trigger caas_setup with:
+curl -k -X POST -d ‘{“cluster_status”: [{“name”: “walnutcr-d9999999-002”, “description”: “wclab”, “location”: “9999999”, “software_version”: “20.06”, “availability”: “ONLINE”, “deploy_status”: “COMPLETE”, “created_at”: “2020-09-10 03:59:42”, “updated_at”: “2020-09-10 03:59:42”}]}’ http://middleware.hqplan.lab/orchestration/caas-status
+
+2. check logs to make sure namespace is created and all cluster_status, namespace creation, and kubeconfig messages are sent to VMB
+tail -f /var/log/apache2/far-edge-ops-api_access_log
+tail -f /var/log/apache2/far-edge-ops-api_error_log
+tail -f /var/log/far-edge-ops-api/orchestration.log
+
+
+How to validate network setup after caas_setup call
+---------------------------------------------------
+1. Export WR_Installed tab of CIQ spreadsheet to csv file
+2. Run paas-setup-check.py to genenerate list of sites passing or failing network setup validation
+ Example:
+ python ./paas-setup-check.py wr_installed.csv
diff --git a/tests/paas-setup-check/audit-vmb-site-list.sh b/tests/paas-setup-check/audit-vmb-site-list.sh
new file mode 100755
index 0000000..08e24f8
--- /dev/null
+++ b/tests/paas-setup-check/audit-vmb-site-list.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+if [ $# -lt 2 ]; then
+ echo "Usage: $0 [file] [filter]"
+ exit 1
+fi
+
+filter=$2
+
+while read site; do
+
+ #grep wsbomagj-d325077-001 /var/log/apache2/far-edge-ops-api_error_log |grep reportName |grep 2020-12-01 |wc -l
+ commandline=$(grep $site /var/log/apache2/far-edge-ops-api_error_log |grep reportName |grep $filter | wc -l)
+
+ if [ $commandline -ge 3 ]; then
+ echo "All msgs sent for ($site)"
+ else
+ echo "Failed to send all msgs to ($site)"
+ fi
+
+done < $1
diff --git a/tests/paas-setup-check/paas-setup-check.py b/tests/paas-setup-check/paas-setup-check.py
new file mode 100644
index 0000000..a6a5119
--- /dev/null
+++ b/tests/paas-setup-check/paas-setup-check.py
@@ -0,0 +1,189 @@
+import subprocess
+import os
+import json
+import requests
+import sys
+import csv
+import re
+
+class PaaSSetupCheck():
+
+ def run_test(self, site_name):
+ try:
+ network_ok = False
+ namespace_ok = False
+ network_unreachable = False
+ cc_unreachable = False
+ url = 'https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/' + site_name + '/site-details'
+ network_setup_url = 'https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/' + site_name + '/setup-network'
+ namespace_setup_url = 'https://middleware.vcpfe.vzwops.com/orchestration/setup-cluster/' + site_name
+
+ #url = 'http://middleware.meter.vzwops.com:3080/orchestration/remote-regions/' + site_name + '/site-details'
+ #network_setup_url = 'http://middleware.meter.vzwops.com:3080/orchestration/remote-regions/' + site_name + '/setup-network'
+ #namespace_setup_url = 'http://middleware.meter.vzwops.com:3080/orchestration/setup-cluster/' + site_name
+
+ #namespace_setup_url_1 = 'https://middleware.vcpfe.vzwops.com/orchestration/setup-cluster/' + site_name + '?network_setup=false'
+ #url = 'http://localhost:8000/orchestration/remote-regions/' + site_name + '/site-details'
+ #network_setup_url = 'http://localhost:8000/orchestration/remote-regions/' + site_name + '/setup-network'
+ #namespace_setup_url = 'http://localhost:8000/orchestration/setup-cluster/' + site_name
+
+ auth = ('SVC-Far-Edge','QxbV44IU4P7t7rN44dxK4xyPB');
+
+ resp = requests.get(url, auth=auth, verify=False)
+ if resp.status_code == 500:
+ network_unreachable = True
+ print(site_name + ' ssh network unreachable')
+ else:
+ print(resp.json())
+
+ cc_status = resp.json()['setup_details']['online_status']
+ if not cc_status:
+ cc_unreachable = True
+ else:
+ namespaces = resp.json()['setup_details']['namespaces']
+ for namespace in namespaces:
+ print('namespace: ' + namespace)
+ if 'Unable to connect to the server' in namespace:
+ network_unreachable = True
+ print(site_name + ' k8s network unreachable')
+ break
+ else:
+ x = re.search("^wsbomagj-.*[0-9]$", namespace.split(' ')[0])
+ if x:
+ # check secrets
+ secrets = resp.json()['setup_details']['ns_secrets']
+ total_secrets = 0
+ for secret in secrets:
+ if 'admin-registry-secret' in secret:
+ total_secrets = total_secrets + 1
+ if 'ceph-pool-kube-rbd' in secret:
+ total_secrets = total_secrets + 1
+ if 'default-token' in secret:
+ total_secrets = total_secrets + 1
+ if 'vran-serviceaccount-token' in secret:
+ total_secrets = total_secrets + 1
+
+ if total_secrets == 4:
+ # check service accounts
+ total_sa = 0
+ serviceaccounts = resp.json()['setup_details']['ns_service_accounts']
+ for sa in serviceaccounts:
+ if 'default' in sa:
+ total_sa = total_sa + 1
+ if 'vran-serviceaccount' in sa:
+ total_sa = total_sa + 1
+
+ if total_sa == 2:
+ print(site_name + ': ' + namespace + ' namespace is created')
+ namespace_ok = True
+
+ if not network_unreachable and not cc_unreachable:
+ network_no = len(resp.json()['setup_details']['network_details'])
+ if network_no and network_no >= 15:
+ print(site_name + ' network setup succeeded')
+ network_ok = True;
+
+ if namespace_ok:
+ if network_ok:
+ print(site_name + " both namespace and network setup correct. yay.")
+ else:
+ print(site_name + ' namespace is okay, network setup failed.')
+ print('calling setup-network for: ' + site_name)
+ # call network_setup
+ network_setup_resp = requests.put(network_setup_url, auth=auth, verify=False)
+ else:
+ if network_ok:
+ print(site_name + " namespace setup failed, network okay.")
+ print('calling setup-cluster with network_setup=false query parameter.')
+ payload = {'network_setup': 'false'}
+ namespace_setup_resp = requests.get(namespace_setup_url, auth=auth, params=payload, verify=False)
+ else:
+ print(site_name + 'both namespace and network NOT setup')
+ namespace_setup_resp = requests.get(namespace_setup_url, auth=auth, verify=False)
+ print('called setup-cluster')
+
+ except Exception as e:
+ print(e)
+
+ return network_ok, namespace_ok, network_unreachable, cc_unreachable
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 1:
+ print("Usage:")
+ print(" python paas-set-check.py <site_list_csv_file>")
+ exit(0)
+
+ site_list_file = sys.argv[1]
+ print("Input WR installed site list file:" + site_list_file)
+ paasSetupCheck = PaaSSetupCheck()
+
+ with open('network_setup_succeed.csv', mode='w') as succeed_file:
+ with open('network_setup_fail.csv', mode='w') as fail_file:
+ with open('namespace_setup_succeed.csv', mode='w') as namespace_succeed_file:
+ with open('namespace_setup_fail.csv', mode='w') as namespace_fail_file:
+ with open('network-namespace_setup_succeed.csv', mode='w') as network_namespace_succeed_file:
+ with open('network-unreachable.csv', mode='w') as network_unreachable_file:
+ with open('cc-unreachable.csv', mode='w') as cc_unreachable_file:
+ writer0 = csv.writer(succeed_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer1 = csv.writer(fail_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer2 = csv.writer(namespace_succeed_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer3 = csv.writer(namespace_fail_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer4 = csv.writer(network_namespace_succeed_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer5 = csv.writer(network_unreachable_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer6 = csv.writer(cc_unreachable_file, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ with open(site_list_file) as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=',')
+ line_count = 0
+ ok_count = 0
+ bad_count = 0
+ network_ok_count = 0
+ network_not_ok_count = 0
+ namespace_ok_count = 0
+ namespace_not_ok_count= 0
+ cc_unreachable_count = 0
+ for row in csv_reader:
+ print(f'\t{row[0]}')
+ #site_name = 'wsbomagj-d325030-001';
+ site_name = row[0];
+ network_ok, namespace_ok, network_unreachable, cc_unreachable = paasSetupCheck.run_test(site_name)
+ print(network_ok, namespace_ok)
+
+ if network_ok and namespace_ok:
+ writer4.writerow({site_name})
+ ok_count += 1
+
+ if network_unreachable:
+ writer5.writerow({site_name})
+ bad_count += 1
+
+ if cc_unreachable:
+ writer6.writerow({site_name})
+ cc_unreachable_count += 1
+
+ if network_ok:
+ writer0.writerow({site_name})
+ network_ok_count += 1
+ elif not network_unreachable:
+ writer1.writerow({site_name})
+ network_not_ok_count += 1
+ print(site_name + ' network setup failed')
+
+ if namespace_ok:
+ writer2.writerow({site_name})
+ namespace_ok_count += 1
+ elif not network_unreachable:
+ writer3.writerow({site_name})
+ namespace_not_ok_count += 1
+ print(site_name + ' namespace setup failed')
+
+ line_count += 1
+
+ print(f'Processed {line_count} lines.')
+ print(f'{ok_count} of {line_count} ready-to-go sites. file: network-namespace_setup_succeed.csv')
+ print(f'{bad_count} of {line_count} bad sites. file: network-unreachable.csv')
+ print(f'{cc_unreachable_count} of {line_count} central controller unreachable sites. file: cc-unreachable.csv')
+ print(f'{network_ok_count} of {line_count} sites with no network problem. file: network_setup_succeed.csv')
+ print(f'{network_not_ok_count} of {line_count} sites with network problem. file: network_setup_fail.csv')
+ print(f'{namespace_ok_count} of {line_count} sites with no namespace problem. file:namespace_setup_succeed.csv')
+ print(f'{namespace_not_ok_count} of {line_count} sites with namespace problem. file: namespace_setup_fail.csv')
diff --git a/tests/paas-setup-check/send-vmb-messages.py b/tests/paas-setup-check/send-vmb-messages.py
new file mode 100644
index 0000000..d94004f
--- /dev/null
+++ b/tests/paas-setup-check/send-vmb-messages.py
@@ -0,0 +1,73 @@
+import requests
+import sys
+import csv
+import time
+
+class SendVMBMessages():
+ auth = ('SVC-Far-Edge','QxbV44IU4P7t7rN44dxK4xyPB');
+ def _is_network_ok(self, site_name):
+ url = 'https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/' + site_name + '/site-details'
+
+ resp = requests.get(url, auth=self.auth, verify=False)
+ if resp.status_code == 500:
+ print(site_name + ' ssh network unreachable')
+ return False, resp.status_code
+
+ print(resp.json())
+ namespaces = resp.json()['setup_details']['namespaces']
+ for namespace in namespaces:
+ print('namespace: ' + namespace)
+ if 'Unable to connect to the server' in namespace:
+ print(site_name + ' k8s network unreachable')
+ return False, 500
+
+ network_no = len(resp.json()['setup_details']['network_details'])
+ if network_no and network_no >= 15:
+ print(site_name + ' network setup succeeded')
+ return True, resp.status_code
+ else:
+ return False, resp.status_code #200
+
+ def run_setup_cluster(self, site_name, network_setup=True):
+ try:
+ namespace_setup_url = 'https://middleware.vcpfe.vzwops.com/orchestration/setup-cluster/' + site_name
+ #namespace_setup_url = 'http://localhost:8000/orchestration/setup-cluster/' + site_name
+ print('calling setup-cluster with network_setup : ' + str(network_setup) )
+ payload = {'network_setup': network_setup}
+ resp = requests.get(namespace_setup_url, auth=self.auth, params=payload, verify=False)
+ if resp.status_code == 500:
+ print(site_name + ' setup-cluster call failed')
+ else:
+ print('called sending message to VMB for ' + site_name)
+ except Exception as e:
+ print(e)
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 1:
+ print("Usage:")
+ print(" python SendVMBMessages.py <site_list_csv_file>")
+ exit(0)
+
+ site_list_file = sys.argv[1]
+ print("Input WR installed site list file:" + site_list_file)
+ sendVMBMessages = SendVMBMessages()
+
+ with open(site_list_file) as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=',')
+ line_count = 0
+ for row in csv_reader:
+ print(f'\t{row[0]}')
+ #site_name = 'wsbomagj-d325030-001';
+ site_name = row[0];
+
+ network_ok, status_code = sendVMBMessages._is_network_ok(site_name)
+ if status_code != 500:
+ if network_ok:
+ sendVMBMessages.run_setup_cluster(site_name, network_setup=False)
+ else:
+ sendVMBMessages.run_setup_cluster(site_name, network_setup=True)
+ line_count += 1
+ time.sleep(3)
+
+ print(f'Processed {line_count} lines.')
diff --git a/tests/paas-setup-check/site_run_command.py b/tests/paas-setup-check/site_run_command.py
new file mode 100644
index 0000000..e19afac
--- /dev/null
+++ b/tests/paas-setup-check/site_run_command.py
@@ -0,0 +1,130 @@
+import sys
+import csv
+import time
+import pexpect
+import psycopg2
+
+class RunCmdFromList():
+
+ def run_cmd(self, site_name):
+ try:
+
+ #get oam_ip
+ site_ip = self._get_remote_region_oam_ip(site_name)
+ print('site_ip ' + site_ip)
+
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+ raw_cmd = " sudo reboot "
+ cmd1 = ssh_prefix + raw_cmd
+ print(cmd1)
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, 'xxxxxxx', additional_password=True)
+ print('called ' + raw_cmd + ' for ' + site_name)
+
+ except Exception as e:
+ print(e)
+
+ def _execute_cmd_and_save_op(self, cmd, raw_cmd, password, additional_password=False):
+ cmds = []
+ #print(cmd)
+ cmds.append(cmd)
+ all_lines = self._run_command_get_all_lines(cmds, password, additional_password=additional_password)
+ print(all_lines)
+ print(raw_cmd + ":\n")
+ print("-------------------------\n")
+ for line in all_lines.split("\n"):
+ if 'Connection' not in line:
+ print(line)
+ print("\n")
+ print("\n")
+
+ def _run_command_get_all_lines(self, commands, host_password, timeout=None, additional_password=False):
+ #print("Inside _run_command_get_all_lines")
+ all_lines = []
+ for command in commands:
+ print("Executing.." + command)
+ child = pexpect.spawn(command)
+ child.timeout=timeout
+ try:
+ i = child.expect(['password: ','Connection refused\r\r\n'], timeout=timeout)
+ if i == 0:
+ child.sendline(host_password)
+ if additional_password:
+ child.expect_exact("Password:")
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ #logger.info(all_lines)
+ if i == 1:
+ print("Connection refused")
+ except:
+ print(str(child))
+ return all_lines
+
+ def _get_remote_region_oam_ip(self, cluster_name):
+
+ """ Connect to the PostgreSQL database server """
+ conn = None
+ oam_ip = None
+ try:
+ print('Connecting to the PostgreSQL database...')
+
+ conn = psycopg2.connect(
+ host='xxxxxxx',
+ port='5432',
+ database="faredge",
+ user="faredge",
+ password='xxxxxx')
+
+ # create a cursor
+ cur = conn.cursor()
+
+ # execute a statement
+ print('select oam_vip_address from caas_wrbatch where cluster_name=' + site_name )
+ sql = 'select oam_vip_address from caas_wrbatch where cluster_name=' + '\'' + site_name + '\''
+ cur.execute(sql)
+
+ #print('PostgreSQL database version:')
+ #cur.execute('SELECT version()')
+
+ # display the PostgreSQL database server version
+ oam_ip = cur.fetchone()
+ print( site_name + ' ' + str(oam_ip))
+
+ # close the communication with the PostgreSQL
+ cur.close()
+ except (Exception, psycopg2.DatabaseError) as error:
+ print(error)
+ finally:
+ if conn is not None:
+ conn.close()
+ print('Database connection closed.')
+
+ print(site_name + " OAM IP:" + oam_ip[0])
+ return oam_ip[0]
+
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 1:
+ print("Usage:")
+ print(" python site_run_command.py <site_list_csv_file>")
+ exit(0)
+
+ site_list_file = sys.argv[1]
+ print("Input WR installed site list file:" + site_list_file)
+ runCmdFromList = RunCmdFromList()
+
+ with open(site_list_file) as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=',')
+ line_count = 0
+ for row in csv_reader:
+ print(f'\t{row[0]}')
+ #site_name = 'wsbomagj-d325030-001';
+ site_name = row[0];
+ runCmdFromList.run_cmd(site_name)
+ line_count += 1
+ time.sleep(3)
+
+
+ print(f'Processed {line_count} lines.')
diff --git a/tests/perf-tests/cluster-status.json b/tests/perf-tests/cluster-status.json
new file mode 100644
index 0000000..80b82cb
--- /dev/null
+++ b/tests/perf-tests/cluster-status.json
@@ -0,0 +1,14 @@
+ {"cluster_status": [
+ {
+ "name": "wsbomagj-d654321-001",
+ "description": "NE CONCORD 8_N",
+ "location": 654321,
+ "software_version": "20.06",
+ "availability": "ONLINE",
+ "deploy_status": "COMPLETE",
+ "created_at": "2020-01-07 04:16:15.743617",
+ "updated_at": "2020-01-07 04:16:15.743617"
+ }
+ ]
+ }
+
diff --git a/tests/perf-tests/data-ss-prod.json b/tests/perf-tests/data-ss-prod.json
new file mode 100644
index 0000000..f087dae
--- /dev/null
+++ b/tests/perf-tests/data-ss-prod.json
@@ -0,0 +1,4 @@
+{
+ "images": ["vnf-twb.vzwnet.com/docker-images/samsung_vdu_vdu_svr20aa5vvzwg06a3_r05_20.a.0-0101_6.0.0/vzw-adpf-cmp:svr20aa5vvzwg06a3_r05"],
+ "remoteRegions": ["wsbomagj-d325030-001","wsbomagj-d2341539-001"]
+}
diff --git a/tests/perf-tests/data-ss-test1.json b/tests/perf-tests/data-ss-test1.json
new file mode 100755
index 0000000..ce6a700
--- /dev/null
+++ b/tests/perf-tests/data-ss-test1.json
@@ -0,0 +1,4 @@
+{
+ "images": ["vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-cmp:latest","vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-dip:latest","vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-dmp:latest","vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-dpp:latest","vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-pmp:latest","vsp.vici.verizon.com:7443/dit_cnf_docker/vdu_vz_samsung_vdu.v19.a.0-01_1.0.0/vzw-rmp:latest"],
+ "remoteRegions": ["fd00:4888:2000:120a::a"]
+}
diff --git a/tests/perf-tests/orchestration-tests.txt b/tests/perf-tests/orchestration-tests.txt
new file mode 100644
index 0000000..32d9993
--- /dev/null
+++ b/tests/perf-tests/orchestration-tests.txt
@@ -0,0 +1,99 @@
+# For testing locally on WC Lab VM
+curl -v http://localhost:8000/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-test1.json
+curl -v http://localhost:8000/orchestration/image-upload/<> -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/status -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -v http://localhost:8000/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -v http://localhost:8000/orchestration/setup-cluster/<> -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/site-details -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -v http://localhost:8000/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing against WC Lab VM from Jump server with Django app deployed to Apache
+curl -v http://192.168.31.20/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-test1.json
+curl -v http://192.168.31.20/orchestration/image-upload/<> -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/status -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -v http://192.168.31.20/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -v http://192.168.31.20/orchestration/setup-cluster/<> -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/site-details -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -v http://192.168.31.20/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing against WCLab middleware endpoint
+curl -kv http://middleware.hqplan.lab/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-test1.json
+curl -kv http://middleware.hqplan.lab/orchestration/image-upload/<> -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/status -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -kv http://middleware.hqplan.lab/orchestration/setup-cluster/<> -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/site-details -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -kv http://middleware.hqplan.lab/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing on production app server (app1) via ssh/django proxy
+curl -kv http://localhost:18000/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-prod.json
+curl -kv http://localhost:18000/orchestration/image-upload/<> -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/status -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -kv http://localhost:18000/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -kv http://localhost:18000/orchestration/setup-cluster/<> -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/site-details -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -kv http://localhost:18000/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing locally on production app server (app1 or app2)
+curl -kv 127.0.0.1/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-prod.json
+curl -kv 127.0.0.1/orchestration/image-upload/<> -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/status -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -kv 127.0.0.1/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -kv 127.0.0.1/orchestration/setup-cluster/<> -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/site-details -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -kv 127.0.0.1/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing against production endpoint
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-prod.json
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/image-upload/<> -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/status -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/setup-cluster/<> -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/site-details -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+# For testing against middleware2 endpoint
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-prod.json
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/image-upload/<> -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/status -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/images/<>/status -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/images/<>/tags/<> -X DELETE
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/setup-cluster/<> -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/site-details -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/<>/setup-network -X GET (this should be PUT)
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=orchestration -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=application -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details?team=samsung -X GET
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/<>/connection-details/<transaction_id> -X GET
+
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/caas-status -X POST -H "Content-Type: application/json" -d @caas-status.json
+
+curl -kv https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/wsbomagj-d666654-001/images/vzw-adpf-dip/tags/svr20aa5vvzwg06a4_r06 -X DELETE
+
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/wsbomagj-d674811-001/site-details -X GET
+curl -kv https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/wsbomagj-d666654-001/site-details -X GET | python -m json.tool
+
diff --git a/tests/perf-tests/test-caas-status.sh b/tests/perf-tests/test-caas-status.sh
new file mode 100755
index 0000000..0b4fe1e
--- /dev/null
+++ b/tests/perf-tests/test-caas-status.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+for (( c=1; c<=50; c++ ))
+do
+ curl -v http://localhost:8000/orchestration/caas-status -X POST -H "Content-Type: application/json" -d@cluster-status.json
+done
diff --git a/tests/perf-tests/test-conn-details.sh b/tests/perf-tests/test-conn-details.sh
new file mode 100755
index 0000000..002e848
--- /dev/null
+++ b/tests/perf-tests/test-conn-details.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+for (( c=1; c<=50; c++ ))
+do
+ curl -v http://localhost:8000/orchestration/remote-regions/wsbomagj-p1257637-002/connection-details -X GET
+done
diff --git a/tests/perf-tests/test-image-upload.sh b/tests/perf-tests/test-image-upload.sh
new file mode 100755
index 0000000..3f5ff09
--- /dev/null
+++ b/tests/perf-tests/test-image-upload.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+for (( c=1; c<=50; c++ ))
+do
+ curl -v http://localhost:8000/orchestration/image-upload -X POST -H "Content-Type: application/json" -d @data-ss-test1.json
+done
diff --git a/tools/audit-vmb.sh b/tools/audit-vmb.sh
new file mode 100755
index 0000000..f8439f3
--- /dev/null
+++ b/tools/audit-vmb.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+site_name=$1
+grep $site_name /var/log/apache2/far-edge-ops-api_error_log | grep reportName
diff --git a/tools/check-images.sh b/tools/check-images.sh
new file mode 100755
index 0000000..130cc68
--- /dev/null
+++ b/tools/check-images.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+site_ip=$1
+password=$2
+echo $site_ip
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-cmp"
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-dip"
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-dmp"
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-dpp"
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-pmp"
+python3 collect-logs.py command imagetest $site_ip $password "system registry-image-tags vzw-adpf-rmp"
+
diff --git a/tools/collect-logs.py b/tools/collect-logs.py
new file mode 100644
index 0000000..8ec3ce5
--- /dev/null
+++ b/tools/collect-logs.py
@@ -0,0 +1,524 @@
+import datetime
+import pexpect
+import os
+import logging
+import sys
+import tarfile
+import os.path
+import gzip
+import psycopg2
+
+class LogCollector:
+
+ def __init__(self):
+ pass
+ self.logger = logging.getLogger()
+
+ def get_connection(self):
+ conn = psycopg2.connect(
+ host="",
+ database="",
+ user="",
+ password="")
+ return conn
+
+ def run_command_and_collect_output(self, site_name, site_ip, password, command):
+ #print(site_name)
+ #print(site_ip)
+ #print(password)
+ #print(command)
+ #conn = self.get_connection()
+ # create a cursor
+ #try:
+ # cur = conn.cursor()
+
+ # execute a statement
+ # print('PostgreSQL database version:')
+ # cur.execute('SELECT version()')
+
+ # display the PostgreSQL database server version
+ # db_version = cur.fetchone()
+ # print(db_version)
+
+ # close the communication with the PostgreSQL
+ # cur.close()
+ #except (Exception, psycopg2.DatabaseError) as error:
+ # print(error)
+ #finally:
+ # if conn is not None:
+ # conn.close()
+ # print('Database connection closed.')
+
+ source_dir = site_name
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+ debugLog = site_name + "/cmd-output.log"
+ fp = open(debugLog, "a")
+
+ parts = command.split(" ")
+
+ which_cmd = ssh_prefix + " which " + parts[0]
+ cmds = []
+ cmds.append(which_cmd)
+ all_lines = self.run_command_get_all_lines(cmds, password, self.logger)
+ raw_cmd = ''
+ for line in all_lines.split("\n"):
+ line = line.rstrip().lstrip()
+ if line and 'Connection' not in line:
+ raw_cmd = line
+ break
+
+ #kubectl get ns --kubeconfig=/etc/kubernetes/admin.conf | grep wsbomagj | awk '{print $1}' | xargs kubectl delete ns --kubeconfig=/etc/kubernetes/admin.conf
+
+ #cmd = ssh_prefix + " /usr/sbin/" + raw_cmd
+ raw_cmd = raw_cmd + " " + ' '.join(parts[1:])
+ if 'system' in raw_cmd:
+ cmd = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ elif 'kubectl' in raw_cmd:
+ cmd = ssh_prefix + " " + raw_cmd + " --kubeconfig=/etc/kubernetes/admin.conf"
+ else:
+ cmd = ssh_prefix + " " + raw_cmd
+ print(cmd)
+ self._execute_cmd_and_save_op(cmd, raw_cmd, password, fp)
+ fp.close()
+
+ def collect_samsung_logs(self, site_name, site_ip, password):
+ source_dir = site_name
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+ debugLog = site_name + "/DebugLog"
+ fp = open(debugLog, "w")
+
+ # 1. uptime
+ raw_cmd = " uptime "
+ uptime = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(uptime, raw_cmd, password, fp)
+
+ # 2. ls -l /var/lib/systemd/coredump
+ raw_cmd = " ls -l /var/lib/systemd/coredump "
+ coredump_ls = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(coredump_ls, raw_cmd, password, fp)
+
+ # 3. fm alarm-list
+ raw_cmd = " fm alarm-list"
+ fm_alarm_list = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ self._execute_cmd_and_save_op(fm_alarm_list, raw_cmd, password, fp)
+
+ # 4. date
+ raw_cmd = " date "
+ date = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(date, raw_cmd, password, fp)
+
+ # 5. build.info
+ raw_cmd = " cat /etc/build.info "
+ build_info = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(build_info, raw_cmd, password, fp)
+
+ # 6. ip link
+ raw_cmd = " /usr/sbin/ip link "
+ ip_link = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(ip_link, raw_cmd, password, fp)
+ fp.close()
+
+ # 7. pmond.log
+ pmond_log = '/var/log/pmond.log'
+ cmds = []
+ cmd = 'scp -o StrictHostKeyChecking=no sysadmin@[' + site_ip + ']:' + pmond_log + " " + site_name + "/."
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ # 8. /var/log/kern.log
+ kern_logs_cmd = ssh_prefix + " ls /var/log/kern.log*"
+ cmds = [kern_logs_cmd]
+ kern_logs = self.run_command_get_all_lines(cmds, password, self.logger)
+ print(kern_logs)
+ for kern_log in kern_logs.split('\n'):
+ if 'Connection to' not in kern_log:
+ kern_log = kern_log.rstrip().lstrip()
+ kern_parts = kern_log.split("\t")
+ print("Kern parts:" + str(len(kern_parts)))
+ if len(kern_parts) == 1:
+ kern_parts = kern_log.split(" ")
+ for p in kern_parts:
+ if p != '' and p != '\n' and p != '\t' and 'Connection' not in p:
+ cmds = []
+ print(p)
+ cmd = 'scp -o StrictHostKeyChecking=no sysadmin@[' + site_ip + ']:' + p + " " + site_name + "/."
+ print(cmd)
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ # 6. /var/log/user.log*
+ user_logs_cmd = ssh_prefix + " ls /var/log/user.log*"
+ cmds = [user_logs_cmd]
+ user_logs = self.run_command_get_all_lines(cmds, password, self.logger)
+ print(user_logs)
+ for user_log in user_logs.split('\n'):
+ if 'Connection to' not in user_log:
+ cmds = []
+ user_log = user_log.rstrip().lstrip()
+ user_parts = user_log.split("\t")
+ print("User parts:" + str(len(user_parts)))
+ if len(user_parts) == 1:
+ user_parts = user_log.split(" ")
+ for p in user_parts:
+ if p != '' and p != '\n' and p != '\t' and 'Connection' not in p:
+ print(p)
+ cmd = 'scp -o StrictHostKeyChecking=no sysadmin@[' + site_ip + ']:' + p + " " + site_name + "/."
+ print(cmd)
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ # 7. /var/log/pods/*adpf*
+ pod_logs_cmd = ssh_prefix + " ls /var/log/pods/ | grep adpf"
+ cmds = [pod_logs_cmd]
+ pod_logs = self.run_command_get_all_lines(cmds, password, self.logger)
+ print(pod_logs)
+ for pod_log in pod_logs.split('\n'):
+ cmds = []
+ if ((pod_log is not None) and (pod_log != '')):
+ cmd = 'scp -o StrictHostKeyChecking=no -r sysadmin@[' + site_ip + ']:/var/log/pods/' + pod_log + " " + site_name + "/."
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ # 8. /var/lib/systemd/coredump/*
+ core_dump_cmd = ssh_prefix + " ls /var/lib/systemd/coredump "
+ cmds = [core_dump_cmd]
+ core_dump_logs = self.run_command_get_all_lines(cmds, password, self.logger)
+ print(core_dump_logs)
+ #for core_dump_log in core_dump_logs.split('\n'):
+ cmds = []
+ cmd = 'scp -o StrictHostKeyChecking=no -r sysadmin@[' + site_ip + ']:/var/lib/systemd/coredump/' + " " + site_name + "/."
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ # create tar ball
+ date = datetime.datetime.now().strftime("%b-%d-%Y")
+ tar_file_name = site_name + '-' + date + '.tar'
+ gzip_file_name = tar_file_name + '.gz'
+ os.system('tar -cvf ' + tar_file_name + ' ./' + site_name)
+ os.system('gzip ' + tar_file_name)
+ print(gzip_file_name + " created.")
+ print("Upload it to Samsung FTP site and send a note to Samsung team. FTP coordinates:")
+ #print(" url: https://zone.samsungtelecom.com/")
+ #print(" username:vzw_femto_ndbm_jumpbox")
+ #print(" password:S@msung123")
+ suser = "verizon_snap"
+ shost = "zone.samsungtelecom.com"
+ spasswd = "VZWSn@p786"
+ print(" ur: "+shost+"/ 65.169.250.6")
+ print(" username:"+suser)
+ print(" password:"+spasswd)
+ # sftp the file up to samsung
+ remotepath = '/VZW_4G_5G/Controller\ Logs'
+ p = pexpect.spawn('sftp %s@%s' %(suser,shost))
+ p.logfile = sys.stdout.buffer
+ try:
+ p.expect('(?i)password:')
+ x = p.sendline(spasswd)
+ x = p.expect(['Permission denied','sftp>'])
+ if x == 0:
+ print ('Permission denied for password:')
+ print (spasswd)
+ p.kill(0)
+ else:
+ x = p.sendline('cd ' + remotepath)
+ x = p.expect('sftp>')
+ x = p.sendline('put ' + gzip_file_name)
+ x = p.expect('sftp>')
+ x = p.isalive()
+ x = p.close()
+ retval = p.exitstatus
+ except EOF:
+ print (str(p))
+ print ('SFTP file transfer failed due to premature end of file.')
+ except TIMEOUT:
+ print (str(p))
+ print ('SFTP file transfer failed due to timeout.')
+
+ def _handle_collect(self, site_name, site_ip, password):
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+ collect_cmd = ssh_prefix + " collect all"
+ cmds = []
+ cmds.append(collect_cmd)
+ all_lines = self._run_collect(cmds, password)
+ for line in all_lines.split("\n"):
+ if 'creating' in line:
+ parts = line.split(" ")
+ collect_file_location = parts[3]
+ print("Collect file location:" + collect_file_location)
+
+ cmds = []
+ cmd = 'scp -o StrictHostKeyChecking=no sysadmin@[' + site_ip + ']:' + collect_file_location + " " + site_name + "/."
+ cmds.append(cmd)
+ self.run_commands_scp(cmds, password, self.logger)
+
+ def collect_windriver_logs_steps(self, site_name, site_ip, password):
+ source_dir = site_name
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ # 1. collect
+ self._handle_collect(site_name, site_ip, password)
+
+ # 2. network interface details
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+ port_list_cmd = ssh_prefix + " \"source /etc/platform/openrc; system host-port-list controller-0 | grep -v name | grep -v '+' | cut -d ' ' -f 4\""
+ cmds = []
+ cmds.append(port_list_cmd)
+ all_lines = self.run_command_get_all_lines(cmds, password, self.logger)
+
+ for interface in all_lines.split("\n"):
+ if 'Connection' not in interface:
+ print("Interface..." + interface)
+ interface_file = site_name + "/" + interface + ".txt"
+ fp = open(interface_file, "a")
+
+ raw_cmd = " /usr/sbin/ethtool -i " + interface
+ cmd1 = ssh_prefix + raw_cmd
+ print(cmd1)
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp)
+
+ raw_cmd = " /usr/sbin/ethtool -l " + interface
+ cmd1 = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp)
+
+ raw_cmd = " /usr/sbin/ethtool -S " + interface
+ cmd1 = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp)
+
+ fp.close()
+
+ # 3. interrupts
+ interrupt_file = site_name + "/interrupts.txt"
+ fp = open(interrupt_file, "a")
+ raw_cmd = " cat /proc/interrupts"
+ cmd1 = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp)
+ fp.close()
+
+ debugLog = site_name + "/wr-debug.log"
+ fp = open(debugLog, "w")
+
+ # 4. date
+ raw_cmd = " date "
+ date = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(date, raw_cmd, password, fp)
+
+ # 5. ipmitool
+ raw_cmd = " sudo ipmitool sel time get "
+ cmd1 = ssh_prefix + raw_cmd
+ print(cmd1)
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp, additional_password=True)
+
+ # 6. uptime
+ raw_cmd = " uptime "
+ uptime = ssh_prefix + raw_cmd
+ self._execute_cmd_and_save_op(uptime, raw_cmd, password, fp)
+
+ # 7. host_if_list
+ raw_cmd = "system host-if-list controller-0"
+ host_if_list = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ self._execute_cmd_and_save_op(host_if_list, raw_cmd, password, fp)
+
+ # 8. interface_network_list
+ raw_cmd = "system interface-network-list controller-0"
+ interface_network_list = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ self._execute_cmd_and_save_op(interface_network_list, raw_cmd, password, fp)
+
+ # 9. fm alarm-list
+ raw_cmd = " fm alarm-list"
+ fm_alarm_list = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ self._execute_cmd_and_save_op(fm_alarm_list, raw_cmd, password, fp)
+
+ # 5. fm event-list
+ #raw_cmd = " fm event-list"
+ #fm_event_list = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ #self._execute_cmd_and_save_op(fm_event_list, raw_cmd, password, fp)
+
+ # 10. sw-patch query
+ raw_cmd = " /usr/sbin/sw-patch query"
+ sw_patch_query = ssh_prefix + " source /etc/platform/openrc; " + raw_cmd
+ self._execute_cmd_and_save_op(sw_patch_query, raw_cmd, password, fp)
+ fp.close()
+
+ # 11. drive details
+ drive_list_cmd = ssh_prefix + " \"source /etc/platform/openrc; system host-disk-list controller-0 | grep \"/dev/disk\" | awk '{print $4}' \""
+ cmds = []
+ cmds.append(drive_list_cmd)
+ all_lines = self.run_command_get_all_lines(cmds, password, self.logger)
+
+ if not os.path.exists(site_name + "/dev"):
+ os.makedirs(site_name + "/dev")
+
+ for drive in all_lines.split("\n"):
+ if 'Connection' not in drive:
+ print("Drive..." + drive)
+ drive_file = site_name + drive + ".txt"
+ fp = open(drive_file, "w")
+ raw_cmd = " sudo smartctl -a " + drive
+ cmd1 = ssh_prefix + raw_cmd
+ print(cmd1)
+ self._execute_cmd_and_save_op(cmd1, raw_cmd, password, fp, additional_password=True)
+ fp.close()
+
+ # create tar ball
+ tar_file_name = source_dir + '.tar'
+ gzip_file_name = tar_file_name + '.gz'
+ os.system('tar -cvf ' + tar_file_name + ' ./' + source_dir)
+ os.system('gzip ' + tar_file_name)
+ #with tarfile.open(tar_file_name, "w:gz") as tar:
+ # tar.add(source_dir, arcname=os.path.basename(source_dir))
+ print(gzip_file_name + " created.")
+
+ def collect_windriver_logs(self, site_name, site_ip, password):
+ source_dir = site_name
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ ssh_prefix = 'ssh -o StrictHostKeyChecking=no -t sysadmin@' + site_ip
+
+ # Running collect
+ cmds = []
+ cmd = ssh_prefix + " collect"
+ cmds.append(cmd)
+ all_lines = self._run_collect(cmds, password)
+ for line in all_lines.split("\n"):
+ if 'creating' in line:
+ parts = line.split(" ")
+ collect_file_location = parts[3]
+ print("Collect file location:" + collect_file_location)
+
+ def _execute_cmd_and_save_op(self, cmd, raw_cmd, password, fp, additional_password=False):
+ cmds = []
+ #print(cmd)
+ cmds.append(cmd)
+ all_lines = self.run_command_get_all_lines(cmds, password, self.logger, additional_password=additional_password)
+ #print(all_lines)
+ fp.write(raw_cmd + ":\n")
+ fp.write("-------------------------\n")
+ for line in all_lines.split("\n"):
+ if 'Connection' not in line:
+ fp.write(line)
+ fp.write("\n")
+ fp.write("\n")
+
+ def _run_collect(self, commands, host_password, cmd_timeout=None):
+ all_lines = []
+ for command in commands:
+ print(command)
+ child = pexpect.spawn(command)
+ child.timeout=cmd_timeout
+ try:
+ child.expect_exact(['password: '], timeout=cmd_timeout)
+ child.sendline(host_password)
+ child.expect_exact(['[sudo] password for sysadmin:'], timeout=cmd_timeout)
+ child.sendline(host_password)
+ #child.sendline("\n")
+ line = child.readline()
+ print(line)
+ line = child.readline()
+ print(line)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ print(all_lines)
+ except:
+ print(str(child))
+ return all_lines
+
+ def run_command_get_all_lines(self, commands, host_password, logger, block=False, timeout=None, additional_password=False):
+ #print("Inside _run_command_get_all_lines")
+ all_lines = []
+ for command in commands:
+ print("Executing.." + command)
+ child = pexpect.spawn(command)
+ child.timeout=timeout
+ try:
+ i = child.expect(['password: ','Connection refused\r\r\n'], timeout=timeout)
+ if i == 0:
+ child.sendline(host_password)
+ if additional_password:
+ child.expect_exact("Password:")
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ #logger.info(all_lines)
+ if i == 1:
+ print("Connection refused")
+ except:
+ print(str(child))
+ return all_lines
+
+ def run_commands_scp(self, commands, host_password, logger, block=False, timeout=None):
+ successful = False
+ #print("Inside run_command_scp")
+ #print(commands)
+ for command in commands:
+ print("Executing.." + command)
+ try:
+ child = pexpect.spawn(str(command))
+ except:
+ print(str(child))
+ child.timeout = None
+ try:
+ i = child.expect([b'', 'Are you .*(yes/no)? ', pexpect.EOF], timeout=None)
+ if i == 0:
+ child.sendline('')
+ if i == 1:
+ child.sendline('yes')
+ child.expect(['password: '], timeout=timeout)
+ child.sendline(host_password)
+ all_lines = child.read()
+ all_lines = all_lines.rstrip().lstrip()
+ all_lines = all_lines.decode('utf-8').replace('\r\n', '\n')
+ successful = True # Tentative
+ for line in all_lines.split("\n"):
+ #print(line)
+ if re.search('error', line, re.IGNORECASE):
+ successful = False
+ if re.search('unable', line, re.IGNORECASE):
+ successful = False
+ except:
+ pass
+ #print(str(child))
+ #print("Status:" + str(successful))
+ return successful
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 5:
+ print("Samsung logs:")
+ print(" python collect-logs.py samsung <site-name> <site-ip> <password>")
+ print("WindRiver logs:")
+ print(" python collect-logs.py windriver <site-name> <site-ip> <password>")
+ print("One-off command o/p:")
+ print(" python collect-logs.py command <site-name> <site-ip> <password> \"<command>\"")
+ print(" python3 collect-logs.py command longmeadow-oct8 2001:4888:2a10:e0ad:122:40a:0:f400 password \"ifconfig\"")
+ print(" python3 collect-logs.py command longmeadow-oct8 2001:4888:2a10:e0ad:122:40a:0:f400 password \"ip link\"")
+ exit(0)
+
+ logs_for = sys.argv[1]
+ site_name = sys.argv[2]
+ site_ip = sys.argv[3]
+ password = sys.argv[4]
+
+ print("Option chosen:" + logs_for)
+ print("Site:" + site_name)
+ print("Site IP:" + site_ip)
+ print("Site Password:" + password)
+ log_collector = LogCollector()
+
+ if logs_for == 'samsung':
+ log_collector.collect_samsung_logs(site_name, site_ip, password)
+ elif logs_for == 'windriver':
+ log_collector.collect_windriver_logs_steps(site_name, site_ip, password)
+ else:
+ command = sys.argv[5]
+ #print(command)
+ log_collector.run_command_and_collect_output(site_name, site_ip, password, command)
diff --git a/tools/diff.py b/tools/diff.py
new file mode 100644
index 0000000..667010b
--- /dev/null
+++ b/tools/diff.py
@@ -0,0 +1,37 @@
+import sys
+
+if len(sys.argv) < 3:
+ print("Find the items that are in file1 and not in file2")
+ print(" Usage:")
+ print(" python diff.py <file1> <file2>")
+ exit(0)
+
+f1 = sys.argv[1]
+f2 = sys.argv[2]
+
+f1_list = []
+f1_fp = open(f1, "r")
+f1_lines = f1_fp.readlines()
+
+for line in f1_lines:
+ line1 = line.rstrip().lstrip()
+ f1_list.append(line1)
+
+f2_list = []
+f2_fp = open(f2, "r")
+f2_lines = f2_fp.readlines()
+
+for line in f2_lines:
+ line1 = line.rstrip().lstrip()
+ f2_list.append(line1)
+
+diff = []
+for i in f1_list:
+ present = False
+ for j in f2_list:
+ if i == j:
+ present = True
+ if not present:
+ diff.append(i)
+ print(i)
+
diff --git a/tools/find-image-status.sh b/tools/find-image-status.sh
new file mode 100755
index 0000000..6a71ab4
--- /dev/null
+++ b/tools/find-image-status.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+if (( $# < 1 )); then
+ echo "./find-image-status <cluster-name>"
+ exit 0
+fi
+
+site=$1
+printf "Cluster: $site\n"
+
+printf "Checking cmp..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-cmp/status
+printf "\nChecking pmp..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-pmp/status
+printf "\nChecking rmp..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-rmp/status
+printf "\nChecking dmp..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-dmp/status
+printf "\nChecking dpp..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-dpp/status
+printf "\nChecking dip..."
+curl -k https://middleware2.vcpfe.vzwops.com/orchestration/remote-regions/$site/images/vzw-adpf-dip/status
+printf "\n"
diff --git a/tools/get-ss-kubeconfig.py b/tools/get-ss-kubeconfig.py
new file mode 100644
index 0000000..b04db8a
--- /dev/null
+++ b/tools/get-ss-kubeconfig.py
@@ -0,0 +1,92 @@
+import json
+import requests
+import os
+import os.path
+import sys
+import time
+import psycopg2
+
+class KubeconfigGetter():
+
+ def _get_db_connection(self):
+ conn = None
+ try:
+ conn = psycopg2.connect(
+ host='xxxxx',
+ port='5432',
+ database="xxxxx",
+ user="xxxxx",
+ password='xxxxx')
+ except Exception as e:
+ print(e)
+ return conn
+
+ def get_cluster_name(self, fuze_spm_site_id):
+ cluster_name = ''
+ try:
+ conn = self._get_db_connection()
+
+ # create a cursor
+ cur = conn.cursor()
+
+ # execute a statement
+ #print('select cluster_name, fuze_spm_site_id, fuze_spm_site_name, oam_vip_address from caas_cluster join caas_location on caas_cluster.location_id=caas_location.id where fuze_spm_site_id = ' + fuze_spm_site_id)
+ sql = 'select cluster_name from caas_cluster join caas_location on caas_cluster.location_id=caas_location.id where fuze_spm_site_id=' + '\'' + fuze_spm_site_id + '\''
+ cur.execute(sql)
+
+ # display the PostgreSQL database server version
+ cluster_name = cur.fetchone()[0]
+ #print(cluster_name)
+
+ # close the communication with the PostgreSQL
+ cur.close()
+ except (Exception, psycopg2.DatabaseError) as error:
+ print(error)
+ if conn is not None:
+ conn.close()
+ print('Database connection closed.')
+ return str(cluster_name)
+
+ def get_kubeconfig(self, site_name):
+ #url = "http://192.168.31.20/orchestration/remote-regions/" + site_name + "/kubeconfig/vendor/samsung"
+ url = "https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/" + site_name + "/connection-details?team=samsung"
+ url1 = "https://middleware.vcpfe.vzwops.com/orchestration/remote-regions/" + site_name + "/connection-details"
+ resp = requests.get(url, verify=False)
+ #print(resp.json())
+ transaction_id = resp.json()['transaction_id']
+ #print(transaction_id)
+ url2 = url1 + "/" + transaction_id
+ #print(url2)
+ time.sleep(10)
+ resp1 = requests.get(url2, verify=False)
+ #print(resp1.json())
+ kubeconfig = resp1.json()['remote_region_setup'][0]["kube_config"]
+ #print(kubeconfig)
+ if not os.path.exists("samsung-kubeconfigs"):
+ os.makedirs("samsung-kubeconfigs")
+ fp = open("samsung-kubeconfigs/" + site_name + ".conf", "w")
+ fp.write(kubeconfig)
+ fp.close()
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 2:
+ print("python get-ss-kubeconfig.py <site-list-file>")
+ print(" Make sure to populate database creds in this file.")
+ print(" Use it from a server from which database is accessible (e.g.: app1 or app2)")
+ exit(0)
+
+ site_list_file = sys.argv[1]
+ fp = open(site_list_file, "r")
+ lines = fp.read()
+ kubeconfigGetter = KubeconfigGetter()
+ for fuze_site_id in lines.split("\n"):
+ fuze_site_id = fuze_site_id.lstrip().rstrip()
+ if fuze_site_id != '':
+ #print(fuze_site_id)
+ cluster_name = kubeconfigGetter.get_cluster_name(fuze_site_id)
+ kubeconfigGetter.get_kubeconfig(cluster_name)
+ #fuze_site_id = sys.argv[1]
+ #print(lines)
+ #print(fuze_site_id + " " + cluster_name)
+ print("Done")