From ca05aed68c7ff7524b8f16ed101985d1f3636ea8 Mon Sep 17 00:00:00 2001 From: Iwona Kotlarska Date: Tue, 29 Aug 2017 19:58:44 +0200 Subject: [PATCH] Add import of cluster templates to GUI. Partially-Implements: bp portable-node-group-and-cluster-templates This change adds button "Import Template" to cluster templates tab in dashboard. Change-Id: If83aaf34f84e82052c3a82ab36cb61648ff3125f --- .../cluster_templates/forms/__init__.py | 0 .../cluster_templates/forms/import_forms.py | 219 ++++++++++++++++++ .../clusters/cluster_templates/tables.py | 10 + .../clusters/cluster_templates/views.py | 57 +++++ .../templates/cluster_templates/_import.html | 24 ++ .../cluster_templates/_import_nodegroups.html | 25 ++ .../templates/cluster_templates/import.html | 7 + .../cluster_templates/import_nodegroups.html | 7 + .../content/data_processing/clusters/urls.py | 9 + 9 files changed, 358 insertions(+) create mode 100644 sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/__init__.py create mode 100644 sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/import_forms.py create mode 100644 sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import.html create mode 100644 sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import_nodegroups.html create mode 100644 sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import.html create mode 100644 sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import_nodegroups.html diff --git a/sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/__init__.py b/sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/import_forms.py b/sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/import_forms.py new file mode 100644 index 00000000..d2f438d6 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/cluster_templates/forms/import_forms.py @@ -0,0 +1,219 @@ +# 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. + +from django.utils.translation import ugettext_lazy as _ +from oslo_serialization import jsonutils as json + +from horizon import exceptions +from horizon import forms +from saharaclient.api import base as api_base + +from sahara_dashboard.api import sahara as saharaclient +import sahara_dashboard.content.data_processing. \ + utils.workflow_helpers as whelpers +from sahara_dashboard import utils + +BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register" + + +class ImportClusterTemplateFileForm(forms.SelfHandlingForm): + + class Meta(object): + name = _("Import Cluster Template") + + def __init__(self, *args, **kwargs): + self.next_view = kwargs.pop("next_view") + super(ImportClusterTemplateFileForm, self).__init__( + *args, **kwargs) + + template_upload = forms.FileField( + label=_("Template File"), + required=True) + + def handle(self, request, data): + kwargs = {"template_upload": data["template_upload"]} + request.method = "GET" + return self.next_view.as_view()(request, **kwargs) + + +class ImportClusterTemplateNameForm(forms.SelfHandlingForm): + + class Meta(object): + name = _("Import Cluster Template") + + template_upload = forms.CharField( + widget=forms.widgets.HiddenInput) + + name = forms.CharField(label=_("Name"), + required=False, + help_text=_("Name must be provided " + "either here or in the template. If " + "provided in both places, this one " + "will be used.")) + + def __init__(self, *args, **kwargs): + try: + request = args[0] + template_string = "" + self.next_view = kwargs.pop("next_view") + + if "template_upload" in kwargs: + template_upload = kwargs.pop("template_upload") + super(ImportClusterTemplateNameForm, self).__init__( + *args, **kwargs) + + template_string = template_upload.read() + self.fields["template_upload"].initial = template_string + + else: + super(ImportClusterTemplateNameForm, self).__init__( + *args, **kwargs) + except (ValueError, KeyError): + raise exceptions.BadRequest(_("Could not parse template")) + except Exception: + exceptions.handle(request) + + def handle(self, request, data): + template = data["template_upload"] + if data["name"]: + template = json.loads(template) + template["cluster_template"]["name"] = data["name"] + template = json.dumps(template) + kwargs = {"template_upload": template} + request.method = "GET" + return self.next_view.as_view()(request, **kwargs) + + +class ImportClusterTemplateNodegroupsForm(forms.SelfHandlingForm): + + class Meta(object): + name = _("Import Cluster Template") + + template_upload = forms.CharField( + widget=forms.widgets.HiddenInput) + hidden_nodegroups_field = forms.CharField( + required=False, + widget=forms.HiddenInput(attrs={"class": "hidden_nodegroups_field"})) + forms_ids = forms.CharField( + required=False, + widget=forms.HiddenInput()) + + def __init__(self, *args, **kwargs): + try: + request = args[0] + template_string = "" + + if "template_upload" in kwargs: + template_string = kwargs.pop("template_upload") + super(ImportClusterTemplateNodegroupsForm, self).__init__( + *args, **kwargs) + + self.fields["template_upload"].initial = template_string + + else: + super(ImportClusterTemplateNodegroupsForm, self).__init__( + *args, **kwargs) + template_string = self.data["template_upload"] + + template_json = json.loads(template_string) + template_json = template_json["cluster_template"] + + req = request.GET.copy() + req.update(request.POST) + + plugin = template_json["plugin_name"] + version = template_json["hadoop_version"] + + if not plugin or not version: + self.templates = saharaclient.nodegroup_template_find(request) + else: + self.templates = saharaclient.nodegroup_template_find( + request, plugin_name=plugin, hadoop_version=version) + + deletable = req.get("deletable", dict()) + + if "forms_ids" in req: + self.groups = [] + for id in json.loads(req["forms_ids"]): + group_name = "group_name_" + str(id) + template_id = "template_id_" + str(id) + count = "count_" + str(id) + serialized = "serialized_" + str(id) + self.groups.append({"name": req[group_name], + "template_id": req[template_id], + "count": req[count], + "id": id, + "deletable": deletable.get( + req[group_name], "true"), + "serialized": req[serialized]}) + + whelpers.build_node_group_fields(self, + group_name, + template_id, + count, + serialized) + + except (ValueError, KeyError): + raise exceptions.BadRequest(_("Could not parse template")) + except Exception: + exceptions.handle(request) + + def handle(self, request, data): + try: + template = data["template_upload"] + template = json.loads(template) + template = template["cluster_template"] + + if "name" not in template.keys(): + return False + + if "neutron_management_network" in template: + template["net_id"] = ( + template.pop("neutron_management_network")) + + # default_image_id is not supported by the client now + if "default_image_id" in template: + template.pop("default_image_id") + + node_groups = [] + ids = json.loads(data['forms_ids']) + for id in ids: + name = data['group_name_' + str(id)] + template_id = data['template_id_' + str(id)] + count = data['count_' + str(id)] + + raw_ng = data.get("serialized_" + str(id)) + + if raw_ng and raw_ng != 'null': + ng = json.loads(utils.deserialize(str(raw_ng))) + else: + ng = dict() + ng["name"] = name + ng["count"] = count + if template_id and template_id != u'None': + ng["node_group_template_id"] = template_id + node_groups.append(ng) + + template["node_groups"] = node_groups + + saharaclient.cluster_template_create(request, **template) + return True + except api_base.APIException as e: + self.error_description = str(e) + return False + except Exception as e: + if isinstance(e, TypeError): + raise exceptions.BadRequest( + _("Template JSON contained invalid key")) + else: + raise exceptions.BadRequest(_("Could not parse template")) diff --git a/sahara_dashboard/content/data_processing/clusters/cluster_templates/tables.py b/sahara_dashboard/content/data_processing/clusters/cluster_templates/tables.py index 0e4cbc4b..66e982a3 100644 --- a/sahara_dashboard/content/data_processing/clusters/cluster_templates/tables.py +++ b/sahara_dashboard/content/data_processing/clusters/cluster_templates/tables.py @@ -95,6 +95,15 @@ class CreateClusterTemplate(tables.LinkAction): icon = "plus" +class ImportClusterTemplate(tables.LinkAction): + name = "import" + verbose_name = _("Import Template") + url = ("horizon:project:data_processing.clusters:" + "import-cluster-template-file") + classes = ("ajax-modal",) + icon = "plus" + + class ConfigureClusterTemplate(tables.LinkAction): name = "configure" verbose_name = _("Configure Cluster Template") @@ -157,6 +166,7 @@ class ClusterTemplatesTable(sahara_table.SaharaPaginateTabbedTable): name = "cluster_templates" verbose_name = _("Cluster Templates") table_actions = (CreateClusterTemplate, + ImportClusterTemplate, ConfigureClusterTemplate, DeleteTemplate, ClusterTemplatesFilterAction,) diff --git a/sahara_dashboard/content/data_processing/clusters/cluster_templates/views.py b/sahara_dashboard/content/data_processing/clusters/cluster_templates/views.py index 0441b0a5..50991023 100644 --- a/sahara_dashboard/content/data_processing/clusters/cluster_templates/views.py +++ b/sahara_dashboard/content/data_processing/clusters/cluster_templates/views.py @@ -12,9 +12,11 @@ # limitations under the License. from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext_lazy as _ from horizon import exceptions +from horizon import forms from horizon import tabs from horizon.utils import memoized from horizon import workflows @@ -30,6 +32,8 @@ import sahara_dashboard.content.data_processing.clusters. \ cluster_templates.workflows.create as create_flow import sahara_dashboard.content.data_processing.clusters. \ cluster_templates.workflows.edit as edit_flow +import sahara_dashboard.content.data_processing.clusters. \ + cluster_templates.forms.import_forms as import_forms class ClusterTemplateDetailsView(tabs.TabView): @@ -122,3 +126,56 @@ class EditClusterTemplateView(CopyClusterTemplateView): success_url = ("horizon:project:data_processing.clusters" ":index") template_name = "cluster_templates/configure.html" + + +class ImportClusterTemplateFileView(forms.ModalFormView): + template_name = "cluster_templates/import.html" + form_class = import_forms.ImportClusterTemplateFileForm + submit_label = _("Next") + submit_url = reverse_lazy("horizon:project:data_processing." + "clusters:import-cluster-template-file") + success_url = reverse_lazy("horizon:project:data_processing." + "clusters:import-cluster-template-name") + page_title = _("Import Cluster Template") + + def get_form_kwargs(self): + kwargs = super(ImportClusterTemplateFileView, self).get_form_kwargs() + kwargs['next_view'] = ImportClusterTemplateNameView + return kwargs + + +class ImportClusterTemplateNameView(forms.ModalFormView): + template_name = "cluster_templates/import.html" + form_class = import_forms.ImportClusterTemplateNameForm + submit_label = _("Next") + submit_url = reverse_lazy("horizon:project:data_processing." + "clusters:import-cluster-template-name") + success_url = reverse_lazy("horizon:project:data_processing." + "clusters:import-cluster-template-nodegroups") + page_title = _("Import Cluster Template") + + def get_form_kwargs(self): + kwargs = super(ImportClusterTemplateNameView, self).get_form_kwargs() + kwargs['next_view'] = ImportClusterTemplateNodegroupsView + if 'template_upload' in self.kwargs: + kwargs['template_upload'] = self.kwargs['template_upload'] + return kwargs + + +class ImportClusterTemplateNodegroupsView(forms.ModalFormView): + template_name = "cluster_templates/import_nodegroups.html" + # template_name = "some_random_stuff.html" + form_class = import_forms.ImportClusterTemplateNodegroupsForm + submit_label = _("Import") + submit_url = reverse_lazy("horizon:project:data_processing." + "clusters:import-cluster-template-nodegroups") + success_url = reverse_lazy("horizon:project:data_processing." + "clusters:index") + page_title = _("Import Cluster Template") + + def get_form_kwargs(self): + kwargs = super(ImportClusterTemplateNodegroupsView, + self).get_form_kwargs() + if 'template_upload' in self.kwargs: + kwargs['template_upload'] = self.kwargs['template_upload'] + return kwargs diff --git a/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import.html b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import.html new file mode 100644 index 00000000..2869dc41 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} + + +{% load i18n %} + +{% block form_id %}import-cluster-template{% endblock %} +{% block form_action %}{{ submit_url }}{% endblock %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} + +{% block modal-header %}{{ page_title }}{% endblock %} + +{% block modal-body %} +
+
+ {% include "horizon/common/_form_fields.html" %} +
+
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} diff --git a/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import_nodegroups.html b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import_nodegroups.html new file mode 100644 index 00000000..a850a825 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/_import_nodegroups.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} + + +{% load i18n %} + +{% block form_id %}import-cluster-template{% endblock %} +{% block form_action %}{{ submit_url }}{% endblock %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} + +{% block modal-header %}{{ page_title }}{% endblock %} + +{% block modal-body %} +
+
+ {% include "horizon/common/_form_fields.html" %} + {% include "cluster_templates/cluster_node_groups_template.html" %} +
+
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} diff --git a/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import.html b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import.html new file mode 100644 index 00000000..549d74e4 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Import Cluster Template" %}{% endblock %} + +{% block main %} + {% include 'cluster_templates/_import.html' %} +{% endblock %} diff --git a/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import_nodegroups.html b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import_nodegroups.html new file mode 100644 index 00000000..549d74e4 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/templates/cluster_templates/import_nodegroups.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Import Cluster Template" %}{% endblock %} + +{% block main %} + {% include 'cluster_templates/_import.html' %} +{% endblock %} diff --git a/sahara_dashboard/content/data_processing/clusters/urls.py b/sahara_dashboard/content/data_processing/clusters/urls.py index 1e10860f..eefd1539 100644 --- a/sahara_dashboard/content/data_processing/clusters/urls.py +++ b/sahara_dashboard/content/data_processing/clusters/urls.py @@ -45,6 +45,15 @@ urlpatterns = [url(r'^$', views.IndexView.as_view(), name='index'), url(r'^configure-cluster-template$', ct_views.ConfigureClusterTemplateView.as_view(), name='configure-cluster-template'), + url(r'^import-cluster-template-file$', + ct_views.ImportClusterTemplateFileView.as_view(), + name='import-cluster-template-file'), + url(r'^import-cluster-template-name$', + ct_views.ImportClusterTemplateNameView.as_view(), + name='import-cluster-template-name'), + url(r'^import-cluster-template-nodegroups$', + ct_views.ImportClusterTemplateNodegroupsView.as_view(), + name='import-cluster-template-nodegroups'), url(r'^configure-nodegroup-template$', ngt_views.ConfigureNodegroupTemplateView.as_view(), name='configure-nodegroup-template'),