Merge "Adding cluster template support for shares"
This commit is contained in:
commit
d773bac4e8
|
@ -217,7 +217,8 @@ def nodegroup_template_update(request, ngt_id, name, plugin_name,
|
||||||
def cluster_template_create(request, name, plugin_name, hadoop_version,
|
def cluster_template_create(request, name, plugin_name, hadoop_version,
|
||||||
description=None, cluster_configs=None,
|
description=None, cluster_configs=None,
|
||||||
node_groups=None, anti_affinity=None,
|
node_groups=None, anti_affinity=None,
|
||||||
net_id=None, use_autoconfig=None):
|
net_id=None, use_autoconfig=None,
|
||||||
|
shares=None):
|
||||||
return client(request).cluster_templates.create(
|
return client(request).cluster_templates.create(
|
||||||
name=name,
|
name=name,
|
||||||
plugin_name=plugin_name,
|
plugin_name=plugin_name,
|
||||||
|
@ -227,7 +228,8 @@ def cluster_template_create(request, name, plugin_name, hadoop_version,
|
||||||
node_groups=node_groups,
|
node_groups=node_groups,
|
||||||
anti_affinity=anti_affinity,
|
anti_affinity=anti_affinity,
|
||||||
net_id=net_id,
|
net_id=net_id,
|
||||||
use_autoconfig=use_autoconfig)
|
use_autoconfig=use_autoconfig,
|
||||||
|
shares=shares)
|
||||||
|
|
||||||
|
|
||||||
def cluster_template_list(request, search_opts=None):
|
def cluster_template_list(request, search_opts=None):
|
||||||
|
@ -246,7 +248,7 @@ def cluster_template_update(request, ct_id, name, plugin_name,
|
||||||
hadoop_version, description=None,
|
hadoop_version, description=None,
|
||||||
cluster_configs=None, node_groups=None,
|
cluster_configs=None, node_groups=None,
|
||||||
anti_affinity=None, net_id=None,
|
anti_affinity=None, net_id=None,
|
||||||
use_autoconfig=None):
|
use_autoconfig=None, shares=None):
|
||||||
try:
|
try:
|
||||||
template = client(request).cluster_templates.update(
|
template = client(request).cluster_templates.update(
|
||||||
cluster_template_id=ct_id,
|
cluster_template_id=ct_id,
|
||||||
|
@ -258,7 +260,8 @@ def cluster_template_update(request, ct_id, name, plugin_name,
|
||||||
node_groups=node_groups,
|
node_groups=node_groups,
|
||||||
anti_affinity=anti_affinity,
|
anti_affinity=anti_affinity,
|
||||||
net_id=net_id,
|
net_id=net_id,
|
||||||
use_autoconfig=use_autoconfig)
|
use_autoconfig=use_autoconfig,
|
||||||
|
shares=shares)
|
||||||
|
|
||||||
except APIException as e:
|
except APIException as e:
|
||||||
raise exceptions.Conflict(e)
|
raise exceptions.Conflict(e)
|
||||||
|
|
|
@ -21,9 +21,10 @@ from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from openstack_dashboard import api as dash_api
|
from openstack_dashboard import api as dash_api
|
||||||
from sahara_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
from sahara_dashboard import api
|
from sahara_dashboard import api
|
||||||
|
from sahara_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:data_processing.cluster_templates:index')
|
INDEX_URL = reverse('horizon:project:data_processing.cluster_templates:index')
|
||||||
DETAILS_URL = reverse(
|
DETAILS_URL = reverse(
|
||||||
|
@ -132,7 +133,8 @@ class DataProcessingClusterTemplateTests(test.TestCase):
|
||||||
cluster_configs=ct.cluster_configs,
|
cluster_configs=ct.cluster_configs,
|
||||||
node_groups=ct.node_groups,
|
node_groups=ct.node_groups,
|
||||||
anti_affinity=ct.anti_affinity,
|
anti_affinity=ct.anti_affinity,
|
||||||
use_autoconfig=False)\
|
use_autoconfig=False,
|
||||||
|
shares=ct.shares)\
|
||||||
.AndReturn(new_ct)
|
.AndReturn(new_ct)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,31 @@ class CopyClusterTemplate(create_flow.ConfigureClusterTemplate):
|
||||||
fields['use_autoconfig'].initial = (
|
fields['use_autoconfig'].initial = (
|
||||||
self.template.use_autoconfig)
|
self.template.use_autoconfig)
|
||||||
fields["description"].initial = self.template.description
|
fields["description"].initial = self.template.description
|
||||||
|
elif isinstance(step, create_flow.SelectClusterShares):
|
||||||
|
fields = step.action.fields
|
||||||
|
fields["shares"].initial = (
|
||||||
|
self._get_share_defaults(fields["shares"].choices)
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(request,
|
exceptions.handle(request,
|
||||||
_("Unable to fetch template to copy."))
|
_("Unable to fetch template to copy."))
|
||||||
|
|
||||||
|
def _get_share_defaults(self, choices):
|
||||||
|
values = dict()
|
||||||
|
for i, choice in enumerate(choices):
|
||||||
|
share_id = choice[0]
|
||||||
|
s = filter(lambda s: s['id'] == share_id, self.template.shares)
|
||||||
|
if len(s) > 0:
|
||||||
|
path = s[0]["path"] if "path" in s[0] else ""
|
||||||
|
values["share_id_{0}".format(i)] = {
|
||||||
|
"id": s[0]["id"],
|
||||||
|
"path": path,
|
||||||
|
"access_level": s[0]["access_level"]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
values["share_id_{0}".format(i)] = {
|
||||||
|
"id": None,
|
||||||
|
"path": None,
|
||||||
|
"access_level": None
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
|
|
@ -21,15 +21,16 @@ from saharaclient.api import base as api_base
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
from horizon import workflows
|
from horizon import workflows
|
||||||
|
|
||||||
|
from sahara_dashboard.api import manila as manilaclient
|
||||||
from sahara_dashboard.api import sahara as saharaclient
|
from sahara_dashboard.api import sahara as saharaclient
|
||||||
from sahara_dashboard.content.data_processing. \
|
|
||||||
utils import helpers as helpers
|
from sahara_dashboard.content.data_processing.utils import helpers as helpers
|
||||||
from sahara_dashboard.content.data_processing. \
|
from sahara_dashboard.content.data_processing. \
|
||||||
utils import anti_affinity as aa
|
utils import anti_affinity as aa
|
||||||
import sahara_dashboard.content.data_processing. \
|
import sahara_dashboard.content.data_processing. \
|
||||||
utils.workflow_helpers as whelpers
|
utils.workflow_helpers as whelpers
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,6 +239,60 @@ class ConfigureNodegroups(workflows.Step):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class SelectClusterSharesAction(workflows.Action):
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(SelectClusterSharesAction, self).__init__(
|
||||||
|
request, *args, **kwargs)
|
||||||
|
|
||||||
|
possible_shares = self.get_possible_shares(request)
|
||||||
|
|
||||||
|
self.fields["shares"] = whelpers.MultipleShareChoiceField(
|
||||||
|
label=_("Select Shares"),
|
||||||
|
widget=whelpers.ShareWidget(choices=possible_shares),
|
||||||
|
required=False,
|
||||||
|
choices=possible_shares
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_possible_shares(self, request):
|
||||||
|
try:
|
||||||
|
shares = manilaclient.share_list(request)
|
||||||
|
choices = [(s.id, s.name) for s in shares]
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request, _("Failed to get list of shares"))
|
||||||
|
choices = []
|
||||||
|
return choices
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(SelectClusterSharesAction, self).clean()
|
||||||
|
self._errors = dict()
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Shares")
|
||||||
|
help_text = _("Select the manila shares for this cluster")
|
||||||
|
|
||||||
|
|
||||||
|
class SelectClusterShares(workflows.Step):
|
||||||
|
action_class = SelectClusterSharesAction
|
||||||
|
|
||||||
|
def contribute(self, data, context):
|
||||||
|
post = self.workflow.request.POST
|
||||||
|
shares_details = []
|
||||||
|
for index in range(0, len(self.action.fields['shares'].choices) * 3):
|
||||||
|
if index % 3 == 0:
|
||||||
|
share = post.get("shares_{0}".format(index))
|
||||||
|
if share:
|
||||||
|
path = post.get("shares_{0}".format(index + 1))
|
||||||
|
permissions = post.get("shares_{0}".format(index + 2))
|
||||||
|
shares_details.append({
|
||||||
|
"id": share,
|
||||||
|
"path": path,
|
||||||
|
"access_level": permissions
|
||||||
|
})
|
||||||
|
context['ct_shares'] = shares_details
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
||||||
whelpers.StatusFormatMixin):
|
whelpers.StatusFormatMixin):
|
||||||
slug = "configure_cluster_template"
|
slug = "configure_cluster_template"
|
||||||
|
@ -262,6 +317,9 @@ class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
||||||
plugin,
|
plugin,
|
||||||
hadoop_version)
|
hadoop_version)
|
||||||
|
|
||||||
|
if saharaclient.base.is_service_enabled(request, 'share'):
|
||||||
|
ConfigureClusterTemplate._register_step(self, SelectClusterShares)
|
||||||
|
|
||||||
self._populate_tabs(general_parameters, service_parameters)
|
self._populate_tabs(general_parameters, service_parameters)
|
||||||
|
|
||||||
super(ConfigureClusterTemplate, self).__init__(request,
|
super(ConfigureClusterTemplate, self).__init__(request,
|
||||||
|
@ -308,6 +366,10 @@ class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
||||||
plugin, hadoop_version = whelpers.\
|
plugin, hadoop_version = whelpers.\
|
||||||
get_plugin_and_hadoop_version(request)
|
get_plugin_and_hadoop_version(request)
|
||||||
|
|
||||||
|
ct_shares = []
|
||||||
|
if "ct_shares" in context:
|
||||||
|
ct_shares = context["ct_shares"]
|
||||||
|
|
||||||
# TODO(nkonovalov): Fix client to support default_image_id
|
# TODO(nkonovalov): Fix client to support default_image_id
|
||||||
saharaclient.cluster_template_create(
|
saharaclient.cluster_template_create(
|
||||||
request,
|
request,
|
||||||
|
@ -318,7 +380,8 @@ class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
||||||
configs_dict,
|
configs_dict,
|
||||||
node_groups,
|
node_groups,
|
||||||
context["anti_affinity_info"],
|
context["anti_affinity_info"],
|
||||||
use_autoconfig=context['general_use_autoconfig']
|
use_autoconfig=context['general_use_autoconfig'],
|
||||||
|
shares=ct_shares
|
||||||
)
|
)
|
||||||
|
|
||||||
hlps = helpers.Helpers(request)
|
hlps = helpers.Helpers(request)
|
||||||
|
|
|
@ -81,6 +81,10 @@ class EditClusterTemplate(copy_flow.CopyClusterTemplate):
|
||||||
plugin, hadoop_version = whelpers. \
|
plugin, hadoop_version = whelpers. \
|
||||||
get_plugin_and_hadoop_version(request)
|
get_plugin_and_hadoop_version(request)
|
||||||
|
|
||||||
|
ct_shares = []
|
||||||
|
if "ct_shares" in context:
|
||||||
|
ct_shares = context["ct_shares"]
|
||||||
|
|
||||||
saharaclient.cluster_template_update(
|
saharaclient.cluster_template_update(
|
||||||
request=request,
|
request=request,
|
||||||
ct_id=self.cluster_template_id,
|
ct_id=self.cluster_template_id,
|
||||||
|
@ -91,7 +95,8 @@ class EditClusterTemplate(copy_flow.CopyClusterTemplate):
|
||||||
cluster_configs=configs_dict,
|
cluster_configs=configs_dict,
|
||||||
node_groups=node_groups,
|
node_groups=node_groups,
|
||||||
anti_affinity=context["anti_affinity_info"],
|
anti_affinity=context["anti_affinity_info"],
|
||||||
use_autoconfig=context['general_use_autoconfig']
|
use_autoconfig=context['general_use_autoconfig'],
|
||||||
|
shares=ct_shares
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except exceptions.Conflict as e:
|
except exceptions.Conflict as e:
|
||||||
|
|
|
@ -15,7 +15,6 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils import encoding
|
from django.utils import encoding
|
||||||
from django.utils import html
|
from django.utils import html
|
||||||
from django.utils import safestring
|
from django.utils import safestring
|
||||||
|
@ -334,66 +333,6 @@ class SelectNodeProcessesAction(workflows.Action):
|
||||||
help_text = _("Select node processes for the node group")
|
help_text = _("Select node processes for the node group")
|
||||||
|
|
||||||
|
|
||||||
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("<table>")
|
|
||||||
output.append("<tr><th>Share</th><th>Enabled</th>"
|
|
||||||
"<th>Path</th><th>Permissions</th></tr>")
|
|
||||||
for i, widget in enumerate(rendered_widgets):
|
|
||||||
item_widget_index = i % 3
|
|
||||||
if item_widget_index == 0:
|
|
||||||
output.append("<tr>")
|
|
||||||
output.append(
|
|
||||||
"<td class='col-sm-2 small-padding'>{0}</td>".format(
|
|
||||||
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("<td class='col-sm-{0} small-padding'>".format(size)
|
|
||||||
+ widget + "</td>")
|
|
||||||
if item_widget_index == 2:
|
|
||||||
output.append("</tr>")
|
|
||||||
output.append("</table>")
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SelectNodeGroupSharesAction(workflows.Action):
|
class SelectNodeGroupSharesAction(workflows.Action):
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
super(SelectNodeGroupSharesAction, self).__init__(
|
super(SelectNodeGroupSharesAction, self).__init__(
|
||||||
|
@ -401,9 +340,9 @@ class SelectNodeGroupSharesAction(workflows.Action):
|
||||||
|
|
||||||
possible_shares = self.get_possible_shares(request)
|
possible_shares = self.get_possible_shares(request)
|
||||||
|
|
||||||
self.fields["shares"] = MultipleShareChoiceField(
|
self.fields["shares"] = workflow_helpers.MultipleShareChoiceField(
|
||||||
label=_("Select Shares"),
|
label=_("Select Shares"),
|
||||||
widget=ShareWidget(choices=possible_shares),
|
widget=workflow_helpers.ShareWidget(choices=possible_shares),
|
||||||
required=False,
|
required=False,
|
||||||
choices=possible_shares
|
choices=possible_shares
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils import safestring
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -324,3 +326,63 @@ class StatusFormatMixin(workflows.Workflow):
|
||||||
return error_description
|
return error_description
|
||||||
else:
|
else:
|
||||||
return message % self.context[self.name_property]
|
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("<table>")
|
||||||
|
output.append("<tr><th>Share</th><th>Enabled</th>"
|
||||||
|
"<th>Path</th><th>Permissions</th></tr>")
|
||||||
|
for i, widget in enumerate(rendered_widgets):
|
||||||
|
item_widget_index = i % 3
|
||||||
|
if item_widget_index == 0:
|
||||||
|
output.append("<tr>")
|
||||||
|
output.append(
|
||||||
|
"<td class='col-sm-2 small-padding'>{0}</td>".format(
|
||||||
|
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("<td class='col-sm-{0} small-padding'>".format(size)
|
||||||
|
+ widget + "</td>")
|
||||||
|
if item_widget_index == 2:
|
||||||
|
output.append("</tr>")
|
||||||
|
output.append("</table>")
|
||||||
|
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")
|
||||||
|
)
|
||||||
|
|
|
@ -204,6 +204,7 @@ def data(TEST):
|
||||||
"is_proxy_gateway": False
|
"is_proxy_gateway": False
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"shares": [],
|
||||||
"plugin_name": "vanilla",
|
"plugin_name": "vanilla",
|
||||||
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
||||||
"updated_at": None
|
"updated_at": None
|
||||||
|
|
Loading…
Reference in New Issue