sahara-dashboard-12.0.0/ 0000775 0001750 0001750 00000000000 13656751733 015020 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/doc/ 0000775 0001750 0001750 00000000000 13656751733 015565 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/doc/requirements.txt 0000664 0001750 0001750 00000000526 13656751613 021051 0 ustar zuul zuul 0000000 0000000 # The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD
openstackdocstheme>=1.20.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
sahara-dashboard-12.0.0/sahara_dashboard/ 0000775 0001750 0001750 00000000000 13656751733 020266 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/utils.py 0000664 0001750 0001750 00000005270 13656751613 022001 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2016 Mirantis Inc.
#
# 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 base64
import copy
from urllib import parse
def serialize(obj):
"""Serialize the given object
:param obj: string representation of the object
:return: encoded object (its type is unicode string)
"""
result = base64.urlsafe_b64encode(obj)
# this workaround is needed because in case of python 3 the
# urlsafe_b64encode method returns string of 'bytes' class.
result = result.decode()
return result
def deserialize(obj):
"""Deserialize the given object
:param obj: string representation of the encoded object
:return: decoded object (its type is unicode string)
"""
result = base64.urlsafe_b64decode(obj)
# this workaround is needed because in case of python 3 the
# urlsafe_b64decode method returns string of 'bytes' class
result = result.decode()
return result
def delete_pagination_params_from_request(request, save_limit=None):
"""Delete marker and limit parameters from GET requests
:param request: instance of GET request
:param save_limit: if True, 'limit' will not be deleted
:return: instance of GET request without marker or limit
"""
request = copy.copy(request)
request.GET = request.GET.copy()
params = ['marker']
if not save_limit:
params.append('limit')
for param in ['marker', 'limit']:
if param in request.GET:
del(request.GET[param])
query_string = request.META.get('QUERY_STRING', '')
query_dict = parse.parse_qs(query_string)
if param in query_dict:
del(query_dict[param])
query_string = parse.urlencode(query_dict, doseq=True)
request.META['QUERY_STRING'] = query_string
return request
def smart_sort_helper(version):
"""Allows intelligent sorting of plugin versions, for example when
minor version of a plugin is 11, sort numerically so that 11 > 10,
instead of alphabetically so that 2 > 11
"""
def _safe_cast_to_int(obj):
try:
return int(obj)
except ValueError:
return obj
return [_safe_cast_to_int(part) for part in version.split('.')]
sahara-dashboard-12.0.0/sahara_dashboard/__init__.py 0000664 0001750 0001750 00000000000 13656751613 022362 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/ 0000775 0001750 0001750 00000000000 13656751733 021740 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/__init__.py 0000664 0001750 0001750 00000000000 13656751613 024034 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/ 0000775 0001750 0001750 00000000000 13656751733 025105 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/__init__.py 0000664 0001750 0001750 00000000000 13656751613 027201 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/ 0000775 0001750 0001750 00000000000 13656751733 026245 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/__init__.py 0000664 0001750 0001750 00000000000 13656751613 030341 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/helpers.py 0000664 0001750 0001750 00000020140 13656751613 030253 0 ustar zuul zuul 0000000 0000000 # 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 pytz import timezone as ptz
from django.template import defaultfilters as filters
from django.utils import timezone
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from oslo_utils import timeutils
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as work_helpers
from sahara_dashboard.api import sahara as saharaclient
class Helpers(object):
def __init__(self, request):
self.request = request
def _get_node_processes(self, plugin):
processes = []
for proc_lst in plugin.node_processes.values():
processes += proc_lst
return [(proc_name, proc_name) for proc_name in processes]
def get_node_processes(self, plugin_name, hadoop_version):
plugin = saharaclient.plugin_get_version_details(self.request,
plugin_name,
hadoop_version)
return self._get_node_processes(plugin)
def _extract_parameters(self, configs, scope, applicable_target):
parameters = []
for config in configs:
if (config['scope'] == scope and
config['applicable_target'] == applicable_target):
parameters.append(work_helpers.Parameter(config))
return parameters
def get_cluster_general_configs(self, plugin_name, hadoop_version):
plugin = saharaclient.plugin_get_version_details(self.request,
plugin_name,
hadoop_version)
return self._extract_parameters(plugin.configs, 'cluster', "general")
def get_general_node_group_configs(self, plugin_name, hadoop_version):
plugin = saharaclient.plugin_get_version_details(self.request,
plugin_name,
hadoop_version)
return self._extract_parameters(plugin.configs, 'node', 'general')
def get_general_and_service_nodegroups_parameters(self, plugin_name,
hadoop_version):
plugin = saharaclient.plugin_get_version_details(
self.request, plugin_name, hadoop_version)
general_parameters = self._extract_parameters(
plugin.configs, 'node', 'general')
service_parameters = {}
for service in plugin.node_processes.keys():
service_parameters[service] = self._extract_parameters(
plugin.configs, 'node', service)
return general_parameters, service_parameters
def get_targeted_cluster_configs(self, plugin_name, hadoop_version):
plugin = saharaclient.plugin_get_version_details(self.request,
plugin_name,
hadoop_version)
parameters = {}
for service in plugin.node_processes.keys():
parameters[service] = self._extract_parameters(plugin.configs,
'cluster', service)
return parameters
def is_from_guide(self):
referer = self.request.environ.get("HTTP_REFERER")
if referer and ("/cluster_guide" in referer
or "/jobex_guide" in referer):
return True
return False
def reset_guide(self):
try:
self.request.session.update(
{"plugin_name": None,
"plugin_version": None,
"master_name": None,
"master_id": None,
"worker_name": None,
"worker_id": None,
"guide_cluster_template_name": None})
except Exception:
return False
return True
def reset_job_guide(self):
try:
self.request.session.update(
{"guide_job_type": None,
"guide_job_name": None,
"guide_job_id": None,
"guide_datasource_id": None,
"guide_datasource_name": None, })
except Exception:
return False
return True
def get_duration(self, start_time, end_time=None):
"""Calculates time delta between start and end timestamps
Calculates the delta between given timestamps. The arguments should
be provided as strings. The format should match %Y-%m-%dT%H:%M:%S
which is returned by default from Sahara API.
The end time may be skipped. In this case datetime.now() will be used.
:param start_time: Start timestamp.
:param end_time: (optional) End timestamp.
:return: The delta between timestamps.
"""
start_datetime = timeutils.parse_isotime(start_time)
if end_time:
end_datetime = timeutils.parse_isotime(end_time)
else:
end_datetime = timeutils.utcnow(True)
end_datetime = end_datetime.replace(microsecond=0)
return str(end_datetime - start_datetime)
def to_time_zone(self, datetime, tzone=None,
input_fmt=None, localize=False):
"""Changes given datetime string into given timezone
:param datetime: datetime string
:param tzone: (optional) timezone as a string (e.g. "Europe/Paris"),
by default it's the current django timezone
:param input_fmt: (optional) format of datetime param, if None then
the default Sahara API format (%Y-%m-%dT%H:%M:%S) will be used
:param localize: (optional) if True then format of datetime will be
localized according to current timezone else it will be in
the default Sahara API format (%Y-%m-%dT%H:%M:%S)
:return datetime string in the current django timezone
"""
default_fmt = '%Y-%m-%dT%H:%M:%S'
if tzone is None:
tzone = self.request.session.get('django_timezone', 'UTC')
if input_fmt is None:
input_fmt = default_fmt
dt_in_utc = timezone.utc.localize(
timeutils.parse_strtime(datetime, input_fmt))
dt_in_zone = dt_in_utc.astimezone(ptz(tzone))
if localize:
return filters.date(dt_in_zone, "DATETIME_FORMAT")
else:
return dt_in_zone.strftime(default_fmt)
# Map needed because switchable fields need lower case
# and our server is expecting upper case. We will be
# using the 0 index as the display name and the 1 index
# as the value to pass to the server.
JOB_TYPE_MAP = {"pig": [_("Pig"), "Pig"],
"hive": [_("Hive"), "Hive"],
"spark": [_("Spark"), "Spark"],
"storm": [_("Storm"), "Storm"],
"storm.pyleus": [_("Storm Pyleus"), "Storm.Pyleus"],
"mapreduce": [_("MapReduce"), "MapReduce"],
"mapreduce.streaming": [_("Streaming MapReduce"),
"MapReduce.Streaming"],
"java": [_("Java"), "Java"],
"shell": [_("Shell"), "Shell"]}
# Statuses of clusters that we can choose for job execution and
# suitable messages that will be displayed next to the cluster name
ALLOWED_STATUSES = {
"Active": "",
"Error": "(in error state)",
}
# Cluster status and suitable warning message that will be displayed
# in the Job Launch form
STATUS_MESSAGE_MAP = {
"Error": ugettext("You\'ve chosen a cluster that is in \'Error\' state. "
"Appropriate execution of the job can't be guaranteed."),
}
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/acl.py 0000664 0001750 0001750 00000007475 13656751613 027370 0 ustar zuul zuul 0000000 0000000 # 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 abc
import functools
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import forms
from horizon import tables
MESSAGE_MAPPING_PRESENT = {
'public': functools.partial(
ungettext_lazy,
u"Make public",
u"Make public"),
'private': functools.partial(
ungettext_lazy,
u"Make private",
u"Make private"),
'protected': functools.partial(
ungettext_lazy,
u"Make protected",
u"Make protected"),
'unprotected': functools.partial(
ungettext_lazy,
u"Make unprotected",
u"Make unprotected"),
}
MESSAGE_MAPPING_PAST = {
'public': functools.partial(
ungettext_lazy,
u"Made public",
u"Made public"),
'private': functools.partial(
ungettext_lazy,
u"Made private",
u"Made private"),
'protected': functools.partial(
ungettext_lazy,
u"Made protected",
u"Made protected"),
'unprotected': functools.partial(
ungettext_lazy,
u"Made unprotected",
u"Made unprotected"),
}
class RuleChangeAction(tables.BatchAction):
rule = "no-rule"
def action_present(self, count):
return MESSAGE_MAPPING_PRESENT[self.rule](count)
def action_past(self, count):
return MESSAGE_MAPPING_PAST[self.rule](count)
def action(self, request, datum_id):
try:
update_kwargs = {}
if self.rule in ['public', 'private']:
update_kwargs['is_public'] = self.rule == "public"
if self.rule in ["protected", "unprotected"]:
update_kwargs['is_protected'] = self.rule == "protected"
self.change_rule_method(request, datum_id, **update_kwargs)
except Exception as e:
exceptions.handle(request, e)
raise
class MakePublic(RuleChangeAction):
name = "make_public"
rule = "public"
@abc.abstractmethod
def change_rule_method(self, request, datum_id, **update_kwargs):
pass
class MakePrivate(RuleChangeAction):
name = "make_private"
rule = "private"
@abc.abstractmethod
def change_rule_method(self, request, datum_id, **update_kwargs):
pass
class MakeProtected(RuleChangeAction):
name = "make_protected"
rule = "protected"
@abc.abstractmethod
def change_rule_method(self, request, datum_id, **update_kwargs):
pass
class MakeUnProtected(RuleChangeAction):
name = "make_unprotected"
rule = "unprotected"
@abc.abstractmethod
def change_rule_method(self, request, datum_id, **update_kwargs):
pass
def get_is_public_form(object_type):
return forms.BooleanField(
label=_("Public"),
help_text=_("If selected, %s will be shared across the "
"tenants") % object_type,
required=False,
widget=forms.CheckboxInput(),
initial=False,
)
def get_is_protected_form(object_type):
return forms.BooleanField(
label=_("Protected"),
help_text=_("If selected, %s will be protected from modifications "
"until this will be unselected") % object_type,
required=False,
widget=forms.CheckboxInput(),
initial=False)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/workflow_helpers.py 0000664 0001750 0001750 00000037701 13656751613 032220 0 ustar zuul zuul 0000000 0000000 # 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 oslo_log import log as logging
from django.core.exceptions import ValidationError
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
from openstack_dashboard.api import neutron
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard import utils
LOG = logging.getLogger(__name__)
class Parameter(object):
def __init__(self, config):
self.name = config['name']
self.description = config.get('description', "No description")
self.required = not config['is_optional']
self.default_value = config.get('default_value', None)
self.initial_value = self.default_value
self.param_type = config['config_type']
self.priority = int(config.get('priority', 2))
self.choices = config.get('config_values', None)
def build_control(parameter):
attrs = {"priority": parameter.priority,
"placeholder": parameter.default_value}
if parameter.param_type == "string":
return forms.CharField(
widget=forms.TextInput(attrs=attrs),
label=parameter.name,
required=(parameter.required and
parameter.default_value is None),
help_text=parameter.description,
initial=parameter.initial_value)
if parameter.param_type == "int":
return forms.IntegerField(
widget=forms.TextInput(attrs=attrs),
label=parameter.name,
required=parameter.required,
help_text=parameter.description,
initial=parameter.initial_value)
elif parameter.param_type == "bool":
return forms.BooleanField(
widget=forms.CheckboxInput(attrs=attrs),
label=parameter.name,
required=False,
initial=parameter.initial_value,
help_text=parameter.description)
elif parameter.param_type == "dropdown":
return forms.ChoiceField(
widget=forms.Select(attrs=attrs),
label=parameter.name,
required=parameter.required,
choices=parameter.choices,
help_text=parameter.description)
def _create_step_action(name, title, parameters, advanced_fields=None,
service=None):
class_fields = {}
contributes_field = ()
for param in parameters:
field_name = "CONF:" + service + ":" + param.name
contributes_field += (field_name,)
class_fields[field_name] = build_control(param)
if advanced_fields is not None:
for ad_field_name, ad_field_value in advanced_fields:
class_fields[ad_field_name] = ad_field_value
action_meta = type('Meta', (object, ),
dict(help_text_template=("nodegroup_templates/"
"_fields_help.html")))
class_fields['Meta'] = action_meta
action = type(str(title),
(workflows.Action,),
class_fields)
step_meta = type('Meta', (object,), dict(name=title))
step = type(str(name),
(workflows.Step, ),
dict(name=name,
process_name=name,
action_class=action,
contributes=contributes_field,
Meta=step_meta))
return step
def build_node_group_fields(action, name, template, count, serialized=None):
action.fields[name] = forms.CharField(
label=_("Name"),
widget=forms.TextInput())
action.fields[template] = forms.CharField(
label=_("Node group cluster"),
widget=forms.HiddenInput())
action.fields[count] = forms.IntegerField(
label=_("Count"),
min_value=0,
widget=forms.HiddenInput())
action.fields[serialized] = forms.CharField(
widget=forms.HiddenInput())
def build_interface_argument_fields(
action, name, description, mapping_type, location, value_type,
required, default_value):
action.fields[name] = forms.CharField(
label=_("Name"),
widget=forms.TextInput(),
required=True)
action.fields[description] = forms.CharField(
label=_("Description"),
widget=forms.TextInput(),
required=False)
action.fields[mapping_type] = forms.ChoiceField(
label=_("Mapping Type"),
widget=forms.Select(),
required=True,
choices=[("args", _("Positional Argument")),
("configs", _("Configuration Value")),
("params", _("Named Parameter"))])
action.fields[location] = forms.CharField(
label=_("Location"),
widget=forms.TextInput(),
required=True)
action.fields[value_type] = forms.ChoiceField(
label=_("Value Type"),
widget=forms.Select(),
required=True,
choices=[("string", _("String")),
("number", _("Number")),
("data_source", _("Data Source"))])
action.fields[required] = forms.BooleanField(
widget=forms.CheckboxInput(),
label=_("Required"),
required=False,
initial=True)
action.fields[default_value] = forms.CharField(
label=_("Default Value"),
widget=forms.TextInput(),
required=False)
def parse_configs_from_context(context, defaults):
configs_dict = dict()
for key, val in context.items():
if str(key).startswith("CONF"):
key_split = str(key).split(":")
service = key_split[1]
config = key_split[2]
if service not in configs_dict:
configs_dict[service] = dict()
if val is None:
continue
if str(defaults[service][config]) == str(val):
continue
configs_dict[service][config] = val
return configs_dict
def get_security_groups(request, security_group_ids):
security_groups = []
for group in security_group_ids or []:
try:
security_groups.append(neutron.security_group_get(
request, group))
except Exception:
LOG.info(_('Unable to retrieve security group %(group)s.') %
{'group': group})
security_groups.append({'name': group})
return security_groups
def get_plugin_and_hadoop_version(request):
plugin_name = None
hadoop_version = None
# In some cases request contains valuable info in both GET and POST methods
req = request.GET.copy()
req.update(request.POST)
if req.get("plugin_name"):
plugin_name = req["plugin_name"]
hadoop_version = (
req.get("plugin_version", None) or req["hadoop_version"]
)
return plugin_name, hadoop_version
def clean_node_group(node_group):
node_group_copy = dict((key, value)
for key, value in node_group.items() if value)
for key in ["id", "created_at", "updated_at"]:
if key in node_group_copy:
node_group_copy.pop(key)
return node_group_copy
def populate_image_choices(self, request, context, empty_choice=False):
try:
all_images = saharaclient.image_list(request)
plugin, hadoop_version = get_plugin_and_hadoop_version(request)
details = saharaclient.plugin_get_version_details(request,
plugin,
hadoop_version)
choices = [(image.id, image.name) for image in all_images
if (set(details.required_image_tags).
issubset(set(image.tags)))]
except Exception:
exceptions.handle(request,
_("Unable to fetch image choices."))
choices = []
if empty_choice:
choices = [(None, _('No image specified'))] + choices
if not choices:
choices.append(("", _("No Images Available")))
return choices
class PluginAndVersionMixin(object):
def _generate_plugin_version_fields(self, sahara):
plugins = [p for p in sahara.plugins.list()
if is_plugin_not_hidden_for_user(p)]
plugin_choices = [(plugin.name, plugin.title) for plugin in plugins]
self.fields["plugin_name"] = forms.ChoiceField(
label=_("Plugin Name"),
choices=plugin_choices,
widget=forms.Select(
attrs={"class": "plugin_name_choice switchable",
'data-slug': 'pluginname'}))
for plugin in plugins:
versions = [(version, version)
for version in get_enabled_versions(plugin)]
version_choices = sorted(
versions,
reverse=True,
key=lambda v: utils.smart_sort_helper(v[0]))
field_name = plugin.name + "_version"
choice_field = forms.ChoiceField(
label=_("Version"),
choices=version_choices,
widget=forms.Select(
attrs={"class": "plugin_version_choice switched "
+ field_name + "_choice",
"data-switch-on": "pluginname",
"data-pluginname-%s" % plugin.name: _("Version")})
)
self.fields[field_name] = choice_field
class PatchedDynamicWorkflow(workflows.Workflow):
"""Overrides Workflow to fix its issues."""
def _ensure_dynamic_exist(self):
if not hasattr(self, 'dynamic_steps'):
self.dynamic_steps = list()
def _register_step(self, step):
# Use that method instead of 'register' to register step.
# Note that a step could be registered in descendant class constructor
# only before this class constructor is invoked.
self._ensure_dynamic_exist()
self.dynamic_steps.append(step)
def _order_steps(self):
# overrides method of Workflow
# crutch to fix https://bugs.launchpad.net/horizon/+bug/1196717
# and another not filed issue that dynamic creation of tabs is
# not thread safe
self._ensure_dynamic_exist()
self._registry = dict([(step, step(self))
for step in self.dynamic_steps])
return list(self.default_steps) + self.dynamic_steps
class ServiceParametersWorkflow(PatchedDynamicWorkflow):
"""Base class for Workflows having services tabs with parameters."""
def _populate_tabs(self, general_parameters, service_parameters):
# Populates tabs for 'general' and service parameters
# Also populates defaults and initial values
self.defaults = dict()
self._init_step('general', 'General Parameters', general_parameters)
for service, parameters in service_parameters.items():
self._init_step(service, service + ' Parameters', parameters)
def _init_step(self, service, title, parameters):
if not parameters:
return
self._populate_initial_values(service, parameters)
step = _create_step_action(service, title=title, parameters=parameters,
service=service)
self.defaults[service] = dict()
for param in parameters:
self.defaults[service][param.name] = param.default_value
self._register_step(step)
def _set_configs_to_copy(self, configs):
self.configs_to_copy = configs
def _populate_initial_values(self, service, parameters):
if not hasattr(self, 'configs_to_copy'):
return
configs = self.configs_to_copy
for param in parameters:
if (service in configs and
param.name in configs[service]):
param.initial_value = configs[service][param.name]
class StatusFormatMixin(workflows.Workflow):
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
super(StatusFormatMixin, self).__init__(request,
context_seed,
entry_point,
*args,
**kwargs)
def format_status_message(self, message):
error_description = getattr(self, 'error_description', None)
if error_description:
return error_description
else:
return message % self.context[self.name_property]
class ShareWidget(forms.MultiWidget):
def __init__(self, choices=()):
widgets = []
for choice in choices:
widgets.append(forms.CheckboxInput(
attrs={
"label": choice[1],
"value": choice[0],
}))
widgets.append(forms.TextInput())
widgets.append(forms.Select(
choices=(("rw", _("Read/Write")), ("ro", _("Read only")))))
super(ShareWidget, self).__init__(widgets)
def decompress(self, value):
if value:
values = []
for share in value:
values.append(value[share]["id"])
values.append(value[share]["path"])
values.append(value[share]["access_level"])
return values
return [None] * len(self.widgets)
def format_output(self, rendered_widgets):
output = []
output.append("
")
output.append("
Share
Enabled
"
"
Path
Permissions
")
for i, widget in enumerate(rendered_widgets):
item_widget_index = i % 3
if item_widget_index == 0:
output.append("
")
output.append(
"
%s
" %
self.widgets[i].attrs["label"])
# The last 2 form field td need get a larger size
if item_widget_index in [1, 2]:
size = 4
else:
size = 2
output.append("
" % size
+ widget + "
")
if item_widget_index == 2:
output.append("
")
output.append("
")
return safestring.mark_safe('\n'.join(output))
class MultipleShareChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'], code='required')
if not isinstance(value, list):
raise ValidationError(
_("The value of shares must be a list of values")
)
def is_plugin_not_hidden_for_user(plugin):
hidden_lbl = plugin.plugin_labels.get('hidden')
if hidden_lbl and hidden_lbl['status']:
return False
if get_enabled_versions(plugin):
return True
return False
def get_enabled_versions(plugin):
lbs = plugin.version_labels
versions = []
for version, data in lbs.items():
if data.get('enabled', {'status': True}).get('status', True):
versions.append(version)
if not plugin.plugin_labels.get(
'enabled', {'status': True}).get('status', True):
versions = []
return versions
def is_version_of_plugin_deprecated(plugin, version):
lbs = plugin.version_labels
for iter_version, data in lbs.items():
if iter_version == version:
if data.get('deprecated', {'status': False}).get('status', False):
return True
else:
return False
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/neutron_support.py 0000664 0001750 0001750 00000002250 13656751613 032101 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2013 Mirantis Inc.
#
# 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 horizon import exceptions
from openstack_dashboard.api import neutron
def populate_neutron_management_network_choices(self, request, context):
try:
tenant_id = self.request.user.tenant_id
networks = neutron.network_list_for_tenant(request, tenant_id)
network_list = [(network.id, network.name_or_id)
for network in networks]
except Exception:
network_list = []
exceptions.handle(request,
_('Unable to retrieve networks.'))
return network_list
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/utils/anti_affinity.py 0000664 0001750 0001750 00000004317 13656751613 031445 0 ustar zuul zuul 0000000 0000000 # 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 horizon import exceptions
from horizon import forms
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as whelpers
def anti_affinity_field():
return forms.MultipleChoiceField(
label=_("Use anti-affinity groups for: "),
required=False,
help_text=_("Use anti-affinity groups for processes"),
widget=forms.CheckboxSelectMultiple()
)
def populate_anti_affinity_choices(self, request, context):
try:
sahara = saharaclient.client(request)
plugin, version = whelpers.get_plugin_and_hadoop_version(request)
version_details = sahara.plugins.get_version_details(plugin, version)
process_choices = []
for processes in version_details.node_processes.values():
for process in processes:
process_choices.append((process, process))
cluster_template_id = request.GET.get("cluster_template_id", None)
if cluster_template_id is None:
selected_processes = request.GET.get("aa_groups", [])
else:
cluster_template = (
sahara.cluster_templates.get(cluster_template_id))
selected_processes = cluster_template.anti_affinity
checked_dict = dict()
for process in selected_processes:
checked_dict[process] = process
self.fields['anti_affinity'].initial = checked_dict
except Exception:
process_choices = []
exceptions.handle(request,
_("Unable to populate anti-affinity processes."))
return process_choices
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/ 0000775 0001750 0001750 00000000000 13656751733 027557 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/__init__.py 0000664 0001750 0001750 00000000000 13656751613 031653 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/panel.py 0000664 0001750 0001750 00000001657 13656751613 031236 0 ustar zuul zuul 0000000 0000000 # 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 _
import horizon
from openstack_dashboard.dashboards.project import dashboard
class PluginsPanel(horizon.Panel):
name = _("Plugins")
slug = 'data_processing.data_plugins'
permissions = (('openstack.services.data-processing',
'openstack.services.data_processing'),)
dashboard.Project.register(PluginsPanel)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/ 0000775 0001750 0001750 00000000000 13656751733 031555 5 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011216 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/ sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins0000775 0001750 0001750 00000000000 13656751733 034150 5 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011213 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/update.html sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins0000664 0001750 0001750 00000000267 13656751613 034154 0 ustar zuul zuul 0000000 0000000 {% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Plugin" %}{% endblock %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %} ././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011215 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/_details.html sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins0000664 0001750 0001750 00000000756 13656751613 034157 0 ustar zuul zuul 0000000 0000000 {% load i18n %}
{% endblock %}
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/urls.py 0000664 0001750 0001750 00000001656 13656751613 031123 0 ustar zuul zuul 0000000 0000000 # 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.conf.urls import url
from sahara_dashboard.content.data_processing.data_plugins import views
urlpatterns = [
url(r'^$', views.PluginsView.as_view(), name='index'),
url(r'^(?P[^/]+)$',
views.PluginDetailsView.as_view(), name='plugin-details'),
url(r'^(?P[^/]+)/update',
views.UpdatePluginView.as_view(),
name='update'),
]
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/tabs.py 0000664 0001750 0001750 00000007642 13656751613 031070 0 ustar zuul zuul 0000000 0000000 # 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 oslo_log import log as logging
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing.data_plugins \
import tables as plugin_tables
from sahara_dashboard.content.data_processing \
import tabs as sahara_tabs
LOG = logging.getLogger(__name__)
class PluginsTab(sahara_tabs.SaharaTableTab):
table_classes = (plugin_tables.PluginsTable, )
name = _("Plugins")
slug = "plugins_tab"
template_name = "horizon/common/_detail_table.html"
def get_plugins_data(self):
try:
plugins = saharaclient.plugin_list(self.request)
except Exception:
plugins = []
exceptions.handle(self.request,
_("Unable to fetch plugin list"))
return plugins
class DetailsTab(tabs.Tab):
name = _("Details")
slug = "plugin_details_tab"
template_name = "data_plugins/_details.html"
def _generate_context(self, plugin):
if not plugin:
return {'plugin': plugin}
def get_context_data(self, request):
plugin_id = self.tab_group.kwargs['plugin_id']
plugin = None
try:
plugin = saharaclient.plugin_get(request, plugin_id)
except Exception as e:
LOG.error("Unable to get plugin with plugin_id %s (%s)" %
(plugin_id, str(e)))
exceptions.handle(self.tab_group.request,
_('Unable to retrieve plugin.'))
return {"plugin": plugin}
class LabelsTab(tabs.Tab):
name = _("Label details")
slug = "label_details_tab"
template_name = "data_plugins/_label_details.html"
def _label_color(self, label):
color = 'info'
if label == 'deprecated':
color = 'danger'
elif label == 'stable':
color = 'success'
return color
def get_context_data(self, request, **kwargs):
plugin_id = self.tab_group.kwargs['plugin_id']
plugin = None
try:
plugin = saharaclient.plugin_get(request, plugin_id)
except Exception as e:
LOG.error("Unable to get plugin with plugin_id %s (%s)" %
(plugin_id, str(e)))
exceptions.handle(self.tab_group.request,
_('Unable to retrieve plugin.'))
labels = []
for label, data in plugin.plugin_labels.items():
labels.append(
{'name': label,
'color': self._label_color(label),
'description': data.get('description', _("No description")),
'scope': _("Plugin"), 'status': data.get('status', False)})
for version, version_data in plugin.version_labels.items():
for label, data in version_data.items():
labels.append(
{'name': label,
'color': self._label_color(label),
'description': data.get('description',
_("No description")),
'scope': _("Plugin version %s") % version,
'status': data.get('status', False)})
return {"labels": labels}
class PluginDetailsTabs(tabs.TabGroup):
slug = "cluster_details"
tabs = (DetailsTab, LabelsTab)
sticky = True
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/tests.py 0000664 0001750 0001750 00000003615 13656751613 031275 0 ustar zuul zuul 0000000 0000000 # 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 unittest import mock
from django.urls import reverse
from sahara_dashboard import api
from sahara_dashboard.test import helpers as test
from sahara_dashboard.test.helpers import IsHttpRequest
INDEX_URL = reverse(
'horizon:project:data_processing.data_plugins:index')
DETAILS_URL = reverse(
'horizon:project:data_processing.data_plugins:plugin-details', args=['id'])
class DataProcessingPluginsTests(test.TestCase):
@test.create_mocks({api.sahara: ('plugin_list',)})
def test_index(self):
self.mock_plugin_list.return_value = self.plugins.list()
res = self.client.get(INDEX_URL)
self.mock_plugin_list.assert_called_once_with(IsHttpRequest())
self.assertTemplateUsed(res, 'data_plugins/plugins.html')
self.assertContains(res, 'vanilla')
self.assertContains(res, 'plugin')
@test.create_mocks({api.sahara: ('plugin_get',)})
def test_details(self):
self.mock_plugin_get.return_value = self.plugins.list()[0]
res = self.client.get(DETAILS_URL)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_plugin_get, 2, mock.call(test.IsHttpRequest(),
test.IsA(str)))
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
self.assertContains(res, 'vanilla')
self.assertContains(res, 'plugin')
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/views.py 0000664 0001750 0001750 00000005340 13656751613 031265 0 ustar zuul zuul 0000000 0000000 # 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 horizon import exceptions
from horizon import tables
from horizon import tabs
from horizon import workflows
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.data_plugins. \
tables as p_tables
import sahara_dashboard.content.data_processing.data_plugins. \
tabs as p_tabs
from sahara_dashboard.content.data_processing.data_plugins.workflows \
import update
class PluginsView(tables.DataTableView):
table_class = p_tables.PluginsTable
template_name = 'data_plugins/plugins.html'
page_title = _("Data Processing Plugins")
def get_data(self):
try:
plugins = saharaclient.plugin_list(self.request)
except Exception:
plugins = []
msg = _('Unable to retrieve data processing plugins.')
exceptions.handle(self.request, msg)
return plugins
class PluginDetailsView(tabs.TabView):
tab_group_class = p_tabs.PluginDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = _("Data Processing Plugin Details")
class UpdatePluginView(workflows.WorkflowView):
workflow_class = update.UpdatePlugin
success_url = "horizon:project:data_processing.data_plugins"
classes = ("ajax-modal",)
template_name = "data_plugins/update.html"
page_title = _("Update Plugin")
def get_context_data(self, **kwargs):
context = super(UpdatePluginView, self) \
.get_context_data(**kwargs)
context["plugin_name"] = kwargs["plugin_name"]
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
plugin_name = self.kwargs['plugin_name']
try:
plugin = saharaclient.plugin_get(self.request, plugin_name)
except Exception:
exceptions.handle(self.request,
_("Unable to fetch plugin object."))
self._object = plugin
return self._object
def get_initial(self):
initial = super(UpdatePluginView, self).get_initial()
initial['plugin_name'] = self.kwargs['plugin_name']
return initial
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/tables.py 0000664 0001750 0001750 00000003447 13656751613 031410 0 ustar zuul zuul 0000000 0000000 # 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.template import loader
from django.utils.translation import ugettext_lazy as _
from horizon import tables
from sahara_dashboard.content.data_processing.utils \
import workflow_helpers as w_helpers
class UpdatePluginAction(tables.LinkAction):
name = "update_plugin"
verbose_name = _("Update Plugin")
url = "horizon:project:data_processing.data_plugins:update"
classes = ("ajax-modal", "btn-edit")
def versions_to_string(plugin):
template_name = 'data_plugins/_list_versions.html'
versions = w_helpers.get_enabled_versions(plugin)
context = {"versions": versions}
return loader.render_to_string(template_name, context)
class PluginsTable(tables.DataTable):
title = tables.Column("title",
verbose_name=_("Title"),
link=("horizon:project:data_processing."
"data_plugins:plugin-details"))
versions = tables.Column(versions_to_string,
verbose_name=_("Enabled Versions"))
description = tables.Column("description",
verbose_name=_("Description"))
class Meta(object):
name = "plugins"
verbose_name = _("Plugins")
row_actions = (UpdatePluginAction,)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/workflows/ 0000775 0001750 0001750 00000000000 13656751733 031614 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/workflows/__init__.py 0000664 0001750 0001750 00000000000 13656751613 033710 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/data_plugins/workflows/update.py 0000664 0001750 0001750 00000010141 13656751613 033442 0 ustar zuul zuul 0000000 0000000 # 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 saharaclient.api import base as api_base
from horizon import exceptions
from horizon import forms
from horizon import workflows
from sahara_dashboard.api import sahara as saharaclient
class UpdateLabelsAction(workflows.Action):
def __init__(self, request, *args, **kwargs):
super(UpdateLabelsAction, self).__init__(request, *args, **kwargs)
plugin_name = [
x['plugin_name'] for x in args if 'plugin_name' in x][0]
plugin = saharaclient.plugin_get(request, plugin_name)
self._serialize_labels(
'plugin_', _("Plugin label"), plugin.plugin_labels)
vers_labels = plugin.version_labels
for version in vers_labels.keys():
field_label = _("Plugin version %(version)s label") % {
'version': version}
self._serialize_labels(
'version_%s_' % version, field_label, vers_labels[version])
self.fields["plugin_name"] = forms.CharField(
widget=forms.HiddenInput(),
initial=plugin_name)
def _serialize_labels(self, prefix, prefix_trans, labels):
for name, label in labels.items():
if not label['mutable']:
continue
res_name_translated = "%s: %s" % (prefix_trans, name)
res_name = "label_%s%s" % (prefix, name)
self.fields[res_name] = forms.BooleanField(
label=res_name_translated,
help_text=label['description'],
widget=forms.CheckboxInput(),
initial=label['status'],
required=False,
)
class Meta(object):
name = _("Plugin")
help_text = _("Update the plugin labels")
class UpdatePluginStep(workflows.Step):
action_class = UpdateLabelsAction
depends_on = ('plugin_name', )
def contribute(self, data, context):
for name, item in data.items():
context[name] = item
return context
class UpdatePlugin(workflows.Workflow):
slug = "update_plugin"
name = _("Update Plugin")
success_message = _("Updated")
failure_message = _("Could not update plugin")
success_url = "horizon:project:data_processing.data_plugins:index"
default_steps = (UpdatePluginStep,)
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
super(UpdatePlugin, self).__init__(
request, context_seed, entry_point, *args, **kwargs)
def _get_update_values(self, context):
values = {'plugin_labels': {}, 'version_labels': {}}
for item, item_value in context.items():
if not item.startswith('label_'):
continue
name = item.split('_')[1:]
if name[0] == 'plugin':
values['plugin_labels'][name[1]] = {'status': item_value}
else:
if name[1] not in values['version_labels']:
values['version_labels'][name[1]] = {}
values['version_labels'][
name[1]][name[2]] = {'status': item_value}
return values
def handle(self, request, context):
try:
update_values = self._get_update_values(context)
saharaclient.plugin_update(
request, context['plugin_name'], update_values)
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_("Plugin update failed."))
return False
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/ 0000775 0001750 0001750 00000000000 13656751733 026751 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/ 0000775 0001750 0001750 00000000000 13656751733 031763 5 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/__init__.py0000664 0001750 0001750 00000000000 13656751613 034057 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/tabs.py 0000664 0001750 0001750 00000002476 13656751613 033274 0 ustar zuul zuul 0000000 0000000 # 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 horizon import exceptions
from horizon import tabs
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing.clusters.image_registry \
import tables as image_registry_tables
class ImageRegistryTab(tabs.TableTab):
table_classes = (image_registry_tables.ImageRegistryTable, )
name = _("Image Registry")
slug = "image_registry_tab"
template_name = "horizon/common/_detail_table.html"
def get_image_registry_data(self):
try:
images = saharaclient.image_list(self.request)
except Exception:
images = []
msg = _('Unable to retrieve image list')
exceptions.handle(self.request, msg)
return images
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/tests.py 0000664 0001750 0001750 00000012125 13656751613 033475 0 ustar zuul zuul 0000000 0000000 # 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.urls import reverse
from openstack_dashboard import api as dash_api
from sahara_dashboard import api
from sahara_dashboard.test import helpers as test
from sahara_dashboard.test.helpers import IsHttpRequest
INDEX_URL = reverse(
'horizon:project:data_processing.clusters:image-registry-tab')
REGISTER_URL = reverse(
'horizon:project:data_processing.clusters:register')
SUCCESS_URL = reverse(
'horizon:project:data_processing.clusters:index')
class DataProcessingImageRegistryTests(test.TestCase):
@test.create_mocks({api.sahara: ('cluster_template_list',
'image_list',
'cluster_list',
'nodegroup_template_list')})
def test_index(self):
self.mock_image_list.return_value = self.images.list()
res = self.client.get(INDEX_URL)
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.assertTemplateUsed(res, 'clusters/index.html')
self.assertContains(res, 'Image Registry')
self.assertContains(res, 'Image')
self.assertContains(res, 'Tags')
@test.create_mocks({api.sahara: ('image_get',
'image_update',
'image_tags_update',
'image_list'),
dash_api.glance: ('image_list_detailed',)})
def test_register(self):
image = self.images.first()
image_id = image.id
test_username = 'myusername'
test_description = 'mydescription'
self.mock_image_get.return_value = image
self.mock_image_list_detailed.return_value = (
self.images.list(), False, False)
self.mock_image_update.return_value = True
self.mock_image_tags_update.return_value = True
self.mock_image_list.return_value = []
res = self.client.post(
REGISTER_URL,
{'image_id': image_id,
'user_name': test_username,
'description': test_description,
'tags_list': '{}'})
self.mock_image_list_detailed.assert_called_once_with(
IsHttpRequest(),
filters={'owner': self.user.id,
'status': 'active'})
self.mock_image_update.assert_called_once_with(
IsHttpRequest(), image_id, test_username, test_description)
self.mock_image_tags_update.assert_called_once_with(
IsHttpRequest(), image_id, {})
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, SUCCESS_URL)
self.assertMessageCount(success=1)
@test.create_mocks({api.sahara: ('image_list',
'image_unregister')})
def test_unregister(self):
image = self.images.first()
self.mock_image_list.return_value = self.images.list()
self.mock_image_unregister.return_value = None
form_data = {'action': 'image_registry__unregister__%s' % image.id}
res = self.client.post(INDEX_URL, form_data)
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.mock_image_unregister.assert_called_once_with(
IsHttpRequest(), image.id)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
@test.create_mocks({api.sahara: ('image_get',
'image_update',
'image_tags_update')})
def test_edit_tags(self):
image = self.registered_images.first()
self.mock_image_get.return_value = image
self.mock_image_update.return_value = True
self.mock_image_tags_update.return_value = True
edit_tags_url = reverse(
'horizon:project:data_processing.clusters:edit_tags',
args=[image.id])
res = self.client.post(
edit_tags_url,
{'image_id': image.id,
'user_name': image.username,
'description': image.description,
'tags_list': '{"0": "mytag"}'})
self.mock_image_update.assert_called_once_with(
IsHttpRequest(), image.id, image.username,
image.description)
self.mock_image_tags_update.assert_called_once_with(
IsHttpRequest(), image.id, {"0": "mytag"})
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, SUCCESS_URL)
self.assertMessageCount(success=1)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/views.py 0000664 0001750 0001750 00000007561 13656751613 033500 0 ustar zuul zuul 0000000 0000000 # 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 collections import OrderedDict
import json
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content. \
data_processing.clusters.image_registry.forms import EditTagsForm
from sahara_dashboard.content. \
data_processing.clusters.image_registry.forms import RegisterImageForm
from sahara_dashboard import utils
def update_context_with_plugin_tags(request, context):
try:
plugins = saharaclient.plugin_list(request)
except Exception:
plugins = []
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
plugins_object = dict()
for plugin in plugins:
plugins_object[plugin.name] = OrderedDict()
for version in sorted(plugin.versions, reverse=True,
key=utils.smart_sort_helper):
try:
details = saharaclient. \
plugin_get_version_details(request,
plugin.name,
version)
plugins_object[plugin.name][version] = (
details.required_image_tags)
except Exception:
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
context["plugins"] = plugins_object
class EditTagsView(forms.ModalFormView):
form_class = EditTagsForm
template_name = 'image_registry/edit_tags.html'
success_url = reverse_lazy(
'horizon:project:data_processing.clusters:index')
page_title = _("Edit Image Tags")
def get_context_data(self, **kwargs):
context = super(EditTagsView, self).get_context_data(**kwargs)
context['image'] = self.get_object()
update_context_with_plugin_tags(self.request, context)
return context
@memoized.memoized_method
def get_object(self):
try:
image = saharaclient.image_get(self.request,
self.kwargs["image_id"])
except Exception:
image = None
msg = _("Unable to fetch the image details")
exceptions.handle(self.request, msg)
return image
def get_initial(self):
image = self.get_object()
return {"image_id": image.id,
"tags_list": json.dumps(image.tags),
"user_name": image.username,
"description": image.description}
class RegisterImageView(forms.ModalFormView):
form_class = RegisterImageForm
template_name = 'image_registry/register_image.html'
success_url = reverse_lazy(
'horizon:project:data_processing.clusters:index')
page_title = _("Register Image")
def get_context_data(self, **kwargs):
context = super(RegisterImageView, self).get_context_data(**kwargs)
context['action_url'] = ('horizon:project'
':data_processing.clusters:register')
update_context_with_plugin_tags(self.request, context)
return context
def get_initial(self):
# need this initialization to allow registration
# of images without tags
return {"tags_list": json.dumps([])}
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/tables.py 0000664 0001750 0001750 00000004654 13656751613 033615 0 ustar zuul zuul 0000000 0000000 # 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 import template
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from sahara_dashboard.api import sahara as saharaclient
class EditTagsAction(tables.LinkAction):
name = "edit_tags"
verbose_name = _("Edit Tags")
url = "horizon:project:data_processing.clusters:edit_tags"
classes = ("ajax-modal",)
def tags_to_string(image):
template_name = 'image_registry/_list_tags.html'
context = {"image": image}
return template.loader.render_to_string(template_name, context)
class RegisterImage(tables.LinkAction):
name = "register"
verbose_name = _("Register Image")
url = "horizon:project:data_processing.clusters:register"
classes = ("ajax-modal",)
icon = "plus"
class UnregisterImages(tables.DeleteAction):
name = "unregister"
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Unregister Image",
u"Unregister Images",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Unregistered Image",
u"Unregistered Images",
count
)
def delete(self, request, obj_id):
saharaclient.image_unregister(request, obj_id)
class ImageRegistryTable(tables.DataTable):
name = tables.Column("name",
verbose_name=_("Image"),
link=("horizon:project:"
"images:images:detail"))
tags = tables.Column(tags_to_string,
verbose_name=_("Tags"))
user = tables.Column("username", verbose_name=_("User"))
class Meta(object):
name = "image_registry"
verbose_name = _("Image Registry")
table_actions = (RegisterImage, UnregisterImages,)
row_actions = (EditTagsAction, UnregisterImages,)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/image_registry/forms.py 0000664 0001750 0001750 00000010340 13656751613 033456 0 ustar zuul zuul 0000000 0000000 # 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
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard.api import glance
from sahara_dashboard.api import sahara as saharaclient
class ImageForm(forms.SelfHandlingForm):
image_id = forms.CharField(widget=forms.HiddenInput())
tags_list = forms.CharField(widget=forms.HiddenInput())
user_name = forms.CharField(max_length=80, label=_("User Name"))
description = forms.CharField(max_length=80,
label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'rows': 4}))
def handle(self, request, data):
try:
image_id = data['image_id']
user_name = data['user_name']
desc = data['description']
saharaclient.image_update(request, image_id, user_name, desc)
image_tags = json.loads(data["tags_list"])
saharaclient.image_tags_update(request, image_id, image_tags)
updated_image = saharaclient.image_get(request, image_id)
messages.success(request,
_("Successfully updated image."))
return updated_image
except Exception:
exceptions.handle(request,
_("Failed to update image."))
return False
class EditTagsForm(ImageForm):
image_id = forms.CharField(widget=forms.HiddenInput())
class RegisterImageForm(ImageForm):
image_id = forms.ChoiceField(label=_("Image"))
def __init__(self, request, *args, **kwargs):
super(RegisterImageForm, self).__init__(request, *args, **kwargs)
self._populate_image_id_choices()
def _populate_image_id_choices(self):
images = self._get_available_images(self.request)
choices = [(image.id, image.name)
for image in images
if image.to_dict()['properties'].get(
"image_type") != "snapshot"]
if choices:
choices.insert(0, ("", _("Select Image")))
else:
choices.insert(0, ("", _("No images available.")))
self.fields['image_id'].choices = choices
def _get_images(self, request, filter):
try:
images, _more, _prev = (
glance.image_list_detailed(request, filters=filter))
except Exception:
images = []
exceptions.handle(request,
_("Unable to retrieve images with filter %s.") %
filter)
return images
def _get_public_images(self, request):
filter = {"is_public": True,
"status": "active"}
return self._get_images(request, filter)
def _get_tenant_images(self, request):
filter = {"owner": request.user.tenant_id,
"status": "active"}
return self._get_images(request, filter)
def _get_available_images(self, request):
images = self._get_tenant_images(request)
if request.user.is_superuser:
images += self._get_public_images(request)
final_images = []
try:
image_ids = set(img.id for img in saharaclient.image_list(request))
except Exception:
image_ids = set()
exceptions.handle(request,
_("Unable to fetch available images."))
for image in images:
if (image not in final_images and
image.id not in image_ids and
image.container_format not in ('aki', 'ari')):
final_images.append(image)
return final_images
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/__init__.py 0000664 0001750 0001750 00000000000 13656751613 031045 0 ustar zuul zuul 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/panel.py 0000664 0001750 0001750 00000001656 13656751613 030427 0 ustar zuul zuul 0000000 0000000 # 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 _
import horizon
from openstack_dashboard.dashboards.project import dashboard
class ClustersPanel(horizon.Panel):
name = _("Clusters")
slug = 'data_processing.clusters'
permissions = (('openstack.services.data-processing',
'openstack.services.data_processing'),)
dashboard.Project.register(ClustersPanel)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/ 0000775 0001750 0001750 00000000000 13656751733 033031 5 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011213 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/__init__.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/__init0000664 0001750 0001750 00000000000 13656751613 034200 0 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011216 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tabs.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tabs.p0000664 0001750 0001750 00000012031 13656751613 034135 0 ustar zuul zuul 0000000 0000000 # 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 oslo_log import log as logging
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard.api import neutron
from openstack_dashboard.api import nova
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing \
import tabs as sahara_tabs
from sahara_dashboard.content. \
data_processing.utils import workflow_helpers as helpers
from sahara_dashboard.content.data_processing.clusters.nodegroup_templates \
import tables as node_group_template_tables
LOG = logging.getLogger(__name__)
class NodeGroupTemplatesTab(sahara_tabs.SaharaTableTab):
table_classes = (node_group_template_tables.NodegroupTemplatesTable, )
name = _("Node Group Templates")
slug = "node_group_templates_tab"
template_name = "horizon/common/_detail_table.html"
def get_nodegroup_templates_data(self):
try:
table = self._tables['nodegroup_templates']
search_opts = {}
filter = self.get_server_filter_info(table.request, table)
if filter['value'] and filter['field']:
search_opts = {filter['field']: filter['value']}
node_group_templates = saharaclient.nodegroup_template_list(
self.request, search_opts)
except Exception:
node_group_templates = []
exceptions.handle(self.request,
_("Unable to fetch node group template list"))
return node_group_templates
class GeneralTab(tabs.Tab):
name = _("General Info")
slug = "nodegroup_template_details_tab"
template_name = "nodegroup_templates/_details.html"
def get_context_data(self, request):
template_id = self.tab_group.kwargs['template_id']
try:
template = saharaclient.nodegroup_template_get(
request, template_id)
except Exception as e:
template = {}
LOG.error(
"Unable to fetch node group template details: %s" % str(e))
return {"template": template}
try:
flavor = nova.flavor_get(request, template.flavor_id)
except Exception:
flavor = {}
exceptions.handle(request,
_("Unable to fetch flavor for template."))
floating_ip_pool_name = None
if template.floating_ip_pool:
try:
floating_ip_pool_name = self._get_floating_ip_pool_name(
request, template.floating_ip_pool)
except Exception:
exceptions.handle(request,
_("Unable to fetch floating ip pools."))
base_image_name = None
if template.image_id:
try:
base_image_name = saharaclient.image_get(
request, template.image_id).name
except Exception:
exceptions.handle(request,
_("Unable to fetch Base Image with id: %s.")
% template.image_id)
security_groups = helpers.get_security_groups(
request, template.security_groups)
if getattr(template, 'boot_from_volume', None) is None:
show_bfv = False
else:
show_bfv = True
return {"template": template, "flavor": flavor,
"floating_ip_pool_name": floating_ip_pool_name,
"base_image_name": base_image_name,
"security_groups": security_groups,
"show_bfv": show_bfv}
def _get_floating_ip_pool_name(self, request, pool_id):
pools = [pool for pool in neutron.floating_ip_pools_list(
request) if pool.id == pool_id]
return pools[0].name if pools else pool_id
class ConfigsTab(tabs.Tab):
name = _("Service Configurations")
slug = "nodegroup_template_service_configs_tab"
template_name = "nodegroup_templates/_service_confs.html"
def get_context_data(self, request):
template_id = self.tab_group.kwargs['template_id']
try:
template = saharaclient.nodegroup_template_get(
request, template_id)
except Exception as e:
template = {}
LOG.error(
"Unable to fetch node group template details: %s" % str(e))
return {"template": template}
class NodegroupTemplateDetailsTabs(tabs.TabGroup):
slug = "nodegroup_template_details"
tabs = (GeneralTab, ConfigsTab, )
sticky = True
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011217 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tests.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tests.0000664 0001750 0001750 00000040117 13656751613 034174 0 ustar zuul zuul 0000000 0000000 # 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 unittest import mock
from django.urls import reverse
from openstack_dashboard import api as dash_api
from sahara_dashboard import api
from sahara_dashboard.content.data_processing.utils \
import workflow_helpers
from sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.workflows import create as create_workflow
from sahara_dashboard.test import helpers as test
INDEX_URL = reverse(
'horizon:project:data_processing.clusters:nodegroup-templates-tab')
DETAILS_URL = reverse(
'horizon:project:data_processing.clusters:details',
args=['id'])
CREATE_URL = reverse(
'horizon:project:data_processing.clusters:' +
'configure-nodegroup-template')
class DataProcessingNodeGroupTests(test.TestCase):
@mock.patch('openstack_dashboard.api.base.is_service_enabled')
def _setup_copy_test(self, service_checker):
service_checker.return_value = True
ngt = self.nodegroup_templates.first()
configs = self.plugins_configs.first()
self.mock_extension_supported.return_value = True
self.mock_availability_zone_list.return_value = \
self.availability_zones.list()
self.mock_volume_type_list.return_value = []
self.mock_nodegroup_template_get.return_value = ngt
self.mock_plugin_get_version_details.return_value = configs
self.mock_floating_ip_pools_list.return_value = []
self.mock_security_group_list.return_value = []
url = reverse(
'horizon:project:data_processing.clusters:copy',
args=[ngt.id])
res = self.client.get(url)
self.mock_availability_zone_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_availability_zone_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_volume_type_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_nodegroup_template_get.assert_called_once_with(
test.IsHttpRequest(), ngt.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_plugin_get_version_details, 6,
mock.call(test.IsHttpRequest(), ngt.plugin_name,
ngt.hadoop_version))
self.mock_floating_ip_pools_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_security_group_list.assert_called_once_with(
test.IsHttpRequest())
return ngt, configs, res
@test.create_mocks({api.sahara: ('cluster_template_list',
'image_list',
'cluster_list',
'nodegroup_template_list')})
def test_index(self):
self.mock_nodegroup_template_list.return_value = \
self.nodegroup_templates.list()
res = self.client.get(INDEX_URL +
"?tab=cluster_tabs__node_group_templates_tab")
self.assertTemplateUsed(res, 'clusters/index.html')
self.assertContains(res, 'Node Group Templates')
self.assertContains(res, 'Name')
self.assertContains(res, 'Plugin')
self.mock_nodegroup_template_list.assert_called_once_with(
test.IsHttpRequest(), {})
@test.create_mocks({api.sahara: ('nodegroup_template_get',),
dash_api.nova: ('flavor_get',)})
def test_details(self):
flavor = self.flavors.first()
ngt = self.nodegroup_templates.first()
self.mock_flavor_get.return_value = flavor
self.mock_nodegroup_template_get.return_value = ngt
res = self.client.get(DETAILS_URL)
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
self.assertContains(res, 'sample-template')
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_flavor_get, 1,
mock.call(test.IsHttpRequest(), flavor.id))
@test.create_mocks({api.sahara: ('nodegroup_template_list',
'nodegroup_template_delete')})
def test_delete(self):
ngt = self.nodegroup_templates.first()
self.mock_nodegroup_template_list.return_value = \
self.nodegroup_templates.list()
self.mock_nodegroup_template_delete.return_value = None
form_data = {'action': 'nodegroup_templates__delete__%s' % ngt.id}
res = self.client.post(INDEX_URL, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
self.mock_nodegroup_template_list.assert_called_once_with(
test.IsHttpRequest(), {})
self.mock_nodegroup_template_delete.assert_called_once_with(
test.IsHttpRequest(), ngt.id)
@test.create_mocks({api.sahara: ('nodegroup_template_get',
'plugin_get_version_details',
'image_list'),
dash_api.nova: ('availability_zone_list',
'flavor_list'),
dash_api.neutron: ('floating_ip_pools_list',
'security_group_list'),
dash_api.cinder: ('extension_supported',
'availability_zone_list',
'volume_type_list')})
def test_copy(self):
ngt, configs, res = self._setup_copy_test()
workflow = res.context['workflow']
step = workflow.get_step("generalconfigaction")
self.assertEqual(step.action['nodegroup_name'].field.initial,
ngt.name + "-copy")
@test.create_mocks({api.sahara: ('client',
'nodegroup_template_create',
'plugin_get_version_details'),
dash_api.neutron: ('floating_ip_pools_list',
'security_group_list'),
dash_api.nova: ('flavor_list',
'availability_zone_list'),
dash_api.cinder: ('extension_supported',
'availability_zone_list',
'volume_type_list')})
@mock.patch('openstack_dashboard.api.base.is_service_enabled')
@mock.patch.object(workflow_helpers, 'parse_configs_from_context')
def test_create(self, mock_workflow, service_checker):
service_checker.return_value = True
mock_workflow.return_value = {}
flavor = self.flavors.first()
ngt = self.nodegroup_templates.first()
configs = self.plugins_configs.first()
new_name = ngt.name + '-new'
self.mock_extension_supported.return_value = True
self.mock_availability_zone_list.return_value = \
self.availability_zones.list()
self.mock_volume_type_list.return_value = []
self.mock_flavor_list.return_value = [flavor]
self.mock_plugin_get_version_details.return_value = configs
self.mock_floating_ip_pools_list.return_value = []
self.mock_security_group_list.return_value = []
self.mock_nodegroup_template_create.return_value = True
res = self.client.post(
CREATE_URL,
{'nodegroup_name': new_name,
'plugin_name': ngt.plugin_name,
ngt.plugin_name + '_version': '1.2.1',
'hadoop_version': ngt.hadoop_version,
'description': ngt.description,
'flavor': flavor.id,
'availability_zone': '',
'storage': 'ephemeral_drive',
'volumes_per_node': 0,
'volumes_size': 0,
'volume_type': '',
'volume_local_to_instance': False,
'volumes_availability_zone': '',
'floating_ip_pool': '',
'security_autogroup': True,
'processes': 'HDFS:namenode',
'use_autoconfig': True,
'shares': [],
'is_public': False,
'is_protected': False})
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
self.mock_extension_supported.assert_called_once_with(
test.IsHttpRequest(), 'AvailabilityZones')
self.mock_availability_zone_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_flavor_list.assert_called_once_with(
test.IsHttpRequest())
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_plugin_get_version_details, 4,
mock.call(test.IsHttpRequest(), ngt.plugin_name,
ngt.hadoop_version))
self.mock_floating_ip_pools_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_security_group_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_nodegroup_template_create(
test.IsHttpRequest(),
**{'name': new_name,
'plugin_name': ngt.plugin_name,
'hadoop_version': ngt.hadoop_version,
'description': ngt.description,
'flavor_id': flavor.id,
'volumes_per_node': None,
'volumes_size': None,
'volume_type': None,
'volume_local_to_instance': False,
'volumes_availability_zone': None,
'node_processes': ['namenode'],
'node_configs': {},
'floating_ip_pool': None,
'security_groups': [],
'image_id': None,
'auto_security_group': True,
'availability_zone': None,
'is_proxy_gateway': False,
'use_autoconfig': True,
'shares': [],
'is_public': False,
'is_protected': False})
@test.create_mocks({api.sahara: ('client',
'nodegroup_template_create',
'nodegroup_template_update',
'nodegroup_template_get',
'plugin_get_version_details'),
dash_api.neutron: ('floating_ip_pools_list',
'security_group_list'),
dash_api.nova: ('flavor_list',
'availability_zone_list'),
dash_api.cinder: ('extension_supported',
'availability_zone_list',
'volume_type_list')})
@mock.patch('openstack_dashboard.api.base.is_service_enabled')
@mock.patch.object(workflow_helpers, 'parse_configs_from_context')
def test_update(self, mock_workflow, service_checker):
service_checker.return_value = True
flavor = self.flavors.first()
ngt = self.nodegroup_templates.first()
configs = self.plugins_configs.first()
new_name = ngt.name + '-updated'
UPDATE_URL = reverse(
'horizon:project:data_processing.clusters:edit',
kwargs={'template_id': ngt.id})
mock_workflow.return_value = {}
self.mock_extension_supported.return_value = True
self.mock_availability_zone_list.return_value = \
self.availability_zones.list()
self.mock_volume_type_list.return_value = []
self.mock_flavor_list.return_value = [flavor]
self.mock_plugin_get_version_details.return_value = configs
self.mock_floating_ip_pools_list.return_value = []
self.mock_security_group_list.return_value = []
self.mock_nodegroup_template_get.return_value = ngt
self.mock_nodegroup_template_update.return_value = True
res = self.client.post(
UPDATE_URL,
{'ng_id': ngt.id,
'nodegroup_name': new_name,
'plugin_name': ngt.plugin_name,
ngt.plugin_name + '_version': '1.2.1',
'hadoop_version': ngt.hadoop_version,
'description': ngt.description,
'flavor': flavor.id,
'availability_zone': '',
'storage': 'ephemeral_drive',
'volumes_per_node': 0,
'volumes_size': 0,
'volume_type': '',
'volume_local_to_instance': False,
'volumes_availability_zone': '',
'floating_ip_pool': '',
'is_proxy_gateway': False,
'security_autogroup': True,
'processes': 'HDFS:namenode',
'use_autoconfig': True})
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
self.mock_extension_supported.assert_called_once_with(
test.IsHttpRequest(), 'AvailabilityZones')
self.mock_availability_zone_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_volume_type_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_flavor_list.assert_called_once_with(
test.IsHttpRequest())
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_plugin_get_version_details, 5,
mock.call(test.IsHttpRequest(), ngt.plugin_name,
ngt.hadoop_version))
self.mock_floating_ip_pools_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_security_group_list.assert_called_once_with(
test.IsHttpRequest())
self.mock_nodegroup_template_get.assert_called_once_with(
test.IsHttpRequest(), ngt.id)
self.mock_nodegroup_template_update.assert_called_once_with(
request=test.IsHttpRequest(),
ngt_id=ngt.id,
name=new_name,
plugin_name=ngt.plugin_name,
hadoop_version=ngt.hadoop_version,
flavor_id=flavor.id,
description=ngt.description,
volumes_per_node=0,
volumes_size=None,
volume_type=None,
volume_local_to_instance=False,
volumes_availability_zone=None,
node_processes=['namenode'],
node_configs={},
floating_ip_pool='',
security_groups=[],
auto_security_group=True,
availability_zone='',
use_autoconfig=True,
is_proxy_gateway=False,
shares=[],
is_protected=False,
is_public=False,
image_id=ngt.image_id,
)
@test.create_mocks({api.sahara: ('nodegroup_template_get',
'plugin_get_version_details',
'image_list'),
dash_api.nova: ('availability_zone_list',
'flavor_list'),
dash_api.neutron: ('floating_ip_pools_list',
'security_group_list'),
dash_api.cinder: ('extension_supported',
'availability_zone_list',
'volume_type_list')})
def test_workflow_steps(self):
# since the copy workflow is the child of create workflow
# it's better to test create workflow through copy workflow
ngt, configs, res = self._setup_copy_test()
workflow = res.context['workflow']
expected_instances = [
create_workflow.GeneralConfig,
create_workflow.SelectNodeProcesses,
create_workflow.SecurityConfig
]
for expected, observed in zip(expected_instances, workflow.steps):
self.assertIsInstance(observed, expected)
sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/forms/0000775 0001750 0001750 00000000000 13656751733 034157 5 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000160 00000000000 011212 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/forms/__init__.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/forms/0000664 0001750 0001750 00000000000 13656751613 034144 0 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011216 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/forms/import_forms.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/forms/0000664 0001750 0001750 00000014563 13656751613 034167 0 ustar zuul zuul 0000000 0000000 # 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 horizon import exceptions
from horizon import forms
from openstack_dashboard.api import neutron
from openstack_dashboard.dashboards.project.instances \
import utils as nova_utils
from oslo_serialization import jsonutils as json
from saharaclient.api import base as api_base
from sahara_dashboard.api import sahara as saharaclient
BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register"
class ImportNodegroupTemplateFileForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Node Group Template")
def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop('next_view')
super(ImportNodegroupTemplateFileForm, 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 ImportNodegroupTemplateDetailsForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Node Group Template")
template = 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."))
security_groups = forms.MultipleChoiceField(
label=_("Security Groups"),
widget=forms.CheckboxSelectMultiple(),
help_text=_("Launch instances in these security groups. "
"Auto security group will be determined by the "
"value present in the imported template."),
required=False)
floating_ip_pool = forms.ChoiceField(
label=_("Floating IP Pool"),
required=False)
flavor = forms.ChoiceField(label=_("OpenStack Flavor"))
image_id = forms.DynamicChoiceField(label=_("Base Image"),
add_item_link=BASE_IMAGE_URL)
def _populate_image_choices(self, request, plugin, hadoop_version):
all_images = saharaclient.image_list(request)
details = saharaclient.plugin_get_version_details(request,
plugin,
hadoop_version)
return [(image.id, image.name) for image in all_images
if (set(details.required_image_tags).
issubset(set(image.tags)))]
def __init__(self, *args, **kwargs):
try:
request = args[0]
template_string = ""
if "template_upload" in kwargs:
template_upload = kwargs.pop('template_upload')
super(ImportNodegroupTemplateDetailsForm, self).__init__(
*args, **kwargs)
template_string = template_upload.read()
self.fields["template"].initial = template_string
else:
super(ImportNodegroupTemplateDetailsForm, self).__init__(
*args, **kwargs)
template_string = self.data["template"]
template_json = json.loads(template_string)
template_json = template_json["node_group_template"]
security_group_list = neutron.security_group_list(request)
security_group_choices = \
[(sg.id, sg.name) for sg in security_group_list]
self.fields["security_groups"].choices = security_group_choices
pools = neutron.floating_ip_pools_list(request)
pool_choices = [(pool.id, pool.name) for pool in pools]
pool_choices.insert(0, (None, "Do not assign floating IPs"))
self.fields["floating_ip_pool"].choices = pool_choices
flavors = nova_utils.flavor_list(request)
if flavors:
self.fields["flavor"].choices = nova_utils.sort_flavor_list(
request, flavors)
else:
self.fields["flavor"].choices = []
version = (template_json.get("hadoop_version", None) or
template_json["plugin_version"])
self.fields["image_id"].choices = \
self._populate_image_choices(request,
template_json["plugin_name"],
version)
except (ValueError, KeyError):
raise exceptions.BadRequest(_("Could not parse template"))
except Exception:
exceptions.handle(request)
def handle(self, request, data):
try:
template = data["template"]
template = json.loads(template)
template = template["node_group_template"]
if not data["name"] and "name" not in template.keys():
return False
if data["name"]:
template["name"] = data["name"]
template["security_groups"] = data["security_groups"]
template["floating_ip_pool"] = data["floating_ip_pool"]
template["flavor_id"] = data["flavor"]
template["image_id"] = data["image_id"]
saharaclient.nodegroup_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"))
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011217 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/views.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/views.0000664 0001750 0001750 00000015143 13656751613 034170 0 ustar zuul zuul 0000000 0000000 # 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.urls import reverse
from django.urls 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
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.tables as _tables
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.tabs as _tabs
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.workflows.copy as copy_flow
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.workflows.create as create_flow
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.workflows.edit as edit_flow
import sahara_dashboard.content.data_processing.clusters. \
nodegroup_templates.forms.import_forms as import_forms
class NodegroupTemplateDetailsView(tabs.TabView):
tab_group_class = _tabs.NodegroupTemplateDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ template.name|default:template.id }}"
@memoized.memoized_method
def get_object(self):
ngt_id = self.kwargs["template_id"]
try:
return saharaclient.nodegroup_template_get(self.request, ngt_id)
except Exception:
msg = _('Unable to retrieve details for '
'node group template "%s".') % ngt_id
redirect = self.get_redirect_url()
exceptions.handle(self.request, msg, redirect=redirect)
def get_context_data(self, **kwargs):
context = super(
NodegroupTemplateDetailsView, self).get_context_data(**kwargs)
node_group_template = self.get_object()
context['template'] = node_group_template
context['url'] = self.get_redirect_url()
context['actions'] = self._get_actions(node_group_template)
return context
def _get_actions(self, node_group_template):
table = _tables.NodegroupTemplatesTable(self.request)
return table.render_row_actions(node_group_template)
@staticmethod
def get_redirect_url():
return reverse("horizon:project:data_processing."
"clusters:index")
class CreateNodegroupTemplateView(workflows.WorkflowView):
workflow_class = create_flow.CreateNodegroupTemplate
success_url = (
"horizon:project:data_processing.clusters:"
"create-nodegroup-template")
classes = ("ajax-modal",)
template_name = "nodegroup_templates/create.html"
page_title = _("Create Node Group Template")
class ConfigureNodegroupTemplateView(workflows.WorkflowView):
workflow_class = create_flow.ConfigureNodegroupTemplate
success_url = ("horizon:project:"
"data_processing.clusters:index")
template_name = "nodegroup_templates/configure.html"
page_title = _("Create Node Group Template")
def get_initial(self):
initial = super(ConfigureNodegroupTemplateView, self).get_initial()
initial.update(self.kwargs)
return initial
class CopyNodegroupTemplateView(workflows.WorkflowView):
workflow_class = copy_flow.CopyNodegroupTemplate
success_url = ("horizon:project:"
"data_processing.clusters:index")
template_name = "nodegroup_templates/configure.html"
def get_context_data(self, **kwargs):
context = super(
CopyNodegroupTemplateView, self).get_context_data(**kwargs)
context["template_id"] = kwargs["template_id"]
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
template_id = self.kwargs['template_id']
try:
template = saharaclient.nodegroup_template_get(self.request,
template_id)
except Exception:
template = None
exceptions.handle(self.request,
_("Unable to fetch template object."))
self._object = template
return self._object
def get_initial(self):
initial = super(CopyNodegroupTemplateView, self).get_initial()
initial['template_id'] = self.kwargs['template_id']
return initial
class EditNodegroupTemplateView(CopyNodegroupTemplateView):
workflow_class = edit_flow.EditNodegroupTemplate
success_url = ("horizon:project:"
"data_processing.clusters:index")
template_name = "nodegroup_templates/configure.html"
class ImportNodegroupTemplateFileView(forms.ModalFormView):
template_name = "nodegroup_templates/import.html"
form_class = import_forms.ImportNodegroupTemplateFileForm
submit_label = _("Next")
submit_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-nodegroup-template-file")
success_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-nodegroup-template-details")
page_title = _("Import Node Group Template")
def get_form_kwargs(self):
kwargs = super(
ImportNodegroupTemplateFileView, self).get_form_kwargs()
kwargs['next_view'] = ImportNodegroupTemplateDetailsView
return kwargs
class ImportNodegroupTemplateDetailsView(forms.ModalFormView):
template_name = "nodegroup_templates/import.html"
form_class = import_forms.ImportNodegroupTemplateDetailsForm
submit_label = _("Import")
submit_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-nodegroup-template-details")
success_url = reverse_lazy("horizon:project:data_processing."
"clusters:index")
page_title = _("Import Node Group Template")
def get_form_kwargs(self):
kwargs = super(
ImportNodegroupTemplateDetailsView, self).get_form_kwargs()
if 'template_upload' in self.kwargs:
kwargs['template_upload'] = self.kwargs['template_upload']
return kwargs
././@LongLink 0000000 0000000 0000000 00000000150 00000000000 011211 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tables.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/tables0000664 0001750 0001750 00000014064 13656751613 034230 0 ustar zuul zuul 0000000 0000000 # 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 import http
from django.template import defaultfilters as filters
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from horizon.tabs import base as tabs_base
from oslo_serialization import jsonutils as json
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing \
import tables as sahara_table
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
class NodeGroupTemplatesFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = (('name', _("Name"), True),
('plugin_name', _("Plugin"), True),
('hadoop_version', _("Version"), True))
class CreateNodegroupTemplate(tables.LinkAction):
name = "create"
verbose_name = _("Create Template")
url = ("horizon:project:data_processing.clusters:"
"create-nodegroup-template")
classes = ("ajax-modal", "create-nodegrouptemplate-btn")
icon = "plus"
class ImportNodegroupTemplate(tables.LinkAction):
name = "import"
verbose_name = _("Import Template")
url = ("horizon:project:data_processing.clusters:"
"import-nodegroup-template-file")
classes = ("ajax-modal",)
icon = "plus"
class ConfigureNodegroupTemplate(tables.LinkAction):
name = "configure"
verbose_name = _("Configure Template")
url = ("horizon:project:data_processing.clusters:"
"configure-nodegroup-template")
classes = ("ajax-modal", "configure-nodegrouptemplate-btn")
icon = "plus"
attrs = {"style": "display: none"}
class CopyTemplate(tables.LinkAction):
name = "copy"
verbose_name = _("Copy Template")
url = "horizon:project:data_processing.clusters:copy"
classes = ("ajax-modal", )
class EditTemplate(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Template")
url = "horizon:project:data_processing.clusters:edit"
classes = ("ajax-modal", )
class ExportTemplate(tables.Action):
name = "export"
verbose_name = _("Export Template")
classes = ("ajax-modal", )
def single(self, data_table, request, object_id):
content = json.dumps(saharaclient.nodegroup_template_export(
request, object_id)._info)
response = http.HttpResponse(content, content_type="application/json")
filename = '%s-node-group-template.json' % object_id
disposition = 'attachment; filename="%s"' % filename
response['Content-Disposition'] = disposition.encode('utf-8')
response['Content-Length'] = str(len(response.content))
return response
class DeleteTemplate(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Template",
u"Delete Templates",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Template",
u"Deleted Templates",
count
)
def delete(self, request, template_id):
saharaclient.nodegroup_template_delete(request, template_id)
class MakePublic(acl_utils.MakePublic):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.nodegroup_update_acl_rules(
request, datum_id, **update_kwargs)
class MakePrivate(acl_utils.MakePrivate):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.nodegroup_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeProtected(acl_utils.MakeProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.nodegroup_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeUnProtected(acl_utils.MakeUnProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.nodegroup_update_acl_rules(
request, datum_id, **update_kwargs)
class NodegroupTemplatesTable(sahara_table.SaharaPaginateTabbedTable):
tab_name = 'cluster_tabs%snode_group_templates_tab' % tabs_base.SEPARATOR
name = tables.Column(
"name",
verbose_name=_("Name"),
link="horizon:project:data_processing.clusters:details")
plugin_name = tables.Column("plugin_name",
verbose_name=_("Plugin"))
if saharaclient.VERSIONS.active == '2':
version_attr = "plugin_version"
else:
version_attr = "hadoop_version"
hadoop_version = tables.Column(version_attr,
verbose_name=_("Version"))
node_processes = tables.Column("node_processes",
verbose_name=_("Node Processes"),
wrap_list=True,
filters=(filters.unordered_list,))
class Meta(object):
name = "nodegroup_templates"
verbose_name = _("Node Group Templates")
table_actions = (CreateNodegroupTemplate,
ImportNodegroupTemplate,
ConfigureNodegroupTemplate,
DeleteTemplate,
NodeGroupTemplatesFilterAction,)
table_actions_menu = (MakePublic, MakePrivate, MakeProtected,
MakeUnProtected)
row_actions = (EditTemplate,
CopyTemplate,
ExportTemplate,
DeleteTemplate, MakePublic, MakePrivate, MakeProtected,
MakeUnProtected)
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workflows/ sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workfl0000775 0001750 0001750 00000000000 13656751733 034256 5 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011216 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workflows/__init__.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workfl0000664 0001750 0001750 00000000000 13656751613 034243 0 ustar zuul zuul 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workflows/create.py sahara-dashboard-12.0.0/sahara_dashboard/content/data_processing/clusters/nodegroup_templates/workfl0000664 0001750 0001750 00000070152 13656751613 034262 0 ustar zuul zuul 0000000 0000000 # 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 itertools
from django.utils import encoding
from django.utils import html
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from oslo_log import log as logging
from oslo_utils import uuidutils
from saharaclient.api import base as api_base
from horizon import exceptions
from horizon import forms
from horizon import workflows
from openstack_dashboard.api import cinder
from openstack_dashboard.api import neutron
from openstack_dashboard.api import nova
from openstack_dashboard.dashboards.project.instances \
import utils as nova_utils
from sahara_dashboard.api import manila as manilaclient
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
from sahara_dashboard.content.data_processing.utils \
import helpers
from sahara_dashboard.content.data_processing.utils \
import workflow_helpers
LOG = logging.getLogger(__name__)
BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register"
def is_cinder_enabled(request):
for service in ['volumev3', 'volumev2', 'volume']:
if saharaclient.base.is_service_enabled(request, service):
return True
return False
def storage_choices(request):
choices = [("ephemeral_drive", _("Ephemeral Drive")), ]
if is_cinder_enabled(request):
choices.append(("cinder_volume", _("Cinder Volume")))
else:
LOG.warning(_("Cinder service is unavailable now"))
return choices
def volume_availability_zone_list(request):
"""Utility method to retrieve a list of volume availability zones."""
try:
if cinder.extension_supported(request, 'AvailabilityZones'):
return cinder.availability_zone_list(request)
return []
except Exception:
exceptions.handle(request,
_('Unable to retrieve volumes availability zones.'))
return []
def instance_availability_zone_list(request):
"""Utility method to retrieve a list of instance availability zones."""
try:
return nova.availability_zone_list(request)
except Exception:
exceptions.handle(request,
_('Unable to retrieve Nova availability zones.'))
return []
class GeneralConfigAction(workflows.Action):
nodegroup_name = forms.CharField(label=_("Template Name"))
description = forms.CharField(label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'rows': 4}))
flavor = forms.ChoiceField(label=_("OpenStack Flavor"))
availability_zone = forms.ChoiceField(
label=_("Availability Zone"),
help_text=_("Launch instances in this availability zone."),
required=False,
widget=forms.Select(attrs={"class": "availability_zone_field"})
)
storage = forms.ChoiceField(
label=_("Attached storage location"),
help_text=_("Choose a storage location"),
choices=[],
widget=forms.Select(attrs={
"class": "storage_field switchable",
'data-slug': 'storage_loc'
}))
volumes_per_node = forms.IntegerField(
label=_("Volumes per node"),
required=False,
initial=1,
widget=forms.TextInput(attrs={
"class": "volume_per_node_field switched",
"data-switch-on": "storage_loc",
"data-storage_loc-cinder_volume": _('Volumes per node')
})
)
volumes_size = forms.IntegerField(
label=_("Volumes size (GB)"),
required=False,
initial=10,
widget=forms.TextInput(attrs={
"class": "volume_size_field switched",
"data-switch-on": "storage_loc",
"data-storage_loc-cinder_volume": _('Volumes size (GB)')
})
)
volume_type = forms.ChoiceField(
label=_("Volumes type"),
required=False,
widget=forms.Select(attrs={
"class": "volume_type_field switched",
"data-switch-on": "storage_loc",
"data-storage_loc-cinder_volume": _('Volumes type')
})
)
volume_local_to_instance = forms.BooleanField(
label=_("Volume local to instance"),
required=False,
help_text=_("Instance and attached volumes will be created on the "
"same physical host"),
widget=forms.CheckboxInput(attrs={
"class": "volume_local_to_instance_field switched",
"data-switch-on": "storage_loc",
"data-storage_loc-cinder_volume": _('Volume local to instance')
})
)
volumes_availability_zone = forms.ChoiceField(
label=_("Volumes Availability Zone"),
help_text=_("Create volumes in this availability zone."),
required=False,
widget=forms.Select(attrs={
"class": "volumes_availability_zone_field switched",
"data-switch-on": "storage_loc",
"data-storage_loc-cinder_volume": _('Volumes Availability Zone')
})
)
image = forms.DynamicChoiceField(label=_("Base Image"),
required=False,
add_item_link=BASE_IMAGE_URL)
hidden_configure_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"}))
def __init__(self, request, *args, **kwargs):
super(GeneralConfigAction, self).__init__(request, *args, **kwargs)
hlps = helpers.Helpers(request)
plugin, hadoop_version = (
workflow_helpers.get_plugin_and_hadoop_version(request))
if not saharaclient.SAHARA_FLOATING_IP_DISABLED:
pools = neutron.floating_ip_pools_list(request)
pool_choices = [(pool.id, pool.name) for pool in pools]
pool_choices.insert(0, (None, "Do not assign floating IPs"))
self.fields['floating_ip_pool'] = forms.ChoiceField(
label=_("Floating IP Pool"),
choices=pool_choices,
required=False)
self.fields["use_autoconfig"] = forms.BooleanField(
label=_("Auto-configure"),
help_text=_("If selected, instances of a node group will be "
"automatically configured during cluster "
"creation. Otherwise you should manually specify "
"configuration values."),
required=False,
widget=forms.CheckboxInput(),
initial=True,
)
self.fields["proxygateway"] = forms.BooleanField(
label=_("Proxy Gateway"),
widget=forms.CheckboxInput(),
help_text=_("Sahara will use instances of this node group to "
"access other cluster instances."),
required=False)
self.fields['is_public'] = acl_utils.get_is_public_form(
_("node group template"))
self.fields['is_protected'] = acl_utils.get_is_protected_form(
_("node group template"))
if saharaclient.VERSIONS.active == '2':
self.fields['boot_storage'] = forms.ChoiceField(
label=_("Boot storage location"),
help_text=_("Choose a boot mode"),
choices=storage_choices(request),
widget=forms.Select(attrs={
"class": "boot_storage_field switchable",
'data-slug': 'boot_storage_loc'
}))
self.fields['boot_volume_type'] = forms.ChoiceField(
label=_("Boot volume type"),
required=False,
widget=forms.Select(attrs={
"class": "boot_volume_type_field switched",
"data-switch-on": "boot_storage_loc",
"data-boot_storage_loc-cinder_volume":
_('Boot volume type')
})
)
self.fields['boot_volume_local_to_instance'] = forms.BooleanField(
label=_("Boot volume local to instance"),
required=False,
help_text=_("Boot volume locality"),
widget=forms.CheckboxInput(attrs={
"class": "boot_volume_local_to_instance_field switched",
"data-switch-on": "boot_storage_loc",
"data-boot_storage_loc-cinder_volume":
_('Boot volume local to instance')
})
)
self.fields['boot_volume_availability_zone'] = forms.ChoiceField(
label=_("Boot volume availability Zone"),
choices=self.populate_volumes_availability_zone_choices(
request, None),
help_text=_("Create boot volume in this availability zone."),
required=False,
widget=forms.Select(attrs={
"class": "boot_volume_availability_zone_field switched",
"data-switch-on": "boot_storage_loc",
"data-boot_storage_loc-cinder_volume":
_('Boot volume availability zone')
})
)
self.fields["plugin_name"] = forms.CharField(
widget=forms.HiddenInput(),
initial=plugin
)
self.fields["hadoop_version"] = forms.CharField(
widget=forms.HiddenInput(),
initial=hadoop_version
)
self.fields["storage"].choices = storage_choices(request)
node_parameters = hlps.get_general_node_group_configs(plugin,
hadoop_version)
for param in node_parameters:
self.fields[param.name] = workflow_helpers.build_control(param)
# when we copy or edit a node group template then
# request contains valuable info in both GET and POST methods
req = request.GET.copy()
req.update(request.POST)
if req.get("guide_template_type"):
self.fields["guide_template_type"] = forms.CharField(
required=False,
widget=forms.HiddenInput(),
initial=req.get("guide_template_type"))
if is_cinder_enabled(request):
volume_types = cinder.volume_type_list(request)
else:
volume_types = []
self.fields['volume_type'].choices = [(None, _("No volume type"))] + \
[(type.name, type.name)
for type in volume_types]
if saharaclient.VERSIONS.active == '2':
self.fields['boot_volume_type'].choices = (
self.fields['volume_type'].choices)
def populate_flavor_choices(self, request, context):
flavors = nova_utils.flavor_list(request)
if flavors:
return nova_utils.sort_flavor_list(request, flavors)
return []
def populate_availability_zone_choices(self, request, context):
# The default is None, i.e. not specifying any availability zone
az_list = [(None, _('No availability zone specified'))]
az_list.extend([(az.zoneName, az.zoneName)
for az in instance_availability_zone_list(request)
if az.zoneState['available']])
return az_list
def populate_volumes_availability_zone_choices(self, request, context):
az_list = [(None, _('No availability zone specified'))]
if is_cinder_enabled(request):
az_list.extend([(az.zoneName, az.zoneName)
for az in volume_availability_zone_list(
request) if az.zoneState['available']])
return az_list
def populate_image_choices(self, request, context):
return workflow_helpers.populate_image_choices(self, request, context,
empty_choice=True)
def get_help_text(self):
extra = dict()
plugin_name, hadoop_version = (
workflow_helpers.get_plugin_and_hadoop_version(self.request))
extra["plugin_name"] = plugin_name
extra["hadoop_version"] = hadoop_version
plugin = saharaclient.plugin_get_version_details(
self.request, plugin_name, hadoop_version)
extra["deprecated"] = workflow_helpers.is_version_of_plugin_deprecated(
plugin, hadoop_version)
return super(GeneralConfigAction, self).get_help_text(extra)
class Meta(object):
name = _("Configure Node Group Template")
help_text_template = "nodegroup_templates/_configure_general_help.html"
class SecurityConfigAction(workflows.Action):
def __init__(self, request, *args, **kwargs):
super(SecurityConfigAction, self).__init__(request, *args, **kwargs)
self.fields["security_autogroup"] = forms.BooleanField(
label=_("Auto Security Group"),
widget=forms.CheckboxInput(),
help_text=_("Create security group for this Node Group."),
required=False,
initial=True)
try:
groups = neutron.security_group_list(request)
except Exception:
exceptions.handle(request,
_("Unable to get security group list."))
raise
security_group_list = [(sg.id, sg.name) for sg in groups]
self.fields["security_groups"] = forms.MultipleChoiceField(
label=_("Security Groups"),
widget=forms.CheckboxSelectMultiple(),
help_text=_("Launch instances in these security groups."),
choices=security_group_list,
required=False)
class Meta(object):
name = _("Security")
help_text = _("Control access to instances of the node group.")
class CheckboxSelectMultiple(forms.CheckboxSelectMultiple):
# TODO(shuyingya): should rewrite this class using the
# new method "get_context" when Django version less than 1.11
# no longer support.
def build_attrs(self, extra_attrs=None, **kwargs):
"Helper function for building an attribute dictionary."
attrs = dict(self.attrs, **kwargs)
if extra_attrs:
attrs.update(extra_attrs)
return attrs
def render(self, name, value, attrs=None, choices=(), renderer=None):
if value is None:
value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = []
initial_service = uuidutils.generate_uuid()
str_values = set([encoding.force_text(v) for v in value])
for i, (option_value, option_label) in enumerate(
itertools.chain(self.choices, choices)):
current_service = option_value.split(':')[0]
if current_service != initial_service:
if i > 0:
output.append("")
service_description = _("%s processes: ") % current_service
service_description = html.conditional_escape(
encoding.force_text(service_description))
output.append("" % service_description)
initial_service = current_service
output.append(encoding.force_text("
{% blocktrans %}Image Registry is used to provide additional information about images for Data Processing.{% endblocktrans %}
{% blocktrans %}Specified User Name will be used by Data Processing to apply configs and manage processes on instances.{% endblocktrans %}
{% blocktrans %}Warning!{% endblocktrans %}
{% blocktrans trimmed %} Input correct User Name for image. For Ubuntu images input 'ubuntu', 'fedora' for Fedora images,
'cloud-user' for CentOS 6.x images and 'centos' for CentOS 7.x images.{% endblocktrans %}
{% blocktrans trimmed %}Tags are used for filtering images suitable for each plugin and each Data Processing version.
To add required tags, select a plugin and a Data Processing version and click the "Add" button.{% endblocktrans %}
{% blocktrans %}You may also add any custom tag.{% endblocktrans %}
{% blocktrans %}Unnecessary tags may be removed by clicking a cross near tag's name.{% endblocktrans %}
{% blocktrans %}This Node Group Template will be created for:{% endblocktrans %}
{% blocktrans %}Plugin{% endblocktrans %}: {{ plugin_name }}
{% blocktrans %}Version{% endblocktrans %}: {{ hadoop_version }}
{% if deprecated %}
{% blocktrans %}Warning!{% endblocktrans %}
{% blocktrans trimmed %} Version: {{ hadoop_version }}
of plugin {{ plugin_name }}
is now deprecated. {% endblocktrans %}
{% endif %}
{% blocktrans trimmed %}The Node Group Template object specifies the processes
that will be launched on each instance. Check one or more processes.
When processes are selected, you may set node scoped
configurations on corresponding tabs.{% endblocktrans %}
{% blocktrans %}You must choose a flavor to determine the size (VCPUs, memory and storage) of all launched VMs.{% endblocktrans %}
{% blocktrans %}Data Processing provides different storage location options. You may choose Ephemeral Drive or a Cinder Volume to be attached to instances.{% endblocktrans %}
{% blocktrans trimmed %}The first step is to determine which type of
cluster you want to run. You may have several choices
available depending on the configuration of your system.
Click on "choose plugin" to bring up the list of data
processing plugins. There you will be able to choose the
data processing plugin along with the version number.
Choosing this up front will allow the rest of the cluster
creation steps to focus only on options that are pertinent
to your desired cluster type.{% endblocktrans %}
{% trans "Current choice:" %}
{% if request.session.plugin_name and request.session.plugin_version %}
{% trans "Plugin:" %}
{{ request.session.plugin_name }}
{% trans "Version:" %}
{{ request.session.plugin_version }}
{% else %}
{% trans "No plugin chosen" %}
{% endif %}
{% blocktrans trimmed %}You need to register an image to launch instances
of your cluster. Skip this step if you already have a registered image
for your plugin, otherwise click the link below. In the form you will
need to choose the image, input the username and add tags for the chosen plugin.
{% endblocktrans %}
{% blocktrans trimmed %}Next, you need to define the different
types of machines in your cluster. This is done by
defining a Node Group Template for each type of
machine. A very common case is where you
need to have one or more machines running a "master"
set of processes while another set of machines need
to be running the "worker" processes. Here,
you will define the Node Group Template for your
"master" node(s).
{% endblocktrans %}
{% trans "Current choice:" %}
{% if request.session.master_name %}
{% trans "Master Node Group Template:" %}
{{ request.session.master_name }}
{% else %}
{% trans "No Master Node Group Template Created" %}
{% endif %}
{% blocktrans trimmed %}Repeat the Node Group Template
creation process, but this time you are creating
your "worker" Node Group Template.{% endblocktrans %}
{% trans "Current choice:" %}
{% if request.session.worker_name %}
{% trans "Worker Node Group Template:" %}
{{ request.session.worker_name }}
{% else %}
{% trans "No Worker Node Group Template Created" %}
{% endif %}
{% blocktrans trimmed %}Now you need to set the layout of your
cluster. By
creating a Cluster Template, you will be choosing the
number of instances of each Node Group Template that
will appear in your cluster. Additionally,
you will have a chance to set any cluster-specific
configuration items in the additional tabs on the
create Cluster Template form.{% endblocktrans %}
{% trans "Current choice:" %}
{% if request.session.guide_cluster_template_name %}
{% trans "Worker Node Group Template:" %}
{{ request.session.guide_cluster_template_name }}
{% else %}
{% trans "No Cluster Template Created" %}
{% endif %}
{% blocktrans trimmed %}You are now ready to
launch your cluster. When you click on the link
below, you will need to give your cluster a name,
choose the Cluster Template to use and choose which
image to use to build your instances. After you
click on "Create", your instances will begin to
spawn. Your cluster should be operational in a few
minutes.{% endblocktrans %}
{% blocktrans %}Select the domain name for internal and external hostname resolution.{% endblocktrans %}
{% blocktrans trimmed %}Selected domain name should already exist in the Designate.
You can check it in "DNS" tab on the left menu or by executing
"designate domain-list"
on the controller node.{% endblocktrans %}
{% blocktrans %}This Cluster Template will be created for:{% endblocktrans %}
{% blocktrans %}Plugin{% endblocktrans %}: {{ plugin_name }}
{% blocktrans %}Version{% endblocktrans %}: {{ hadoop_version }}
{% if deprecated %}
{% blocktrans %}Warning!{% endblocktrans %}
{% blocktrans trimmed %} Version: {{ hadoop_version }}
of plugin {{ plugin_name }}
is now deprecated. {% endblocktrans %}
{% endif %}
{% blocktrans trimmed %}The Cluster Template object should specify Node Group Templates that will be used to build a Cluster.
You can add Node Groups using Node Group Templates on a "Node Groups" tab.{% endblocktrans %}
{% blocktrans %}You may set cluster scoped configurations on corresponding tabs.{% endblocktrans %}
{% blocktrans trimmed %}The Cluster Template object may specify a list of processes in anti-affinity group.
That means these processes may not be launched more than once on a single host.{% endblocktrans %}
{% blocktrans trimmed %}Important: The name that you give your job binary will be the name used in your job execution.
If your binary requires a particular name or extension (ie: ".jar"), be sure to include it here.{% endblocktrans %}
{% blocktrans %}Select the storage type for your job binary.{% endblocktrans %}
{% blocktrans trimmed %}Select which type of job that you want to run.
This choice will dictate which steps are required to successfully
execute your job.
{% endblocktrans %}
{% blocktrans trimmed %}First, select which type of job that
you want to run. This choice will determine which
other steps are required
{% endblocktrans %}
{% trans "Current type:" %}
{% if request.session.guide_job_type %}
{{ request.session.guide_job_type}}
{% else %}
{% trans "No type chosen" %}
{% endif %}
{% if request.session.guide_job_type %}
{% if view.show_data_sources %}
{% blocktrans trimmed %}Data Sources are what your
job uses for input and output. Depending on the type
of job you will be running, you may need to define one
or more data sources. You can create multiple data
sources by repeating this step.
{% endblocktrans %}
{% blocktrans trimmed %}Define your Job Template.
This is where you choose the type of job that you
want to run (Pig, Java Action, Spark, etc) and choose
or upload the files necessary to run it. The inputs
and outputs will be defined later.
{% endblocktrans %}
{% trans "Job template:" %}
{% if request.session.guide_job_name %}
{{ request.session.guide_job_name }}
{% else %}
{% trans "No job template created" %}
{% endif %}
{% blocktrans trimmed %}Launch your job. When
launching, you may need to choose your input and
output data sources. This is where you would also
add any special configuration values, parameters,
or arguments that you need to pass along
to your job.
{% endblocktrans %}
{% blocktrans %}Add libraries to your job template.{% endblocktrans %}
{% blocktrans %}Choose from the list of binaries and click "choose" to add the library to your job template. This can be repeated for additional libraries.{% endblocktrans %}
{% blocktrans %}For Shell Action jobs, any required files beyond the main script may be added as "libraries".{% endblocktrans %}