mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-19 06:50:07 +03:00
Merge branch 'feature-132'
This commit is contained in:
commit
4df973b95c
@ -210,6 +210,15 @@ public:
|
||||
int resubmit(
|
||||
int vid);
|
||||
|
||||
/**
|
||||
* Reboots a VM preserving any resource and RUNNING state
|
||||
* @param vid VirtualMachine identification
|
||||
* @return 0 on success, -1 if the VM does not exits or -2 if the VM is
|
||||
* in a wrong a state
|
||||
*/
|
||||
int reboot(
|
||||
int vid);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Thread id for the Dispatch Manager
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
LIVE_MIGRATE, /**< Sent by the DM to live-migrate a VM */
|
||||
SHUTDOWN, /**< Sent by the DM to shutdown a running VM */
|
||||
RESTART, /**< Sent by the DM to restart a deployed VM */
|
||||
REBOOT, /**< Sent by the DM to reboot a running VM */
|
||||
DELETE, /**< Sent by the DM to delete a VM */
|
||||
CLEAN, /**< Sent by the DM to cleanup a VM for resubmission*/
|
||||
FINALIZE
|
||||
@ -194,6 +195,8 @@ private:
|
||||
|
||||
void restart_action(int vid);
|
||||
|
||||
void reboot_action(int vid);
|
||||
|
||||
void delete_action(int vid);
|
||||
|
||||
void clean_action(int vid);
|
||||
|
@ -51,6 +51,7 @@ public:
|
||||
CANCEL_PREVIOUS,
|
||||
MIGRATE,
|
||||
RESTORE,
|
||||
REBOOT,
|
||||
POLL,
|
||||
TIMER,
|
||||
DRIVER_CANCEL,
|
||||
@ -262,6 +263,13 @@ private:
|
||||
void restore_action(
|
||||
int vid);
|
||||
|
||||
/**
|
||||
* Reboots a running VM.
|
||||
* @param vid the id of the VM.
|
||||
*/
|
||||
void reboot_action(
|
||||
int vid);
|
||||
|
||||
/**
|
||||
* Polls a VM.
|
||||
* @param vid the id of the VM.
|
||||
|
@ -127,6 +127,15 @@ private:
|
||||
const int oid,
|
||||
const string& drv_msg) const;
|
||||
|
||||
/**
|
||||
* Sends a reboot request to the MAD: "REBOOT ID XML_DRV_MSG"
|
||||
* @param oid the virtual machine id.
|
||||
* @param drv_msg xml data for the mad operation
|
||||
*/
|
||||
void reboot (
|
||||
const int oid,
|
||||
const string& drv_msg) const;
|
||||
|
||||
/**
|
||||
* Sends a cancel request to the MAD: "CANCEL ID XML_DRV_MSG"
|
||||
* @param oid the virtual machine id.
|
||||
|
@ -587,6 +587,7 @@ VMM_EXEC_KVM_SCRIPTS="src/vmm_mad/remotes/kvm/cancel \
|
||||
src/vmm_mad/remotes/kvm/migrate \
|
||||
src/vmm_mad/remotes/kvm/migrate_local \
|
||||
src/vmm_mad/remotes/kvm/restore \
|
||||
src/vmm_mad/remotes/kvm/reboot \
|
||||
src/vmm_mad/remotes/kvm/save \
|
||||
src/vmm_mad/remotes/kvm/poll \
|
||||
src/vmm_mad/remotes/kvm/poll_ganglia \
|
||||
@ -601,6 +602,7 @@ VMM_EXEC_XEN_SCRIPTS="src/vmm_mad/remotes/xen/cancel \
|
||||
src/vmm_mad/remotes/xen/xenrc \
|
||||
src/vmm_mad/remotes/xen/migrate \
|
||||
src/vmm_mad/remotes/xen/restore \
|
||||
src/vmm_mad/remotes/xen/reboot \
|
||||
src/vmm_mad/remotes/xen/save \
|
||||
src/vmm_mad/remotes/xen/poll \
|
||||
src/vmm_mad/remotes/xen/poll_ganglia \
|
||||
|
@ -182,6 +182,19 @@ cmd=CommandParser::CmdParser.new(ARGV) do
|
||||
end
|
||||
end
|
||||
|
||||
reboot_desc = <<-EOT.unindent
|
||||
Reboots the given VM, this is equivalent to execute the reboot command
|
||||
from the VM console.
|
||||
|
||||
States: RUNNING
|
||||
EOT
|
||||
|
||||
command :reboot, reboot_desc, [:range,:vmid_list] do
|
||||
helper.perform_actions(args[0],options,"rebooting") do |vm|
|
||||
vm.reboot
|
||||
end
|
||||
end
|
||||
|
||||
deploy_desc = <<-EOT.unindent
|
||||
Deploys the given VM in the specified Host. This command forces the
|
||||
deployment, in a standard installation the Scheduler is in charge
|
||||
|
@ -539,6 +539,50 @@ error:
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int DispatchManager::reboot(int vid)
|
||||
{
|
||||
VirtualMachine * vm;
|
||||
ostringstream oss;
|
||||
|
||||
vm = vmpool->get(vid,true);
|
||||
|
||||
if ( vm == 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
oss << "Rebooting VM " << vid;
|
||||
NebulaLog::log("DiM",Log::DEBUG,oss);
|
||||
|
||||
if (vm->get_state() == VirtualMachine::ACTIVE &&
|
||||
vm->get_lcm_state() == VirtualMachine::RUNNING )
|
||||
{
|
||||
Nebula& nd = Nebula::instance();
|
||||
LifeCycleManager * lcm = nd.get_lcm();
|
||||
|
||||
lcm->trigger(LifeCycleManager::REBOOT,vid);
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
vm->unlock();
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
oss.str("");
|
||||
oss << "Could not reboot VM " << vid << ", wrong state.";
|
||||
NebulaLog::log("DiM",Log::ERROR,oss);
|
||||
|
||||
vm->unlock();
|
||||
|
||||
return -2;
|
||||
}
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int DispatchManager::finalize(
|
||||
int vid)
|
||||
{
|
||||
|
@ -427,6 +427,37 @@ void LifeCycleManager::cancel_action(int vid)
|
||||
|
||||
return;
|
||||
}
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void LifeCycleManager::reboot_action(int vid)
|
||||
{
|
||||
VirtualMachine * vm;
|
||||
|
||||
vm = vmpool->get(vid,true);
|
||||
|
||||
if ( vm == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (vm->get_state() == VirtualMachine::ACTIVE &&
|
||||
vm->get_lcm_state() == VirtualMachine::RUNNING)
|
||||
{
|
||||
Nebula& nd = Nebula::instance();
|
||||
VirtualMachineManager * vmm = nd.get_vmm();
|
||||
|
||||
vmm->trigger(VirtualMachineManager::REBOOT,vid);
|
||||
}
|
||||
else
|
||||
{
|
||||
vm->log("LCM", Log::ERROR, "reboot_action, VM in a wrong state.");
|
||||
}
|
||||
|
||||
vm->unlock();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
@ -165,6 +165,10 @@ void LifeCycleManager::trigger(Actions action, int _vid)
|
||||
aname = "RESTART";
|
||||
break;
|
||||
|
||||
case REBOOT:
|
||||
aname = "REBOOT";
|
||||
break;
|
||||
|
||||
case DELETE:
|
||||
aname = "DELETE";
|
||||
break;
|
||||
@ -298,6 +302,10 @@ void LifeCycleManager::do_action(const string &action, void * arg)
|
||||
{
|
||||
restart_action(vid);
|
||||
}
|
||||
else if (action == "REBOOT")
|
||||
{
|
||||
reboot_action(vid);
|
||||
}
|
||||
else if (action == "DELETE")
|
||||
{
|
||||
delete_action(vid);
|
||||
|
@ -87,7 +87,7 @@ class OpenNebulaDriver < ActionManager
|
||||
:ssh_stream => nil
|
||||
}.merge(ops)
|
||||
|
||||
params = parameters+" #{id} #{host}"
|
||||
params = parameters + " #{id} #{host}"
|
||||
command = action_command_line(aname, params, options[:script_name])
|
||||
|
||||
if action_is_local?(aname)
|
||||
|
@ -36,6 +36,7 @@ class VirtualMachineDriver < OpenNebulaDriver
|
||||
ACTION = {
|
||||
:deploy => "DEPLOY",
|
||||
:shutdown => "SHUTDOWN",
|
||||
:reboot => "REBOOT",
|
||||
:cancel => "CANCEL",
|
||||
:save => "SAVE",
|
||||
:restore => "RESTORE",
|
||||
@ -80,6 +81,7 @@ class VirtualMachineDriver < OpenNebulaDriver
|
||||
|
||||
register_action(ACTION[:deploy].to_sym, method("deploy"))
|
||||
register_action(ACTION[:shutdown].to_sym, method("shutdown"))
|
||||
register_action(ACTION[:reboot].to_sym, method("reboot"))
|
||||
register_action(ACTION[:cancel].to_sym, method("cancel"))
|
||||
register_action(ACTION[:save].to_sym, method("save"))
|
||||
register_action(ACTION[:restore].to_sym, method("restore"))
|
||||
@ -119,6 +121,11 @@ class VirtualMachineDriver < OpenNebulaDriver
|
||||
send_message(ACTION[:shutdown],RESULT[:failure],id,error)
|
||||
end
|
||||
|
||||
def reboot(id, drv_message)
|
||||
error = "Action not implemented by driver #{self.class}"
|
||||
send_message(ACTION[:reboot],RESULT[:failure],id,error)
|
||||
end
|
||||
|
||||
def cancel(id, drv_message)
|
||||
error = "Action not implemented by driver #{self.class}"
|
||||
send_message(ACTION[:cancel],RESULT[:failure],id,error)
|
||||
|
@ -248,6 +248,7 @@ public class VirtualMachine extends PoolElement{
|
||||
* It is recommended to use the helper methods instead:
|
||||
* <ul>
|
||||
* <li>{@link VirtualMachine#shutdown()}</li>
|
||||
* <li>{@link VirtualMachine#reboot()}</li>
|
||||
* <li>{@link VirtualMachine#cancel()}</li>
|
||||
* <li>{@link VirtualMachine#hold()}</li>
|
||||
* <li>{@link VirtualMachine#release()}</li>
|
||||
@ -259,7 +260,7 @@ public class VirtualMachine extends PoolElement{
|
||||
* </ul>
|
||||
*
|
||||
* @param action The action name to be performed, can be:<br/>
|
||||
* "shutdown", "hold", "release", "stop", "cancel", "suspend",
|
||||
* "shutdown", "reboot", "hold", "release", "stop", "cancel", "suspend",
|
||||
* "resume", "restart", "finalize".
|
||||
* @return If an error occurs the error message contains the reason.
|
||||
*/
|
||||
@ -357,6 +358,15 @@ public class VirtualMachine extends PoolElement{
|
||||
return action("shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboots a running VM.
|
||||
* @return If an error occurs the error message contains the reason.
|
||||
*/
|
||||
public OneResponse reboot()
|
||||
{
|
||||
return action("reboot");
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the running VM.
|
||||
* @return If an error occurs the error message contains the reason.
|
||||
|
@ -143,6 +143,11 @@ module OpenNebula
|
||||
action('shutdown')
|
||||
end
|
||||
|
||||
# Shutdowns an already deployed VM
|
||||
def reboot
|
||||
action('reboot')
|
||||
end
|
||||
|
||||
# Cancels a running VM
|
||||
def cancel
|
||||
action('cancel')
|
||||
|
@ -233,6 +233,10 @@ void VirtualMachineAction::request_execute(xmlrpc_c::paramList const& paramList,
|
||||
{
|
||||
rc = dm->resubmit(id);
|
||||
}
|
||||
else if (action == "reboot")
|
||||
{
|
||||
rc = dm->reboot(id);
|
||||
}
|
||||
|
||||
switch (rc)
|
||||
{
|
||||
|
@ -62,6 +62,7 @@ module OpenNebulaJSON
|
||||
when "restart" then self.restart
|
||||
when "saveas" then self.save_as(action_hash['params'])
|
||||
when "shutdown" then self.shutdown
|
||||
when "reboot" then self.reboot
|
||||
when "resubmit" then self.resubmit
|
||||
when "chown" then self.chown(action_hash['params'])
|
||||
else
|
||||
|
@ -126,6 +126,10 @@ void VirtualMachineManager::trigger(Actions action, int _vid)
|
||||
aname = "RESTORE";
|
||||
break;
|
||||
|
||||
case REBOOT:
|
||||
aname = "REBOOT";
|
||||
break;
|
||||
|
||||
case SHUTDOWN:
|
||||
aname = "SHUTDOWN";
|
||||
break;
|
||||
@ -198,6 +202,10 @@ void VirtualMachineManager::do_action(const string &action, void * arg)
|
||||
{
|
||||
restore_action(vid);
|
||||
}
|
||||
else if (action == "REBOOT")
|
||||
{
|
||||
reboot_action(vid);
|
||||
}
|
||||
else if (action == "SHUTDOWN")
|
||||
{
|
||||
shutdown_action(vid);
|
||||
@ -573,6 +581,75 @@ error_common:
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void VirtualMachineManager::reboot_action(
|
||||
int vid)
|
||||
{
|
||||
VirtualMachine * vm;
|
||||
const VirtualMachineManagerDriver * vmd;
|
||||
|
||||
string vm_tmpl;
|
||||
string * drv_msg;
|
||||
ostringstream os;
|
||||
|
||||
// Get the VM from the pool
|
||||
vm = vmpool->get(vid,true);
|
||||
|
||||
if (vm == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vm->hasHistory())
|
||||
{
|
||||
goto error_history;
|
||||
}
|
||||
|
||||
// Get the driver for this VM
|
||||
vmd = get(vm->get_vmm_mad());
|
||||
|
||||
if ( vmd == 0 )
|
||||
{
|
||||
goto error_driver;
|
||||
}
|
||||
|
||||
// Invoke driver method
|
||||
drv_msg = format_message(
|
||||
vm->get_hostname(),
|
||||
vm->get_vnm_mad(),
|
||||
"",
|
||||
"",
|
||||
vm->get_deploy_id(),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
vm->to_xml(vm_tmpl));
|
||||
|
||||
vmd->reboot(vid, *drv_msg);
|
||||
|
||||
delete drv_msg;
|
||||
|
||||
vm->unlock();
|
||||
|
||||
return;
|
||||
|
||||
error_history:
|
||||
os.str("");
|
||||
os << "reboot_action, VM has no history";
|
||||
goto error_common;
|
||||
|
||||
error_driver:
|
||||
os.str("");
|
||||
os << "reboot_action, error getting driver " << vm->get_vmm_mad();
|
||||
|
||||
error_common:
|
||||
vm->log("VMM", Log::ERROR, os);
|
||||
vm->unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void VirtualMachineManager::cancel_action(
|
||||
int vid)
|
||||
{
|
||||
|
@ -211,6 +211,20 @@ void VirtualMachineManagerDriver::poll (
|
||||
write(os);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void VirtualMachineManagerDriver::reboot (
|
||||
const int oid,
|
||||
const string& drv_msg) const
|
||||
{
|
||||
ostringstream os;
|
||||
|
||||
os << "REBOOT " << oid << " " << drv_msg << endl;
|
||||
|
||||
write(os);
|
||||
};
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* MAD Interface */
|
||||
/* ************************************************************************** */
|
||||
@ -424,6 +438,18 @@ void VirtualMachineManagerDriver::protocol(
|
||||
lcm->trigger(LifeCycleManager::DEPLOY_FAILURE, id);
|
||||
}
|
||||
}
|
||||
else if ( action == "REBOOT" )
|
||||
{
|
||||
if (result == "SUCCESS")
|
||||
{
|
||||
vm->log("VMM",Log::ERROR,"VM Successfully rebooted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error(vm,os,is,"Error rebooting VM, assume it's still running");
|
||||
vmpool->update(vm);
|
||||
}
|
||||
}
|
||||
else if ( action == "POLL" )
|
||||
{
|
||||
if (result == "SUCCESS")
|
||||
|
@ -52,6 +52,10 @@ class DummyDriver < VirtualMachineDriver
|
||||
send_message(ACTION[:shutdown],RESULT[:success],id)
|
||||
end
|
||||
|
||||
def reboot(id, drv_message)
|
||||
send_message(ACTION[:reboot],RESULT[:success],id)
|
||||
end
|
||||
|
||||
def cancel(id, drv_message)
|
||||
send_message(ACTION[:cancel],RESULT[:success],id)
|
||||
end
|
||||
|
@ -52,6 +52,7 @@ class EC2Driver < VirtualMachineDriver
|
||||
:terminate => "#{EC2_LOCATION}/bin/ec2-terminate-instances",
|
||||
:describe => "#{EC2_LOCATION}/bin/ec2-describe-instances",
|
||||
:associate => "#{EC2_LOCATION}/bin/ec2-associate-address",
|
||||
:reboot => "#{EC2_LOCATION}/bin/ec2-reboot-instances",
|
||||
:authorize => "#{EC2_LOCATION}/bin/ec2-authorize"
|
||||
}
|
||||
|
||||
@ -186,6 +187,20 @@ class EC2Driver < VirtualMachineDriver
|
||||
|
||||
ec2_terminate(ACTION[:shutdown], id, deploy_id)
|
||||
end
|
||||
|
||||
# Reboot a EC2 instance
|
||||
def reboot(id, drv_message)
|
||||
cmd = "#{EC2_LOCATION}/bin/ec2-reboot-instances #{deploy_id}"
|
||||
exe = LocalCommand.run(cmd, log_method(id))
|
||||
|
||||
if exe.code != 0
|
||||
result = RESULT[:failure]
|
||||
else
|
||||
result = RESULT[:success]
|
||||
end
|
||||
|
||||
send_message(action,result,id)
|
||||
end
|
||||
|
||||
# Cancel a EC2 instance
|
||||
def cancel(id, drv_message)
|
||||
|
@ -450,12 +450,23 @@ class ExecDriver < VirtualMachineDriver
|
||||
# POLL action, gets information of a VM
|
||||
#
|
||||
def poll(id, drv_message)
|
||||
data = decode(drv_message)
|
||||
host = data.elements['HOST'].text
|
||||
deploy_id = data.elements['DEPLOY_ID'].text
|
||||
data = decode(drv_message)
|
||||
host = data.elements['HOST'].text
|
||||
deploy_id = data.elements['DEPLOY_ID'].text
|
||||
|
||||
do_action("#{deploy_id} #{host}", id, host, ACTION[:poll])
|
||||
end
|
||||
|
||||
#
|
||||
# REBOOT action, reboots a running VM
|
||||
#
|
||||
def reboot(id, drv_message)
|
||||
data = decode(drv_message)
|
||||
host = data.elements['HOST'].text
|
||||
deploy_id = data.elements['DEPLOY_ID'].text
|
||||
|
||||
do_action("#{deploy_id} #{host}", id, host, ACTION[:reboot])
|
||||
end
|
||||
end
|
||||
|
||||
################################################################################
|
||||
|
@ -22,4 +22,4 @@ else
|
||||
MAD_LOCATION=$ONE_LOCATION/lib/mads
|
||||
fi
|
||||
|
||||
exec $MAD_LOCATION/one_vmm_exec -l deploy,shutdown,cancel,save,restore,migrate,poll,pre,post,clean $*
|
||||
exec $MAD_LOCATION/one_vmm_exec -l deploy,shutdown,reboot,cancel,save,restore,migrate,poll,pre,post,clean $*
|
||||
|
27
src/vmm_mad/remotes/kvm/reboot
Executable file
27
src/vmm_mad/remotes/kvm/reboot
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# -------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) #
|
||||
# #
|
||||
# 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. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
source $(dirname $0)/kvmrc
|
||||
source $(dirname $0)/../../scripts_common.sh
|
||||
|
||||
deploy_id=$1
|
||||
|
||||
exec_and_log "virsh --connect $LIBVIRT_URI reboot $deploy_id" \
|
||||
"Could not reboot domain $deploy_id"
|
||||
|
||||
exit 0
|
24
src/vmm_mad/remotes/xen/reboot
Executable file
24
src/vmm_mad/remotes/xen/reboot
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# -------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) #
|
||||
# #
|
||||
# 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. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
source $(dirname $0)/xenrc
|
||||
source $(dirname $0)/../../scripts_common.sh
|
||||
|
||||
deploy_id=$1
|
||||
|
||||
exec_and_log "$XM_REBOOT $deploy_id" "Could not reboot domain $deploy_id"
|
@ -22,6 +22,7 @@ export XM_CREATE="sudo $XM_PATH create"
|
||||
export XM_CREDITS="sudo $XM_PATH sched-cred"
|
||||
export XM_MIGRATE="sudo $XM_PATH migrate -l"
|
||||
export XM_SAVE="sudo $XM_PATH save"
|
||||
export XM_REBOOT="sudo $XM_PATH reboot"
|
||||
export XM_RESTORE="sudo $XM_PATH restore"
|
||||
export XM_LIST="sudo $XM_PATH list"
|
||||
export XM_SHUTDOWN="sudo $XM_PATH shutdown"
|
||||
|
Loading…
x
Reference in New Issue
Block a user