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: object
|
||||
type: object
|
||||
cdromImage:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
bootindex:
|
||||
type: integer
|
||||
required:
|
||||
- volumeClaimTemplate
|
||||
oneOf:
|
||||
- properties:
|
||||
volumeClaimTemplate:
|
||||
required:
|
||||
- volumeClaimTemplate
|
||||
- properties:
|
||||
cdromImage:
|
||||
required:
|
||||
- cdromImage
|
||||
default: []
|
||||
display:
|
||||
type: object
|
||||
|
|
|
|||
|
|
@ -136,15 +136,28 @@ data:
|
|||
drives:
|
||||
<#assign drvCounter = 0/>
|
||||
<#list cr.spec.vm.disks.asList() as disk>
|
||||
<#if disk.volumeClaimTemplate.metadata??
|
||||
<#if disk.volumeClaimTemplate??
|
||||
&& disk.volumeClaimTemplate.metadata??
|
||||
&& disk.volumeClaimTemplate.metadata.name??>
|
||||
<#assign name = disk.volumeClaimTemplate.metadata.name.asString>
|
||||
<#else>
|
||||
<#assign name = "" + drvCounter>
|
||||
</#if>
|
||||
<#if disk.volumeClaimTemplate??>
|
||||
- type: raw
|
||||
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>
|
||||
|
||||
display:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ spec:
|
|||
volumeDevices:
|
||||
<#assign diskCounter = 0/>
|
||||
<#list cr.spec.vm.disks.asList() as disk>
|
||||
<#if disk.volumeClaimTemplate??>
|
||||
<#if disk.volumeClaimTemplate.metadata??
|
||||
&& disk.volumeClaimTemplate.metadata.name??>
|
||||
<#assign diskName = "disk-" + disk.volumeClaimTemplate.metadata.name.asString>
|
||||
|
|
@ -45,6 +46,7 @@ spec:
|
|||
- name: ${ diskName }
|
||||
devicePath: /dev/${ diskName }
|
||||
<#assign diskCounter = diskCounter + 1/>
|
||||
</#if>
|
||||
</#list>
|
||||
securityContext:
|
||||
privileged: true
|
||||
|
|
@ -72,6 +74,7 @@ spec:
|
|||
claimName: vmop-image-repository
|
||||
<#assign diskCounter = 0/>
|
||||
<#list cr.spec.vm.disks.asList() as disk>
|
||||
<#if disk.volumeClaimTemplate??>
|
||||
<#if disk.volumeClaimTemplate.metadata??
|
||||
&& disk.volumeClaimTemplate.metadata.name??>
|
||||
<#assign claimName = disk.volumeClaimTemplate.metadata.name.asString>
|
||||
|
|
@ -84,6 +87,7 @@ spec:
|
|||
persistentVolumeClaim:
|
||||
claimName: ${ claimName }
|
||||
<#assign diskCounter = diskCounter + 1/>
|
||||
</#if>
|
||||
</#list>
|
||||
hostNetwork: true
|
||||
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,
|
||||
int index, JsonObject diskDef, WatchChannel channel)
|
||||
throws ApiException {
|
||||
if (!diskDef.has("volumeClaimTemplate")) {
|
||||
return;
|
||||
}
|
||||
var pvcObject = new DynamicKubernetesObject();
|
||||
var pvcRaw = GsonPtr.to(pvcObject.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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
||||
private void checkDrives() {
|
||||
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;
|
||||
}
|
||||
if (drive.resource == null) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
package org.jdrupes.vmoperator.runner.qemu;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
|
@ -106,6 +105,7 @@ public class QemuMonitor extends Component {
|
|||
}
|
||||
attach(new RamController(channel(), this));
|
||||
attach(new CpuController(channel(), this));
|
||||
attach(new CdromController(channel(), this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -246,7 +246,7 @@ public class QemuMonitor extends Component {
|
|||
fire(new MonitorReady());
|
||||
return;
|
||||
}
|
||||
if (response.has("return")) {
|
||||
if (response.has("return") || response.has("error")) {
|
||||
String executed = executing.poll();
|
||||
logger.fine(
|
||||
() -> String.format("(Previous \"monitor(in)\" is result "
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import java.nio.file.Paths;
|
|||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
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.Options;
|
||||
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 static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.CONTINUE;
|
||||
import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_CPUS;
|
||||
|
|
@ -155,7 +157,8 @@ import org.jgrapes.util.events.WatchFile;
|
|||
* @enduml
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace" })
|
||||
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace",
|
||||
"PMD.DataflowAnomalyAnalysis" })
|
||||
public class Runner extends Component {
|
||||
|
||||
public static final String APP_NAME = "vmrunner";
|
||||
|
|
@ -331,37 +334,49 @@ public class Runner extends Component {
|
|||
return yamlMapper.readValue(out.toString(), JsonNode.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||
"PMD.AvoidInstantiatingObjectsInLoops" })
|
||||
private void updateConfiguration(Map<String, Object> conf) {
|
||||
logger.fine(() -> "Updating configuration");
|
||||
Optional.ofNullable((Map<String, Object>) conf.get("vm"))
|
||||
.map(vm -> vm.get("currentRam")).map(Configuration::parseMemory)
|
||||
.ifPresent(cr -> {
|
||||
if (config.vm.currentRam != null
|
||||
&& config.vm.currentRam.equals(cr)) {
|
||||
return;
|
||||
var newConf = yamlMapper.convertValue(conf, Configuration.class);
|
||||
Optional.ofNullable(newConf.vm.currentRam).ifPresent(cr -> {
|
||||
if (config.vm.currentRam != null
|
||||
&& config.vm.currentRam.equals(cr)) {
|
||||
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) {
|
||||
config.vm.currentRam = cr;
|
||||
if (state.get() == State.RUNNING) {
|
||||
fire(new MonitorCommand(SET_CURRENT_RAM, cr));
|
||||
}
|
||||
fire(new ChangeMediumCommand("cd" + cdCounter, newFile));
|
||||
}
|
||||
});
|
||||
Optional.ofNullable((Map<String, Object>) conf.get("vm"))
|
||||
.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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
cdCounter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
public enum Command {
|
||||
CONTINUE, SET_CURRENT_CPUS, SET_CURRENT_RAM
|
||||
CONTINUE, SET_CURRENT_CPUS, SET_CURRENT_RAM, CHANGE_MEDIUM
|
||||
}
|
||||
|
||||
private final Command command;
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@
|
|||
<#assign cdCounter = 0/>
|
||||
<#list vm.drives![] as drive>
|
||||
<#if (drive.type!"") == "ide-cd">
|
||||
- [ "-drive", "id=drive-cdrom${ cdCounter },if=none,media=cdrom,cache=none\
|
||||
<#if drive.file??>,file=${ drive.file }</#if>" ]
|
||||
- [ "-drive", "id=drive-cdrom${ cdCounter },if=none,media=cdrom,\
|
||||
readonly=on<#if drive.file??>,file=${ drive.file }</#if>" ]
|
||||
# (IDE is old, but faster than usb-storage. virtio-blk-pci does not
|
||||
# work without file [empty drive])
|
||||
- [ "-device", "ide-cd,id=cd${ cdCounter },bus=ide.${ cdCounter },\
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue