mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
FEATURE-3823: kvm agent hooks (#3839)
This commit is contained in:
parent
d4b537efa7
commit
750abf3551
@ -97,6 +97,31 @@ domr.scripts.dir=scripts/network/domr/kvm
|
||||
# migration will finish quickly. Less than 1 means disabled.
|
||||
#vm.migrate.pauseafter=0
|
||||
|
||||
# Agent hooks is the way to override default agent behavior to extend the functionality without excessive coding
|
||||
# for a custom deployment. The first hook promoted is libvirt-vm-xml-transformer which allows provider to modify
|
||||
# VM XML specification before send to libvirt. Hooks are implemented in Groovy and must be implemented in the way
|
||||
# to keep default CS behaviour is something goes wrong.
|
||||
# All hooks are located in a special directory defined in 'agent.hooks.basedir'
|
||||
#
|
||||
# agent.hooks.basedir=/etc/cloudstack/agent/hooks
|
||||
|
||||
# every hook has two major attributes - script name, specified in 'agent.hooks.*.script' and method name
|
||||
# specified in 'agent.hooks.*.method'.
|
||||
|
||||
# Libvirt XML transformer hook does XML-to-XML transformation which provider can use to add/remove/modify some
|
||||
# sort of attributes in Libvirt XML domain specification.
|
||||
# agent.hooks.libvirt_vm_xml_transformer.script=libvirt-vm-xml-transformer.groovy
|
||||
# agent.hooks.libvirt_vm_xml_transformer.method=transform
|
||||
#
|
||||
# The hook is called right after libvirt successfuly launched VM
|
||||
# agent.hooks.libvirt_vm_on_start.script=libvirt-vm-state-change.groovy
|
||||
# agent.hooks.libvirt_vm_on_start.method=onStart
|
||||
#
|
||||
# The hook is called right after libvirt successfuly stopped VM
|
||||
# agent.hooks.libvirt_vm_on_stop.script=libvirt-vm-state-change.groovy
|
||||
# agent.hooks.libvirt_vm_on_stop.method=onStop
|
||||
#
|
||||
|
||||
# set the type of bridge used on the hypervisor, this defines what commands the resource
|
||||
# will use to setup networking. Currently supported NATIVE, OPENVSWITCH
|
||||
#network.bridge.type=native
|
||||
|
||||
@ -28,6 +28,11 @@
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<version>${cs.groovy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
|
||||
@ -289,6 +289,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
protected String _rngPath = "/dev/random";
|
||||
protected int _rngRatePeriod = 1000;
|
||||
protected int _rngRateBytes = 2048;
|
||||
protected String _agentHooksBasedir = "/etc/cloudstack/agent/hooks";
|
||||
|
||||
protected String _agentHooksLibvirtXmlScript = "libvirt-vm-xml-transformer.groovy";
|
||||
protected String _agentHooksLibvirtXmlMethod = "transform";
|
||||
|
||||
protected String _agentHooksVmOnStartScript = "libvirt-vm-state-change.groovy";
|
||||
protected String _agentHooksVmOnStartMethod = "onStart";
|
||||
|
||||
protected String _agentHooksVmOnStopScript = "libvirt-vm-state-change.groovy";
|
||||
protected String _agentHooksVmOnStopMethod = "onStop";
|
||||
|
||||
|
||||
protected File _qemuSocketsPath;
|
||||
private final String _qemuGuestAgentSocketName = "org.qemu.guest_agent.0";
|
||||
protected WatchDogAction _watchDogAction = WatchDogAction.NONE;
|
||||
@ -391,6 +403,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
return new ExecutionResult(true, null);
|
||||
}
|
||||
|
||||
public LibvirtKvmAgentHook getTransformer() throws IOException {
|
||||
return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksLibvirtXmlScript, _agentHooksLibvirtXmlMethod);
|
||||
}
|
||||
|
||||
public LibvirtKvmAgentHook getStartHook() throws IOException {
|
||||
return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksVmOnStartScript, _agentHooksVmOnStartMethod);
|
||||
}
|
||||
|
||||
public LibvirtKvmAgentHook getStopHook() throws IOException {
|
||||
return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksVmOnStopScript, _agentHooksVmOnStopMethod);
|
||||
}
|
||||
|
||||
public LibvirtUtilitiesHelper getLibvirtUtilitiesHelper() {
|
||||
return libvirtUtilitiesHelper;
|
||||
}
|
||||
@ -1097,6 +1121,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
value = (String) params.get("vm.migrate.pauseafter");
|
||||
_migratePauseAfter = NumbersUtil.parseInt(value, -1);
|
||||
|
||||
configureAgentHooks(params);
|
||||
|
||||
value = (String)params.get("vm.migrate.speed");
|
||||
_migrateSpeed = NumbersUtil.parseInt(value, -1);
|
||||
if (_migrateSpeed == -1) {
|
||||
@ -1155,6 +1181,50 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
return true;
|
||||
}
|
||||
|
||||
private void configureAgentHooks(final Map<String, Object> params) {
|
||||
String value = (String) params.get("agent.hooks.basedir");
|
||||
if (null != value) {
|
||||
_agentHooksBasedir = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.basedir is " + _agentHooksBasedir);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_xml_transformer.script");
|
||||
if (null != value) {
|
||||
_agentHooksLibvirtXmlScript = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.script is " + _agentHooksLibvirtXmlScript);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_xml_transformer.method");
|
||||
if (null != value) {
|
||||
_agentHooksLibvirtXmlMethod = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.method is " + _agentHooksLibvirtXmlMethod);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_on_start.script");
|
||||
if (null != value) {
|
||||
_agentHooksVmOnStartScript = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_on_start.script is " + _agentHooksVmOnStartScript);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_on_start.method");
|
||||
if (null != value) {
|
||||
_agentHooksVmOnStartMethod = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_on_start.method is " + _agentHooksVmOnStartMethod);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_on_stop.script");
|
||||
if (null != value) {
|
||||
_agentHooksVmOnStopScript = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_on_stop.script is " + _agentHooksVmOnStopScript);
|
||||
|
||||
value = (String) params.get("agent.hooks.libvirt_vm_on_stop.method");
|
||||
if (null != value) {
|
||||
_agentHooksVmOnStopMethod = value;
|
||||
}
|
||||
s_logger.debug("agent.hooks.libvirt_vm_on_stop.method is " + _agentHooksVmOnStopMethod);
|
||||
}
|
||||
|
||||
private void loadUefiProperties() throws FileNotFoundException {
|
||||
|
||||
if (_uefiProperties != null && _uefiProperties.getProperty("guest.loader.legacy") != null) {
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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.
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.GroovyObject;
|
||||
import groovy.util.GroovyScriptEngine;
|
||||
import groovy.util.ResourceException;
|
||||
import groovy.util.ScriptException;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LibvirtKvmAgentHook {
|
||||
private final String script;
|
||||
private final String method;
|
||||
private final GroovyScriptEngine gse;
|
||||
private final Binding binding = new Binding();
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(LibvirtKvmAgentHook.class);
|
||||
|
||||
public LibvirtKvmAgentHook(String path, String script, String method) throws IOException {
|
||||
this.script = script;
|
||||
this.method = method;
|
||||
File full_path = new File(path, script);
|
||||
if (!full_path.canRead()) {
|
||||
s_logger.warn("Groovy script '" + full_path.toString() + "' is not available. Transformations will not be applied.");
|
||||
this.gse = null;
|
||||
} else {
|
||||
this.gse = new GroovyScriptEngine(path);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return this.gse != null;
|
||||
}
|
||||
|
||||
public Object handle(Object arg) throws ResourceException, ScriptException {
|
||||
if (!isInitialized()) {
|
||||
s_logger.warn("Groovy scripting engine is not initialized. Data transformation skipped.");
|
||||
return arg;
|
||||
}
|
||||
|
||||
GroovyObject cls = (GroovyObject) this.gse.run(this.script, binding);
|
||||
if (null == cls) {
|
||||
s_logger.warn("Groovy object is not received from script '" + this.script + "'.");
|
||||
return arg;
|
||||
} else {
|
||||
Object[] params = {s_logger, arg};
|
||||
try {
|
||||
Object res = cls.invokeMethod(this.method, params);
|
||||
return res;
|
||||
} catch (MissingMethodExceptionNoStack e) {
|
||||
s_logger.error("Error occured when calling method from groovy script, {}", e);
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,7 @@ import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
|
||||
import com.cloud.exception.InternalErrorException;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.network.Networks.TrafficType;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
@ -79,7 +80,10 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper<StartComman
|
||||
libvirtComputingResource.createVifs(vmSpec, vm);
|
||||
|
||||
s_logger.debug("starting " + vmName + ": " + vm.toString());
|
||||
libvirtComputingResource.startVM(conn, vmName, vm.toString());
|
||||
String vmInitialSpecification = vm.toString();
|
||||
String vmFinalSpecification = performXmlTransformHook(vmInitialSpecification, libvirtComputingResource);
|
||||
libvirtComputingResource.startVM(conn, vmName, vmFinalSpecification);
|
||||
performAgentStartHook(vmName, libvirtComputingResource);
|
||||
|
||||
libvirtComputingResource.applyDefaultNetworkRules(conn, vmSpec, false);
|
||||
|
||||
@ -136,4 +140,30 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper<StartComman
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void performAgentStartHook(String vmName, LibvirtComputingResource libvirtComputingResource) {
|
||||
try {
|
||||
LibvirtKvmAgentHook onStartHook = libvirtComputingResource.getStartHook();
|
||||
onStartHook.handle(vmName);
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Exception occurred when handling LibVirt VM onStart hook: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String performXmlTransformHook(String vmInitialSpecification, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String vmFinalSpecification;
|
||||
try {
|
||||
// if transformer fails, everything must go as it's just skipped.
|
||||
LibvirtKvmAgentHook t = libvirtComputingResource.getTransformer();
|
||||
vmFinalSpecification = (String) t.handle(vmInitialSpecification);
|
||||
if (null == vmFinalSpecification) {
|
||||
s_logger.warn("Libvirt XML transformer returned NULL, will use XML specification unchanged.");
|
||||
vmFinalSpecification = vmInitialSpecification;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
s_logger.warn("Exception occurred when handling LibVirt XML transformer hook: {}", e);
|
||||
vmFinalSpecification = vmInitialSpecification;
|
||||
}
|
||||
return vmFinalSpecification;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
@ -92,6 +93,8 @@ public final class LibvirtStopCommandWrapper extends CommandWrapper<StopCommand,
|
||||
libvirtComputingResource.destroyNetworkRulesForVM(conn, vmName);
|
||||
final String result = libvirtComputingResource.stopVM(conn, vmName, command.isForceStop());
|
||||
|
||||
performAgentStopHook(vmName, libvirtComputingResource);
|
||||
|
||||
if (result == null) {
|
||||
if (disks != null && disks.size() > 0) {
|
||||
for (final DiskDef disk : disks) {
|
||||
@ -147,4 +150,14 @@ public final class LibvirtStopCommandWrapper extends CommandWrapper<StopCommand,
|
||||
return new StopAnswer(command, e.getMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void performAgentStopHook(String vmName, final LibvirtComputingResource libvirtComputingResource) {
|
||||
try {
|
||||
LibvirtKvmAgentHook onStopHook = libvirtComputingResource.getStopHook();
|
||||
onStopHook.handle(vmName);
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Exception occurred when handling LibVirt VM onStop hook: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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.
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource;
|
||||
|
||||
import groovy.util.ResourceException;
|
||||
import groovy.util.ScriptException;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LibvirtKvmAgentHookTest extends TestCase {
|
||||
|
||||
private final String source = "<xml />";
|
||||
private final String dir = "/tmp";
|
||||
private final String script = "xml-transform-test.groovy";
|
||||
private final String method = "transform";
|
||||
private final String methodNull = "transform2";
|
||||
private final String testImpl = "package groovy\n" +
|
||||
"\n" +
|
||||
"class BaseTransform {\n" +
|
||||
" String transform(Object logger, String xml) {\n" +
|
||||
" return xml + xml\n" +
|
||||
" }\n" +
|
||||
" String transform2(Object logger, String xml) {\n" +
|
||||
" return null\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"new BaseTransform()\n" +
|
||||
"\n";
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
PrintWriter pw = new PrintWriter(new File(dir, script));
|
||||
pw.println(testImpl);
|
||||
pw.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
new File(dir, script).delete();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testTransform() throws IOException, ResourceException, ScriptException {
|
||||
LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, method);
|
||||
assertEquals(t.isInitialized(), true);
|
||||
String result = (String)t.handle(source);
|
||||
assertEquals(result, source + source);
|
||||
}
|
||||
|
||||
public void testWrongMethod() throws IOException, ResourceException, ScriptException {
|
||||
LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, "methodX");
|
||||
assertEquals(t.isInitialized(), true);
|
||||
assertEquals(t.handle(source), source);
|
||||
}
|
||||
|
||||
public void testNullMethod() throws IOException, ResourceException, ScriptException {
|
||||
LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, methodNull);
|
||||
assertEquals(t.isInitialized(), true);
|
||||
assertEquals(t.handle(source), null);
|
||||
}
|
||||
|
||||
public void testWrongScript() throws IOException, ResourceException, ScriptException {
|
||||
LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, "wrong-script.groovy", method);
|
||||
assertEquals(t.isInitialized(), false);
|
||||
assertEquals(t.handle(source), source);
|
||||
}
|
||||
|
||||
public void testWrongDir() throws IOException, ResourceException, ScriptException {
|
||||
LibvirtKvmAgentHook t = new LibvirtKvmAgentHook("/" + UUID.randomUUID().toString() + "-dir", script, method);
|
||||
assertEquals(t.isInitialized(), false);
|
||||
assertEquals(t.handle(source), source);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user