Merge "[Sahara] Unified job interface map UI"
This commit is contained in:
@@ -380,13 +380,14 @@ def job_binary_internal_delete(request, jbi_id):
|
|||||||
client(request).job_binary_internals.delete(job_binary_id=jbi_id)
|
client(request).job_binary_internals.delete(job_binary_id=jbi_id)
|
||||||
|
|
||||||
|
|
||||||
def job_create(request, name, j_type, mains, libs, description):
|
def job_create(request, name, j_type, mains, libs, description, interface):
|
||||||
return client(request).jobs.create(
|
return client(request).jobs.create(
|
||||||
name=name,
|
name=name,
|
||||||
type=j_type,
|
type=j_type,
|
||||||
mains=mains,
|
mains=mains,
|
||||||
libs=libs,
|
libs=libs,
|
||||||
description=description)
|
description=description,
|
||||||
|
interface=interface)
|
||||||
|
|
||||||
|
|
||||||
def job_list(request, search_opts=None):
|
def job_list(request, search_opts=None):
|
||||||
@@ -406,13 +407,15 @@ def job_get_configs(request, job_type):
|
|||||||
|
|
||||||
|
|
||||||
def job_execution_create(request, job_id, cluster_id,
|
def job_execution_create(request, job_id, cluster_id,
|
||||||
input_id, output_id, configs):
|
input_id, output_id, configs,
|
||||||
|
interface):
|
||||||
return client(request).job_executions.create(
|
return client(request).job_executions.create(
|
||||||
job_id=job_id,
|
job_id=job_id,
|
||||||
cluster_id=cluster_id,
|
cluster_id=cluster_id,
|
||||||
input_id=input_id,
|
input_id=input_id,
|
||||||
output_id=output_id,
|
output_id=output_id,
|
||||||
configs=configs)
|
configs=configs,
|
||||||
|
interface=interface)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_job_execution_names(job_execution, cluster=None,
|
def _resolve_job_execution_names(job_execution, cluster=None,
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<input type="hidden" value="[]" name="argument_ids" id="argument_ids">
|
||||||
|
<label for="value_type">{% trans "Select a Value Type for your next argument:" %}</label>
|
||||||
|
<span class="row">
|
||||||
|
<span class="input-group col-sm-4">
|
||||||
|
<select id="value_type" name="value_type" class="form-control">
|
||||||
|
<option>{% trans "Select" %}</option>
|
||||||
|
<option value="string">{% trans "String" %}</option>
|
||||||
|
<option value="number">{% trans "Number" %}</option>
|
||||||
|
<option value="data_source">{% trans "Data Source" %}</option>
|
||||||
|
</select>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="add_argument_button" class="btn btn-default disabled" onclick="horizon.job_interface_arguments.add_interface_argument();">
|
||||||
|
<span class="fa fa-plus"></span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div id="job_interface_arguments">
|
||||||
|
<div id="arguments_table">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
horizon.job_interface_arguments.init_arguments();
|
||||||
|
{% for argument in form.arguments %}
|
||||||
|
horizon.job_interface_arguments.add_argument_node(
|
||||||
|
horizon.job_interface_arguments.get_next_argument_id(),
|
||||||
|
"{{ argument.name }}",
|
||||||
|
"{{ argument.description }}",
|
||||||
|
"{{ argument.mapping_type }}",
|
||||||
|
"{{ argument.location }}",
|
||||||
|
"{{ argument.value_type }}",
|
||||||
|
"{{ argument.required }}",
|
||||||
|
"{{ argument.default_value }}");
|
||||||
|
{% endfor %}
|
||||||
|
{% for field_id in form.errors_fields %}
|
||||||
|
horizon.job_interface_arguments.mark_argument_element_as_wrong("{{ field_id }}");
|
||||||
|
{% endfor %}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
@@ -34,9 +34,6 @@
|
|||||||
$navbar.hide();
|
$navbar.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".hidden_nodegroups_field").val("");
|
|
||||||
$(".hidden_configure_field").val("");
|
|
||||||
|
|
||||||
lower_limit = 0;
|
lower_limit = 0;
|
||||||
$(".count-field").change();
|
$(".count-field").change();
|
||||||
if ($(modal).find(".hidden_create_field").length > 0) {
|
if ($(modal).find(".hidden_create_field").length > 0) {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var remove_btn_label = '{% trans "Remove" %}';
|
var remove_btn_label = '{% trans "Remove" %}';
|
||||||
var template = '<tr id_attr="$id">' +
|
var lib_template = '<tr id_attr="$id">' +
|
||||||
'<td>' +
|
'<td>' +
|
||||||
'<div class="input form-group" style="padding-right:15px;"><input disabled value="$lib_name" class="form-control" /></div>' +
|
'<div class="input form-group" style="padding-right:15px;"><input disabled value="$lib_name" class="form-control" /></div>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
'</td>' +
|
'</td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
|
|
||||||
function get_next_id() {
|
function get_next_lib_id() {
|
||||||
var max = -1;
|
var max = -1;
|
||||||
$("#libs tbody tr").each(function () {
|
$("#libs tbody tr").each(function () {
|
||||||
max = Math.max(max, parseInt($(this).attr("id_attr")));
|
max = Math.max(max, parseInt($(this).attr("id_attr")));
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
$("#lib_ids").val(JSON.stringify(ids));
|
$("#lib_ids").val(JSON.stringify(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_node(lib_name, id) {
|
function add_lib_node(lib_name, id) {
|
||||||
var tmp = template.
|
var tmp = lib_template.
|
||||||
replace(/\$id/g, id).
|
replace(/\$id/g, id).
|
||||||
replace(/\$lib_name/g, lib_name);
|
replace(/\$lib_name/g, lib_name);
|
||||||
$("#libs tbody").append(tmp);
|
$("#libs tbody").append(tmp);
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
if (lib_val == "") {
|
if (lib_val == "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
add_node(chosen.html(), chosen.val());
|
add_lib_node(chosen.html(), chosen.val());
|
||||||
}
|
}
|
||||||
function delete_lib(el) {
|
function delete_lib(el) {
|
||||||
var tr = $(el).parents("tr")[0];
|
var tr = $(el).parents("tr")[0];
|
||||||
tr.parentNode.removeChild(tr);
|
tr.parentNode.removeChild(tr);
|
||||||
var id = get_next_id();
|
var id = get_next_lib_id();
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
$("#libs").hide();
|
$("#libs").hide();
|
||||||
}
|
}
|
||||||
|
@@ -54,7 +54,8 @@ class DataProcessingJobTests(test.TestCase):
|
|||||||
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
||||||
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
||||||
api.sahara.job_create(IsA(http.HttpRequest),
|
api.sahara.job_create(IsA(http.HttpRequest),
|
||||||
'test', 'Pig', [], [], 'test create')
|
'test', 'Pig', [], [], 'test create',
|
||||||
|
interface=[])
|
||||||
api.sahara.job_types_list(IsA(http.HttpRequest)) \
|
api.sahara.job_types_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.job_types.list())
|
.AndReturn(self.job_types.list())
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
@@ -62,7 +63,66 @@ class DataProcessingJobTests(test.TestCase):
|
|||||||
'job_type': 'pig',
|
'job_type': 'pig',
|
||||||
'lib_binaries': [],
|
'lib_binaries': [],
|
||||||
'lib_ids': '[]',
|
'lib_ids': '[]',
|
||||||
'job_description': 'test create'}
|
'job_description': 'test create',
|
||||||
|
'hidden_arguments_field': [],
|
||||||
|
'argument_ids': '[]'}
|
||||||
|
url = reverse('horizon:project:data_processing.jobs:create-job')
|
||||||
|
res = self.client.post(url, form_data)
|
||||||
|
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
|
||||||
|
@test.create_stubs({api.sahara: ('job_binary_list',
|
||||||
|
'job_create',
|
||||||
|
'job_types_list')})
|
||||||
|
def test_create_with_interface(self):
|
||||||
|
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
||||||
|
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
|
||||||
|
api.sahara.job_create(IsA(http.HttpRequest),
|
||||||
|
'test_interface', 'Pig', [], [], 'test create',
|
||||||
|
interface=[
|
||||||
|
{
|
||||||
|
"name": "argument",
|
||||||
|
"description": None,
|
||||||
|
"mapping_type": "args",
|
||||||
|
"location": "0",
|
||||||
|
"value_type": "number",
|
||||||
|
"required": True,
|
||||||
|
"default": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "config",
|
||||||
|
"description": "Really great config",
|
||||||
|
"mapping_type": "configs",
|
||||||
|
"location": "edp.important.config",
|
||||||
|
"value_type": "string",
|
||||||
|
"required": False,
|
||||||
|
"default": "A value"
|
||||||
|
}])
|
||||||
|
api.sahara.job_types_list(IsA(http.HttpRequest)) \
|
||||||
|
.AndReturn(self.job_types.list())
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
form_data = {'job_name': 'test_interface',
|
||||||
|
'job_type': 'pig',
|
||||||
|
'lib_binaries': [],
|
||||||
|
'lib_ids': '[]',
|
||||||
|
'job_description': 'test create',
|
||||||
|
'hidden_arguments_field': [],
|
||||||
|
'argument_ids': '["0", "1"]',
|
||||||
|
'argument_id_0': '0',
|
||||||
|
'argument_name_0': 'argument',
|
||||||
|
'argument_description_0': '',
|
||||||
|
'argument_mapping_type_0': 'args',
|
||||||
|
'argument_location_0': '0',
|
||||||
|
'argument_value_type_0': 'number',
|
||||||
|
'argument_required_0': True,
|
||||||
|
'argument_default_value_0': '',
|
||||||
|
'argument_id_1': '1',
|
||||||
|
'argument_name_1': 'config',
|
||||||
|
'argument_description_1': 'Really great config',
|
||||||
|
'argument_mapping_type_1': 'configs',
|
||||||
|
'argument_location_1': 'edp.important.config',
|
||||||
|
'argument_value_type_1': 'string',
|
||||||
|
'argument_default_value_1': 'A value'}
|
||||||
url = reverse('horizon:project:data_processing.jobs:create-job')
|
url = reverse('horizon:project:data_processing.jobs:create-job')
|
||||||
res = self.client.post(url, form_data)
|
res = self.client.post(url, form_data)
|
||||||
|
|
||||||
@@ -106,11 +166,14 @@ class DataProcessingJobTests(test.TestCase):
|
|||||||
.MultipleTimes().AndReturn(self.data_sources.list())
|
.MultipleTimes().AndReturn(self.data_sources.list())
|
||||||
api.sahara.job_list(IsA(http.HttpRequest)) \
|
api.sahara.job_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.jobs.list())
|
.AndReturn(self.jobs.list())
|
||||||
|
api.sahara.job_get(IsA(http.HttpRequest), IsA(unicode)) \
|
||||||
|
.AndReturn(job)
|
||||||
api.sahara.job_execution_create(IsA(http.HttpRequest),
|
api.sahara.job_execution_create(IsA(http.HttpRequest),
|
||||||
IsA(unicode),
|
IsA(unicode),
|
||||||
IsA(unicode),
|
IsA(unicode),
|
||||||
IsA(unicode),
|
IsA(unicode),
|
||||||
IsA(unicode),
|
IsA(unicode),
|
||||||
|
IsA(dict),
|
||||||
IsA(dict)).AndReturn(job_execution)
|
IsA(dict)).AndReturn(job_execution)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
@@ -121,6 +184,7 @@ class DataProcessingJobTests(test.TestCase):
|
|||||||
'job_input': input_ds.id,
|
'job_input': input_ds.id,
|
||||||
'job_output': output_ds.id,
|
'job_output': output_ds.id,
|
||||||
'config': {},
|
'config': {},
|
||||||
|
'argument_ids': '{}',
|
||||||
'adapt_oozie': 'on',
|
'adapt_oozie': 'on',
|
||||||
'hbase_common_lib': 'on',
|
'hbase_common_lib': 'on',
|
||||||
'java_opts': '',
|
'java_opts': '',
|
||||||
|
@@ -23,6 +23,8 @@ from horizon import workflows
|
|||||||
|
|
||||||
from openstack_dashboard.contrib.sahara.content.data_processing \
|
from openstack_dashboard.contrib.sahara.content.data_processing \
|
||||||
.utils import helpers
|
.utils import helpers
|
||||||
|
import openstack_dashboard.contrib.sahara.content.data_processing \
|
||||||
|
.utils.workflow_helpers as whelpers
|
||||||
from openstack_dashboard.contrib.sahara.api import sahara as saharaclient
|
from openstack_dashboard.contrib.sahara.api import sahara as saharaclient
|
||||||
|
|
||||||
|
|
||||||
@@ -140,6 +142,62 @@ class GeneralConfigAction(workflows.Action):
|
|||||||
"project/data_processing.jobs/_create_job_help.html")
|
"project/data_processing.jobs/_create_job_help.html")
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigureInterfaceArgumentsAction(workflows.Action):
|
||||||
|
hidden_arguments_field = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
widget=forms.HiddenInput(attrs={"class": "hidden_arguments_field"}))
|
||||||
|
argument_ids = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(ConfigureInterfaceArgumentsAction, self).__init__(
|
||||||
|
request, *args, **kwargs)
|
||||||
|
request_source = None
|
||||||
|
if 'argument_ids' in request.POST:
|
||||||
|
request_source = request.POST
|
||||||
|
elif 'argument_ids' in request.REQUEST:
|
||||||
|
request_source = request.REQUEST
|
||||||
|
if request_source:
|
||||||
|
self.arguments = []
|
||||||
|
for id in json.loads(request_source['argument_ids']):
|
||||||
|
fields = {
|
||||||
|
"name": "argument_name_" + str(id),
|
||||||
|
"description": "argument_description_" + str(id),
|
||||||
|
"mapping_type": "argument_mapping_type_" + str(id),
|
||||||
|
"location": "argument_location_" + str(id),
|
||||||
|
"value_type": "argument_value_type_" + str(id),
|
||||||
|
"default_value": "argument_default_value_" + str(id)}
|
||||||
|
argument = {k: request_source[v]
|
||||||
|
for k, v in fields.items()}
|
||||||
|
required_field = "argument_required_" + str(id)
|
||||||
|
fields.update({"required": required_field})
|
||||||
|
argument.update(
|
||||||
|
{"required": required_field in request_source})
|
||||||
|
self.arguments.append(argument)
|
||||||
|
|
||||||
|
whelpers.build_interface_argument_fields(self, **fields)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(ConfigureInterfaceArgumentsAction, self).clean()
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Interface Arguments")
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigureArguments(workflows.Step):
|
||||||
|
action_class = ConfigureInterfaceArgumentsAction
|
||||||
|
contributes = ("hidden_arguments_field", )
|
||||||
|
template_name = ("project/data_processing.jobs/"
|
||||||
|
"job_interface_arguments_template.html")
|
||||||
|
|
||||||
|
def contribute(self, data, context):
|
||||||
|
for k, v in data.items():
|
||||||
|
context[k] = v
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class GeneralConfig(workflows.Step):
|
class GeneralConfig(workflows.Step):
|
||||||
action_class = GeneralConfigAction
|
action_class = GeneralConfigAction
|
||||||
contributes = ("job_name", "job_type", "job_description", "main_binary")
|
contributes = ("job_name", "job_type", "job_description", "main_binary")
|
||||||
@@ -171,7 +229,7 @@ class CreateJob(workflows.Workflow):
|
|||||||
success_message = _("Job created")
|
success_message = _("Job created")
|
||||||
failure_message = _("Could not create job template")
|
failure_message = _("Could not create job template")
|
||||||
success_url = "horizon:project:data_processing.jobs:index"
|
success_url = "horizon:project:data_processing.jobs:index"
|
||||||
default_steps = (GeneralConfig, ConfigureLibs)
|
default_steps = (GeneralConfig, ConfigureLibs, ConfigureArguments)
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
main_locations = []
|
main_locations = []
|
||||||
@@ -184,6 +242,22 @@ class CreateJob(workflows.Workflow):
|
|||||||
if context.get("main_binary", None):
|
if context.get("main_binary", None):
|
||||||
main_locations.append(context["main_binary"])
|
main_locations.append(context["main_binary"])
|
||||||
|
|
||||||
|
argument_ids = json.loads(context['argument_ids'])
|
||||||
|
interface = [
|
||||||
|
{
|
||||||
|
"name": context['argument_name_' + str(arg_id)],
|
||||||
|
"description": (context['argument_description_' + str(arg_id)]
|
||||||
|
or None),
|
||||||
|
"mapping_type": context['argument_mapping_type_'
|
||||||
|
+ str(arg_id)],
|
||||||
|
"location": context['argument_location_' + str(arg_id)],
|
||||||
|
"value_type": context['argument_value_type_' + str(arg_id)],
|
||||||
|
"required": context['argument_required_' + str(arg_id)],
|
||||||
|
"default": (context['argument_default_value_' + str(arg_id)]
|
||||||
|
or None)
|
||||||
|
} for arg_id in argument_ids
|
||||||
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
job = saharaclient.job_create(
|
job = saharaclient.job_create(
|
||||||
request,
|
request,
|
||||||
@@ -191,7 +265,8 @@ class CreateJob(workflows.Workflow):
|
|||||||
context["job_type"],
|
context["job_type"],
|
||||||
main_locations,
|
main_locations,
|
||||||
lib_locations,
|
lib_locations,
|
||||||
context["job_description"])
|
context["job_description"],
|
||||||
|
interface=interface)
|
||||||
|
|
||||||
hlps = helpers.Helpers(request)
|
hlps = helpers.Helpers(request)
|
||||||
if hlps.is_from_guide():
|
if hlps.is_from_guide():
|
||||||
|
@@ -127,6 +127,33 @@ class JobExecutionExistingGeneralConfigAction(JobExecutionGeneralConfigAction):
|
|||||||
"project/data_processing.jobs/_launch_job_help.html")
|
"project/data_processing.jobs/_launch_job_help.html")
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_interface_with_configs(interface, job_configs):
|
||||||
|
interface_by_mapping = {(arg['mapping_type'], arg['location']): arg
|
||||||
|
for arg in interface}
|
||||||
|
mapped_types = ("configs", "params")
|
||||||
|
mapped_configs = {
|
||||||
|
(mapping_type, key): value for mapping_type in mapped_types
|
||||||
|
for key, value in job_configs.get(mapping_type, {}).items()
|
||||||
|
}
|
||||||
|
for index, arg in enumerate(job_configs.get('args', [])):
|
||||||
|
mapped_configs['args', str(index)] = arg
|
||||||
|
free_arguments, interface_arguments = {}, {}
|
||||||
|
for mapping, value in mapped_configs.items():
|
||||||
|
if mapping in interface_by_mapping:
|
||||||
|
arg = interface_by_mapping[mapping]
|
||||||
|
interface_arguments[arg['id']] = value
|
||||||
|
else:
|
||||||
|
free_arguments[mapping] = value
|
||||||
|
configs = {"configs": {}, "params": {}, "args": {}}
|
||||||
|
for mapping, value in free_arguments.items():
|
||||||
|
mapping_type, location = mapping
|
||||||
|
configs[mapping_type][location] = value
|
||||||
|
configs["args"] = [
|
||||||
|
value for key, value in sorted(configs["args"].items(),
|
||||||
|
key=lambda x: int(x[0]))]
|
||||||
|
return configs, interface_arguments
|
||||||
|
|
||||||
|
|
||||||
class JobConfigAction(workflows.Action):
|
class JobConfigAction(workflows.Action):
|
||||||
MAIN_CLASS = "edp.java.main_class"
|
MAIN_CLASS = "edp.java.main_class"
|
||||||
JAVA_OPTS = "edp.java.java_opts"
|
JAVA_OPTS = "edp.java.java_opts"
|
||||||
@@ -182,9 +209,10 @@ class JobConfigAction(workflows.Action):
|
|||||||
super(JobConfigAction, self).__init__(request, *args, **kwargs)
|
super(JobConfigAction, self).__init__(request, *args, **kwargs)
|
||||||
job_ex_id = request.REQUEST.get("job_execution_id")
|
job_ex_id = request.REQUEST.get("job_execution_id")
|
||||||
if job_ex_id is not None:
|
if job_ex_id is not None:
|
||||||
job_ex_id = request.REQUEST.get("job_execution_id")
|
|
||||||
job_ex = saharaclient.job_execution_get(request, job_ex_id)
|
job_ex = saharaclient.job_execution_get(request, job_ex_id)
|
||||||
job_configs = job_ex.job_configs
|
job = saharaclient.job_get(request, job_ex.job_id)
|
||||||
|
job_configs, interface_args = _merge_interface_with_configs(
|
||||||
|
job.interface, job_ex.job_configs)
|
||||||
edp_configs = {}
|
edp_configs = {}
|
||||||
|
|
||||||
if 'configs' in job_configs:
|
if 'configs' in job_configs:
|
||||||
@@ -365,6 +393,57 @@ class ClusterGeneralConfig(workflows.Step):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class JobExecutionInterfaceConfigAction(workflows.Action):
|
||||||
|
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(JobExecutionInterfaceConfigAction, self).__init__(
|
||||||
|
request, *args, **kwargs)
|
||||||
|
job_id = (request.GET.get("job_id")
|
||||||
|
or request.POST.get("job"))
|
||||||
|
job = saharaclient.job_get(request, job_id)
|
||||||
|
interface = job.interface or []
|
||||||
|
interface_args = {}
|
||||||
|
|
||||||
|
job_ex_id = request.REQUEST.get("job_execution_id")
|
||||||
|
if job_ex_id is not None:
|
||||||
|
job_ex = saharaclient.job_execution_get(request, job_ex_id)
|
||||||
|
job = saharaclient.job_get(request, job_ex.job_id)
|
||||||
|
job_configs, interface_args = _merge_interface_with_configs(
|
||||||
|
job.interface, job_ex.job_configs)
|
||||||
|
|
||||||
|
for argument in interface:
|
||||||
|
field = forms.CharField(
|
||||||
|
required=argument.get('required'),
|
||||||
|
label=argument['name'],
|
||||||
|
initial=(interface_args.get(argument['id']) or
|
||||||
|
argument.get('default')),
|
||||||
|
help_text=argument.get('description'),
|
||||||
|
widget=forms.TextInput()
|
||||||
|
)
|
||||||
|
self.fields['argument_%s' % argument['id']] = field
|
||||||
|
self.fields['argument_ids'] = forms.CharField(
|
||||||
|
initial=json.dumps({argument['id']: argument['name']
|
||||||
|
for argument in interface}),
|
||||||
|
widget=forms.HiddenInput()
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(JobExecutionInterfaceConfigAction, self).clean()
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Interface Arguments")
|
||||||
|
|
||||||
|
|
||||||
|
class JobExecutionInterfaceConfig(workflows.Step):
|
||||||
|
action_class = JobExecutionInterfaceConfigAction
|
||||||
|
|
||||||
|
def contribute(self, data, context):
|
||||||
|
for k, v in data.items():
|
||||||
|
context[k] = v
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class LaunchJob(workflows.Workflow):
|
class LaunchJob(workflows.Workflow):
|
||||||
slug = "launch_job"
|
slug = "launch_job"
|
||||||
name = _("Launch Job")
|
name = _("Launch Job")
|
||||||
@@ -372,16 +451,22 @@ class LaunchJob(workflows.Workflow):
|
|||||||
success_message = _("Job launched")
|
success_message = _("Job launched")
|
||||||
failure_message = _("Could not launch job")
|
failure_message = _("Could not launch job")
|
||||||
success_url = "horizon:project:data_processing.job_executions:index"
|
success_url = "horizon:project:data_processing.job_executions:index"
|
||||||
default_steps = (JobExecutionExistingGeneralConfig, JobConfig)
|
default_steps = (JobExecutionExistingGeneralConfig, JobConfig,
|
||||||
|
JobExecutionInterfaceConfig)
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
|
argument_ids = json.loads(context['argument_ids'])
|
||||||
|
interface = {name: context["argument_" + str(arg_id)]
|
||||||
|
for arg_id, name in argument_ids.items()}
|
||||||
|
|
||||||
saharaclient.job_execution_create(
|
saharaclient.job_execution_create(
|
||||||
request,
|
request,
|
||||||
context["job_general_job"],
|
context["job_general_job"],
|
||||||
context["job_general_cluster"],
|
context["job_general_cluster"],
|
||||||
context["job_general_job_input"],
|
context["job_general_job_input"],
|
||||||
context["job_general_job_output"],
|
context["job_general_job_output"],
|
||||||
context["job_config"])
|
context["job_config"],
|
||||||
|
interface)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -411,11 +496,10 @@ class SelectHadoopPluginAction(t_flows.SelectPluginAction):
|
|||||||
if job_ex_id is not None:
|
if job_ex_id is not None:
|
||||||
self.fields["job_execution_id"] = forms.ChoiceField(
|
self.fields["job_execution_id"] = forms.ChoiceField(
|
||||||
label=_("Job Execution ID"),
|
label=_("Job Execution ID"),
|
||||||
initial=request.REQUEST.get("job_execution_id"),
|
initial=job_ex_id,
|
||||||
widget=forms.HiddenInput(
|
widget=forms.HiddenInput(
|
||||||
attrs={"class": "hidden_create_field"}))
|
attrs={"class": "hidden_create_field"}))
|
||||||
|
|
||||||
job_ex_id = request.REQUEST.get("job_execution_id")
|
|
||||||
job_configs = (
|
job_configs = (
|
||||||
saharaclient.job_execution_get(request,
|
saharaclient.job_execution_get(request,
|
||||||
job_ex_id).job_configs)
|
job_ex_id).job_configs)
|
||||||
@@ -459,7 +543,8 @@ class LaunchJobNewCluster(workflows.Workflow):
|
|||||||
success_url = "horizon:project:data_processing.jobs:index"
|
success_url = "horizon:project:data_processing.jobs:index"
|
||||||
default_steps = (ClusterGeneralConfig,
|
default_steps = (ClusterGeneralConfig,
|
||||||
JobExecutionGeneralConfig,
|
JobExecutionGeneralConfig,
|
||||||
JobConfig)
|
JobConfig,
|
||||||
|
JobExecutionInterfaceConfig)
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
node_groups = None
|
node_groups = None
|
||||||
@@ -470,6 +555,10 @@ class LaunchJobNewCluster(workflows.Workflow):
|
|||||||
ct_id = context["cluster_general_cluster_template"] or None
|
ct_id = context["cluster_general_cluster_template"] or None
|
||||||
user_keypair = context["cluster_general_keypair"] or None
|
user_keypair = context["cluster_general_keypair"] or None
|
||||||
|
|
||||||
|
argument_ids = json.loads(context['argument_ids'])
|
||||||
|
interface = {name: context["argument_" + str(arg_id)]
|
||||||
|
for arg_id, name in argument_ids.items()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cluster = saharaclient.cluster_create(
|
cluster = saharaclient.cluster_create(
|
||||||
request,
|
request,
|
||||||
@@ -496,7 +585,8 @@ class LaunchJobNewCluster(workflows.Workflow):
|
|||||||
cluster.id,
|
cluster.id,
|
||||||
context["job_general_job_input"],
|
context["job_general_job_input"],
|
||||||
context["job_general_job_output"],
|
context["job_general_job_output"],
|
||||||
context["job_config"])
|
context["job_config"],
|
||||||
|
interface)
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(request,
|
exceptions.handle(request,
|
||||||
_("Unable to launch job."))
|
_("Unable to launch job."))
|
||||||
|
@@ -0,0 +1,175 @@
|
|||||||
|
horizon.job_interface_arguments = {
|
||||||
|
|
||||||
|
argument_template: '' +
|
||||||
|
'<div id_attr="$id">' +
|
||||||
|
'<table class="argument-form table table-striped table-hover" id_attr="$id">' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<input type="button" class="btn btn-danger" id="delete_btn_$id" data-toggle="dropdown" onclick="horizon.job_interface_arguments.delete_interface_argument(this)" value="' + gettext('Remove') + '" />' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<input id="argument_id_$id" value="$id" type="hidden" name="argument_id_$id">' +
|
||||||
|
'<label for="argument_name_$id">' + gettext("Name") + ':</label>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<input id="argument_name_$id" value="$name" type="text" name="argument_name_$id" class="form-control">' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_description_$id">' + gettext("Description") + ':</label>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<input id="argument_description_$id" value="$description" type="text" name="argument_description_$id" class="form-control">' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_mapping_type_$id">' + gettext("Mapping Type") + ':</label>' +
|
||||||
|
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("See http://docs.openstack.org/developer/sahara/userdoc/edp.html for definitions.") + '">' +
|
||||||
|
'<span class="fa fa-question-circle"></span>' +
|
||||||
|
'</span>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<select id="argument_mapping_type_$id" selected="$mapping_type" name="argument_mapping_type_$id" class="form-control">' +
|
||||||
|
'<option value="args">' + gettext("Positional Argument") + '</option>' +
|
||||||
|
'<option value="params">' + gettext("Named Parameter") + '</option>' +
|
||||||
|
'<option value="configs">' + gettext("Configuration Value") + '</option>' +
|
||||||
|
'</select>' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_location_$id">' + gettext("Location") + ':</label>' +
|
||||||
|
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("For configs and params, type the key name; for args, type the index as an integer, starting from 0.") + '">' +
|
||||||
|
'<span class="fa fa-question-circle"></span>' +
|
||||||
|
'</span>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<input id="argument_location_$id" value="$location" type="text" name="argument_location_$id" class="form-control">' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_value_type_$id">' + gettext("Value Type") + ':</label>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<select id="argument_value_type_$id" selected="$value_type" name="argument_value_type_$id" class="form-control">' +
|
||||||
|
'<option value="string">' + gettext("String") + '</option>' +
|
||||||
|
'<option value="number">' + gettext("Number") + '</option>' +
|
||||||
|
'<option value="data_source">' + gettext("Data Source") + '</option>' +
|
||||||
|
'</select>' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_required_$id">' + gettext("Required?") + ':</label>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<input id="argument_required_$id" type="checkbox" name="argument_required_$id" checked class="form-control">' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="col-sm-2 small-padding"></td>' +
|
||||||
|
'<td class="col-sm-2 small-padding">' +
|
||||||
|
'<label for="argument_default_value_$id">' + gettext("Default Value") + ':</label>' +
|
||||||
|
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("For data sources, use a data source UUID or a path (as per data source creation.)") + '">' +
|
||||||
|
'<span class="fa fa-question-circle"></span>' +
|
||||||
|
'</span>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="col-sm-4 small-padding">' +
|
||||||
|
'<input id="argument_default_value_$id" value="$default_value" type="text" name="argument_default_value_$id" class="form-control">' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>' +
|
||||||
|
'</table>' +
|
||||||
|
'</div>',
|
||||||
|
|
||||||
|
job_interface: null,
|
||||||
|
argument_ids: null,
|
||||||
|
value_type: null,
|
||||||
|
add_argument_button: null,
|
||||||
|
value_type_default: null,
|
||||||
|
|
||||||
|
current_value_type: function() {
|
||||||
|
return this.value_type.find("option:selected").html();
|
||||||
|
},
|
||||||
|
|
||||||
|
mark_argument_element_as_wrong: function(id) {
|
||||||
|
$("#" + id).addClass("error");
|
||||||
|
},
|
||||||
|
|
||||||
|
get_next_argument_id: function() {
|
||||||
|
var max = -1;
|
||||||
|
$(".argument-form").each(function () {
|
||||||
|
max = Math.max(max, parseInt($(this).attr("id_attr")));
|
||||||
|
});
|
||||||
|
return max + 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
set_argument_ids: function() {
|
||||||
|
var ids = [];
|
||||||
|
$(".argument-form").each(function () {
|
||||||
|
var id = parseInt($(this).attr("id_attr"));
|
||||||
|
if (!isNaN(id)) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.argument_ids.val(JSON.stringify(ids));
|
||||||
|
},
|
||||||
|
|
||||||
|
add_argument_node: function(id, name, description, mapping_type, location, value_type, required, default_value) {
|
||||||
|
var tmp = this.argument_template.
|
||||||
|
replace(/\$id/g, id).
|
||||||
|
replace(/\$name/g, name).
|
||||||
|
replace(/\$description/g, description).
|
||||||
|
replace(/\$mapping_type/g, mapping_type).
|
||||||
|
replace(/\$location/g, location).
|
||||||
|
replace(/\$value_type/g, value_type).
|
||||||
|
replace(/\$required/g, required).
|
||||||
|
replace(/\$default_value/g, default_value);
|
||||||
|
this.job_interface.find("div:last").after(tmp);
|
||||||
|
this.job_interface.show();
|
||||||
|
this.set_argument_ids();
|
||||||
|
},
|
||||||
|
|
||||||
|
add_interface_argument: function() {
|
||||||
|
var value_type = this.current_value_type();
|
||||||
|
if (value_type === this.value_type_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.add_argument_node(this.get_next_argument_id(), "", "", "args", "", value_type, true, "");
|
||||||
|
$(".count-field").change();
|
||||||
|
},
|
||||||
|
|
||||||
|
delete_interface_argument: function(el) {
|
||||||
|
$(el).closest("div").remove();
|
||||||
|
var id = this.get_next_argument_id();
|
||||||
|
if (id === 0) {
|
||||||
|
this.job_interface.hide();
|
||||||
|
}
|
||||||
|
this.set_argument_ids();
|
||||||
|
},
|
||||||
|
|
||||||
|
init_arguments: function() {
|
||||||
|
// This line enables tooltips on this tab to properly display their help text.
|
||||||
|
$("body").tooltip({selector: ".help-icon"});
|
||||||
|
this.job_interface = $("#job_interface_arguments");
|
||||||
|
this.argument_ids = $("#argument_ids");
|
||||||
|
this.value_type = $("#value_type");
|
||||||
|
this.add_argument_button = $("#add_argument_button");
|
||||||
|
this.value_type_default = this.current_value_type();
|
||||||
|
this.value_type.change(function () {
|
||||||
|
if (horizon.job_interface_arguments.current_value_type() === this.value_type_default) {
|
||||||
|
horizon.job_interface_arguments.add_argument_button.addClass("disabled");
|
||||||
|
} else {
|
||||||
|
horizon.job_interface_arguments.add_argument_button.removeClass("disabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.job_interface.hide();
|
||||||
|
}
|
||||||
|
}
|
@@ -126,6 +126,46 @@ def build_node_group_fields(action, name, template, count, serialized=None):
|
|||||||
widget=forms.HiddenInput())
|
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):
|
def parse_configs_from_context(context, defaults):
|
||||||
configs_dict = dict()
|
configs_dict = dict()
|
||||||
for key, val in context.items():
|
for key, val in context.items():
|
||||||
|
@@ -19,6 +19,11 @@ PANEL_DASHBOARD = 'project'
|
|||||||
PANEL_GROUP = 'data_processing'
|
PANEL_GROUP = 'data_processing'
|
||||||
|
|
||||||
# Python panel class of the PANEL to be added.
|
# Python panel class of the PANEL to be added.
|
||||||
ADD_PANEL = \
|
ADD_PANEL = (
|
||||||
('openstack_dashboard.contrib.sahara.'
|
'openstack_dashboard.contrib.sahara.'
|
||||||
'content.data_processing.jobs.panel.JobsPanel')
|
'content.data_processing.jobs.panel.JobsPanel')
|
||||||
|
|
||||||
|
ADD_JS_FILES = [
|
||||||
|
'dashboard/project/data_processing/'
|
||||||
|
'data_processing.job_interface_arguments.js'
|
||||||
|
]
|
||||||
|
@@ -464,6 +464,7 @@ def data(TEST):
|
|||||||
"url": "internal-db://80121dea-f8bd-4ad3-bcc7-096f4bfc722d"
|
"url": "internal-db://80121dea-f8bd-4ad3-bcc7-096f4bfc722d"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"interface": [],
|
||||||
"name": "pigjob",
|
"name": "pigjob",
|
||||||
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
||||||
"type": "Pig",
|
"type": "Pig",
|
||||||
@@ -549,6 +550,7 @@ def data(TEST):
|
|||||||
"configs": {},
|
"configs": {},
|
||||||
"params": {}
|
"params": {}
|
||||||
},
|
},
|
||||||
|
"interface": {},
|
||||||
"job_id": "a077b851-46be-4ad7-93c3-2d83894546ef",
|
"job_id": "a077b851-46be-4ad7-93c3-2d83894546ef",
|
||||||
"oozie_job_id": "0000000-140604200538581-oozie-hado-W",
|
"oozie_job_id": "0000000-140604200538581-oozie-hado-W",
|
||||||
"output_id": "426fb01c-5c7e-472d-bba2-b1f0fe7e0ede",
|
"output_id": "426fb01c-5c7e-472d-bba2-b1f0fe7e0ede",
|
||||||
|
Reference in New Issue
Block a user