diff options
Diffstat (limited to 'src')
179 files changed, 16449 insertions, 97 deletions
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. + + + +## 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  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 Binary files differnew file mode 100644 index 0000000..a949625 --- /dev/null +++ b/src/dashboard/documentation/images/dashboard_layout.jpg 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 |
