From 549dbf41c95d7240e0f51fd16a2d001f8baa43e1 Mon Sep 17 00:00:00 2001 From: Jorge Miguel Lobo Escalona Date: Mon, 13 Jun 2022 19:10:16 +0200 Subject: [PATCH 01/43] B #5819: 2FA manipulation (#2146) --- .../src/server/routes/api/2fa/functions.js | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/fireedge/src/server/routes/api/2fa/functions.js b/src/fireedge/src/server/routes/api/2fa/functions.js index b7df8e0242..bdc792e72b 100644 --- a/src/fireedge/src/server/routes/api/2fa/functions.js +++ b/src/fireedge/src/server/routes/api/2fa/functions.js @@ -90,27 +90,30 @@ const setup = ( userData = {}, oneConnection = defaultEmptyFunction ) => { + const { user, password } = userData + if (!(user && password)) { + next() + + return + } + const { token } = params - const oneConnect = oneConnection() - getUserInfoAuthenticated(oneConnect, next, (user) => { + const oneConnect = oneConnection(user, password) + getUserInfoAuthenticated(oneConnect, next, (data) => { if ( - user && - user.USER && - user.USER.ID && - user.USER.TEMPLATE && - user.USER.TEMPLATE.SUNSTONE && - user.USER.TEMPLATE.SUNSTONE[default2FAOpennebulaTmpVar] && + Number.isInteger(parseInt(data?.USER?.ID, 10)) && + data?.USER?.TEMPLATE?.SUNSTONE?.[default2FAOpennebulaTmpVar] && token ) { - const sunstone = user.USER.TEMPLATE.SUNSTONE + const sunstone = data.USER.TEMPLATE.SUNSTONE const secret = sunstone[default2FAOpennebulaTmpVar] if (check2Fa(secret, token)) { oneConnect({ action: Actions.USER_UPDATE, parameters: [ - parseInt(user.USER.ID, 10), + parseInt(data.USER.ID, 10), generateNewResourceTemplate( - user.USER.TEMPLATE.SUNSTONE || {}, + data.USER.TEMPLATE.SUNSTONE || {}, { [default2FAOpennebulaVar]: secret }, [default2FAOpennebulaTmpVar] ), @@ -157,6 +160,13 @@ const qr = ( userData = {}, oneConnection = defaultEmptyFunction ) => { + const { user, password } = userData + if (!(user && password)) { + next() + + return + } + const secret = speakeasy.generateSecret({ length: 10, name: twoFactorAuthIssuer, @@ -168,15 +178,15 @@ const qr = ( res.locals.httpCode = httpResponse(internalServerError) next() } else { - const oneConnect = oneConnection() - getUserInfoAuthenticated(oneConnect, next, (user) => { - if (user && user.USER && user.USER.ID && user.USER.TEMPLATE) { + const oneConnect = oneConnection(user, password) + getUserInfoAuthenticated(oneConnect, next, (data) => { + if (data?.USER?.ID && data?.USER?.TEMPLATE) { oneConnect({ action: Actions.USER_UPDATE, parameters: [ - parseInt(user.USER.ID, 10), + parseInt(data.USER.ID, 10), generateNewResourceTemplate( - user.USER.TEMPLATE.SUNSTONE || {}, + data.USER.TEMPLATE.SUNSTONE || {}, { [default2FAOpennebulaTmpVar]: base32 }, [default2FAOpennebulaVar] ), @@ -228,20 +238,21 @@ const del = ( userData = {}, oneConnection = defaultEmptyFunction ) => { - const oneConnect = oneConnection() - getUserInfoAuthenticated(oneConnect, next, (user) => { - if ( - user && - user.USER && - user.USER.ID && - user.USER.TEMPLATE && - user.USER.TEMPLATE.SUNSTONE - ) { + const { user, password } = userData + if (!(user && password)) { + next() + + return + } + + const oneConnect = oneConnection(user, password) + getUserInfoAuthenticated(oneConnect, next, (data) => { + if (data?.USER?.TEMPLATE?.SUNSTONE) { oneConnect({ action: Actions.USER_UPDATE, parameters: [ - parseInt(user.USER.ID, 10), - generateNewResourceTemplate(user.USER.TEMPLATE.SUNSTONE || {}, {}, [ + parseInt(data.USER.ID, 10), + generateNewResourceTemplate(data.USER.TEMPLATE.SUNSTONE || {}, {}, [ default2FAOpennebulaTmpVar, default2FAOpennebulaVar, ]), From 61fa88d5a1ae8977e327326dde8c46f2ecacbf45 Mon Sep 17 00:00:00 2001 From: Jorge Miguel Lobo Escalona Date: Mon, 13 Jun 2022 19:20:14 +0200 Subject: [PATCH 02/43] B #5819: 2FA login FireEdge (#2147) --- src/fireedge/src/server/routes/api/auth/utils.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/fireedge/src/server/routes/api/auth/utils.js b/src/fireedge/src/server/routes/api/auth/utils.js index c5b11edf51..5541d4419a 100644 --- a/src/fireedge/src/server/routes/api/auth/utils.js +++ b/src/fireedge/src/server/routes/api/auth/utils.js @@ -252,17 +252,13 @@ const updaterResponse = (code) => { */ const validate2faAuthentication = (informationUser) => { let rtn = false - if ( - informationUser.TEMPLATE && - informationUser.TEMPLATE.SUNSTONE && - informationUser.TEMPLATE.SUNSTONE[default2FAOpennebulaVar] - ) { + if (informationUser?.TEMPLATE?.SUNSTONE?.[default2FAOpennebulaVar]) { /********************************************************* * Validate 2FA *********************************************************/ if (tfatoken.length <= 0) { - updaterResponse(httpResponse(accepted)) + updaterResponse(httpResponse(accepted, { id: informationUser?.ID })) } else { const secret = informationUser.TEMPLATE.SUNSTONE[default2FAOpennebulaVar] if (!check2Fa(secret, tfatoken)) { From 37f8b7fe983c10b6866d792bbc3753dc4b926ae3 Mon Sep 17 00:00:00 2001 From: Jorge Miguel Lobo Escalona Date: Tue, 14 Jun 2022 11:10:47 +0200 Subject: [PATCH 03/43] M #~: http code with login fails (#2149) --- .../src/server/routes/api/auth/functions.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/fireedge/src/server/routes/api/auth/functions.js b/src/fireedge/src/server/routes/api/auth/functions.js index aabbeff2c6..1e3d1bb1c1 100644 --- a/src/fireedge/src/server/routes/api/auth/functions.js +++ b/src/fireedge/src/server/routes/api/auth/functions.js @@ -60,7 +60,7 @@ const loginUser = ( if (value && value.USER && !err) { success(value) } else { - error() + error(err) } } @@ -103,11 +103,14 @@ const auth = ( } /** - * Run if no have information. + * Catch error login. + * + * @param {string} err - error. */ - const error = () => { - updaterResponse(new Map(unauthorized).toObject()) - writeInLogger(unauthorized) + const error = (err) => { + const httpCodeError = err ? internalServerError : unauthorized + updaterResponse(new Map(httpCodeError).toObject()) + writeInLogger(httpCodeError) next() } From 57be128ead5aed823e7680503e1eccd03b57b7be Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Wed, 15 Jun 2022 12:17:35 +0200 Subject: [PATCH 04/43] M #~: Minor fix to fireedge user command (#2153) --- src/fireedge/src/server/utils/constants/commands/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fireedge/src/server/utils/constants/commands/user.js b/src/fireedge/src/server/utils/constants/commands/user.js index d0172da7a2..4fb9349a51 100644 --- a/src/fireedge/src/server/utils/constants/commands/user.js +++ b/src/fireedge/src/server/utils/constants/commands/user.js @@ -61,7 +61,7 @@ module.exports = { params: { username: { from: postBody, - default: 0, + default: '', }, password: { from: postBody, From ba5de1e3006d576f7a8aa4c7ab46807c2fe7fca0 Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Wed, 15 Jun 2022 14:35:10 +0200 Subject: [PATCH 05/43] F #5814: Add retry method when auth error (#2151) --- src/flow/etc/oneflow-server.conf | 3 + src/flow/lib/LifeCycleManager.rb | 647 +++++++++++++++++-------------- src/flow/oneflow-server.rb | 5 +- 3 files changed, 372 insertions(+), 283 deletions(-) diff --git a/src/flow/etc/oneflow-server.conf b/src/flow/etc/oneflow-server.conf index e56504e711..ed695285a8 100644 --- a/src/flow/etc/oneflow-server.conf +++ b/src/flow/etc/oneflow-server.conf @@ -41,6 +41,9 @@ # Defaults ################################################################################ +# Default retries in case of aborting call due to authentication issue +:retries: 5 + # Default cooldown period after a scale operation, in seconds :default_cooldown: 300 diff --git a/src/flow/lib/LifeCycleManager.rb b/src/flow/lib/LifeCycleManager.rb index 54eb312ce0..8b70b0c264 100644 --- a/src/flow/lib/LifeCycleManager.rb +++ b/src/flow/lib/LifeCycleManager.rb @@ -59,10 +59,11 @@ class ServiceLCM :release_cb ] - def initialize(client, concurrency, cloud_auth) + def initialize(client, concurrency, cloud_auth, retries) @cloud_auth = cloud_auth @am = ActionManager.new(concurrency, true) @srv_pool = ServicePool.new(@cloud_auth, nil) + @retries = retries em_conf = { :cloud_auth => @cloud_auth, @@ -672,6 +673,34 @@ class ServiceLCM private + # Retry on authentication error + # + # @param client [OpenNebula::Client] Client to perform operation + # @param &block Code block to execute + def retry_op(client, &block) + finished = false + retries = 0 + rc = nil + + until finished + rc = block.call(client) + + if OpenNebula.is_error?(rc) && rc.errno != 256 + # There is an error different from authentication + finished = true + elsif !OpenNebula.is_error?(rc) || retries > @retries + # There is no error or the retries limit has been reached + finished = true + else + # Error is 256, reset the client to renew the token + client = nil + retries += 1 + end + end + + rc + end + ############################################################################ # Callbacks ############################################################################ @@ -679,38 +708,40 @@ class ServiceLCM def deploy_cb(client, service_id, role_name, nodes) undeploy = false - rc = @srv_pool.get(service_id, client) do |service| - service.roles[role_name].set_state(Role::STATE['RUNNING']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.roles[role_name].set_state(Role::STATE['RUNNING']) - service.roles[role_name].nodes.delete_if do |node| - if nodes[node] && service.roles[role_name].cardinalitty > 0 - service.roles[role_name].cardinality -= 1 + service.roles[role_name].nodes.delete_if do |node| + if nodes[node] && service.roles[role_name].cardinalitty > 0 + service.roles[role_name].cardinality -= 1 + end + + nodes[node] end - nodes[node] + # If the role has 0 nodes, deleteƒ role + undeploy = service.check_role(service.roles[role_name]) + + if service.all_roles_running? + service.set_state(Service::STATE['RUNNING']) + elsif service.strategy == 'straight' + set_deploy_strategy(service) + + deploy_roles(c, + service.roles_deploy, + 'DEPLOYING', + 'FAILED_DEPLOYING', + :wait_deploy_action, + service.report_ready?) + end + + rc = service.update + + return rc if OpenNebula.is_error?(rc) + + @wd.add_service(service) if service.all_roles_running? end - - # If the role has 0 nodes, deleteƒ role - undeploy = service.check_role(service.roles[role_name]) - - if service.all_roles_running? - service.set_state(Service::STATE['RUNNING']) - elsif service.strategy == 'straight' - set_deploy_strategy(service) - - deploy_roles(client, - service.roles_deploy, - 'DEPLOYING', - 'FAILED_DEPLOYING', - :wait_deploy_action, - service.report_ready?) - end - - rc = service.update - - return rc if OpenNebula.is_error?(rc) - - @wd.add_service(service) if service.all_roles_running? end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -723,14 +754,18 @@ class ServiceLCM end def deploy_failure_cb(client, service_id, role_name) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_DEPLOYING']) - service.roles[role_name].set_state(Role::STATE['FAILED_DEPLOYING']) + service.set_state(Service::STATE['FAILED_DEPLOYING']) + service.roles[role_name].set_state( + Role::STATE['FAILED_DEPLOYING'] + ) - service.update + service.update + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -741,12 +776,14 @@ class ServiceLCM end def deploy_nets_failure_cb(client, service_id) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_DEPLOYING_NETS']) - service.update + service.set_state(Service::STATE['FAILED_DEPLOYING_NETS']) + service.update + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -755,36 +792,38 @@ class ServiceLCM def undeploy_cb(client, service_id, role_name, nodes) undeploy_nets = false - rc = @srv_pool.get(service_id, client) do |service| - service.roles[role_name].set_state(Role::STATE['DONE']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.roles[role_name].set_state(Role::STATE['DONE']) - service.roles[role_name].nodes.delete_if do |node| - !nodes[:failure].include?(node['deploy_id']) && - nodes[:successful].include?(node['deploy_id']) - end - - if service.all_roles_done? - rc = service.delete_networks - - if rc && !rc.empty? - Log.info LOG_COMP, 'Error trying to delete '\ - "Virtual Networks #{rc}" + service.roles[role_name].nodes.delete_if do |node| + !nodes[:failure].include?(node['deploy_id']) && + nodes[:successful].include?(node['deploy_id']) end - undeploy_nets = true + if service.all_roles_done? + rc = service.delete_networks - break - elsif service.strategy == 'straight' - set_deploy_strategy(service) + if rc && !rc.empty? + Log.info LOG_COMP, 'Error trying to delete '\ + "Virtual Networks #{rc}" + end - undeploy_roles(client, - service.roles_shutdown, - 'UNDEPLOYING', - 'FAILED_UNDEPLOYING', - :wait_undeploy_action) + undeploy_nets = true + + break + elsif service.strategy == 'straight' + set_deploy_strategy(service) + + undeploy_roles(c, + service.roles_shutdown, + 'UNDEPLOYING', + 'FAILED_UNDEPLOYING', + :wait_undeploy_action) + end + + service.update end - - service.update end undeploy_nets_action(client, service_id) if undeploy_nets @@ -793,128 +832,149 @@ class ServiceLCM end def undeploy_nets_cb(client, service_id) - rc = @srv_pool.get(service_id, client) do |service| - service.delete + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.delete + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def undeploy_nets_failure_cb(client, service_id) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_UNDEPLOYING_NETS']) - service.update + service.set_state(Service::STATE['FAILED_UNDEPLOYING_NETS']) + service.update + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def undeploy_failure_cb(client, service_id, role_name, nodes) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_UNDEPLOYING']) - service.roles[role_name] - .set_state(Role::STATE['FAILED_UNDEPLOYING']) + service.set_state(Service::STATE['FAILED_UNDEPLOYING']) + service.roles[role_name].set_state( + Role::STATE['FAILED_UNDEPLOYING'] + ) - service.roles[role_name].nodes.delete_if do |node| - !nodes[:failure].include?(node['deploy_id']) && - nodes[:successful].include?(node['deploy_id']) + service.roles[role_name].nodes.delete_if do |node| + !nodes[:failure].include?(node['deploy_id']) && + nodes[:successful].include?(node['deploy_id']) + end + + service.update end - - service.update end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def scaleup_cb(client, service_id, role_name, nodes) - rc = @srv_pool.get(service_id, client) do |service| - service.roles[role_name].nodes.delete_if do |node| - if nodes[node] && service.roles[role_name].cardinalitty > 0 - service.roles[role_name].cardinality -= 1 + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.roles[role_name].nodes.delete_if do |node| + if nodes[node] && service.roles[role_name].cardinalitty > 0 + service.roles[role_name].cardinality -= 1 + end + + nodes[node] end - nodes[node] + service.set_state(Service::STATE['COOLDOWN']) + service.roles[role_name].set_state(Role::STATE['COOLDOWN']) + + @event_manager.trigger_action( + :wait_cooldown_action, + service.id, + c, + service.id, + role_name, + service.roles[role_name].cooldown + ) + + service.roles[role_name].clean_scale_way + + service.update end - - service.set_state(Service::STATE['COOLDOWN']) - service.roles[role_name].set_state(Role::STATE['COOLDOWN']) - - @event_manager.trigger_action(:wait_cooldown_action, - service.id, - client, - service.id, - role_name, - service.roles[role_name].cooldown) - - service.roles[role_name].clean_scale_way - - service.update end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def scaledown_cb(client, service_id, role_name, nodes) - rc = @srv_pool.get(service_id, client) do |service| - service.set_state(Service::STATE['COOLDOWN']) - service.roles[role_name].set_state(Role::STATE['COOLDOWN']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.set_state(Service::STATE['COOLDOWN']) + service.roles[role_name].set_state(Role::STATE['COOLDOWN']) - service.roles[role_name].nodes.delete_if do |node| - !nodes[:failure].include?(node['deploy_id']) && - nodes[:successful].include?(node['deploy_id']) + service.roles[role_name].nodes.delete_if do |node| + !nodes[:failure].include?(node['deploy_id']) && + nodes[:successful].include?(node['deploy_id']) + end + + @event_manager.trigger_action( + :wait_cooldown_action, + service.id, + c, + service.id, + role_name, + service.roles[role_name].cooldown + ) + + service.roles[role_name].clean_scale_way + + service.update end - - @event_manager.trigger_action(:wait_cooldown_action, - service.id, - client, - service.id, - role_name, - service.roles[role_name].cooldown) - - service.roles[role_name].clean_scale_way - - service.update end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def scaleup_failure_cb(client, service_id, role_name) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_SCALING']) - service.roles[role_name].set_state(Role::STATE['FAILED_SCALING']) + service.set_state(Service::STATE['FAILED_SCALING']) + service.roles[role_name].set_state( + Role::STATE['FAILED_SCALING'] + ) - service.update + service.update + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def scaledown_failure_cb(client, service_id, role_name, nodes) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - role = service.roles[role_name] + role = service.roles[role_name] - service.set_state(Service::STATE['FAILED_SCALING']) - role.set_state(Role::STATE['FAILED_SCALING']) + service.set_state(Service::STATE['FAILED_SCALING']) + role.set_state(Role::STATE['FAILED_SCALING']) - role.nodes.delete_if do |node| - !nodes[:failure].include?(node['deploy_id']) && - nodes[:successful].include?(node['deploy_id']) + role.nodes.delete_if do |node| + !nodes[:failure].include?(node['deploy_id']) && + nodes[:successful].include?(node['deploy_id']) + end + + service.update end - - service.update end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -923,16 +983,18 @@ class ServiceLCM def cooldown_cb(client, service_id, role_name) undeploy = false - rc = @srv_pool.get(service_id, client) do |service| - service.set_state(Service::STATE['RUNNING']) - service.roles[role_name].set_state(Role::STATE['RUNNING']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.set_state(Service::STATE['RUNNING']) + service.roles[role_name].set_state(Role::STATE['RUNNING']) - service.update + service.update - # If the role has 0 nodes, delete role - undeploy = service.check_role(service.roles[role_name]) + # If the role has 0 nodes, delete role + undeploy = service.check_role(service.roles[role_name]) - @wd.add_service(service) + @wd.add_service(service) + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -945,49 +1007,10 @@ class ServiceLCM end def add_cb(client, service_id, role_name, _) - rc = @srv_pool.get(service_id, client) do |service| - service.roles[role_name].set_state(Role::STATE['RUNNING']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.roles[role_name].set_state(Role::STATE['RUNNING']) - service.set_state(Service::STATE['RUNNING']) - - rc = service.update - - return rc if OpenNebula.is_error?(rc) - - @wd.add_service(service) if service.all_roles_running? - end - - Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) - end - - def add_failure_cb(client, service_id, role_name) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) - - service.set_state(Service::STATE['FAILED_DEPLOYING']) - service.roles[role_name].set_state(Role::STATE['FAILED_DEPLOYING']) - - service.update - end - - Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) - end - - def remove_cb(client, service_id, role_name, _) - rc = @srv_pool.get(service_id, client) do |service| - service.remove_role(role_name) - - if service.all_roles_done? - rc = service.delete_networks - - if rc && !rc.empty? - Log.info LOG_COMP, 'Error trying to delete '\ - "Virtual Networks #{rc}" - end - - service.delete - else service.set_state(Service::STATE['RUNNING']) rc = service.update @@ -1001,49 +1024,101 @@ class ServiceLCM Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end - def remove_failure_cb(client, service_id, role_name, nodes) - rc = @srv_pool.get(service_id, client) do |service| - # stop actions for the service if deploy fails - @event_manager.cancel_action(service_id) + def add_failure_cb(client, service_id, role_name) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) - service.set_state(Service::STATE['FAILED_UNDEPLOYING']) - service.roles[role_name] - .set_state(Role::STATE['FAILED_UNDEPLOYING']) + service.set_state(Service::STATE['FAILED_DEPLOYING']) + service.roles[role_name].set_state(Role::STATE['FAILED_DEPLOYING']) - service.roles[role_name].nodes.delete_if do |node| - !nodes[:failure].include?(node['deploy_id']) && - nodes[:successful].include?(node['deploy_id']) + service.update end + end - service.update + Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) + end + + def remove_cb(client, service_id, role_name, _) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.remove_role(role_name) + + if service.all_roles_done? + rc = service.delete_networks + + if rc && !rc.empty? + Log.info LOG_COMP, 'Error trying to delete '\ + "Virtual Networks #{rc}" + end + + service.delete + else + service.set_state(Service::STATE['RUNNING']) + + rc = service.update + + return rc if OpenNebula.is_error?(rc) + + @wd.add_service(service) if service.all_roles_running? + end + end + end + + Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) + end + + def remove_failure_cb(client, service_id, role_name, nodes) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + # stop actions for the service if deploy fails + @event_manager.cancel_action(service_id) + + service.set_state(Service::STATE['FAILED_UNDEPLOYING']) + service.roles[role_name].set_state( + Role::STATE['FAILED_UNDEPLOYING'] + ) + + service.roles[role_name].nodes.delete_if do |node| + !nodes[:failure].include?(node['deploy_id']) && + nodes[:successful].include?(node['deploy_id']) + end + + service.update + end end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) end def hold_cb(client, service_id, role_name) - rc = @srv_pool.get(service_id, client) do |service| - if service.roles[role_name].state != Role::STATE['HOLD'] - service.roles[role_name].set_state(Role::STATE['HOLD']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + if service.roles[role_name].state != Role::STATE['HOLD'] + service.roles[role_name].set_state(Role::STATE['HOLD']) + end + + if service.all_roles_hold? && + service.state != Service::STATE['HOLD'] + service.set_state(Service::STATE['HOLD']) + elsif service.strategy == 'straight' + set_deploy_strategy(service) + + deploy_roles( + c, + service.roles_hold, + 'DEPLOYING', + 'FAILED_DEPLOYING', + :wait_deploy_action, + service.report_ready? + ) + end + + rc = service.update + + return rc if OpenNebula.is_error?(rc) end - - if service.all_roles_hold? && - service.state != Service::STATE['HOLD'] - service.set_state(Service::STATE['HOLD']) - elsif service.strategy == 'straight' - set_deploy_strategy(service) - - deploy_roles(client, - service.roles_hold, - 'DEPLOYING', - 'FAILED_DEPLOYING', - :wait_deploy_action, - service.report_ready?) - end - - rc = service.update - - return rc if OpenNebula.is_error?(rc) end Log.error 'WD', rc.message if OpenNebula.is_error?(rc) @@ -1052,38 +1127,42 @@ class ServiceLCM def release_cb(client, service_id, role_name, nodes) undeploy = false - rc = @srv_pool.get(service_id, client) do |service| - service.roles[role_name].set_state(Role::STATE['RUNNING']) + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + service.roles[role_name].set_state(Role::STATE['RUNNING']) - service.roles[role_name].nodes.delete_if do |node| - if nodes[node] && service.roles[role_name].cardinalitty > 0 - service.roles[role_name].cardinality -= 1 + service.roles[role_name].nodes.delete_if do |node| + if nodes[node] && service.roles[role_name].cardinalitty > 0 + service.roles[role_name].cardinality -= 1 + end + + nodes[node] end - nodes[node] + # If the role has 0 nodes, delete role + undeploy = service.check_role(service.roles[role_name]) + + if service.all_roles_running? + service.set_state(Service::STATE['RUNNING']) + elsif service.strategy == 'straight' + set_deploy_strategy(service) + + release_roles( + c, + service.roles_release, + 'DEPLOYING', + 'FAILED_DEPLOYING', + :wait_deploy_action, + service.report_ready? + ) + end + + rc = service.update + + return rc if OpenNebula.is_error?(rc) + + @wd.add_service(service) if service.all_roles_running? end - - # If the role has 0 nodes, delete role - undeploy = service.check_role(service.roles[role_name]) - - if service.all_roles_running? - service.set_state(Service::STATE['RUNNING']) - elsif service.strategy == 'straight' - set_deploy_strategy(service) - - release_roles(client, - service.roles_release, - 'DEPLOYING', - 'FAILED_DEPLOYING', - :wait_deploy_action, - service.report_ready?) - end - - rc = service.update - - return rc if OpenNebula.is_error?(rc) - - @wd.add_service(service) if service.all_roles_running? end Log.error LOG_COMP, rc.message if OpenNebula.is_error?(rc) @@ -1100,16 +1179,18 @@ class ServiceLCM ############################################################################ def error_wd_cb(client, service_id, role_name, _node) - rc = @srv_pool.get(service_id, client) do |service| - if service.state != Service::STATE['WARNING'] - service.set_state(Service::STATE['WARNING']) - end + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + if service.state != Service::STATE['WARNING'] + service.set_state(Service::STATE['WARNING']) + end - if service.roles[role_name].state != Role::STATE['WARNING'] - service.roles[role_name].set_state(Role::STATE['WARNING']) - end + if service.roles[role_name].state != Role::STATE['WARNING'] + service.roles[role_name].set_state(Role::STATE['WARNING']) + end - service.update + service.update + end end Log.error 'WD', rc.message if OpenNebula.is_error?(rc) @@ -1118,29 +1199,31 @@ class ServiceLCM def done_wd_cb(client, service_id, role_name, node) undeploy = false - rc = @srv_pool.get(service_id, client) do |service| - role = service.roles[role_name] + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + role = service.roles[role_name] - next unless role + next unless role - cardinality = role.cardinality - 1 + cardinality = role.cardinality - 1 - next unless role.nodes.find {|n| n['deploy_id'] == node } + next unless role.nodes.find {|n| n['deploy_id'] == node } - # just update if the cardinality is positive - set_cardinality(role, cardinality, true) if cardinality >= 0 + # just update if the cardinality is positive + set_cardinality(role, cardinality, true) if cardinality >= 0 - role.nodes.delete_if {|n| n['deploy_id'] == node } + role.nodes.delete_if {|n| n['deploy_id'] == node } - # If the role has 0 nodes, delete role - undeploy = service.check_role(role) + # If the role has 0 nodes, delete role + undeploy = service.check_role(role) - service.update + service.update - Log.info 'WD', - "Node #{node} is done, " \ - "updating service #{service_id}:#{role_name} " \ - "cardinality to #{cardinality}" + Log.info 'WD', + "Node #{node} is done, " \ + "updating service #{service_id}:#{role_name} " \ + "cardinality to #{cardinality}" + end end Log.error 'WD', rc.message if OpenNebula.is_error?(rc) @@ -1155,22 +1238,24 @@ class ServiceLCM def running_wd_cb(client, service_id, role_name, _node) undeploy = false - rc = @srv_pool.get(service_id, client) do |service| - role = service.roles[role_name] + rc = retry_op(client) do |c| + @srv_pool.get(service_id, c) do |service| + role = service.roles[role_name] - if service.roles[role_name].state != Role::STATE['RUNNING'] - service.roles[role_name].set_state(Role::STATE['RUNNING']) + if service.roles[role_name].state != Role::STATE['RUNNING'] + service.roles[role_name].set_state(Role::STATE['RUNNING']) + end + + if service.all_roles_running? && + service.state != Service::STATE['RUNNING'] + service.set_state(Service::STATE['RUNNING']) + end + + # If the role has 0 nodes, delete role + undeploy = service.check_role(role) + + service.update end - - if service.all_roles_running? && - service.state != Service::STATE['RUNNING'] - service.set_state(Service::STATE['RUNNING']) - end - - # If the role has 0 nodes, delete role - undeploy = service.check_role(role) - - service.update end Log.error 'WD', rc.message if OpenNebula.is_error?(rc) diff --git a/src/flow/oneflow-server.rb b/src/flow/oneflow-server.rb index 28bb1eb330..d11f4f77c0 100644 --- a/src/flow/oneflow-server.rb +++ b/src/flow/oneflow-server.rb @@ -102,8 +102,9 @@ conf[:action_period] ||= 60 conf[:vm_name_template] ||= DEFAULT_VM_NAME_TEMPLATE conf[:wait_timeout] ||= 30 conf[:concurrency] ||= 10 -conf[:auth] = 'opennebula' +conf[:auth] = 'opennebula' conf[:page_size] ||= 10 +conf[:retries] ||= 5 set :bind, conf[:host] set :port, conf[:port] @@ -210,7 +211,7 @@ GENERAL_EC = 500 # general error ############################################################################## # TODO: make thread number configurable? -lcm = ServiceLCM.new(@client, conf[:concurrency], cloud_auth) +lcm = ServiceLCM.new(@client, conf[:concurrency], cloud_auth, conf[:retries]) ############################################################################## # Service From 02db5554026ea283e0ad5ec48bb694a5a76eba5a Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Wed, 15 Jun 2022 16:28:54 +0200 Subject: [PATCH 06/43] L #-: fix linting typo (#2156) --- src/flow/lib/LifeCycleManager.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/flow/lib/LifeCycleManager.rb b/src/flow/lib/LifeCycleManager.rb index 8b70b0c264..9431b9697e 100644 --- a/src/flow/lib/LifeCycleManager.rb +++ b/src/flow/lib/LifeCycleManager.rb @@ -1031,7 +1031,9 @@ class ServiceLCM @event_manager.cancel_action(service_id) service.set_state(Service::STATE['FAILED_DEPLOYING']) - service.roles[role_name].set_state(Role::STATE['FAILED_DEPLOYING']) + service.roles[role_name].set_state( + Role::STATE['FAILED_DEPLOYING'] + ) service.update end From 1e8baa5e8350b28a2a502124c8fb888deb43e85d Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Wed, 15 Jun 2022 16:52:39 +0200 Subject: [PATCH 07/43] M #~: Add missed marketplace app actions (#2154) --- src/fireedge/src/client/constants/actions.js | 1 - src/fireedge/src/client/constants/group.js | 2 -- src/fireedge/src/client/constants/host.js | 1 - src/fireedge/src/client/constants/marketplaceApp.js | 12 ++++++++++-- src/fireedge/src/client/constants/user.js | 1 - src/fireedge/src/client/constants/vm.js | 1 - src/fireedge/src/client/constants/vmTemplate.js | 1 - 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/fireedge/src/client/constants/actions.js b/src/fireedge/src/client/constants/actions.js index e4fa180dfc..7583b148da 100644 --- a/src/fireedge/src/client/constants/actions.js +++ b/src/fireedge/src/client/constants/actions.js @@ -15,7 +15,6 @@ * ------------------------------------------------------------------------- */ // INFORMATION -export const REFRESH = 'refresh' export const RENAME = 'rename' // ATTRIBUTES diff --git a/src/fireedge/src/client/constants/group.js b/src/fireedge/src/client/constants/group.js index d226f2cb90..adae2f8eef 100644 --- a/src/fireedge/src/client/constants/group.js +++ b/src/fireedge/src/client/constants/group.js @@ -15,7 +15,6 @@ * ------------------------------------------------------------------------- */ // eslint-disable-next-line prettier/prettier, no-unused-vars import { VmQuota, NetworkQuota, DatastoreQuota, ImageQuota } from 'client/constants/quota' -import * as ACTIONS from 'client/constants/actions' /** * @typedef Group @@ -37,7 +36,6 @@ import * as ACTIONS from 'client/constants/actions' */ export const GROUP_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', UPDATE_DIALOG: 'update_dialog', QUOTAS_DIALOG: 'quotas_dialog', diff --git a/src/fireedge/src/client/constants/host.js b/src/fireedge/src/client/constants/host.js index 08dfa5023a..8d19e1e44b 100644 --- a/src/fireedge/src/client/constants/host.js +++ b/src/fireedge/src/client/constants/host.js @@ -174,7 +174,6 @@ export const HOST_STATES = [ /** @enum {string} Host actions */ export const HOST_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', RENAME: ACTIONS.RENAME, CHANGE_CLUSTER: 'change_cluster', diff --git a/src/fireedge/src/client/constants/marketplaceApp.js b/src/fireedge/src/client/constants/marketplaceApp.js index 749a9322f2..aa8d8e7ee7 100644 --- a/src/fireedge/src/client/constants/marketplaceApp.js +++ b/src/fireedge/src/client/constants/marketplaceApp.js @@ -49,9 +49,17 @@ import * as ACTIONS from 'client/constants/actions' /** @enum {string} Marketplace App actions */ export const MARKETPLACE_APP_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', - RENAME: ACTIONS.RENAME, EXPORT: 'export', DOWNLOAD: 'download', + ENABLE: 'enable', + DISABLE: 'disable', + DELETE: 'delete', + EDIT_LABELS: 'edit_labels', + + // INFORMATION + RENAME: ACTIONS.RENAME, + CHANGE_MODE: ACTIONS.CHANGE_MODE, + CHANGE_OWNER: ACTIONS.CHANGE_OWNER, + CHANGE_GROUP: ACTIONS.CHANGE_GROUP, } diff --git a/src/fireedge/src/client/constants/user.js b/src/fireedge/src/client/constants/user.js index 6eeded2868..fbc5a17228 100644 --- a/src/fireedge/src/client/constants/user.js +++ b/src/fireedge/src/client/constants/user.js @@ -50,7 +50,6 @@ import * as ACTIONS from 'client/constants/actions' */ export const USER_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', QUOTAS_DIALOG: 'quotas_dialog', GROUPS_DIALOG: 'groups_dialog', diff --git a/src/fireedge/src/client/constants/vm.js b/src/fireedge/src/client/constants/vm.js index fec6a63cc6..bf6704a854 100644 --- a/src/fireedge/src/client/constants/vm.js +++ b/src/fireedge/src/client/constants/vm.js @@ -713,7 +713,6 @@ export const VM_LCM_STATES = [ /** @enum {string} Virtual machine actions */ export const VM_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', CREATE_APP_DIALOG: 'create_app_dialog', DEPLOY: 'deploy', diff --git a/src/fireedge/src/client/constants/vmTemplate.js b/src/fireedge/src/client/constants/vmTemplate.js index b15cabb49f..9091102c4c 100644 --- a/src/fireedge/src/client/constants/vmTemplate.js +++ b/src/fireedge/src/client/constants/vmTemplate.js @@ -45,7 +45,6 @@ import { Permissions, LockInfo } from 'client/constants/common' */ export const VM_TEMPLATE_ACTIONS = { - REFRESH: ACTIONS.REFRESH, CREATE_DIALOG: 'create_dialog', IMPORT_DIALOG: 'import_dialog', UPDATE_DIALOG: 'update_dialog', From 8d9a8ed9653e89a30779b5d36efc13febc537672 Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Wed, 15 Jun 2022 18:52:12 +0200 Subject: [PATCH 08/43] M #~: Add marketplace app tab file to users (#2155) --- install.sh | 7 +- .../sunstone/admin/marketplace-app-tab.yaml | 1 + .../sunstone/user/marketplace-app-tab.yaml | 76 +++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/fireedge/etc/sunstone/user/marketplace-app-tab.yaml diff --git a/install.sh b/install.sh index e4df647456..ae6da93af1 100755 --- a/install.sh +++ b/install.sh @@ -2899,12 +2899,13 @@ FIREEDGE_SUNSTONE_ETC="src/fireedge/etc/sunstone/sunstone-server.conf \ src/fireedge/etc/sunstone/sunstone-views.yaml" FIREEDGE_SUNSTONE_ETC_VIEW_ADMIN="src/fireedge/etc/sunstone/admin/vm-tab.yaml \ + src/fireedge/etc/sunstone/admin/vm-template-tab.yaml \ src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml \ - src/fireedge/etc/sunstone/admin/host-tab.yaml \ - src/fireedge/etc/sunstone/admin/vm-template-tab.yaml" + src/fireedge/etc/sunstone/admin/host-tab.yaml" FIREEDGE_SUNSTONE_ETC_VIEW_USER="src/fireedge/etc/sunstone/user/vm-tab.yaml \ - src/fireedge/etc/sunstone/user/vm-template-tab.yaml" + src/fireedge/etc/sunstone/user/vm-template-tab.yaml \ + src/fireedge/etc/sunstone/user/marketplace-app-tab.yaml" #----------------------------------------------------------------------------- # OneGate files diff --git a/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml b/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml index 28606ecede..38ff64cfea 100644 --- a/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml +++ b/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml @@ -32,6 +32,7 @@ actions: enable: true disable: true delete: true + edit_labels: true # Filters - List of criteria to filter the resources diff --git a/src/fireedge/etc/sunstone/user/marketplace-app-tab.yaml b/src/fireedge/etc/sunstone/user/marketplace-app-tab.yaml new file mode 100644 index 0000000000..38ff64cfea --- /dev/null +++ b/src/fireedge/etc/sunstone/user/marketplace-app-tab.yaml @@ -0,0 +1,76 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2022, 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. # +#--------------------------------------------------------------------------- # + +--- +# This file describes the information and actions available in the App tab + +# Resource + +resource_name: "MARKETPLACE-APP" + +# Actions - Which buttons are visible to operate over the resources + +actions: + create_dialog: true + export: true + download: true + chown: true + chgrp: true + enable: true + disable: true + delete: true + edit_labels: true + +# Filters - List of criteria to filter the resources + +filters: + label: true + owner: true + group: true + state: true + type: true + marketplace: true + zone: true + +# Info Tabs - Which info tabs are used to show extended information + +info-tabs: + + info: + enabled: true + information_panel: + enabled: true + actions: + rename: true + permissions_panel: + enabled: true + actions: + chmod: true + ownership_panel: + enabled: true + actions: + chown: true + chgrp: true + attributes_panel: + enabled: true + actions: + copy: true + add: true + edit: true + delete: true + + template: + enabled: true From b1917ce23a3fa592e3057f6447f24feef21dea0a Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Wed, 1 Jun 2022 23:19:00 +0200 Subject: [PATCH 09/43] B #5856: Fix sunstone charters times on non-relative actions Signed-off-by: Kristian Feldsam (cherry picked from commit 559801d08bdbeb49ae2ed7051a992eca8fde45d8) --- .../public/app/tabs/oneflow-services-tab/datatable.js | 11 +++++++++-- .../public/app/tabs/vms-tab/utils/datatable-common.js | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js index e47242d363..c613d54a23 100644 --- a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js +++ b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js @@ -205,8 +205,15 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - if(checkTime(startTime, action.TIME)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:action.TIME, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + if(action.TIME.startsWith("+")){ + endTime = action.TIME; + } else { + endTime = action.TIME - startTime; + endTime = endTime.toString(); + } + + if(checkTime(startTime, endTime)){ + rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && diff --git a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js index 82b89923f7..9fb7fcbb72 100644 --- a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js +++ b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js @@ -173,8 +173,15 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - if(checkTime(element.STIME, action.TIME)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:action.TIME, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + if(action.TIME.startsWith("+")){ + endTime = action.TIME; + } else { + endTime = action.TIME - element.STIME; + endTime = endTime.toString(); + } + + if(checkTime(element.STIME, endTime)){ + rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && From 67582dc9906ca1daab003d8d45c8bedd6a7a982d Mon Sep 17 00:00:00 2001 From: Jorge Lobo Date: Thu, 16 Jun 2022 09:17:55 +0200 Subject: [PATCH 10/43] B #5856: Fix sunstone charters times on non-relative actions Signed-off-by: Jorge Lobo (cherry picked from commit 99cca53e10496f2da4ea5e84766641f7a9569e95) --- .../tabs/oneflow-services-tab/datatable.js | 11 +++----- .../tabs/vms-tab/utils/datatable-common.js | 27 ++++++++----------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js index c613d54a23..cf7b507b39 100644 --- a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js +++ b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js @@ -184,7 +184,7 @@ define(function(require) { function leasesClock(element){ var rtn = ""; - // The charter info is pulled from the schedule action of the first VM of the first role + // The charter info is pulled from the schedule action of the first VM of the first role // (element.TEMPLATE.BODY.roles[0].vm_template_contents.SCHED_ACTION) if(element && element.TEMPLATE && element.TEMPLATE.BODY && element.TEMPLATE.BODY.start_time && element.TEMPLATE.BODY.roles){ var startTime = element.TEMPLATE.BODY.start_time; @@ -205,15 +205,10 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - if(action.TIME.startsWith("+")){ - endTime = action.TIME; - } else { - endTime = action.TIME - startTime; - endTime = endTime.toString(); - } + var endTime = action.TIME.startsWith("+")? action.TIME : action.TIME - startTime; if(checkTime(startTime, endTime)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:endTime.toString(), data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && diff --git a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js index 9fb7fcbb72..4238cb2632 100644 --- a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js +++ b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js @@ -16,14 +16,14 @@ define(function(require) { - var Humanize = require('utils/humanize'); - var LabelsUtils = require('utils/labels/utils'); - var Locale = require('utils/locale'); - var OpenNebulaVM = require('opennebula/vm'); + var Humanize = require("utils/humanize"); + var LabelsUtils = require("utils/labels/utils"); + var Locale = require("utils/locale"); + var OpenNebulaVM = require("opennebula/vm"); var ScheduleActions = require("utils/schedule_action"); - var Status = require('utils/status'); - var TemplateUtils = require('utils/template-utils'); - var VMRemoteActions = require('utils/remote-actions'); + var Status = require("utils/status"); + var TemplateUtils = require("utils/template-utils"); + var VMRemoteActions = require("utils/remote-actions"); var RESOURCE = "VM"; var XML_ROOT = "VM"; @@ -57,7 +57,7 @@ define(function(require) { var rtn = false; if (startTime && addedEndTime) { - var regexNumber = new RegExp('[0-9]*$','gm'); + var regexNumber = new RegExp("[0-9]*$","gm"); var date = parseInt(startTime,10); var added = parseInt(addedEndTime.match(regexNumber)[0],10); @@ -114,7 +114,7 @@ define(function(require) { "padding":"8px", "z-index":"1", "min-width":"8rem", - "font-family": '"Lato","Helvetica Neue",Helvetica,Roboto,Arial,sans-serif', + "font-family": "\"Lato\",\"Helvetica Neue\",Helvetica,Roboto,Arial,sans-serif", "color":"#000", "font-weight": "bold" }; @@ -173,15 +173,10 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - if(action.TIME.startsWith("+")){ - endTime = action.TIME; - } else { - endTime = action.TIME - element.STIME; - endTime = endTime.toString(); - } + var endTime = action.TIME.startsWith("+")? action.TIME : action.TIME - startTime; if(checkTime(element.STIME, endTime)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:endTime.toString(), data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && From fdd1e485124d16d23528dbc79c1fef667a6966d4 Mon Sep 17 00:00:00 2001 From: Jorge Lobo Date: Thu, 16 Jun 2022 09:25:38 +0200 Subject: [PATCH 11/43] B #5856: Fix sunstone charters times on non-relative actions (cherry picked from commit c63fa9eab7b7db1d650b66b7153ac72046d1980b) --- .../public/app/tabs/oneflow-services-tab/datatable.js | 4 ++-- .../public/app/tabs/vms-tab/utils/datatable-common.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js index cf7b507b39..985425d7cf 100644 --- a/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js +++ b/src/sunstone/public/app/tabs/oneflow-services-tab/datatable.js @@ -205,10 +205,10 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - var endTime = action.TIME.startsWith("+")? action.TIME : action.TIME - startTime; + var endTime = (action.TIME.startsWith("+")? action.TIME : action.TIME - startTime).toString(); if(checkTime(startTime, endTime)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:endTime.toString(), data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + rtn = $("",{class:"describeCharter fa fa-clock",data_start:startTime, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && diff --git a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js index 4238cb2632..9dfc0460ae 100644 --- a/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js +++ b/src/sunstone/public/app/tabs/vms-tab/utils/datatable-common.js @@ -173,10 +173,10 @@ define(function(require) { !isNaN(parseInt(leases[action.ACTION].time)) && leases[action.ACTION].color ){ - var endTime = action.TIME.startsWith("+")? action.TIME : action.TIME - startTime; + var endTime = (action.TIME.startsWith("+")? action.TIME : action.TIME - startTime).toString(); if(checkTime(element.STIME, endTime)){ - rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:endTime.toString(), data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); + rtn = $("",{class:"describeCharter fa fa-clock",data_start:element.STIME, data_add:endTime, data_action:action.ACTION}).css({"position":"relative","color":leases[action.ACTION].color}); if( leases[action.ACTION].warning && leases[action.ACTION].warning.time && From c37f2105bd78764ef7cd78fb49c5fc7f66957f63 Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Thu, 16 Jun 2022 18:57:07 +0200 Subject: [PATCH 12/43] B #5189: remove left lock file (#2159) --- install.sh | 3 +++ share/pkgs/services/systemd/opennebula.service | 1 + share/pkgs/services/systemd/pre_cleanup | 5 +++++ 3 files changed, 9 insertions(+) create mode 100755 share/pkgs/services/systemd/pre_cleanup diff --git a/install.sh b/install.sh index ae6da93af1..9cb875469c 100755 --- a/install.sh +++ b/install.sh @@ -758,6 +758,7 @@ INSTALL_FILES=( INSTALL_GEMS_SHARE_FILES:$SHARE_LOCATION ONETOKEN_SHARE_FILE:$SHARE_LOCATION FOLLOWER_CLEANUP_SHARE_FILE:$SHARE_LOCATION + PRE_CLEANUP_SHARE_FILE:$SHARE_LOCATION BACKUP_VMS_SHARE_FILE:$SHARE_LOCATION HOOK_AUTOSTART_FILES:$VAR_LOCATION/remotes/hooks/autostart HOOK_FT_FILES:$VAR_LOCATION/remotes/hooks/ft @@ -2364,6 +2365,8 @@ ONETOKEN_SHARE_FILE="share/onetoken/onetoken.sh" FOLLOWER_CLEANUP_SHARE_FILE="share/hooks/raft/follower_cleanup" +PRE_CLEANUP_SHARE_FILE="share/pkgs/services/systemd/pre_cleanup" + BACKUP_VMS_SHARE_FILE="share/scripts/backup_vms" #------------------------------------------------------------------------------- diff --git a/share/pkgs/services/systemd/opennebula.service b/share/pkgs/services/systemd/opennebula.service index e58c1223da..965e5c9dd8 100644 --- a/share/pkgs/services/systemd/opennebula.service +++ b/share/pkgs/services/systemd/opennebula.service @@ -15,6 +15,7 @@ User=oneadmin Environment="PATH=/usr/lib/one/sh/override:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" EnvironmentFile=-/var/run/one/ssh-agent.env ExecStartPre=-/usr/sbin/logrotate -f /etc/logrotate.d/opennebula -s /var/lib/one/.logrotate.status +ExecStartPre=/usr/share/one/pre_cleanup ExecStart=/usr/bin/oned -f ExecStopPost=/usr/share/one/follower_cleanup PIDFile=/var/lock/one/one diff --git a/share/pkgs/services/systemd/pre_cleanup b/share/pkgs/services/systemd/pre_cleanup new file mode 100755 index 0000000000..a490be6319 --- /dev/null +++ b/share/pkgs/services/systemd/pre_cleanup @@ -0,0 +1,5 @@ +#!/usr/bin/bash + +if [[ ! -f /var/run/one/oned.pid && -f /var/lock/one/one ]]; then + rm /var/lock/one/one +fi From 307a6bb15397ff1cf608b0fe0bd9663fb9066f3f Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Fri, 17 Jun 2022 11:48:25 +0200 Subject: [PATCH 13/43] B #5189: fix typo (#2160) --- share/pkgs/services/systemd/pre_cleanup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/pkgs/services/systemd/pre_cleanup b/share/pkgs/services/systemd/pre_cleanup index a490be6319..1d92028ece 100755 --- a/share/pkgs/services/systemd/pre_cleanup +++ b/share/pkgs/services/systemd/pre_cleanup @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/bin/bash if [[ ! -f /var/run/one/oned.pid && -f /var/lock/one/one ]]; then rm /var/lock/one/one From 0fb4ddaa4fb0317b57b8b605797b3ee360126240 Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Fri, 17 Jun 2022 11:49:36 +0200 Subject: [PATCH 14/43] F #5862: Add labels to user settings (#2161) --- .../src/client/components/Forms/Legend.js | 24 +-- .../src/client/constants/translates.js | 4 + .../Settings/Authentication/index.js | 5 +- .../Settings/ConfigurationUI/index.js | 2 +- .../Settings/LabelsSection/index.js | 163 ++++++++++++++++ .../src/client/containers/Settings/index.js | 13 +- .../src/client/features/OneApi/common.js | 27 +++ .../src/client/features/OneApi/user.js | 177 +++++++++++++++--- src/fireedge/src/client/hooks/useSearch.js | 86 ++++++--- src/fireedge/src/client/theme/defaults.js | 16 ++ 10 files changed, 441 insertions(+), 76 deletions(-) create mode 100644 src/fireedge/src/client/containers/Settings/LabelsSection/index.js diff --git a/src/fireedge/src/client/components/Forms/Legend.js b/src/fireedge/src/client/components/Forms/Legend.js index 3535aa987c..dd35abbac6 100644 --- a/src/fireedge/src/client/components/Forms/Legend.js +++ b/src/fireedge/src/client/components/Forms/Legend.js @@ -21,22 +21,16 @@ import AdornmentWithTooltip from 'client/components/FormControl/Tooltip' import { Translate } from 'client/components/HOC' const StyledLegend = styled((props) => ( - -))( - ({ theme }) => ({ - padding: '0em 1em 0.2em 0.5em', - borderBottom: `2px solid ${theme.palette.secondary.main}`, + +))(({ ownerState }) => ({ + ...(ownerState.tooltip && { + display: 'inline-flex', + alignItems: 'center', }), - ({ ownerState }) => ({ - ...(ownerState.tooltip && { - display: 'inline-flex', - alignItems: 'center', - }), - ...(!ownerState.disableGutters && { - marginBottom: '1em', - }), - }) -) + ...(!ownerState.disableGutters && { + marginBottom: '1em', + }), +})) const Legend = memo( ({ 'data-cy': dataCy, title, tooltip, disableGutters }) => ( diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index b77b1d1920..760c3579b4 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -25,6 +25,7 @@ module.exports = { FilterBy: 'Filter by', FilterLabels: 'Filter labels', FilterByLabel: 'Filter by label', + ApplyLabels: 'Apply labels', Label: 'Label', NoLabels: 'NoLabels', All: 'All', @@ -297,6 +298,9 @@ module.exports = { AddUserSshPrivateKey: 'Add user SSH private key', SshPassphraseKey: 'SSH private key passphrase', AddUserSshPassphraseKey: 'Add user SSH private key passphrase', + Labels: 'Labels', + NewLabelOrSearch: 'New label or search', + LabelAlreadyExists: 'Label already exists', /* sections - system */ User: 'User', diff --git a/src/fireedge/src/client/containers/Settings/Authentication/index.js b/src/fireedge/src/client/containers/Settings/Authentication/index.js index 3ceb630b7e..bc5f56c29c 100644 --- a/src/fireedge/src/client/containers/Settings/Authentication/index.js +++ b/src/fireedge/src/client/containers/Settings/Authentication/index.js @@ -195,7 +195,10 @@ const Settings = () => { } return ( - + {FIELDS.map((field) => ( diff --git a/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js b/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js index 0d0579333b..f6db452e8d 100644 --- a/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js +++ b/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js @@ -79,7 +79,7 @@ const Settings = () => { component="form" onSubmit={handleSubmit(handleUpdateUser)} variant="outlined" - sx={{ p: '1em', maxWidth: { sm: 'auto', md: 550 } }} + sx={{ p: '1em' }} > { + const { user, settings } = useAuth() + const { enqueueError } = useGeneralApi() + const [updateUser, { isLoading }] = useUpdateUserMutation() + + const currentLabels = useMemo( + () => settings?.LABELS?.split(',').filter(Boolean) ?? [], + [settings?.LABELS] + ) + + const { handleSubmit, register, reset, setFocus } = useForm({ + reValidateMode: 'onSubmit', + }) + + const { result, handleChange } = useSearch({ + list: currentLabels, + listOptions: { distance: 50 }, + wait: 500, + condition: !isLoading, + }) + + const handleAddLabel = useCallback( + async (newLabel) => { + try { + const exists = currentLabels.some((label) => label === newLabel) + + if (exists) throw new Error(T.LabelAlreadyExists) + + const newLabels = currentLabels.concat(newLabel).join() + const template = jsonToXml({ LABELS: newLabels }) + await updateUser({ id: user.ID, template, replace: 1 }) + } catch (error) { + enqueueError(error.message ?? T.SomethingWrong) + } finally { + // Reset the search after adding the label + handleChange() + reset({ [NEW_LABEL_ID]: '' }) + setFocus(NEW_LABEL_ID) + } + }, + [updateUser, currentLabels, handleChange, reset] + ) + + const handleDeleteLabel = useCallback( + async (label) => { + try { + const newLabels = currentLabels.filter((l) => l !== label).join() + const template = jsonToXml({ LABELS: newLabels }) + await updateUser({ id: user.ID, template, replace: 1 }) + + // Reset the search after deleting the label + handleChange() + } catch { + enqueueError(T.SomethingWrong) + } + }, + [updateUser, currentLabels, handleChange] + ) + + const handleKeyDown = useCallback( + (evt) => { + if (evt.key !== 'Enter') return + + handleSubmit(async (formData) => { + const newLabel = formData[NEW_LABEL_ID] + + if (newLabel) await handleAddLabel(newLabel) + + // scroll to the new label (if it exists) + setTimeout(() => { + document + ?.querySelector(`[data-cy='${newLabel}']`) + ?.scrollIntoView({ behavior: 'smooth', block: 'center' }) + }, 500) + })(evt) + }, + [handleAddLabel, handleSubmit] + ) + + return ( + + + + + + + + {result?.map((label) => ( + + + + + handleDeleteLabel(label)} + icon={} + /> + + ))} + + + ) : undefined, + }} + {...register(NEW_LABEL_ID, { onChange: handleChange })} + helperText={'Press enter to create a new label'} + /> + + ) +} + +export default Settings diff --git a/src/fireedge/src/client/containers/Settings/index.js b/src/fireedge/src/client/containers/Settings/index.js index eb53ac1f5c..e5c83da1dd 100644 --- a/src/fireedge/src/client/containers/Settings/index.js +++ b/src/fireedge/src/client/containers/Settings/index.js @@ -14,13 +14,14 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { ReactElement } from 'react' -import { Typography, Divider, Stack } from '@mui/material' +import { Typography, Divider, Box } from '@mui/material' import { Translate } from 'client/components/HOC' import { T } from 'client/constants' import ConfigurationUISection from 'client/containers/Settings/ConfigurationUI' import AuthenticationSection from 'client/containers/Settings/Authentication' +import LabelsSection from 'client/containers/Settings/LabelsSection' /** @returns {ReactElement} Settings container */ const Settings = () => ( @@ -31,10 +32,16 @@ const Settings = () => ( - + + - + ) diff --git a/src/fireedge/src/client/features/OneApi/common.js b/src/fireedge/src/client/features/OneApi/common.js index 81d734aa97..f45e354d7a 100644 --- a/src/fireedge/src/client/features/OneApi/common.js +++ b/src/fireedge/src/client/features/OneApi/common.js @@ -294,3 +294,30 @@ export const updateTemplateOnDocument = ? { ...resource.TEMPLATE.BODY, ...template } : template } + +/** + * Updates the current user groups in the store. + * + * @param {object} params - Request params + * @param {string|number} params.id - The id of the user + * @param {string|number} params.group - The group id to update + * @param {boolean} [remove] - Remove the group from the user + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateUserGroups = + ({ id: userId, group: groupId }, remove = false) => + (draft) => { + const updatePool = isUpdateOnPool(draft, userId) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +userId) + : draft + + if ((updatePool && !resource) || groupId === undefined) return + + const currentGroups = [resource.GROUPS.ID].flat() + + resource.GROUPS.ID = remove + ? currentGroups.filter((id) => +id !== +groupId) + : currentGroups.concat(groupId) + } diff --git a/src/fireedge/src/client/features/OneApi/user.js b/src/fireedge/src/client/features/OneApi/user.js index 271c8e00e0..4189310db0 100644 --- a/src/fireedge/src/client/features/OneApi/user.js +++ b/src/fireedge/src/client/features/OneApi/user.js @@ -20,7 +20,15 @@ import { ONE_RESOURCES, ONE_RESOURCES_POOL, } from 'client/features/OneApi' -import authApi from 'client/features/OneApi/auth' +import { actions as authActions } from 'client/features/Auth/slice' + +import { + updateResourceOnPool, + removeResourceOnPool, + updateUserGroups, + updateTemplateOnResource, + updateOwnershipOnResource, +} from 'client/features/OneApi/common' import { User } from 'client/constants' const { USER } = ONE_RESOURCES @@ -67,6 +75,28 @@ const userApi = oneApi.injectEndpoints({ }, transformResponse: (data) => data?.USER ?? {}, providesTags: (_, __, { id }) => [{ type: USER, id }], + async onQueryStarted({ id }, { dispatch, queryFulfilled }) { + try { + const { data: resourceFromQuery } = await queryFulfilled + + dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + updateResourceOnPool({ id, resourceFromQuery }) + ) + ) + } catch { + // if the query fails, we want to remove the resource from the pool + dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + removeResourceOnPool({ id }) + ) + ) + } + }, }), allocateUser: builder.mutation({ /** @@ -75,9 +105,9 @@ const userApi = oneApi.injectEndpoints({ * @param {object} params - Request parameters * @param {string} params.username - Username for the new user * @param {string} params.password - Password for the new user - * @param {string} params.driver - Authentication driver for the new user. + * @param {string} [params.driver] - Authentication driver for the new user. * If it is an empty string, then the default 'core' is used - * @param {string[]} params.group - array of Group IDs. + * @param {string[]} [params.group] - array of Group IDs. * **The first ID will be used as the main group.** * This array can be empty, in which case the default group will be used * @returns {number} The allocated User id @@ -89,7 +119,6 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, - invalidatesTags: [USER_POOL], }), updateUser: builder.mutation({ /** @@ -112,17 +141,37 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: USER, id }], - async onQueryStarted({ id }, { queryFulfilled, dispatch, getState }) { + async onQueryStarted(params, { dispatch, queryFulfilled, getState }) { try { - await queryFulfilled - - if (+id === +getState().auth.user.ID) { - await dispatch( - authApi.endpoints.getAuthUser.initiate(undefined, { - forceRefetch: true, - }) + const patchUser = dispatch( + userApi.util.updateQueryData( + 'getUser', + { id: params.id }, + updateTemplateOnResource(params) ) + ) + + const patchUsers = dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + updateTemplateOnResource(params) + ) + ) + + const authUser = getState().auth.user + + if (+authUser?.ID === +params.id) { + // optimistic update of the auth user + const cloneAuthUser = { ...authUser } + updateTemplateOnResource(params)(cloneAuthUser) + dispatch(authActions.changeAuthUser({ ...cloneAuthUser })) } + + queryFulfilled.catch(() => { + patchUser.undo() + patchUsers.undo() + }) } catch {} }, }), @@ -141,7 +190,7 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, - invalidatesTags: (_, __, { id }) => [{ type: USER, id }, USER_POOL], + invalidatesTags: [USER_POOL], }), changePassword: builder.mutation({ /** @@ -183,7 +232,7 @@ const userApi = oneApi.injectEndpoints({ }), changeGroup: builder.mutation({ /** - * Changes the group of the given user. + * Changes the User's primary group of the given user. * * @param {object} params - Request parameters * @param {string|number} params.id - User id @@ -198,26 +247,32 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: USER, id }], - async onQueryStarted({ id, group }, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { getState, dispatch, queryFulfilled }) { try { - await queryFulfilled - - dispatch( - userApi.util.updateQueryData('getUsers', undefined, (draft) => { - const user = draft.find(({ ID }) => +ID === +id) - user && (user.GID = group) - }) + const patchUser = dispatch( + userApi.util.updateQueryData( + 'getUser', + { id: params.id }, + updateOwnershipOnResource(getState(), params) + ) ) - dispatch( - userApi.util.updateQueryData('getUser', id, (draftUser) => { - draftUser.GID = group - }) + const patchUsers = dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + updateOwnershipOnResource(getState(), params) + ) ) + + queryFulfilled.catch(() => { + patchUser.undo() + patchUsers.undo() + }) } catch {} }, }), - addToGroup: builder.mutation({ + addGroup: builder.mutation({ /** * Adds the User to a secondary group. * @@ -234,6 +289,39 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: USER, id }, USER_POOL], + async onQueryStarted(params, { getState, dispatch, queryFulfilled }) { + try { + const patchUser = dispatch( + userApi.util.updateQueryData( + 'getUser', + { id: params.id }, + updateUserGroups(params) + ) + ) + + const patchUsers = dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + updateUserGroups(params) + ) + ) + + const authUser = getState().auth.user + + if (+authUser?.ID === +params.id) { + // optimistic update of the auth user + const cloneAuthUser = { ...authUser } + updateUserGroups(params)(cloneAuthUser) + dispatch(authActions.changeAuthUser({ ...cloneAuthUser })) + } + + queryFulfilled.catch(() => { + patchUser.undo() + patchUsers.undo() + }) + } catch {} + }, }), removeFromGroup: builder.mutation({ /** @@ -252,6 +340,39 @@ const userApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: USER, id }, USER_POOL], + async onQueryStarted(params, { getState, dispatch, queryFulfilled }) { + try { + const patchUser = dispatch( + userApi.util.updateQueryData( + 'getUser', + { id: params.id }, + updateUserGroups(params, true) + ) + ) + + const patchUsers = dispatch( + userApi.util.updateQueryData( + 'getUsers', + undefined, + updateUserGroups(params, true) + ) + ) + + const authUser = getState().auth.user + + if (+authUser?.ID === +params.id) { + // optimistic update of the auth user + const cloneAuthUser = { ...authUser } + updateUserGroups(params, true)(cloneAuthUser) + dispatch(authActions.changeAuthUser({ ...cloneAuthUser })) + } + + queryFulfilled.catch(() => { + patchUser.undo() + patchUsers.undo() + }) + } catch {} + }, }), enableUser: builder.mutation({ /** @@ -352,7 +473,7 @@ export const { useChangePasswordMutation, useChangeAuthDriverMutation, useChangeGroupMutation, - useAddToGroupMutation, + useAddGroupMutation, useRemoveFromGroupMutation, useEnableUserMutation, useDisableUserMutation, diff --git a/src/fireedge/src/client/hooks/useSearch.js b/src/fireedge/src/client/hooks/useSearch.js index f08cd7f7c6..663226508b 100644 --- a/src/fireedge/src/client/hooks/useSearch.js +++ b/src/fireedge/src/client/hooks/useSearch.js @@ -13,50 +13,80 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { useState, useMemo, useCallback } from 'react' - -import { debounce } from '@mui/material' +import { useState, useEffect, useMemo } from 'react' import Fuse from 'fuse.js' +/** + * @typedef {object} useSearchHook + * @property {string} query - Search term + * @property {Array} result - Result of the search + * @property {Function} handleChange - Function to handle the change event + */ + /** * Hook to manage a search in a list. * * @param {object} params - Search parameters * @param {Array} params.list - List of elements - * @param {Fuse.IFuseOptions} params.listOptions - Search options - * @returns {{ - * query: string, - * result: Array, - * handleChange: Function - * }} - Returns information about the search + * @param {Fuse.IFuseOptions} [params.listOptions] - Search options + * @param {number} [params.wait] - Wait a certain amount of time before searching again. By default 1 second + * @param {boolean} [params.condition] - Search if the condition is true + * @returns {useSearchHook} - Returns information about the search */ -const useSearch = ({ list, listOptions }) => { - const [query, setQuery] = useState('') +const useSearch = ({ list, listOptions, wait, condition }) => { + const [searchTerm, setSearchTerm] = useState('') const [result, setResult] = useState(undefined) + const debouncedSearchTerm = useDebounce(searchTerm, wait, condition) - const listFuse = useMemo( - () => - new Fuse(list, listOptions, Fuse.createIndex(listOptions?.keys, list)), - [list, listOptions] - ) + const listFuse = useMemo(() => { + const indexed = listOptions?.keys + ? Fuse.createIndex(listOptions.keys, list) + : undefined - const debounceResult = useCallback( - debounce((value) => { - const search = listFuse.search(value)?.map(({ item }) => item) + return new Fuse(list, listOptions, indexed) + }, [list, listOptions]) - setResult(value ? search : undefined) - }, 1000), - [list] - ) + useEffect(() => { + const search = debouncedSearchTerm + ? listFuse.search(debouncedSearchTerm).map(({ item }) => item) + : undefined - const handleChange = (event) => { - const { value: nextValue } = event?.target + setResult(search) + }, [debouncedSearchTerm]) - setQuery(nextValue) - debounceResult(nextValue) + return { + query: searchTerm, + result: result ?? list, + handleChange: (evt) => setSearchTerm(evt?.target?.value), } +} - return { query, result, handleChange } +/** + * This hook allows you to debounce any fast changing value. + * The debounced value will only reflect the latest value when + * the useDebounce hook has not been called for the specified time period. + * + * @param {string} value - Value to debounce + * @param {number} [delay] - Delay in milliseconds + * @param {boolean} [condition] - Condition to check if the value should be debounced + * @returns {string} Debounced value + */ +const useDebounce = (value, delay = 1000, condition = true) => { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + // Update debounced value after delay + const handler = setTimeout(() => { + condition && setDebouncedValue(value) + }, delay) + + // Cancel the timeout if value changes (also on delay change or unmount) + return () => { + clearTimeout(handler) + } + }, [value, delay, condition]) + + return debouncedValue } export default useSearch diff --git a/src/fireedge/src/client/theme/defaults.js b/src/fireedge/src/client/theme/defaults.js index 1a0f60bc18..11dbb9bcb9 100644 --- a/src/fireedge/src/client/theme/defaults.js +++ b/src/fireedge/src/client/theme/defaults.js @@ -286,6 +286,22 @@ const createAppTheme = (appTheme, mode = SCHEMES.DARK) => { fieldset: { border: 'none' }, }, }, + MuiTypography: { + variants: [ + { + props: { variant: 'underline' }, + style: { + padding: '0 1em 0.2em 0.5em', + borderBottom: `2px solid ${secondary.main}`, + // subtitle1 variant is used for the underline + fontSize: defaultTheme.typography.pxToRem(18), + lineHeight: 24 / 18, + letterSpacing: 0, + fontWeight: 500, + }, + }, + ], + }, MuiPaper: { defaultProps: { elevation: 0, From de5bc4be13cd330879b1538b4f8de7debeaa7ccd Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Mon, 20 Jun 2022 12:48:17 +0200 Subject: [PATCH 15/43] B #5268: detach nic if prereconfigure fails (#2162) --- src/vmm_mad/exec/one_vmm_exec.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vmm_mad/exec/one_vmm_exec.rb b/src/vmm_mad/exec/one_vmm_exec.rb index e42786a8e3..bbf7fef297 100755 --- a/src/vmm_mad/exec/one_vmm_exec.rb +++ b/src/vmm_mad/exec/one_vmm_exec.rb @@ -1057,7 +1057,14 @@ class ExecDriver < VirtualMachineDriver steps << { :driver => :vmm, :action => :prereconfigure, - :parameters => [:deploy_id, target_device] + :parameters => [:deploy_id, target_device], + :fail_actions => [ + { + :driver => :vmm, + :action => :detach_nic, + :parameters => [:deploy_id, mac] + } + ] } if tm_command && !tm_command.empty? From 7a1a85edb68d22f16379bbb3afea13d07c6ea7d1 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Mon, 20 Jun 2022 18:34:44 +0200 Subject: [PATCH 16/43] B #5867: Fix quotas and fsck co-author: Pavel Czerny (cherry picked from commit e2d4141599ae98f8961023842f7e27a71b05e6e9) --- include/LifeCycleManager.h | 2 +- include/VirtualMachine.h | 5 +- include/VirtualMachineDisk.h | 2 +- src/dm/DispatchManagerActions.cc | 10 +- src/lcm/LifeCycleActions.cc | 34 ++++--- src/lcm/LifeCycleStates.cc | 123 +++++++++++++++++++++---- src/onedb/fsck/host.rb | 6 +- src/onedb/fsck/quotas.rb | 10 ++ src/rm/RequestManagerAllocate.cc | 9 ++ src/rm/RequestManagerVirtualMachine.cc | 13 +++ src/vm/VirtualMachine.cc | 29 ++++-- src/vm/VirtualMachineDisk.cc | 8 +- src/vm/VirtualMachineSystemSnapshot.cc | 8 +- 13 files changed, 200 insertions(+), 59 deletions(-) diff --git a/include/LifeCycleManager.h b/include/LifeCycleManager.h index 33c1f2c615..a4e2703c2d 100644 --- a/include/LifeCycleManager.h +++ b/include/LifeCycleManager.h @@ -242,7 +242,7 @@ private: * image may need to be set to error state. */ void clean_up_vm(VirtualMachine *vm, bool dispose, int& image_id, - int uid, int gid, int req_id); + int uid, int gid, int req_id, Template& quota_tmpl); }; #endif /*LIFE_CYCLE_MANAGER_H_*/ diff --git a/include/VirtualMachine.h b/include/VirtualMachine.h index 96b565ab2d..d08c0dc24c 100644 --- a/include/VirtualMachine.h +++ b/include/VirtualMachine.h @@ -1539,7 +1539,7 @@ public: * @param vm_quotas The SYSTEM_DISK_SIZE freed by the deleted snapshots * @param ds_quotas The DS SIZE freed from image datastores. */ - void delete_non_persistent_disk_snapshots(Template **vm_quotas, + void delete_non_persistent_disk_snapshots(Template& vm_quotas, std::vector