From 299a30ca3051dd4b0af074da754182412740b9ce Mon Sep 17 00:00:00 2001 From: mounikasreeram Date: Mon, 26 Mar 2018 19:03:47 +0530 Subject: [PATCH] Add "Sync create","Sync template" to KB-dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit makes the users to utilize the “Sync create” and “Sync template” features in an easier and interactive way from "ResourceManagement" panel in kingbird-dashboard. Depends-On: Change-Id: Iab7db07610fa8125883609d03a547a70a4e8e59e --- kingbird_dashboard/api/client.py | 90 ++++++++ .../resource_management/forms.py | 200 ++++++++++++++++++ .../resource_management/tables.py | 20 +- .../resource_management/_create.html | 41 ++++ .../resource_management/_create_template.html | 7 + .../templates/resource_management/create.html | 7 + .../resource_management/create_template.html | 7 + .../resource_management/resources.html | 7 + .../resource_management/urls.py | 7 + .../resource_management/views.py | 60 ++++++ 10 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 kingbird_dashboard/resource_management/forms.py create mode 100644 kingbird_dashboard/resource_management/templates/resource_management/_create.html create mode 100644 kingbird_dashboard/resource_management/templates/resource_management/_create_template.html create mode 100644 kingbird_dashboard/resource_management/templates/resource_management/create.html create mode 100644 kingbird_dashboard/resource_management/templates/resource_management/create_template.html create mode 100644 kingbird_dashboard/resource_management/templates/resource_management/resources.html diff --git a/kingbird_dashboard/api/client.py b/kingbird_dashboard/api/client.py index c8fac77..52e005b 100644 --- a/kingbird_dashboard/api/client.py +++ b/kingbird_dashboard/api/client.py @@ -14,12 +14,22 @@ from django.conf import settings +from glanceclient import client as gl_client + from horizon.utils import memoized +from keystoneauth1 import loading + +from keystoneauth1 import session + +from keystoneclient.v3 import client as ks_client + import kingbirdclient from kingbirdclient.api import client as kb_client +from novaclient import client as nv_client + SERVICE_TYPE = 'synchronization' NOVA_API_VERSION = "2.37" @@ -38,6 +48,80 @@ def kingbird_dashboardclient(request): ) +def keystone_session(request): + """Keystone session for establishment of Nova and Glance clients.""" + auth_url = getattr(settings, 'OPENSTACK_KEYSTONE_URL') + loader = loading.get_plugin_loader('token') + auth = loader.load_from_options(auth_url=auth_url, + token=request.user.token.id, + project_id=request.user.tenant_id) + sess = session.Session(auth=auth) + return ks_client.Client(session=sess) + + +def _get_endpoint_from_region(keystone_admin, region): + services_list = keystone_admin.services.list() + endpoints_list = keystone_admin.endpoints.list() + service_id = [service.id for service in + services_list if service.type == 'image'][0] + + glance_endpoint = [endpoint.url for endpoint in + endpoints_list + if endpoint.service_id == service_id + and endpoint.region == region and + endpoint.interface == 'public'][0] + return glance_endpoint + + +def nova_client(request, region): + """Nova Client for API calls.""" + return nv_client.Client( + NOVA_API_VERSION, session=keystone_session(request).session, + region_name=region) + + +def glance_client(request, region): + """Glance Client for API calls.""" + keystone_admin = keystone_session(request) + region_endpoint = _get_endpoint_from_region(keystone_admin, + region) + return gl_client.Client( + GLANCE_API_VERSION, session=keystone_session(request).session, + endpoint=region_endpoint) + + +def regions_list(request): + """List of available regions.""" + return keystone_session(request).regions.list() + + +def images_list(request, region): + """List of images.""" + images_choices = list() + images_list = glance_client(request, region).images.list() + for image in images_list: + images_choices.append(image.id) + return images_choices + + +def flavors_list(request, region): + """List of flavors.""" + flavors_choices = list() + flavors_list = nova_client(request, region).flavors.list() + for flavor in flavors_list: + flavors_choices.append(flavor.name) + return flavors_choices + + +def keypairs_list(request, region): + """List of keypairs.""" + keypairs_choices = list() + keypairs_list = nova_client(request, region).keypairs.list() + for keypair in keypairs_list: + keypairs_choices.append(keypair.name) + return keypairs_choices + + def list_defaults(request): """Default Quota Limits.""" return kingbird_dashboardclient(request).quota_manager.\ @@ -97,3 +181,9 @@ def sync_delete(request, job_id): """Delete sync jobs from database.""" return kingbird_dashboardclient(request).sync_manager.\ delete_sync_job(job_id) + + +def sync_job_create(request, data): + """Sync a job from source to target region.""" + return kingbird_dashboardclient(request).sync_manager.\ + sync_resources(**data) diff --git a/kingbird_dashboard/resource_management/forms.py b/kingbird_dashboard/resource_management/forms.py new file mode 100644 index 0000000..8ad9a5f --- /dev/null +++ b/kingbird_dashboard/resource_management/forms.py @@ -0,0 +1,200 @@ +# Copyright 2017 Ericsson AB. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import json +import yaml + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from kingbird_dashboard.api import client as kb_client + + +class SyncJobForm(forms.SelfHandlingForm): + + source = forms.ChoiceField( + label=_('Source Region'), + help_text=_('Select Source Region.'), + widget=forms.Select( + attrs={'class': 'switchable', + 'data-slug': 'source_region_selection'} + ) + ) + + target = forms.MultipleChoiceField( + label=_('Target Region'), + help_text=_('Select Target Region.'), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', + 'data-slug': 'target_region_selection'} + ) + ) + + resource_type = forms.ChoiceField( + label=_('Resource Type'), + help_text=_('Select Resource Type'), + choices=[('flavor', _('Flavor')), + ('keypair', _('Keypair')), + ('image', _('Image'))], + + widget=forms.Select( + attrs={'class': 'switchable', + 'data-slug': 'resource_type'}) + ) + + flavors = forms.MultipleChoiceField( + label=_('Flavor resources'), + help_text=_('Select flavors'), + widget=forms.SelectMultiple( + attrs={'class': 'switched', + 'data-switch-on': 'resource_type', + 'data-resource_type-flavor': _('Flavors'), + 'data-slug': 'flavors'} + ) + ) + + keypairs = forms.MultipleChoiceField( + label=_('Keypair resources'), + help_text=_('Select keypairs'), + widget=forms.SelectMultiple( + attrs={'class': 'switched', + 'data-switch-on': 'resource_type', + 'data-resource_type-keypair': _('Keypairs'), + 'data-slug': 'keypairs'} + ) + ) + + images = forms.MultipleChoiceField( + label=_('Image resources'), + help_text=_('Select Images'), + widget=forms.SelectMultiple( + attrs={'class': 'switched', + 'data-switch-on': 'resource_type', + 'data-resource_type-image': _('Images'), + 'data-slug': 'images'} + ) + ) + + force = forms.BooleanField( + label=_('Force'), + required=False, + help_text=_('Select force to re-create the resources'), + widget=forms.CheckboxInput( + attrs={'class': 'switchable', + 'data-slug': 'force'} + ) + ) + + def clean(self): + cleaned_data = super(SyncJobForm, self).clean() + + return cleaned_data + + def __init__(self, request, *args, **kwargs): + super(SyncJobForm, self).__init__(request, *args, **kwargs) + region_choices = list() + keypairs_choices = list() + flavors_choices = list() + images_choices = list() + # Choices for Source and Target regions. + regions_list = kb_client.regions_list(request) + for region in regions_list: + region_choices.append((region.id, (region.id))) + self.fields['source'].choices = region_choices + self.fields['target'].choices = region_choices + if 'flavors' in self.data: + flavors_list = kb_client.flavors_list(request, + self.data.get('source')) + for flavor in flavors_list: + flavors_choices.append((flavor, (flavor))) + self.fields['flavors'].choices = flavors_choices + self.fields['flavors'].initial = flavors_choices[0] + if 'keypairs' in self.data: + keypairs_list = kb_client.keypairs_list(request, + self.data.get('source')) + for keypair in keypairs_list: + keypairs_choices.append((keypair, (keypair))) + self.fields['keypairs'].choices = keypairs_choices + self.fields['keypairs'].initial = keypairs_choices[0] + if 'images' in self.data: + images_list = kb_client.images_list(request, + self.data.get('source')) + for image in images_list: + images_choices.append((image, (image))) + self.fields['images'].choices = images_choices + self.fields['images'].initial = images_choices[0] + + def handle(self, request, data): + try: + payload = dict() + payload["source"] = data.get("source") + payload["target"] = data.get("target") + payload["resource_type"] = data.get("resource_type") + if payload["resource_type"] == "flavor": + payload["resources"] = data.get("flavors") + if payload["resource_type"] == "keypair": + payload["resources"] = data.get("keypairs") + if payload["resource_type"] == "image": + payload["resources"] = data.get("images") + payload["force"] = str(data.get("force")) + kb_client.sync_job_create(request, payload) + msg = _('Successfully created Sync Job.') + messages.success(request, msg) + return True + except Exception: + msg = _('Failed to create sync job.') + redirect = reverse('horizon:kingbird:resource_management:index') + exceptions.handle(request, msg, redirect=redirect) + + +class SyncTemplateForm(forms.SelfHandlingForm): + + input_upload = forms.FileField( + label=_('Input File'), + help_text=_('Upload .yaml/.yml/.json template to sync resources.'), + widget=forms.FileInput( + attrs={'class': 'switched', + 'data-switch-on': 'inputsource', + 'data-inputsource-file': _('Input File')} + ), + required=True + ) + + def clean(self): + cleaned_data = super(SyncTemplateForm, self).clean() + + return cleaned_data + + def handle(self, request, data): + try: + if data['input_upload'].name.endswith('.json'): + resource_set = json.load(data['input_upload']) + if data['input_upload'].name.endswith('.yaml') or \ + data['input_upload'].name.endswith('.yml'): + resource_set = yaml.load(data['input_upload']) + del data['input_upload'] + data.update(resource_set) + kb_client.sync_job_create(request, data) + msg = _('Successfully created Sync Template.') + messages.success(request, msg) + return True + except Exception: + msg = _('Failed to create sync template.') + redirect = reverse('horizon:kingbird:resource_management:index') + exceptions.handle(request, msg, redirect=redirect) diff --git a/kingbird_dashboard/resource_management/tables.py b/kingbird_dashboard/resource_management/tables.py index bd234f5..0994f36 100644 --- a/kingbird_dashboard/resource_management/tables.py +++ b/kingbird_dashboard/resource_management/tables.py @@ -18,6 +18,22 @@ from horizon import tables from kingbird_dashboard.api import client as kb_client +class SyncJobCreate(tables.LinkAction): + name = "sync_job" + verbose_name = _("Create Sync Job") + url = "horizon:kingbird:resource_management:create_sync_job" + classes = ("ajax-modal",) + icon = "plus" + + +class SyncTemplateCreate(tables.LinkAction): + name = "sync_template" + verbose_name = _("Create Sync Template") + url = "horizon:kingbird:resource_management:create_sync_template" + classes = ("ajax-modal",) + icon = "plus" + + class DeleteSyncJob(tables.DeleteAction): @staticmethod @@ -110,6 +126,8 @@ class ResourceSyncTable(tables.DataTable): name = "job_set" verbose_name = _("Resource Management") table_actions = ( - DeleteSyncJob, + SyncJobCreate, + SyncTemplateCreate, + DeleteSyncJob ) row_actions = (DeleteSyncJob,) diff --git a/kingbird_dashboard/resource_management/templates/resource_management/_create.html b/kingbird_dashboard/resource_management/templates/resource_management/_create.html new file mode 100644 index 0000000..ee65519 --- /dev/null +++ b/kingbird_dashboard/resource_management/templates/resource_management/_create.html @@ -0,0 +1,41 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} +{% block modal-body-right %} + +

{% trans "Description:" %}

+

{% trans "Select the resources for a resource-type to sync the job from source region to multiple target regions." %}

+{% endblock %} diff --git a/kingbird_dashboard/resource_management/templates/resource_management/_create_template.html b/kingbird_dashboard/resource_management/templates/resource_management/_create_template.html new file mode 100644 index 0000000..dca076c --- /dev/null +++ b/kingbird_dashboard/resource_management/templates/resource_management/_create_template.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Upload a template containing resource-set with extension .yaml/.yml/.json to sync more than one resource-type to multiple target regions." %}

+{% endblock %} diff --git a/kingbird_dashboard/resource_management/templates/resource_management/create.html b/kingbird_dashboard/resource_management/templates/resource_management/create.html new file mode 100644 index 0000000..48e78c0 --- /dev/null +++ b/kingbird_dashboard/resource_management/templates/resource_management/create.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create SyncJob" %}{% endblock %} + +{% block main %} + {% include 'kingbird/resource_management/_create.html' %} +{% endblock %} diff --git a/kingbird_dashboard/resource_management/templates/resource_management/create_template.html b/kingbird_dashboard/resource_management/templates/resource_management/create_template.html new file mode 100644 index 0000000..193fbfa --- /dev/null +++ b/kingbird_dashboard/resource_management/templates/resource_management/create_template.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Sync Template" %}{% endblock %} + +{% block main %} + {% include 'kingbird/resource_management/_create_template.html' %} +{% endblock %} diff --git a/kingbird_dashboard/resource_management/templates/resource_management/resources.html b/kingbird_dashboard/resource_management/templates/resource_management/resources.html new file mode 100644 index 0000000..4c5d2dc --- /dev/null +++ b/kingbird_dashboard/resource_management/templates/resource_management/resources.html @@ -0,0 +1,7 @@ +{% for resource in resources %} + {% if forloop.first %} + + {% else %} + + {% endif %} +{% endfor %} diff --git a/kingbird_dashboard/resource_management/urls.py b/kingbird_dashboard/resource_management/urls.py index e1de17f..2f3e3a1 100644 --- a/kingbird_dashboard/resource_management/urls.py +++ b/kingbird_dashboard/resource_management/urls.py @@ -25,4 +25,11 @@ SYNCJOB = r'^(?P[^/]+)/%s$' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(SYNCJOB % 'detail', views.DetailView.as_view(), name='detail'), + url(r'^create_sync_job$', views.CreateSyncJobView.as_view(), + name='create_sync_job'), + url(r'^create_sync_template$', views.CreateSyncTemplateView.as_view(), + name='create_sync_template'), + url(r'^load_flavors$', views.load_flavors, name='load_flavors'), + url(r'^load_keypairs$', views.load_keypairs, name='load_keypairs'), + url(r'^load_images$', views.load_images, name='load_images'), ] diff --git a/kingbird_dashboard/resource_management/views.py b/kingbird_dashboard/resource_management/views.py index 9b69d1a..d6fa29e 100644 --- a/kingbird_dashboard/resource_management/views.py +++ b/kingbird_dashboard/resource_management/views.py @@ -13,15 +13,51 @@ # limitations under the License. from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.shortcuts import render +from django.utils.translation import ugettext_lazy as _ from horizon import exceptions +from horizon import forms from horizon import tables from kingbird_dashboard.api import client as kb_client +from kingbird_dashboard.resource_management import forms as kb_forms from kingbird_dashboard.resource_management import tables as kb_tables +class CreateSyncJobView(forms.ModalFormView): + template_name = 'kingbird/resource_management/create.html' + form_id = "create_sync_job" + form_class = kb_forms.SyncJobForm + submit_label = _("Create Sync Job") + submit_url = reverse_lazy( + "horizon:kingbird:resource_management:create_sync_job") + success_url = reverse_lazy('horizon:kingbird:resource_management:index') + page_title = _("Create Sync Job") + + def get_form_kwargs(self): + kwargs = super(CreateSyncJobView, self).get_form_kwargs() + return kwargs + + +class CreateSyncTemplateView(forms.ModalFormView): + template_name = 'kingbird/resource_management/create_template.html' + form_id = "create_sync_template" + form_class = kb_forms.SyncTemplateForm + submit_label = _("Create Sync Template") + submit_url = reverse_lazy( + "horizon:kingbird:resource_management:create_sync_template") + success_url = reverse_lazy('horizon:kingbird:resource_management:index') + page_title = _("Create Sync Template") + + def get_form_kwargs(self): + kwargs = super(CreateSyncTemplateView, self).get_form_kwargs() + + return kwargs + + class IndexView(tables.DataTableView): table_id = "sync_jobs" table_class = kb_tables.ResourceSyncTable @@ -49,3 +85,27 @@ class DetailView(tables.DataTableView): msg = _('Unable to get job_details "%s".') % job_id redirect = reverse('horizon:kingbird:resource_management:index') exceptions.handle(self.request, msg, redirect=redirect) + + +def load_flavors(request): + source = request.GET.get('source') + # Choices for flavors. + flavors_list = kb_client.flavors_list(request, source) + return render(request, 'kingbird/resource_management/resources.html', + {'resources': flavors_list}) + + +def load_keypairs(request): + source = request.GET.get('source') + # Choices for keypairs. + keypairs_list = kb_client.keypairs_list(request, source) + return render(request, 'kingbird/resource_management/resources.html', + {'resources': keypairs_list}) + + +def load_images(request): + source = request.GET.get('source') + # Choices for images. + images_list = kb_client.images_list(request, source) + return render(request, 'kingbird/resource_management/resources.html', + {'resources': images_list})