Support cdrom media change.
This commit is contained in:
parent
ae2e6cde7f
commit
e8b10b32b0
12 changed files with 278 additions and 37 deletions
32
deploy/crds/vmops-config-crd.yaml
Normal file
32
deploy/crds/vmops-config-crd.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: vmopconfigs.vmoperator.jdrupes.org
|
||||||
|
spec:
|
||||||
|
group: vmoperator.jdrupes.org
|
||||||
|
# list of versions supported by this CustomResourceDefinition
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
imageRepository:
|
||||||
|
type: object
|
||||||
|
description: Defines the image repository volume.
|
||||||
|
properties: {}
|
||||||
|
# either Namespaced or Cluster
|
||||||
|
scope: Namespaced
|
||||||
|
names:
|
||||||
|
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
|
||||||
|
plural: vmopconfigs
|
||||||
|
# singular name to be used as an alias on the CLI and for display
|
||||||
|
singular: vmopconfig
|
||||||
|
# kind is normally the CamelCased singular type. Your resource manifests use this.
|
||||||
|
kind: VmOpConfig
|
||||||
|
listKind: VmOpConfigList
|
||||||
|
|
@ -431,10 +431,24 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
cdromImage:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- path
|
||||||
bootindex:
|
bootindex:
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
oneOf:
|
||||||
- volumeClaimTemplate
|
- properties:
|
||||||
|
volumeClaimTemplate:
|
||||||
|
required:
|
||||||
|
- volumeClaimTemplate
|
||||||
|
- properties:
|
||||||
|
cdromImage:
|
||||||
|
required:
|
||||||
|
- cdromImage
|
||||||
default: []
|
default: []
|
||||||
display:
|
display:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
|
|
@ -136,15 +136,28 @@ data:
|
||||||
drives:
|
drives:
|
||||||
<#assign drvCounter = 0/>
|
<#assign drvCounter = 0/>
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
<#list cr.spec.vm.disks.asList() as disk>
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
<#if disk.volumeClaimTemplate??
|
||||||
|
&& disk.volumeClaimTemplate.metadata??
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
&& disk.volumeClaimTemplate.metadata.name??>
|
||||||
<#assign name = disk.volumeClaimTemplate.metadata.name.asString>
|
<#assign name = disk.volumeClaimTemplate.metadata.name.asString>
|
||||||
<#else>
|
<#else>
|
||||||
<#assign name = "" + drvCounter>
|
<#assign name = "" + drvCounter>
|
||||||
</#if>
|
</#if>
|
||||||
|
<#if disk.volumeClaimTemplate??>
|
||||||
- type: raw
|
- type: raw
|
||||||
resource: /dev/disk-${ name }
|
resource: /dev/disk-${ name }
|
||||||
<#assign drvCounter = drvCounter + 1/>
|
<#if disk.bootindex??>
|
||||||
|
bootindex: ${ disk.bootindex.asInt?c }
|
||||||
|
</#if>
|
||||||
|
<#assign drvCounter = drvCounter + 1/>
|
||||||
|
</#if>
|
||||||
|
<#if disk.cdromImage??>
|
||||||
|
- type: ide-cd
|
||||||
|
file: "${ disk.cdromImage.path.asString }"
|
||||||
|
<#if disk.bootindex??>
|
||||||
|
bootindex: ${ disk.bootindex.asInt?c }
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
display:
|
display:
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ spec:
|
||||||
volumeDevices:
|
volumeDevices:
|
||||||
<#assign diskCounter = 0/>
|
<#assign diskCounter = 0/>
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
<#list cr.spec.vm.disks.asList() as disk>
|
||||||
|
<#if disk.volumeClaimTemplate??>
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
<#if disk.volumeClaimTemplate.metadata??
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
&& disk.volumeClaimTemplate.metadata.name??>
|
||||||
<#assign diskName = "disk-" + disk.volumeClaimTemplate.metadata.name.asString>
|
<#assign diskName = "disk-" + disk.volumeClaimTemplate.metadata.name.asString>
|
||||||
|
|
@ -45,6 +46,7 @@ spec:
|
||||||
- name: ${ diskName }
|
- name: ${ diskName }
|
||||||
devicePath: /dev/${ diskName }
|
devicePath: /dev/${ diskName }
|
||||||
<#assign diskCounter = diskCounter + 1/>
|
<#assign diskCounter = diskCounter + 1/>
|
||||||
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
|
|
@ -72,6 +74,7 @@ spec:
|
||||||
claimName: vmop-image-repository
|
claimName: vmop-image-repository
|
||||||
<#assign diskCounter = 0/>
|
<#assign diskCounter = 0/>
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
<#list cr.spec.vm.disks.asList() as disk>
|
||||||
|
<#if disk.volumeClaimTemplate??>
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
<#if disk.volumeClaimTemplate.metadata??
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
&& disk.volumeClaimTemplate.metadata.name??>
|
||||||
<#assign claimName = disk.volumeClaimTemplate.metadata.name.asString>
|
<#assign claimName = disk.volumeClaimTemplate.metadata.name.asString>
|
||||||
|
|
@ -84,6 +87,7 @@ spec:
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: ${ claimName }
|
claimName: ${ claimName }
|
||||||
<#assign diskCounter = diskCounter + 1/>
|
<#assign diskCounter = diskCounter + 1/>
|
||||||
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
terminationGracePeriodSeconds: ${ (cr.spec.vm.powerdownTimeout.asInt + 5)?c }
|
terminationGracePeriodSeconds: ${ (cr.spec.vm.powerdownTimeout.asInt + 5)?c }
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||||
private void reconcileDisk(DynamicKubernetesObject vmDefinition,
|
private void reconcileDisk(DynamicKubernetesObject vmDefinition,
|
||||||
int index, JsonObject diskDef, WatchChannel channel)
|
int index, JsonObject diskDef, WatchChannel channel)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
|
if (!diskDef.has("volumeClaimTemplate")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var pvcObject = new DynamicKubernetesObject();
|
var pvcObject = new DynamicKubernetesObject();
|
||||||
var pvcRaw = GsonPtr.to(pvcObject.getRaw());
|
var pvcRaw = GsonPtr.to(pvcObject.getRaw());
|
||||||
var vmRaw = GsonPtr.to(vmDefinition.getRaw());
|
var vmRaw = GsonPtr.to(vmDefinition.getRaw());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.runner.qemu;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.ChangeMediumCommand;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommandCompleted;
|
||||||
|
import org.jgrapes.core.Channel;
|
||||||
|
import org.jgrapes.core.Component;
|
||||||
|
import org.jgrapes.core.annotation.Handler;
|
||||||
|
|
||||||
|
// TODO: Auto-generated Javadoc
|
||||||
|
/**
|
||||||
|
* The Class CdromController.
|
||||||
|
*/
|
||||||
|
public class CdromController extends Component {
|
||||||
|
|
||||||
|
private static ObjectMapper mapper;
|
||||||
|
private static JsonNode openTray;
|
||||||
|
private static JsonNode removeMedium;
|
||||||
|
private static JsonNode changeMedium;
|
||||||
|
private final QemuMonitor monitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new cdrom controller.
|
||||||
|
*
|
||||||
|
* @param componentChannel the component channel
|
||||||
|
* @param monitor the monitor
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.AssignmentToNonFinalStatic")
|
||||||
|
public CdromController(Channel componentChannel, QemuMonitor monitor) {
|
||||||
|
super(componentChannel);
|
||||||
|
if (mapper == null) {
|
||||||
|
mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
openTray = mapper.readValue("{ \"execute\": "
|
||||||
|
+ "\"blockdev-open-tray\",\"arguments\": {"
|
||||||
|
+ "\"id\": \"\" } }", JsonNode.class);
|
||||||
|
removeMedium = mapper.readValue("{ \"execute\": "
|
||||||
|
+ "\"blockdev-remove-medium\",\"arguments\": {"
|
||||||
|
+ "\"id\": \"\" } }", JsonNode.class);
|
||||||
|
changeMedium = mapper.readValue("{ \"execute\": "
|
||||||
|
+ "\"blockdev-change-medium\",\"arguments\": {"
|
||||||
|
+ "\"id\": \"\",\"filename\": \"\","
|
||||||
|
+ "\"format\": \"raw\",\"read-only-mode\": "
|
||||||
|
+ "\"read-only\" } }", JsonNode.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.SEVERE, e,
|
||||||
|
() -> "Cannot initialize class: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On monitor command.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||||
|
public void onChangeMediumCommand(ChangeMediumCommand event) {
|
||||||
|
if (event.command() != Command.CHANGE_MEDIUM) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.file() == null || event.file().isEmpty()) {
|
||||||
|
var msg = openTray.deepCopy();
|
||||||
|
((ObjectNode) msg.get("arguments")).put("id", event.id());
|
||||||
|
monitor.sendToMonitor(msg);
|
||||||
|
msg = removeMedium.deepCopy();
|
||||||
|
((ObjectNode) msg.get("arguments")).put("id", event.id());
|
||||||
|
monitor.sendToMonitor(msg);
|
||||||
|
fire(new MonitorCommandCompleted(event.command(), null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var msg = changeMedium.deepCopy();
|
||||||
|
((ObjectNode) msg.get("arguments")).put("id", event.id());
|
||||||
|
((ObjectNode) msg.get("arguments")).put("filename", event.file());
|
||||||
|
monitor.sendToMonitor(msg);
|
||||||
|
fire(new MonitorCommandCompleted(event.command(), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -312,9 +312,11 @@ class Configuration implements Dto {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
||||||
private void checkDrives() {
|
private void checkDrives() {
|
||||||
for (Drive drive : vm.drives) {
|
for (Drive drive : vm.drives) {
|
||||||
if (drive.file != null || drive.device != null) {
|
if (drive.file != null || drive.device != null
|
||||||
|
|| "ide-cd".equals(drive.type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (drive.resource == null) {
|
if (drive.resource == null) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
package org.jdrupes.vmoperator.runner.qemu;
|
package org.jdrupes.vmoperator.runner.qemu;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
@ -106,6 +105,7 @@ public class QemuMonitor extends Component {
|
||||||
}
|
}
|
||||||
attach(new RamController(channel(), this));
|
attach(new RamController(channel(), this));
|
||||||
attach(new CpuController(channel(), this));
|
attach(new CpuController(channel(), this));
|
||||||
|
attach(new CdromController(channel(), this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -246,7 +246,7 @@ public class QemuMonitor extends Component {
|
||||||
fire(new MonitorReady());
|
fire(new MonitorReady());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.has("return")) {
|
if (response.has("return") || response.has("error")) {
|
||||||
String executed = executing.poll();
|
String executed = executing.poll();
|
||||||
logger.fine(
|
logger.fine(
|
||||||
() -> String.format("(Previous \"monitor(in)\" is result "
|
() -> String.format("(Previous \"monitor(in)\" is result "
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import java.nio.file.Paths;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.LogManager;
|
import java.util.logging.LogManager;
|
||||||
|
|
@ -51,6 +52,7 @@ import org.apache.commons.cli.DefaultParser;
|
||||||
import org.apache.commons.cli.Option;
|
import org.apache.commons.cli.Option;
|
||||||
import org.apache.commons.cli.Options;
|
import org.apache.commons.cli.Options;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.StateController.State;
|
import org.jdrupes.vmoperator.runner.qemu.StateController.State;
|
||||||
|
import org.jdrupes.vmoperator.runner.qemu.events.ChangeMediumCommand;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||||
import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.CONTINUE;
|
import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.CONTINUE;
|
||||||
import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_CPUS;
|
import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_CPUS;
|
||||||
|
|
@ -155,7 +157,8 @@ import org.jgrapes.util.events.WatchFile;
|
||||||
* @enduml
|
* @enduml
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace" })
|
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace",
|
||||||
|
"PMD.DataflowAnomalyAnalysis" })
|
||||||
public class Runner extends Component {
|
public class Runner extends Component {
|
||||||
|
|
||||||
public static final String APP_NAME = "vmrunner";
|
public static final String APP_NAME = "vmrunner";
|
||||||
|
|
@ -331,37 +334,49 @@ public class Runner extends Component {
|
||||||
return yamlMapper.readValue(out.toString(), JsonNode.class);
|
return yamlMapper.readValue(out.toString(), JsonNode.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops" })
|
||||||
private void updateConfiguration(Map<String, Object> conf) {
|
private void updateConfiguration(Map<String, Object> conf) {
|
||||||
logger.fine(() -> "Updating configuration");
|
logger.fine(() -> "Updating configuration");
|
||||||
Optional.ofNullable((Map<String, Object>) conf.get("vm"))
|
var newConf = yamlMapper.convertValue(conf, Configuration.class);
|
||||||
.map(vm -> vm.get("currentRam")).map(Configuration::parseMemory)
|
Optional.ofNullable(newConf.vm.currentRam).ifPresent(cr -> {
|
||||||
.ifPresent(cr -> {
|
if (config.vm.currentRam != null
|
||||||
if (config.vm.currentRam != null
|
&& config.vm.currentRam.equals(cr)) {
|
||||||
&& config.vm.currentRam.equals(cr)) {
|
return;
|
||||||
return;
|
}
|
||||||
|
synchronized (state) {
|
||||||
|
config.vm.currentRam = cr;
|
||||||
|
if (state.get() == State.RUNNING) {
|
||||||
|
fire(new MonitorCommand(SET_CURRENT_RAM, cr));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (config.vm.currentCpus != newConf.vm.currentCpus) {
|
||||||
|
synchronized (state) {
|
||||||
|
config.vm.currentCpus = newConf.vm.currentCpus;
|
||||||
|
if (state.get() == State.RUNNING) {
|
||||||
|
fire(new MonitorCommand(SET_CURRENT_CPUS,
|
||||||
|
newConf.vm.currentCpus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cdCounter = 0;
|
||||||
|
for (int i = 0; i < Math.min(config.vm.drives.length,
|
||||||
|
newConf.vm.drives.length); i++) {
|
||||||
|
if (!"ide-cd".equals(config.vm.drives[i].type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String curFile = config.vm.drives[i].file;
|
||||||
|
String newFile = newConf.vm.drives[i].file;
|
||||||
|
if (!Objects.equals(curFile, newFile)) {
|
||||||
|
config.vm.drives[i].file = newConf.vm.drives[i].file;
|
||||||
synchronized (state) {
|
synchronized (state) {
|
||||||
config.vm.currentRam = cr;
|
fire(new ChangeMediumCommand("cd" + cdCounter, newFile));
|
||||||
if (state.get() == State.RUNNING) {
|
|
||||||
fire(new MonitorCommand(SET_CURRENT_RAM, cr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
Optional.ofNullable((Map<String, Object>) conf.get("vm"))
|
cdCounter += 1;
|
||||||
.map(vm -> vm.get("currentCpus"))
|
}
|
||||||
.map(v -> v instanceof Number number ? number.intValue() : null)
|
|
||||||
.ifPresent(cpus -> {
|
|
||||||
if (config.vm.currentCpus == cpus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
synchronized (state) {
|
|
||||||
config.vm.currentCpus = cpus;
|
|
||||||
if (state.get() == State.RUNNING) {
|
|
||||||
fire(new MonitorCommand(SET_CURRENT_CPUS, cpus));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class ChangeMediumCommand.
|
||||||
|
*/
|
||||||
|
public class ChangeMediumCommand extends MonitorCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new change medium command.
|
||||||
|
*
|
||||||
|
* @param id the id
|
||||||
|
* @param file the file path
|
||||||
|
*/
|
||||||
|
public ChangeMediumCommand(String id, String file) {
|
||||||
|
super(Command.CHANGE_MEDIUM, id, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the id.
|
||||||
|
*
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.ShortMethodName")
|
||||||
|
public String id() {
|
||||||
|
return (String) arguments()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file.
|
||||||
|
*
|
||||||
|
* @return the file
|
||||||
|
*/
|
||||||
|
public String file() {
|
||||||
|
return (String) arguments()[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ public class MonitorCommand extends Event<Void> {
|
||||||
* The available commands.
|
* The available commands.
|
||||||
*/
|
*/
|
||||||
public enum Command {
|
public enum Command {
|
||||||
CONTINUE, SET_CURRENT_CPUS, SET_CURRENT_RAM
|
CONTINUE, SET_CURRENT_CPUS, SET_CURRENT_RAM, CHANGE_MEDIUM
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Command command;
|
private final Command command;
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,8 @@
|
||||||
<#assign cdCounter = 0/>
|
<#assign cdCounter = 0/>
|
||||||
<#list vm.drives![] as drive>
|
<#list vm.drives![] as drive>
|
||||||
<#if (drive.type!"") == "ide-cd">
|
<#if (drive.type!"") == "ide-cd">
|
||||||
- [ "-drive", "id=drive-cdrom${ cdCounter },if=none,media=cdrom,cache=none\
|
- [ "-drive", "id=drive-cdrom${ cdCounter },if=none,media=cdrom,\
|
||||||
<#if drive.file??>,file=${ drive.file }</#if>" ]
|
readonly=on<#if drive.file??>,file=${ drive.file }</#if>" ]
|
||||||
# (IDE is old, but faster than usb-storage. virtio-blk-pci does not
|
# (IDE is old, but faster than usb-storage. virtio-blk-pci does not
|
||||||
# work without file [empty drive])
|
# work without file [empty drive])
|
||||||
- [ "-device", "ide-cd,id=cd${ cdCounter },bus=ide.${ cdCounter },\
|
- [ "-device", "ide-cd,id=cd${ cdCounter },bus=ide.${ cdCounter },\
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue