1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-10 01:17:40 +03:00

F #4912: implement service update operation (#89)

* be able to change service elasticity policies
* be able to change service min vms
* service update view

Co-authored-by: Frederick Borges <fborges@opennebula.io>
This commit is contained in:
Alejandro Huertas Herrero 2020-07-16 16:21:21 +02:00 committed by GitHub
parent b28138f411
commit b9f1abb0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1737 additions and 23 deletions

View File

@ -36,6 +36,7 @@ $LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cli'
require 'json'
require 'tempfile'
require 'command_parser'
require 'opennebula/oneflow_client'
@ -348,4 +349,58 @@ CommandParser::CmdParser.new(ARGV) do
client.post("#{RESOURCE_PATH}/#{service_id}/action", json)
end
end
###
update_desc = <<-EOT.unindent
Update the template contents. If a path is not provided the editor will
be launched to modify the current content.
EOT
command :update, update_desc, :service_id, [:file, nil] do
service_id = args[0]
client = helper.client(options)
if args[1]
path = args[1]
else
response = client.get("#{RESOURCE_PATH}/#{service_id}")
if CloudClient.is_error?(response)
exit_with_code response.code.to_i, response.to_s
else
document = JSON.parse(response.body)['DOCUMENT']
template = document['TEMPLATE']['BODY']
tmp = Tempfile.new(service_id.to_s)
path = tmp.path
tmp.write(JSON.pretty_generate(template))
tmp.flush
if ENV['EDITOR']
editor_path = ENV['EDITOR']
else
editor_path = OpenNebulaHelper::EDITOR_PATH
end
system("#{editor_path} #{path}")
unless $CHILD_STATUS.exitstatus.zero?
STDERR.puts 'Editor not defined'
exit(-1)
end
tmp.close
end
end
response = client.put("#{RESOURCE_PATH}/#{service_id}", File.read(path))
if CloudClient.is_error?(response)
[response.code.to_i, response.to_s]
else
0
end
end
end

View File

@ -203,7 +203,7 @@ class EventManager
Log.info LOG_COMP, "Waiting #{cooldown_time}s for cooldown for " \
"service #{service_id} and role #{role_name}."
sleep cooldown_time
sleep cooldown_time.to_i
@lcm.trigger_action(:cooldown_cb,
service_id,

View File

@ -440,6 +440,41 @@ class ServiceLCM
rc
end
# Update service template
#
# @param client [OpenNebula::Client] Client executing action
# @param service_id [Integer] Service ID
# @param new_tempalte [String] New template
def service_update(client, service_id, new_template)
rc = @srv_pool.get(service_id, client) do |service|
unless service.can_update?
break OpenNebula::Error.new(
"Service cannot be updated in state: #{service.state_str}"
)
end
rc = service.check_new_template(new_template)
unless rc[0]
if rc[1] == 'name'
break OpenNebula::Error.new(
'To change `service/name` use rename operation'
)
else
break OpenNebula::Error.new(
"Immutable value: `#{rc[1]}` can not be changed"
)
end
end
service.update(new_template)
end
Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc)
rc
end
# Update role elasticity/schedule policies
#
# @param client [OpenNebula::Client] Client executing action

View File

@ -120,6 +120,22 @@ module OpenNebula
'DOWN' => 1
}
# List of attributes that can't be changed in update operation
# cardinality: this is internal information managed by OneFlow server
# last_vmname: this is internal information managed by OneFlow server
# nodes: this is internal information managed by OneFlow server
# parents: this has only sense in deploy operation
# state: this is internal information managed by OneFlow server
# vm_template: this will affect scale operation
IMMUTABLE_ATTRS = %w[
cardinality
last_vmname
nodes
parents
state
vm_template
]
# VM information to save in document
VM_INFO = %w[ID UID GID UNAME GNAME NAME]
@ -675,6 +691,22 @@ module OpenNebula
nil
end
# Check that changes values are correct
#
# @param template_json [String] New template
#
# @return [Boolean, String] True, nil if everything is correct
# False, attr if attr was changed
def check_new_template(template)
IMMUTABLE_ATTRS.each do |attr|
next if template[attr] == @body[attr]
return [false, "role/#{attr}"]
end
[true, nil]
end
########################################################################
# Recover
########################################################################

View File

@ -80,6 +80,29 @@ module OpenNebula
SCALING
]
# List of attributes that can't be changed in update operation
#
# custom_attrs: it only has sense when deploying, not in running
# custom_attrs_values: it only has sense when deploying, not in running
# deployment: changing this, changes the undeploy operation
# log: this is just internal information, no sense to change it
# name: this has to be changed using rename operation
# networks: it only has sense when deploying, not in running
# networks_values: it only has sense when deploying, not in running
# ready_status_gate: it only has sense when deploying, not in running
# state: this is internal information managed by OneFlow server
IMMUTABLE_ATTRS = %w[
custom_attrs
custom_attrs_values
deployment
log
name
networks
networks_values
ready_status_gate
state
]
LOG_COMP = 'SER'
# Returns the service state
@ -123,6 +146,12 @@ module OpenNebula
end
end
# Return true if the service can be updated
# @return true if the service can be updated, false otherwise
def can_update?
!transient_state? && !failed_state?
end
def can_recover_deploy?
RECOVER_DEPLOY_STATES.include? STATE_STR[state]
end
@ -471,6 +500,38 @@ module OpenNebula
super(template_raw, append)
end
# Check that changes values are correct
#
# @param template_json [String] New template
#
# @return [Boolean, String] True, nil if everything is correct
# False, attr if attr was changed
def check_new_template(template_json)
template = JSON.parse(template_json)
if template['roles'].size != @roles.size
return [false, 'service/roles size']
end
IMMUTABLE_ATTRS.each do |attr|
next if template[attr] == @body[attr]
return [false, "service/#{attr}"]
end
template['roles'].each do |role|
# Role name can't be changed, if it is changed some problems
# may appear, as name is used to reference roles
return [false, 'name'] unless @roles[role['name']]
rc = @roles[role['name']].check_new_template(role)
return rc unless rc[0]
end
[true, nil]
end
def deploy_networks
body = JSON.parse(self['TEMPLATE/BODY'])

View File

@ -279,26 +279,6 @@ post '/service/:id/action' do
rc = OpenNebula::Error.new("Action #{action['perform']}: " \
'You have to specify a name')
end
# when 'update'
# if opts && opts['append']
# if opts['template_json']
# begin
# service.update(opts['template_json'], true)
# status 204
# rescue Validator::ParseException, JSON::ParserError => e
# OpenNebula::Error.new(e.message)
# end
# elsif opts['template_raw']
# service.update_raw(opts['template_raw'], true)
# status 204
# else
# OpenNebula::Error.new("Action #{action['perform']}: " \
# 'You have to provide a template')
# end
# else
# OpenNebula::Error.new("Action #{action['perform']}: " \
# 'Only supported for append')
# end
when *Role::SCHEDULE_ACTIONS
# Use defaults only if one of the options is supplied
opts['period'] ||= conf[:action_period]
@ -323,6 +303,28 @@ post '/service/:id/action' do
status 204
end
put '/service/:id' do
new_template = request.body.read
begin
# Check that the JSON is valid
json_template = JSON.parse(new_template)
# Check the schema of the new template
ServiceTemplate.validate(json_template)
rescue Validator::ParseException, JSON::ParserError => e
return internal_error(e.message, VALIDATION_EC)
end
rc = lcm.service_update(@client, params[:id], new_template)
if OpenNebula.is_error?(rc)
return internal_error(rc.message, one_error_to_http(rc.errno))
end
status 204
end
# put '/service/:id/role/:name' do
# service_pool = nil # OpenNebula::ServicePool.new(@client)
#

View File

@ -16,6 +16,8 @@
define(function(require) {
var OpenNebulaAction = require('./action');
var OpenNebulaHelper = require("./helper");
var OpenNebulaError = require("./error");
var Locale = require('utils/locale');
var RESOURCE = "DOCUMENT";
@ -111,6 +113,35 @@ define(function(require) {
OpenNebulaAction.simple_action(params, RESOURCE, "update", action_obj, PATH);
},
"update": function(params){
params.cache_name = CACHE_NAME;
var json_aux = JSON.parse(params.data.extra_param);
var action_obj = JSON.stringify(json_aux);
var callback = params.success;
var callbackError = params.error;
var id = params.data.id;
var request = OpenNebulaHelper.request(RESOURCE, "update", [id, action_obj]);
var reqPath = PATH ? PATH : RESOURCE.toLowerCase();
var cache_name = params.cache_name ? params.cache_name : RESOURCE;
$.ajax({
url: reqPath + "/" + id,
type: "PUT",
contentType: "application/json; charset=utf-8",
data: action_obj,
success: function(response) {
//_clearCache(cache_name);
return callback ? callback(request, response) : null;
},
error: function(response) {
return callbackError ?
callbackError(request, OpenNebulaError(response)) : null;
}
});
},
"stateStr" : function(stateId) {
return STATES_STR[stateId];
},
@ -129,4 +160,3 @@ define(function(require) {
return Service;
})

View File

@ -40,7 +40,8 @@ define(function(require) {
];
var _formPanels = [
require('./oneflow-services-tab/form-panels/create')
require('./oneflow-services-tab/form-panels/create'),
require('./oneflow-services-tab/form-panels/update')
];
var Tab = {

View File

@ -30,6 +30,7 @@ define(function(require) {
var ROLES_PANEL_ID = require('./panels/roles/panelId');
var SCALE_DIALOG_ID = require('./dialogs/scale/dialogId');
var CREATE_DIALOG_ID = require('./form-panels/create/formPanelId');
var UPDATE_DIALOG_ID = require('./form-panels/update/formPanelId');
var _commonActions = new CommonActions(OpenNebulaResource, RESOURCE, TAB_ID,
XML_ROOT, Locale.tr("Service created"));
@ -96,6 +97,9 @@ define(function(require) {
"Service.shutdown": _commonActions.multipleAction('shutdown'),
"Service.recover": _commonActions.multipleAction('recover'),
"Service.recover_delete": _commonActions.multipleAction('recover_delete'),
"Service.update" : _commonActions.update(),
"Service.update_dialog" : _commonActions.checkAndShowUpdate(),
"Service.create_dialog" : {
type: "custom",
call: function() {
@ -115,6 +119,21 @@ define(function(require) {
}
},
"Service.show_to_update" : {
type: "single",
call: function(params) {
OpenNebulaResource.show(params);
},
callback: function(request, response) {
Sunstone.showFormPanel(TAB_ID, UPDATE_DIALOG_ID, "update",
function(formPanelInstance, context) {
formPanelInstance.fill(context, response[XML_ROOT]);
}
);
},
error: Notifier.onError
},
//--------------------------------------------------------------------------
"Role.scale_dialog" : {

View File

@ -23,6 +23,11 @@ define(function(require) {
layout: "refresh",
alwaysActive: true
},
"Service.update_dialog" : {
type: "action",
text: Locale.tr("Update"),
layout: "main",
},
"Service.create_dialog" : {
type: "action",
layout: "create",

View File

@ -0,0 +1,252 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require) {
/*
DEPENDENCIES
*/
var InstantiateTemplateFormPanel = require('tabs/oneflow-services-tab/form-panels/update/update');
var Locale = require('utils/locale');
var Tips = require('utils/tips');
var TemplatesTable = require('tabs/oneflow-templates-tab/datatable');
var TemplateUtils = require('utils/template-utils');
var RoleTab = require('tabs/oneflow-services-tab/utils/role-tab');
/*
CONSTANTS
*/
var FORM_PANEL_ID = require('./update/formPanelId');
var TAB_ID = require('../tabId');
/*
CONSTRUCTOR
*/
function FormPanel() {
InstantiateTemplateFormPanel.call(this);
this.formPanelId = FORM_PANEL_ID;
this.tabId = TAB_ID;
this.display_vmgroups = true;
this.actions = {
'update': {
'title': Locale.tr("Update Service"),
'buttonText': Locale.tr("Update"),
'resetButton': true
}
};
this.templatesTable = new TemplatesTable('service_update', {'select': true});
}
FormPanel.FORM_PANEL_ID = FORM_PANEL_ID;
FormPanel.prototype = Object.create(InstantiateTemplateFormPanel.prototype);
FormPanel.prototype.constructor = FormPanel;
FormPanel.prototype.onShow = _onShow;
FormPanel.prototype.setup = _setup;
FormPanel.prototype.fill = _fill;
FormPanel.prototype.addRoleTab = _add_role_tab;
return FormPanel;
/*
FUNCTION DEFINITIONS
*/
function _setup(context) {
var that = this;
this.roleTabObjects = {};
InstantiateTemplateFormPanel.prototype.setup.call(this, context);
$(".selectTemplateTable", context).html(
'<br/>' + this.templatesTable.dataTableHTML + '<br/>');
this.templatesTable.initialize();
$(".instantiate_wrapper", context).hide();
this.templatesTable.idInput().off("change").on("change", function(){
$(".instantiate_wrapper", context).show();
var template_id = $(this).val();
that.setTemplateId(context, template_id);
});
that.roles_index = 0;
//$("#tf_btn_roles", context).unbind("click");
$("#tf_btn_roles", context).bind("click", function(){
that.addRoleTab(that.roles_index, context);
that.roles_index++;
});
// Fill parents table
// Each time a tab is clicked the table is filled with existing tabs (roles)
// Selected roles are kept
// TODO If the name of a role is changed and is selected, selection will be lost
$("#roles_tabs", context).off("click", "a");
$("#roles_tabs", context).on("click", "a", function() {
var tab_id = "#"+this.id+"Tab";
var str = "";
$(tab_id+" .parent_roles").hide();
var parent_role_available = false;
$("#roles_tabs_content #role_name", context).each(function(){
if ($(this).val() && ($(this).val() != $(tab_id+" #role_name", context).val())) {
parent_role_available = true;
str += "<tr>\
<td style='width:20px;text-align:center;'>\
<input class='check_item' type='checkbox' value='"+$(this).val()+"' id='"+$(this).val()+"'/>\
</td>\
<td>"+$(this).val()+"</td>\
</tr>";
}
});
if (parent_role_available) {
$(tab_id+" .parent_roles", context).show();
}
var selected_parents = [];
$(tab_id+" .parent_roles_body input:checked", context).each(function(){
selected_parents.push($(this).val());
});
$(tab_id+" .parent_roles_body", context).html(str);
$.each(selected_parents, function(){
$(tab_id+" .parent_roles_body #"+this, context).attr('checked', true);
});
});
Foundation.reflow(context, 'tabs');
Tips.setup(context);
return false;
}
function _onShow(context) {
this.templatesTable.refreshResourceTableSelect();
InstantiateTemplateFormPanel.prototype.onShow.call(this, context);
}
function _fill(context, element) {
var that = this;
if (this.action != "update") {return;}
this.setHeader(element);
this.resourceId = element.ID;
$("#service_name", context).attr("disabled", "disabled");
$("#service_name", context).val(element.NAME);
$("#description", context).val(element.TEMPLATE.BODY.description);
$('select[name="deployment"]', context).val(element.TEMPLATE.BODY.deployment);
$("select[name='shutdown_action_service']", context).val(element.TEMPLATE.BODY.shutdown_action);
$("input[name='ready_status_gate']", context).prop("checked",element.TEMPLATE.BODY.ready_status_gate || false);
// Remove role tabs
$("#roles_tabs i.remove-tab", context).trigger("click");
var roles_names = [];
$.each(element.TEMPLATE.BODY.roles, function(index, value){
roles_names.push(value.name);
$("#tf_btn_roles", context).click();
var role_context = $('.role_content', context).last();
var role_id = $(role_context).attr("role_id");
that.roleTabObjects[role_id].fill(role_context, value, []);
});
$.each(element.TEMPLATE.BODY.roles, function(index, value){
var role_context = $('.role_content', context)[index];
var str = "";
$.each(roles_names, function(){
if (this != value.name) {
str += "<tr>\
<td style='width:20px;'>\
<input class='check_item' type='checkbox' value='"+this+"' id='"+this+"'/>\
</td>\
<td>"+this+"</td>\
</tr>";
}
});
$(".parent_roles_body", role_context).html(str);
if (value.parents) {
$.each(value.parents, function(index, value){
$(".parent_roles_body #"+this, role_context).attr('checked', true);
});
}
});
return false;
}
function _add_role_tab(role_id, dialog) {
var that = this;
var html_role_id = 'role' + role_id;
var role_tab = new RoleTab(html_role_id);
that.roleTabObjects[role_id] = role_tab;
// Append the new div containing the tab and add the tab to the list
var role_section = $('<div id="'+html_role_id+'Tab" class="tabs-panel role_content wizard_internal_tab" role_id="'+role_id+'">'+
role_tab.html() +
'</div>').appendTo($("#roles_tabs_content", dialog));
Tips.setup(role_section);
var a = $("<li class='tabs-title'>\
<a class='text-center' id='"+html_role_id+"' href='#"+html_role_id+"Tab'>\
<span>\
<i class='off-color fas fa-cube fa-3x'/>\
<br>\
<span id='role_name_text'>"+Locale.tr("Role ")+role_id+"</span>\
</span>\
<i class='fas fa-times-circle remove-tab'></i>\
</a>\
</li>").appendTo($("ul#roles_tabs", dialog));
Foundation.reInit($("ul#roles_tabs", dialog));
$("a", a).trigger("click");
// close icon: removing the tab on click
a.on("click", "i.remove-tab", function() {
var target = $(this).parent().attr("href");
var li = $(this).closest('li');
var ul = $(this).closest('ul');
var content = $(target);
var role_id = content.attr("role_id");
li.remove();
content.remove();
if (li.hasClass('is-active')) {
$('a', ul.children('li').last()).click();
}
delete that.roleTabObjects[role_id];
return false;
});
role_tab.setup(role_section);
role_tab.onShow();
}
});

View File

@ -0,0 +1,19 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require){
return 'updateServiceForm';
});

View File

@ -0,0 +1,126 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<form data-abide novalidate id="{{formPanelId}}Wizard" class="custom creation">
<div class="row">
<div class="service_template_param st_man medium-6 columns">
<label for="service_name">
{{tr "Name"}}
</label>
<input type="text" id="service_name" name="service_name" required/>
</div>
<div class="service_template_param st_man medium-6 columns">
</div>
</div>
<div class="row">
<div class="service_template_param st_man large-12 columns">
<label for="description">
{{tr "Description"}}
</label>
<textarea type="text" id="description" name="description"/>
</div>
</div>
<br>
<div id="network_configuration" style="display: none;">
{{#advancedSection (tr "Network configuration") }}
<div class="row">
<div class="large-12 columns">
<table class="service_networks policies_table dataTable">
<thead>
<tr>
<th width="5%">{{tr "Mandatory"}}</th>
<th width="22%">{{tr "Name"}}</th>
<th width="23%">{{tr "Description"}}</th>
<th width="10%">{{tr "Type"}}</th>
<th id="service_network_id_label" width="15%">{{tr "Network"}}</th>
<th width="20%">{{tr "Extra"}}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="6">
<a type="button" class="add_service_network button small small-12 secondary radius"><i class="fas fa-lg fa-plus-circle"></i> {{tr "Network"}}</a>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
{{/advancedSection}}
</div>
<div id="custom_attr_values" style="display: none;">
{{#advancedSection (tr "Custom Attributes Values Configuration") }}
{{{userInputsHTML}}}
{{/advancedSection}}
</div>
<div id="advanced_service_values" style="display: none;">
{{#advancedSection (tr "Advanced service parameters") }}
<div class="row">
<div class="service_template_param st_man medium-6 columns">
<label for="deployment">
{{tr "Strategy"}}
<span class="tip">{{tr "Straight strategy will instantiate each role in order: parents role will be deployed before their children. None strategy will instantiate the roles regardless of their relationships."}}</span>
</label>
<select name="deployment">
<option value="straight">{{tr "Straight"}}</option>
<option value="none">{{tr "None"}}</option>
</select>
</div>
<div class="service_template_param st_man medium-6 columns">
<label for="shutdown_action_service">
{{tr "VM shutdown action"}}
</label>
<select name="shutdown_action_service">
<option value=""></option>
<option value="terminate">{{tr "Terminate"}}</option>
<option value="terminate-hard">{{tr "Terminate hard"}}</option>
</select>
</div>
</div>
<div class="row">
<div class="service_template_param st_man small-12 columns">
<input type="checkbox" name="ready_status_gate" id="ready_status_gate"/>
<label for="ready_status_gate">
{{tr "Wait for VMs to report that they are READY via OneGate to consider them running"}}
</label>
</div>
</div>
{{/advancedSection}}
</div>
<br>
<div class="row">
<div class="large-12 columns">
<h5>
{{tr "Roles"}}
<a class="button small radius" id="tf_btn_roles" style="vertical-align: text-top; margin-left: 1em; display:none">
<i class="fas fa-lg fa-plus-circle"></i>
</a>
</h5>
</div>
</div>
<br>
<div class="row">
<div id="new_role" class="large-12 columns">
<ul class="tabs" id="roles_tabs" data-tabs>
</ul>
<div class="tabs-content" id="roles_tabs_content" data-tabs-content="roles_tabs">
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,168 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require) {
/*
DEPENDENCIES
*/
var BaseFormPanel = require("utils/form-panels/form-panel");
var Sunstone = require("sunstone");
var Locale = require("utils/locale");
var Tips = require("utils/tips");
var OpenNebulaAction = require('opennebula/action');
var OpenNebula = require('opennebula');
var UserInputs = require("utils/user-inputs");
var Notifier = require("utils/notifier");
/*
TEMPLATES
*/
var TemplateHTML = require("hbs!./html");
/*
CONSTANTS
*/
var FORM_PANEL_ID = require("./formPanelId");
var TAB_ID = require("../../tabId");
var vm_group = "VM_GROUP";
var classButton = 'small button leases right radius';
/*
CONSTRUCTOR
*/
function FormPanel() {
this.formPanelId = FORM_PANEL_ID;
this.tabId = TAB_ID;
this.display_vmgroups = false;
this.actions = {
"update": {
"title": Locale.tr("Update Service"),
"buttonText": Locale.tr("Update"),
"resetButton": false
}
};
BaseFormPanel.call(this);
}
FormPanel.FORM_PANEL_ID = FORM_PANEL_ID;
FormPanel.prototype = Object.create(BaseFormPanel.prototype);
FormPanel.prototype.constructor = FormPanel;
FormPanel.prototype.setTemplateId = _setTemplateId;
FormPanel.prototype.htmlWizard = _html;
FormPanel.prototype.submitWizard = _submitWizard;
FormPanel.prototype.onShow = _onShow;
FormPanel.prototype.setup = _setup;
return FormPanel;
/*
FUNCTION DEFINITIONS
*/
function _html() {
var values_hbs = {
'formPanelId': this.formPanelId,
'userInputsHTML': UserInputs.html(),
};
if(config && config.system_config && config.system_config.leases){
values_hbs.userInputsCharters = $("<div/>").append(
$("<div/>",{style:"display:inline-block;clear:both;width:100%"}).append(
$("<button />", {class: classButton, id:"addCharters"}).append(
$("<i/>", {class: 'fa fa-clock'})
)
).add(
$("<table/>", {class: "service-charters"})
)
).prop('outerHTML');
}
return TemplateHTML(values_hbs);
}
function _setup(context) {
Tips.setup(context);
return false;
}
function _onShow(context) {
//nothing
Sunstone.runAction("Service.show", window.ServiceId);
}
function _setTemplateId(context, templateId) {
this.templateId = templateId;
}
//submit
function _submitWizard(context) {
if (this.action != "update") return;
var that = this;
var description = $('#description', context).val();
var roles = [];
var json_template = {};
var ret = {};
//
OpenNebula.Service.show({
data : {
id: window.ServiceId
},
error: Notifier.onError,
success: function(request, response){
if (
response &&
response.DOCUMENT &&
response.DOCUMENT.TEMPLATE &&
response.DOCUMENT.TEMPLATE.BODY
) {
var body = response.DOCUMENT.TEMPLATE.BODY;
if (body.roles){
$('.role_content', context).each(function() {
var role_id = $(this).attr("role_id");
var role_info = that.roleTabObjects[role_id].retrieve($(this));
role_info['cardinality'] = parseInt(role_info.cardinality, 10);
role_info['cooldown'] = parseInt(role_info.cooldown, 10);
role_info['vm_template'] = parseInt(role_info.vm_template, 10);
role_info['last_vmname'] = body.roles[role_id].last_vmname;
role_info['nodes'] = body.roles[role_id].nodes;
role_info['state'] = body.roles[role_id].state;
roles.push(role_info);
});
json_template = {
description: description,
roles: roles
};
}
Object.assign(ret, body, json_template);
Sunstone.runAction("Service.update",that.resourceId, JSON.stringify(ret));
}
}
});
return false;
}
});

View File

@ -0,0 +1,552 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require) {
// Dependencies
var Locale = require('utils/locale');
var Tips = require('utils/tips');
var TemplatesTable = require('tabs/templates-tab/datatable');
var TemplateUtils = require('utils/template-utils');
var TemplateHTML = require('hbs!./role-tab/html');
var TemplateElasticityRowHTML = require('hbs!./role-tab/elasticity-row');
var TemplateScheRowHTML = require('hbs!./role-tab/sche-row');
function RoleTab(html_role_id) {
this.html_role_id = html_role_id;
this.global_template = {};
this.nics_template = {};
this.alias_template = {};
this.rest_template = "";
this.refresh = _refreshVMTemplate;
return this;
}
RoleTab.prototype = {
'html': _role_tab_content,
'setup': _setup_role_tab_content,
'onShow': _onShow,
'retrieve': _retrieve,
'fill': _fill
};
RoleTab.prototype.constructor = RoleTab;
return RoleTab;
function _role_tab_content(){
var opts = {
info: false,
select: true
};
this.templatesTable = new TemplatesTable("roleTabTemplates"+this.html_role_id, opts);
return TemplateHTML({
'templatesTableHTML': this.templatesTable.dataTableHTML
});
}
function _setup_role_tab_content(role_section) {
this.role_section = role_section;
var that = this;
Tips.setup(role_section);
this.templatesTable.initialize();
this.templatesTable.idInput().attr("required", "");
$("#role_name", role_section).unbind("keyup");
$("#role_name", role_section).bind("keyup", function(){
$("#" + that.html_role_id +" #role_name_text").html($(this).val());
});
role_section.on("change", "select#type", function(){
var new_tr = $(this).closest('tr');
if ($(this).val() == "PERCENTAGE_CHANGE") {
$("#min_adjust_step_td", new_tr).html('<input type="text" id="min_adjust_step" name="min_adjust_step"/>');
} else {
$("#min_adjust_step_td", new_tr).empty();
}
});
$("#tf_btn_elas_policies", role_section).bind("click", function(){
$( TemplateElasticityRowHTML({}) ).appendTo($("#elasticity_policies_tbody", role_section));
});
role_section.on("click", "#elasticity_policies_table i.remove-tab", function() {
var tr = $(this).closest('tr');
tr.remove();
});
$("#tf_btn_sche_policies", role_section).bind("click", function(){
$( TemplateScheRowHTML({}) ).appendTo($("#scheduled_policies_tbody", role_section));
});
role_section.on("click", "#scheduled_policies_table i.remove-tab", function() {
var tr = $(this).closest('tr');
tr.remove();
});
$("#tf_btn_elas_policies", role_section).trigger("click");
$("#tf_btn_sche_policies", role_section).trigger("click");
role_section.on("change", ".service_network_checkbox", role_section, function(){
// Network values
var index = $(this).data("index");
var name = $(this).val();
if (this.checked) {
that.nics_template[index] = { "NAME": "NIC"+index, "NETWORK_ID": "$"+name };
$("#alias_"+this.id, role_section).prop("disabled", false);
$("td.parent_selector select:visible", role_section).each(function() {
updateOptionsParents(this, that.nics_template, that.alias_template);
});
}
else {
delete that.nics_template[index];
delete that.alias_template[index];
$("#alias_"+this.id, role_section).prop("disabled", true).prop("checked", false).change();
}
updateOptionsRDP(role_section, $.extend({}, that.nics_template, that.alias_template));
updateRemoveTabs(role_section, index);
});
role_section.on("change", ".alias_network_checkbox", role_section, function(){
var index = $(this).data("index");
var select = $('td.parent_selector select[data-index='+index+']', role_section);
toogleNicUsedAsAlias(role_section, select, select.val(), null)
select.prop("hidden", !this.checked).prop("required", this.checked);
if (this.checked && that.nics_template[index]) {
that.alias_template[index] = that.nics_template[index];
delete that.nics_template[index];
}
else if (!this.checked && that.alias_template[index]) {
that.nics_template[index] = that.alias_template[index];
that.nics_template[index] && (delete that.nics_template[index]["PARENT"]);
delete that.alias_template[index];
}
$("td.parent_selector select:visible", role_section).each(function() {
updateOptionsParents(this, that.nics_template, that.alias_template);
});
});
role_section.on('focusin', ".parent_selector select", function(){
// save value after change
$(this).data('prev', $(this).val());
}).on("change", ".parent_selector select", function(){
var index = $(this).data('index');
var prevIndexParent = $(this).data('prev');
var indexParent = $(this).val();
toogleNicUsedAsAlias(role_section, this, prevIndexParent, indexParent)
if (that.nics_template[indexParent] && that.alias_template[index]) {
that.alias_template[index]["PARENT"] = "NIC"+indexParent;
}
});
role_section.on("change", ".networks_role_rdp select#rdp", role_section, function(){
var valueSelected = this.value;
var allTemplate = $.extend({}, that.nics_template, that.alias_template);
// remove RDP option in all nics
$.each(Object.entries(allTemplate), function(_, network) {
var nicIndex = network[0];
var nicTemplate = network[1];
if (nicIndex !== valueSelected && nicTemplate["RDP"]) {
(that.nics_template[nicIndex])
? delete that.nics_template[nicIndex]["RDP"]
: delete that.alias_template[nicIndex]["RDP"];
}
});
// then assign if exists
if (valueSelected !== "") {
if(that.nics_template[valueSelected]) {
that.nics_template[valueSelected]["RDP"] = "YES";
}
else if(that.alias_template[valueSelected]) {
that.alias_template[valueSelected]["RDP"] = "YES";
}
}
});
}
function updateRemoveTabs(role_section, index) {
var form = $(role_section).closest("#createServiceTemplateFormWizard");
var networkIsUsed = $(".service_network_checkbox[data-index="+index+"]:checked", form);
(networkIsUsed.length > 0)
? $("#network"+index+" i.remove-tab", form)
.prop("style", "color:currentColor;cursor:not-allowed;opacity:0.5;")
: $("#network"+index+" i.remove-tab", form).prop("style", "");
}
function toogleNicUsedAsAlias(context, currentSelector, prevIndex, index) {
var prevNicCb = $(".service_network_checkbox[data-index='"+prevIndex+"']", context);
var prevAliasCb = $(".alias_network_checkbox[data-index='"+prevIndex+"']", context);
var nicCb = $(".service_network_checkbox[data-index='"+index+"']", context);
var aliasCb = $(".alias_network_checkbox[data-index='"+index+"']", context);
var prevOthers = $(".parent_selector select:visible", context).not(currentSelector)
.children("option[value='"+prevIndex+"']:selected");
var others = $(".parent_selector select", context).children("option[value='"+index+"']:selected");
if (prevOthers.length === 0) {
prevNicCb.prop("disabled", false);
prevAliasCb.prop("disabled", false);
}
if (others.length === 0) {
nicCb.prop("disabled", false);
aliasCb.prop("disabled", false);
}
else {
aliasCb.prop("disabled", true).prop("checked", false);
nicCb.prop("disabled", true).prop("checked", true);
}
}
function updateOptionsRDP(context, currentTemplate) {
var selectRDP = $(".networks_role_rdp select#rdp", context);
// empty all options in select rdp
selectRDP.empty();
selectRDP.append("<option value=''></option>")
$(".service_network_checkbox:checked", context).each(function () {
var index = $(this).data("index");
var name = $(this).val();
selectRDP.append("<option value='"+index+"'>"+name+"</option>")
});
// if some nic has RDP, update selector value
$.each(Object.entries(currentTemplate), function(_, nic) {
var nicIndex = nic[0];
var nicTemplate = nic[1];
if (nicTemplate["RDP"] && String(nicTemplate["RDP"]).toLowerCase() === "yes") {
selectRDP.val(nicIndex).change();
}
});
}
function updateOptionsParents(select, nicsTemplate, aliasTemplate) {
select = $(select);
var nicIndex = $(select).data('index');
// empty all options in select alias parent
select.empty();
select.append("<option value=''></option>");
// possible parents
var possibleParents = filterByNicIndex(nicsTemplate, nicIndex);
$.each(Object.entries(possibleParents), function (_, nic) {
select.append("<option value='"+nic[0]+"'>"+nic[1].NETWORK_ID.substring(1)+"</option>")
});
// update selector parent value
$.each(Object.entries(aliasTemplate), function(_, nic) {
var nicTemplate = nic[1];
if (nicTemplate["PARENT"]) {
var indexParent = nicTemplate["PARENT"].replace("NIC", "");
select.val(indexParent).change();
}
});
}
function filterByNicIndex(object, index) {
const obj = {};
for (const key in object) {
if (key !== String(index)) {
obj[key] = object[key];
}
}
return obj;
}
function _onShow(){
this.templatesTable.refreshResourceTableSelect();
}
function _retrieve(context){
var role = {};
role['name'] = $('input[name="name"]', context).val();
role['cardinality'] = $('input[name="cardinality"]', context).val();
role['vm_template'] = this.templatesTable.retrieveResourceTableSelect();
role['shutdown_action'] = $('select[name="shutdown_action_role"]', context).val();
role['parents'] = [];
role['vm_template_contents'] = TemplateUtils.templateToString({ NIC: Object.values(this.nics_template) });
role['vm_template_contents'] += TemplateUtils.templateToString({ NIC_ALIAS: Object.values(this.alias_template) });
role['vm_template_contents'] += this.rest_template;
$('.parent_roles_body input.check_item:checked', context).each(function(){
role['parents'].push($(this).val());
});
var shutdown_action = $('select[name="shutdown_action_role"]', context).val();
if (shutdown_action) {
role['shutdown_action'] = shutdown_action;
}
var min_vms = $('input[name="min_vms"]', context).val();
if (min_vms) {
role['min_vms'] = min_vms;
}
var max_vms = $('input[name="max_vms"]', context).val();
if (max_vms) {
role['max_vms'] = max_vms;
}
var cooldown = $('input[name="cooldown"]', context).val();
if (cooldown) {
role['cooldown'] = cooldown;
}
role = _removeEmptyObjects(role);
role['elasticity_policies'] = [];
$("#elasticity_policies_tbody tr", context).each(function(){
if ($("#type" ,this).val()) {
var policy = {};
policy['type'] = $("#type" ,this).val();
policy['adjust'] = $("#adjust" ,this).val();
policy['min_adjust_step'] = $("#min_adjust_step" ,this).val();
policy['expression'] = $("#expression" ,this).val();
policy['period_number'] = $("#period_number" ,this).val();
policy['period'] = $("#period" ,this).val();
policy['cooldown'] = $("#cooldown" ,this).val();
// TODO remove empty policies
role['elasticity_policies'].push(_removeEmptyObjects(policy));
}
});
role['scheduled_policies'] = [];
$("#scheduled_policies_tbody tr", context).each(function(){
if ($("#type" ,this).val()) {
var policy = {};
policy['type'] = $("#type" ,this).val();
policy['adjust'] = $("#adjust" ,this).val();
policy['min_adjust_step'] = $("#min_adjust_step" ,this).val();
var time_format = $("#time_format" ,this).val();
policy[time_format] = $("#time" ,this).val();
// TODO remove empty policies
role['scheduled_policies'].push(_removeEmptyObjects(policy));
}
});
return role;
}
function _fill(context, value, network_names) {
var that = this;
$("#role_name", context).val(value.name).keyup();
$("#role_name", context).attr("disabled", "disabled");
$("#cardinality", context).val(value.cardinality);
$("#cardinality", context).attr("disabled", "disabled");
this.templatesTable.selectResourceTableSelect({ids : value.vm_template});
if (value.vm_template_contents){
var vmTemplate = TemplateUtils.stringToTemplate(value.vm_template_contents);
// map nics with index checkbox
var nics = vmTemplate["NIC"];
if (nics) {
nics = Array.isArray(nics) ? nics : [nics];
$.each(network_names, function(index, name) {
$.each(nics, function(_, nic) {
if (nic["NETWORK_ID"] === "$"+name){
nic["NAME"] = "NIC"+index;
$(".service_network_checkbox[data-index='"+index+"']", context).attr('checked', true).change();
that.nics_template[String(index)] = nic;
}
});
});
}
// map alias with index checkbox
var alias = vmTemplate["NIC_ALIAS"];
if (alias) {
alias = Array.isArray(alias) ? alias : [alias];
$.each(network_names, function(index, name) {
$.each(alias, function(_, nic) {
if (nic["NETWORK_ID"] === "$"+name){
nic["NAME"] = "NIC"+index;
$(".service_network_checkbox[data-index='"+index+"']", context).attr('checked', true).change();
$(".alias_network_checkbox[data-index='"+index+"']", context).attr('checked', true).change();
that.alias_template[String(index)] = nic;
}
});
});
$.each(that.alias_template, function(index) {
$("select[data-index="+index+"]", context).each(function() {
updateOptionsParents(this, that.nics_template, that.alias_template);
});
});
}
// copy rest of template
delete vmTemplate["NIC"];
delete vmTemplate["NIC_ALIAS"];
this.rest_template = TemplateUtils.templateToString(vmTemplate);
// update selector options for RDP
updateOptionsRDP(context, $.extend({}, that.nics_template, that.alias_template));
}
$("select[name='shutdown_action_role']", context).val(value.shutdown_action);
$("#min_vms", context).val(value.min_vms);
$("#max_vms", context).val(value.max_vms);
$("#cooldown", context).val(value.cooldown);
if (value['elasticity_policies'].length > 0 ||
value['scheduled_policies'].length > 0) {
$("div.elasticity_accordion a.accordion_advanced_toggle", context).trigger("click");
}
$("#elasticity_policies_table i.remove-tab", context).trigger("click");
$("#scheduled_policies_table i.remove-tab", context).trigger("click");
if (value['elasticity_policies']) {
$.each(value['elasticity_policies'], function(){
$("#tf_btn_elas_policies", context).click();
var td = $("#elasticity_policies_tbody tr", context).last();
$("#type" ,td).val(this['type']);
$("#type" ,td).change();
$("#adjust" ,td).val(this['adjust'] );
$("#min_adjust_step" ,td).val(this['min_adjust_step'] || "");
$("#expression" ,td).val(this.expression);
$("#period_number" ,td).val(this['period_number'] || "");
$("#period" ,td).val(this['period'] || "" );
$("#cooldown" ,td).val(this['cooldown'] || "" );
});
}
if (value['scheduled_policies']) {
$.each(value['scheduled_policies'], function(){
$("#tf_btn_sche_policies", context).click();
var td = $("#scheduled_policies_tbody tr", context).last();
$("#type", td).val(this['type']);
$("#type" ,td).change();
$("#adjust", td).val(this['adjust'] );
$("#min_adjust_step", td).val(this['min_adjust_step'] || "");
if (this['start_time']) {
$("#time_format", td).val('start_time');
$("#time", td).val(this['start_time']);
} else if (this['recurrence']) {
$("#time_format", td).val('recurrence');
$("#time", td).val(this['recurrence']);
}
});
}
}
function _refreshVMTemplate(checkedNetworks, nicToDelete) {
var that = this;
if (that.role_section && that.nics_template) {
if (nicToDelete) {
delete that.nics_template[nicToDelete];
delete that.alias_template[nicToDelete];
}
// update all NAMES and NETWORK IDs
$.each(checkedNetworks, function(index, nic) {
if (nic && that.nics_template[index]) {
if (that.nics_template[index]) {
that.nics_template[index]["NAME"] = "NIC"+index;
that.nics_template[index]["NETWORK_ID"] = "$"+nic.name;
}
else if (nic && that.alias_template[index]) {
that.alias_template[index]["NAME"] = "NIC"+index;
that.alias_template[index]["NETWORK_ID"] = "$"+nic.name;
}
}
});
var allTemplate = $.extend({}, that.nics_template, that.alias_template);
updateOptionsRDP(that.role_section, allTemplate);
$.each(that.alias_template, function(index) {
$("select[data-index="+index+"]", that.role_section).each(function() {
updateOptionsParents(this, that.nics_template, that.alias_template);
});
});
// Update remove-tabs state (unabled/disabled)
var form = $(that.role_section).closest("#createServiceTemplateFormWizard");
$("table.service_networks i.remove-tab", form).each(function() {
updateRemoveTabs(that.role_section, $(this).data("index"));
});
}
}
//----------------------------------------------------------------------------
function _removeEmptyObjects(obj){
for (var elem in obj){
var remove = false;
var value = obj[elem];
if (value instanceof Array){
if (value.length == 0)
remove = true;
else if (value.length > 0){
value = jQuery.grep(value, function (n) {
var obj_length = 0;
for (e in n)
obj_length += 1;
if (obj_length == 0)
return false;
return true;
});
if (value.length == 0)
remove = true;
}
}
else if (value instanceof Object){
var obj_length = 0;
for (e in value)
obj_length += 1;
if (obj_length == 0)
remove = true;
}else{
value = String(value);
if (value.length == 0)
remove = true;
}
if (remove)
delete obj[elem];
}
return obj;
}
});

View File

@ -0,0 +1,46 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<tr>
<td>
<select id="type" name="type">
<option value=""></option>
<option value="CHANGE">{{tr "Change"}}</option>
<option value="CARDINALITY">{{tr "Cardinality"}}</option>
<option value="PERCENTAGE_CHANGE">{{tr "Percentage"}}</option>
</select>
</td>
<td>
<input type="text" id="adjust" name="adjust"/>
</td>
<td id="min_adjust_step_td">
</td>
<td>
<input type="text" id="expression" name="expression"/>
</td>
<td>
<input type="text" id="period_number" name="period_number"/>
</td>
<td>
<input type="text" id="period" name="period"/>
</td>
<td>
<input type="text" id="cooldown" name="cooldown"/>
</td>
<td>
<a href="#"><i class="fas fa-times-circle remove-tab"></i></a>
</td>
</tr>

View File

@ -0,0 +1,50 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<thead style="background:#dfdfdf">
<tr>
<th colspan="8">
{{tr "Elasticity policies"}}
</th>
</tr>
</thead>
<thead>
<tr>
<th class="has-tip" title="{{tr "Type of adjustment."}}
{{tr "CHANGE: Add/subtract the given number of VMs."}}
{{tr "CARDINALITY: Set the cardinality to the given number."}}
{{tr "PERCENTAGE_CHANGE: Add/subtract the given percentage to the current cardinality."}}" style="width:14%">{{tr "Type"}}
</th>
<th class="has-tip" title="{{tr "Positive or negative adjustment. Its meaning depends on 'type'"}}
{{tr "CHANGE: -2, will subtract 2 VMs from the role"}}
{{tr "CARDINALITY: 8, will set cardinality to 8"}}
{{tr "PERCENTAGE_CHANGE: 20, will increment cardinality by 20%"}}" style="width:12%">{{tr "Adjust"}}
</th>
<th class="has-tip" title="{{tr "Optional parameter for PERCENTAGE_CHANGE adjustment type."}}
{{tr " If present, the policy will change the cardinality by at least the number of VMs set in this attribute."}}" style="width:9%">{{tr "Min"}}
</th>
<th class="has-tip" title="{{tr "Expression to trigger the elasticity"}}
{{tr "Example: ATT < 20"}}" style="width:30%">{{tr "Expression"}}
</th>
<th class="has-tip" title="{{tr "Number of periods that the expression must be true before the elasticity is triggered"}}" style="width:8%">#
</th>
<th class="has-tip" title="{{tr "Duration, in seconds, of each period in '# Periods'"}}" style="width:9%">{{tr "Period"}}
</th>
<th class="has-tip" title="{{tr "Cooldown period duration after a scale operation, in seconds"}}" style="width:15%">{{tr "Cooldown"}}
</th>
<th style="width:3%"></th>
</tr>
</thead>

View File

@ -0,0 +1,162 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<div class="row">
<div class="service_param service_role st_man medium-6 columns">
<label>
{{tr "Role name"}}
<input type="text" id="role_name" name="name" aria-describedby="helpText" required pattern="^\w+$"/>
<small class="form-error">{{tr "Can only contain alphanumeric and underscore characters"}}</small>
</label>
</div>
<div class="service_param service_role medium-4 columns end">
<label>
{{tr "Number of VMs"}}
<input type="number" min="0" id="cardinality" name="cardinality" value="1"/>
</label>
</div>
</div>
<div class="row" style="display: none;">
<div class="service_param service_role small-12 columns">
<label>
{{tr "VM template to create this role's VMs"}}
</label>
<br/>
{{{templatesTableHTML}}}
</div>
</div>
<div class="row" style="display: none;">
<div class="service_param service_role large-12 columns">
<table class="parent_roles dataTable">
<thead>
<tr>
<th colspan="2">
{{tr "Parent roles"}}
</th>
</tr>
</thead>
<tbody class="parent_roles_body">
</tbody>
</table>
</div>
<div class="networks_accordion large-12 columns " style="display: none;">
{{#advancedSection (tr "Role network") }}
<div class="service_param service_role">
<table class="networks_role dataTable">
<thead>
<tr>
<th colspan="4">
<label for="interfaces_role">
<i class="fas fa-lg fa-fw fa-globe off-color"/>{{tr "Network Interfaces"}}
<span class="tip">{{tr "This network interface will be configured as an ALIAS in the VM without context configuration."}}</span>
</label>
</th>
</tr>
</thead>
<tbody class="networks_role_body">
</tbody>
</table>
<div class="form-group networks_role_rdp" style="padding:0.6rem;">
<label for="rdp">RDP</label>
<select class="form-control" id="rdp">
<option value=""></option>
</select>
</div>
</div>
{{/advancedSection}}
</div>
</div>
<br>
<div class="row">
<div class="large-12 columns elasticity_accordion">
{{#advancedSection (tr "Role elasticity") }}
<div class="row">
<div class="medium-4 columns">
<label>
{{tr "Min VMs"}}
<input type="number" min="0" id="min_vms" name="min_vms" value="" />
</label>
</div>
<div class="medium-4 columns">
<label>{{tr "Max VMs"}}
<input type="number" min="0" id="max_vms" name="max_vms" value="" />
</label>
</div>
<div class="service_param service_role medium-4 columns">
<label>
{{tr "Cooldown"}}
<span class="tip">{{tr "Cooldown time after an elasticity operation (seconds)"}}</span>
<input type="number" min="0" id="cooldown" name="cooldown" value="" />
</label>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<table id="elasticity_policies_table" class="policies_table dataTable">
{{> tabs/oneflow-templates-tab/utils/role-tab/elasticity-thead}}
<tbody id="elasticity_policies_tbody">
</tbody>
<tfoot>
<tr>
<td colspan="8">
<a type="button" class="button small radius secondary small-12" id="tf_btn_elas_policies"><i class="fas fa-lg fa-plus-circle"></i></a>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<br>
<div class="row">
<div class="large-12 columns">
<table id="scheduled_policies_table" class="policies_table dataTable">
{{> tabs/oneflow-templates-tab/utils/role-tab/sched-thead}}
<tbody id="scheduled_policies_tbody">
</tbody>
<tfoot>
<tr>
<td colspan="6">
<a type="button" class="button small radius secondary small-12" id="tf_btn_sche_policies"><i class="fas fa-lg fa-plus-circle"></i></a>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
{{/advancedSection}}
</div>
</div>
<br>
<div class="row">
<div class="large-12 columns advanced_role_accordion">
{{#advancedSection (tr "Advanced role parameters") }}
<div class="row">
<div class="service_param service_role medium-6 columns">
<label for="shutdown_action_role">{{tr "VM shutdown action"}}
<span class="tip">{{tr "If it is not set, the one set for the Service will be used"}}</span>
</label>
<select name="shutdown_action_role">
<option value=""></option>
<option value="terminate">{{tr "Terminate"}}</option>
<option value="terminate-hard">{{tr "Terminate hard"}}</option>
</select>
</div>
<div class="medium-6 columns">
</div>
</div>
{{/advancedSection}}
</div>
</div>

View File

@ -0,0 +1,43 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<tr>
<td>
<select id="type" name="type">
<option value=""></option>
<option value="CHANGE">{{tr "Change"}}</option>
<option value="CARDINALITY">{{tr "Cardinality"}}</option>
<option value="PERCENTAGE_CHANGE">{{tr "Percentage"}}</option>
</select>
</td>
<td>
<input type="text" id="adjust" name="adjust"/>
</td>
<td id="min_adjust_step_td">
</td>
<td>
<select id="time_format" name="time_format">
<option value="start_time">{{tr "Start time"}}</option>
<option value="recurrence">{{tr "Recurrence"}}</option>
</select>
</td>
<td>
<input type="text" id="time" name="time"/>
</td>
<td>
<a href="#"><i class="fas fa-times-circle remove-tab"></i></a>
</td>
</tr>

View File

@ -0,0 +1,47 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2020, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<thead style="background:#dfdfdf">
<tr>
<th colspan="6">
{{tr "Scheduled policies"}}
</th>
</tr>
</thead>
<thead>
<tr>
<th class="has-tip" title="{{tr "Type of adjustment."}}
{{tr "CHANGE: Add/subtract the given number of VMs."}}
{{tr "CARDINALITY: Set the cardinality to the given number."}}
{{tr "PERCENTAGE_CHANGE: Add/subtract the given percentage to the current cardinality."}}" style="width:14%">{{tr "Type"}}
</th>
<th class="has-tip" title="{{tr "Positive or negative adjustment. Its meaning depends on 'type'"}}
{{tr "CHANGE: -2, will subtract 2 VMs from the role"}}
{{tr "CARDINALITY: 8, will set cardinality to 8"}}
{{tr "PERCENTAGE_CHANGE: 20, will increment cardinality by 20%"}}" style="width:12%">{{tr "Adjust"}}
</th>
<th class="has-tip" title="{{tr "Optional parameter for PERCENTAGE_CHANGE adjustment type. If present, the policy will change the cardinality by at least the number of VMs set in this attribute."}}" style="width:9%">{{tr "Min"}}
</th>
<th class="has-tip" title="{{tr "Recurrence: Time for recurring adjustments. Time is specified with the Unix cron syntax"}}
{{tr "Start time: Exact time for the adjustment"}}" style="width:28%">{{tr "Time format"}}
</th>
<th class="has-tip" title="{{tr "Time expression depends on the the time format selected"}}
{{tr "Recurrence: Time for recurring adjustments. Time is specified with the Unix cron syntax"}}
{{tr "Start time: Exact time for the adjustment"}}" style="width:33%">{{tr "Time expression"}}
</th>
<th style="width:3%"></th>
</tr>
</thead>

View File

@ -221,6 +221,7 @@ define(function(require) {
}
var resourceId = '' + selectedNodes[0];
window.ServiceId = resourceId;
Sunstone.runAction(that.resourceStr + '.show_to_update', resourceId);
}
}

View File

@ -64,6 +64,14 @@ get '/service/:id' do
af_format_response(resp)
end
put '/service/:id' do
client = af_build_client
resp = client.put('/service/' + params[:id], @request_body)
af_format_response(resp)
end
delete '/service/:id' do
client = af_build_client