Merge branch 'main' into testing
This commit is contained in:
commit
8bf6692b8d
12 changed files with 178 additions and 44 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* VM-Operator
|
||||||
* Copyright (C) 2023 Michael N. Lipp
|
* Copyright (C) 2023,2025 Michael N. Lipp
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -83,8 +83,18 @@
|
||||||
* [YamlConfigurationStore] *-right[hidden]- [Controller]
|
* [YamlConfigurationStore] *-right[hidden]- [Controller]
|
||||||
*
|
*
|
||||||
* [Manager] *-- [Controller]
|
* [Manager] *-- [Controller]
|
||||||
* [Controller] *-- [VmWatcher]
|
* Component VmMonitor as VmMonitor <<internal>>
|
||||||
* [Controller] *-- [Reconciler]
|
* [Controller] *-- [VmMonitor]
|
||||||
|
* [VmMonitor] -right[hidden]- [PoolMonitor]
|
||||||
|
* Component PoolMonitor as PoolMonitor <<internal>>
|
||||||
|
* [Controller] *-- [PoolMonitor]
|
||||||
|
* Component PodMonitor as PodMonitor <<internal>>
|
||||||
|
* [Controller] *-- [PodMonitor]
|
||||||
|
* [PodMonitor] -up[hidden]- VmMonitor
|
||||||
|
* Component DisplaySecretMonitor as DisplaySecretMonitor <<internal>>
|
||||||
|
* [Controller] *-- [DisplaySecretMonitor]
|
||||||
|
* [DisplaySecretMonitor] -up[hidden]- VmMonitor
|
||||||
|
* [Controller] *-left- [Reconciler]
|
||||||
* [Controller] -right[hidden]- [GuiHttpServer]
|
* [Controller] -right[hidden]- [GuiHttpServer]
|
||||||
*
|
*
|
||||||
* [Manager] *-down- [GuiSocketServer:8080]
|
* [Manager] *-down- [GuiSocketServer:8080]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
apiVersion: "vmoperator.jdrupes.org/v1"
|
apiVersion: "vmoperator.jdrupes.org/v1"
|
||||||
kind: VirtualMachine
|
kind: VirtualMachine
|
||||||
metadata:
|
metadata:
|
||||||
namespace: vmop-dev
|
namespace: vmop-test
|
||||||
name: unittest-vm
|
name: test-vm
|
||||||
spec:
|
spec:
|
||||||
image:
|
image:
|
||||||
repository: docker-registry.lan.mnl.de
|
repository: docker-registry.lan.mnl.de
|
||||||
|
|
|
||||||
111
org.jdrupes.vmoperator.manager/test-resources/kustomization.yaml
Normal file
111
org.jdrupes.vmoperator.manager/test-resources/kustomization.yaml
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../../deploy
|
||||||
|
|
||||||
|
namespace: vmop-test
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- patch: |-
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: vmop-image-repository
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: local-path
|
||||||
|
|
||||||
|
- patch: |-
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: vm-operator
|
||||||
|
data:
|
||||||
|
# Keep in sync with config.yaml
|
||||||
|
config.yaml: |
|
||||||
|
"/Manager":
|
||||||
|
# clusterName: "test"
|
||||||
|
"/Controller":
|
||||||
|
"/Reconciler":
|
||||||
|
runnerData:
|
||||||
|
storageClassName: null
|
||||||
|
loadBalancerService:
|
||||||
|
labels:
|
||||||
|
label1: label1
|
||||||
|
label2: toBeReplaced
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/loadBalancerIPs: 192.168.168.1
|
||||||
|
metallb.universe.tf/ip-allocated-from-pool: single-common
|
||||||
|
metallb.universe.tf/allow-shared-ip: single-common
|
||||||
|
"/GuiSocketServer":
|
||||||
|
port: 8888
|
||||||
|
"/GuiHttpServer":
|
||||||
|
# This configures the GUI
|
||||||
|
"/ConsoleWeblet":
|
||||||
|
"/WebConsole":
|
||||||
|
"/LoginConlet":
|
||||||
|
users:
|
||||||
|
- name: admin
|
||||||
|
fullName: Administrator
|
||||||
|
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
|
||||||
|
- name: test1
|
||||||
|
fullName: Test Account
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test2
|
||||||
|
fullName: Test Account
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test3
|
||||||
|
fullName: Test Account
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
"/RoleConfigurator":
|
||||||
|
rolesByUser:
|
||||||
|
# User admin has role admin
|
||||||
|
admin:
|
||||||
|
- admin
|
||||||
|
test1:
|
||||||
|
- user
|
||||||
|
test2:
|
||||||
|
- user
|
||||||
|
test3:
|
||||||
|
- user
|
||||||
|
# All users have role other
|
||||||
|
"*":
|
||||||
|
- other
|
||||||
|
replace: false
|
||||||
|
"/RoleConletFilter":
|
||||||
|
conletTypesByRole:
|
||||||
|
# Admins can use all conlets
|
||||||
|
admin:
|
||||||
|
- "*"
|
||||||
|
user:
|
||||||
|
- org.jdrupes.vmoperator.vmviewer.VmViewer
|
||||||
|
# Others cannot use any conlet (except login conlet to log out)
|
||||||
|
other:
|
||||||
|
- org.jgrapes.webconlet.locallogin.LoginConlet
|
||||||
|
"/ComponentCollector":
|
||||||
|
"/VmAccess":
|
||||||
|
displayResource:
|
||||||
|
preferredIpVersion: ipv4
|
||||||
|
syncPreviewsFor:
|
||||||
|
- role: user
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: vm-operator
|
||||||
|
patch: |-
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/image
|
||||||
|
value: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.manager:test
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/imagePullPolicy
|
||||||
|
value: Always
|
||||||
|
- op: replace
|
||||||
|
path: /spec/replicas
|
||||||
|
value: 0
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ class BasicTests {
|
||||||
private static APIResource vmsContext;
|
private static APIResource vmsContext;
|
||||||
private static K8sV1DeploymentStub mgrDeployment;
|
private static K8sV1DeploymentStub mgrDeployment;
|
||||||
private static K8sDynamicStub vmStub;
|
private static K8sDynamicStub vmStub;
|
||||||
private static final String VM_NAME = "unittest-vm";
|
private static final String VM_NAME = "test-vm";
|
||||||
private static final Object EXISTS = new Object();
|
private static final Object EXISTS = new Object();
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
|
|
@ -54,7 +54,7 @@ class BasicTests {
|
||||||
|
|
||||||
// Update manager pod by scaling deployment
|
// Update manager pod by scaling deployment
|
||||||
mgrDeployment
|
mgrDeployment
|
||||||
= K8sV1DeploymentStub.get(client, "vmop-dev", "vm-operator");
|
= K8sV1DeploymentStub.get(client, "vmop-test", "vm-operator");
|
||||||
mgrDeployment.scale(0);
|
mgrDeployment.scale(0);
|
||||||
mgrDeployment.scale(1);
|
mgrDeployment.scale(1);
|
||||||
waitForManager();
|
waitForManager();
|
||||||
|
|
@ -65,13 +65,13 @@ class BasicTests {
|
||||||
vmsContext = apiRes.get();
|
vmsContext = apiRes.get();
|
||||||
|
|
||||||
// Cleanup existing VM
|
// Cleanup existing VM
|
||||||
K8sDynamicStub.get(client, vmsContext, "vmop-dev", VM_NAME)
|
K8sDynamicStub.get(client, vmsContext, "vmop-test", VM_NAME)
|
||||||
.delete();
|
.delete();
|
||||||
ListOptions listOpts = new ListOptions();
|
ListOptions listOpts = new ListOptions();
|
||||||
listOpts.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
listOpts.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||||
+ "app.kubernetes.io/instance=" + VM_NAME + ","
|
+ "app.kubernetes.io/instance=" + VM_NAME + ","
|
||||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
||||||
var secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
|
var secrets = K8sV1SecretStub.list(client, "vmop-test", listOpts);
|
||||||
for (var secret : secrets) {
|
for (var secret : secrets) {
|
||||||
secret.delete();
|
secret.delete();
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ class BasicTests {
|
||||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||||
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
||||||
+ "app.kubernetes.io/instance=" + VM_NAME);
|
+ "app.kubernetes.io/instance=" + VM_NAME);
|
||||||
var knownPvcs = K8sV1PvcStub.list(client, "vmop-dev", listOpts);
|
var knownPvcs = K8sV1PvcStub.list(client, "vmop-test", listOpts);
|
||||||
for (var pvc : knownPvcs) {
|
for (var pvc : knownPvcs) {
|
||||||
pvc.delete();
|
pvc.delete();
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +112,7 @@ class BasicTests {
|
||||||
@AfterAll
|
@AfterAll
|
||||||
static void tearDownAfterClass() throws Exception {
|
static void tearDownAfterClass() throws Exception {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
K8sDynamicStub.get(client, vmsContext, "vmop-dev", VM_NAME)
|
K8sDynamicStub.get(client, vmsContext, "vmop-test", VM_NAME)
|
||||||
.delete();
|
.delete();
|
||||||
deletePvcs();
|
deletePvcs();
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ class BasicTests {
|
||||||
void testConfigMap()
|
void testConfigMap()
|
||||||
throws IOException, InterruptedException, ApiException {
|
throws IOException, InterruptedException, ApiException {
|
||||||
K8sV1ConfigMapStub stub
|
K8sV1ConfigMapStub stub
|
||||||
= K8sV1ConfigMapStub.get(client, "vmop-dev", VM_NAME);
|
= K8sV1ConfigMapStub.get(client, "vmop-test", VM_NAME);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -134,7 +134,7 @@ class BasicTests {
|
||||||
// Check config map
|
// Check config map
|
||||||
var config = stub.model().get();
|
var config = stub.model().get();
|
||||||
Map<List<? extends Object>, Object> toCheck = Map.of(
|
Map<List<? extends Object>, Object> toCheck = Map.of(
|
||||||
List.of("namespace"), "vmop-dev",
|
List.of("namespace"), "vmop-test",
|
||||||
List.of("name"), VM_NAME,
|
List.of("name"), VM_NAME,
|
||||||
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
||||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||||
|
|
@ -191,7 +191,7 @@ class BasicTests {
|
||||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
||||||
Collection<K8sV1SecretStub> secrets = null;
|
Collection<K8sV1SecretStub> secrets = null;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
|
secrets = K8sV1SecretStub.list(client, "vmop-test", listOpts);
|
||||||
if (secrets.size() > 0) {
|
if (secrets.size() > 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +207,7 @@ class BasicTests {
|
||||||
@Test
|
@Test
|
||||||
void testRunnerPvc() throws ApiException, InterruptedException {
|
void testRunnerPvc() throws ApiException, InterruptedException {
|
||||||
var stub
|
var stub
|
||||||
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-runner-data");
|
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-runner-data");
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -227,7 +227,7 @@ class BasicTests {
|
||||||
@Test
|
@Test
|
||||||
void testSystemDiskPvc() throws ApiException, InterruptedException {
|
void testSystemDiskPvc() throws ApiException, InterruptedException {
|
||||||
var stub
|
var stub
|
||||||
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-system-disk");
|
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-system-disk");
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -248,7 +248,7 @@ class BasicTests {
|
||||||
@Test
|
@Test
|
||||||
void testDisk1Pvc() throws ApiException, InterruptedException {
|
void testDisk1Pvc() throws ApiException, InterruptedException {
|
||||||
var stub
|
var stub
|
||||||
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-disk-1");
|
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-disk-1");
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -274,7 +274,7 @@ class BasicTests {
|
||||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
|
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
|
||||||
+ "\", \"value\": \"Running\"}]"),
|
+ "\", \"value\": \"Running\"}]"),
|
||||||
client.defaultPatchOptions()).isPresent());
|
client.defaultPatchOptions()).isPresent());
|
||||||
var stub = K8sV1PodStub.get(client, "vmop-dev", VM_NAME);
|
var stub = K8sV1PodStub.get(client, "vmop-test", VM_NAME);
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -303,7 +303,7 @@ class BasicTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoadBalancer() throws ApiException, InterruptedException {
|
public void testLoadBalancer() throws ApiException, InterruptedException {
|
||||||
var stub = K8sV1ServiceStub.get(client, "vmop-dev", VM_NAME);
|
var stub = K8sV1ServiceStub.get(client, "vmop-test", VM_NAME);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (stub.model().isPresent()) {
|
if (stub.model().isPresent()) {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,15 @@ import org.jgrapes.util.events.WatchFile;
|
||||||
*
|
*
|
||||||
* success --> Running
|
* success --> Running
|
||||||
*
|
*
|
||||||
|
* state Running {
|
||||||
|
* state Booting
|
||||||
|
* state Booted
|
||||||
|
*
|
||||||
|
* [*] -right-> Booting
|
||||||
|
* Booting -down-> Booting: VserportChanged[guest agent connected]/fire GetOsinfo
|
||||||
|
* Booting --> Booted: Osinfo
|
||||||
|
* }
|
||||||
|
*
|
||||||
* state Terminating {
|
* state Terminating {
|
||||||
* state terminate <<entryPoint>>
|
* state terminate <<entryPoint>>
|
||||||
* state qemuRunning <<choice>>
|
* state qemuRunning <<choice>>
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -12,10 +12,10 @@ layout: vm-operator
|
||||||
An overview display shows the current CPU and RAM usage and a graph
|
An overview display shows the current CPU and RAM usage and a graph
|
||||||
with recent changes.
|
with recent changes.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The detail display lists all VMs. From here you can start and stop
|
The detail display lists all VMs. From here you can start and stop
|
||||||
the VMs and adjust the CPU and RAM usages (modifies the definition
|
the VMs and adjust the CPU and RAM usages (modifies the definition
|
||||||
in kubernetes).
|
in kubernetes).
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ layout: vm-operator
|
||||||
|
|
||||||
When users log into the web GUI, they have already authenticated with the
|
When users log into the web GUI, they have already authenticated with the
|
||||||
VM-Operator. In some environments, requiring an additional login on the
|
VM-Operator. In some environments, requiring an additional login on the
|
||||||
guest OS can be cumbersome. To enhance the user experience, the VM-Operator
|
guest OS can be annoying. To enhance the user experience, the VM-Operator
|
||||||
supports automatic login on the guest operating system, thus eliminating
|
supports automatic login on the guest operating system, thus eliminating
|
||||||
the need for multiple logins. However, this feature requires specific
|
the need for multiple logins. However, this feature requires specific
|
||||||
support from the guest OS.
|
support from the guest OS.
|
||||||
|
|
@ -18,9 +18,9 @@ support from the guest OS.
|
||||||
|
|
||||||
Automatic login requires an agent running inside the guest OS. Similar
|
Automatic login requires an agent running inside the guest OS. Similar
|
||||||
to QEMU's standard guest agent, the VM-Operator agent communicates with
|
to QEMU's standard guest agent, the VM-Operator agent communicates with
|
||||||
the host via a tty device (`/dev/virtio-ports/org.jdrupes.vmop_agent.0`). On
|
the host via a tty device (provided in the guest as
|
||||||
modern Linux systems, `udev` can detect this device and trigger the start
|
`/dev/virtio-ports/org.jdrupes.vmop_agent.0`). On modern Linux systems, `udev` can
|
||||||
of an associated systemd service.
|
detect this device and trigger the start of an associated systemd service.
|
||||||
|
|
||||||
Sample configuration files for a VM-Operator agent are available
|
Sample configuration files for a VM-Operator agent are available
|
||||||
[here](https://github.com/mnlipp/VM-Operator/tree/main/dev-example/vmop-agent).
|
[here](https://github.com/mnlipp/VM-Operator/tree/main/dev-example/vmop-agent).
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 588 KiB After Width: | Height: | Size: 591 KiB |
|
|
@ -9,7 +9,7 @@ layout: vm-operator
|
||||||
|
|
||||||
# Welcome to VM-Operator
|
# Welcome to VM-Operator
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This project provides an easy to use and flexible solution for
|
This project provides an easy to use and flexible solution for
|
||||||
running QEMU/KVM based virtual machines (VMs) in Kubernetes pods.
|
running QEMU/KVM based virtual machines (VMs) in Kubernetes pods.
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,20 @@ spec:
|
||||||
The `retention` specifies how long the assignment of a VM from the pool to
|
The `retention` specifies how long the assignment of a VM from the pool to
|
||||||
a user remains valid after the user closes the console. This ensures that
|
a user remains valid after the user closes the console. This ensures that
|
||||||
a user can resume work within this timeframe without the risk of another
|
a user can resume work within this timeframe without the risk of another
|
||||||
user taking over the VM. The time is specified as
|
user taking over the VM. The time is specified as an
|
||||||
[ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations).
|
[ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations).
|
||||||
|
Specifying an ISO 8601 time is also supported, but if you consider
|
||||||
|
using an absolute time, check again whether a dedicated VM for the user
|
||||||
|
isn't the more appropriate choice.
|
||||||
|
|
||||||
Setting `loginOnAssignment` to `true` triggers automatic login of the
|
Setting `loginOnAssignment` to `true` (defaults to `false`) triggers automatic
|
||||||
user (as described in [section auto login](auto-login.html)) when
|
login of the user (as described in [section auto login](auto-login.html))
|
||||||
the VM is assigned. The `permissions` property specifies the actions
|
when the VM is assigned. The `permissions` property specifies the actions
|
||||||
that users or roles can perform on assigned VMs.
|
that users or roles can perform on assigned VMs. The `may` property defaults
|
||||||
|
to `[accessConsole]` if not specified.
|
||||||
|
|
||||||
VMs become members of one (or more) pools by adding the pool name to
|
VMs become members of one (or more) pools by adding the pool name to
|
||||||
the `spec.pools` array, as shown below:
|
the `spec.pools` array in the VM definition, as shown below:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: "vmoperator.jdrupes.org/v1"
|
apiVersion: "vmoperator.jdrupes.org/v1"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue