-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathvm_toggle_wol.rs
More file actions
266 lines (236 loc) · 10.1 KB
/
vm_toggle_wol.rs
File metadata and controls
266 lines (236 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! Toggle Wake-on-LAN for Virtual Machine Network Adapters
//!
//! Demonstrates how to modify virtual machine hardware configuration by toggling the
//! Wake-on-LAN (WOL) setting on all ethernet network adapters. This example showcases
//! advanced device reconfiguration patterns including type-based filtering, polymorphic
//! device handling, and asynchronous task monitoring.
//!
//! # What is Wake-on-LAN?
//!
//! Wake-on-LAN is a hardware feature that allows a computer to be awakened from a
//! low-power state (suspended or powered off) by receiving a special network packet
//! called a "magic packet". For virtual machines, this setting is configured at the
//! virtual ethernet adapter level and allows remote power management of VMs.
//!
//! # Device Reconfiguration Workflow
//!
//! This example demonstrates the complete pattern for modifying existing virtual devices:
//!
//! 1. **Locate the VM** - Use `SearchIndex::find_by_inventory_path()` to find VM by path
//! 2. **Retrieve device list** - Use PropertyCollector with `vim_retrievable!` macro to fetch
//! `config.hardware.device` property efficiently
//! 3. **Filter devices by type** - Use `StructType` enum to identify ethernet adapters without
//! trial downcasting
//! 4. **Downcast to concrete types** - Convert `Box<dyn VirtualDeviceTrait>` to concrete
//! ethernet card types (VirtualE1000, VirtualVmxnet3, etc.)
//! 5. **Modify device properties** - Toggle the `wake_on_lan_enabled` field
//! 6. **Build configuration spec** - Wrap modified devices in `VirtualDeviceConfigSpec`
//! with operation `Edit`
//! 7. **Submit reconfiguration** - Call `VirtualMachine::reconfig_vm_task()` to apply changes
//! 8. **Monitor task completion** - Use `TaskTracker` to wait for task completion
//!
//! # Polymorphic Virtual Device Handling
//!
//! Virtual devices in vSphere are polymorphic - the API returns them as trait objects
//! (`Box<dyn VirtualDeviceTrait>`). This example demonstrates the recommended pattern
//! for handling polymorphic types:
//!
//! - **Type identification**: Use `device.data_type()` to get the `StructType` enum variant
//! - **Type filtering**: Check `obj_type.child_of(StructType::VirtualDevice)` for inheritance
//! - **Safe downcasting**: Use `as_any_box().downcast::<ConcreteType>()` to convert to concrete types
//!
//! ## Supported Ethernet Adapter Types
//!
//! The example handles all 9 virtual ethernet card types:
//! - `VirtualEthernetCard` (base type)
//! - `VirtualE1000` (Intel E1000)
//! - `VirtualE1000E` (Intel E1000e)
//! - `VirtualPcNet32` (AMD PCNet32)
//! - `VirtualVmxnet` (VMware Vmxnet)
//! - `VirtualVmxnet2` (VMware Vmxnet2)
//! - `VirtualVmxnet3` (VMware Vmxnet3 - most common)
//! - `VirtualVmxnet3Vrdma` (Vmxnet3 with RDMA)
//! - `VirtualSriovEthernetCard` (SR-IOV passthrough)
//!
//! # Example Output
//!
//! ```text
//! [INFO] Connected to VMware vCenter Server 8.0.3 build-24305161
//! [INFO] Searching for VM at path: /Home/vm/production/ubuntu_test__
//! [INFO] Found VM: /Home/vm/production/ubuntu_test__ (vm-7009)
//! [INFO] VM name: ubuntu_test__
//! [INFO] Processing 14 devices
//! [INFO] Wake-on-LAN will be disabled for the VirtualVmxnet3 adapter with key 4000
//! [INFO] Found 1 ethernet adapter(s), submitting reconfiguration
//! [INFO] Calling reconfig_vm_task...
//! [INFO] Reconfigure task created: task-247019
//! [INFO] Task in progress... (0%)
//! [INFO] ✅ Task completed successfully
//! [INFO] Successfully toggled Wake-on-LAN for 1 NIC(s)
//! ```
//!
//! # Environment Variables
//!
//! The following environment variables must be set (typically loaded from `.env` file):
//!
//! - `VIM_SERVER` - vCenter Server hostname or IP (e.g., "vcenter.example.com")
//! - `VIM_USERNAME` - vSphere username (e.g., "administrator@vsphere.local")
//! - `VIM_PASSWORD` - vSphere password
//! - `VM_INVENTORY_PATH` - Full inventory path to VM (e.g., "/Datacenter/vm/MyVM")
//!
//! # Required Permissions
//!
//! - `VirtualMachine.Config.Settings` - Required to modify VM configuration
//!
//! # Important Behavior Notes
//!
//! - **VM Power State**: This operation works regardless of VM power state (on, off, suspended)
//! - **Immediate Effect**: Configuration changes are applied immediately
//! - **WOL Functionality**: Wake-on-LAN only functions when VM is suspended or powered off,
//! not when the VM is in "guest powered off" state
//! - **Guest OS**: Changes may require guest OS network driver restart to take full effect
//! - **Idempotent**: Running the example multiple times toggles WOL on/off each time
//!
//! # Key API Patterns Demonstrated
//!
//! - **SearchIndex** for locating managed objects by inventory path
//! - **PropertyCollector** via `vim_retrievable!` macro for efficient bulk property retrieval
//! - **ObjectRetriever** pattern with `retrieve_objects_from_list()`
//! - **Type-safe downcasting** using `StructType` enum and `downcast::<T>()`
//! - **Device reconfiguration** with `VirtualDeviceConfigSpec` and operation enum
//! - **Asynchronous task monitoring** with `TaskTracker`
//!
//! # Use Cases
//!
//! - **Compliance automation** - Enforce WOL policies across VM fleet
//! - **Power management** - Enable WOL for VMs that need remote wake capability
//! - **Configuration drift remediation** - Standardize network adapter settings
//! - **Learning resource** - Study advanced vim_rs patterns for device modification
//!
//! # See Also
//!
//! - `vm_rename.rs` - Example of simple VM reconfiguration without device changes
//! - `retrieve_host_info.rs` - Example of PropertyCollector usage patterns
use anyhow::{Context, Result};
use log::info;
use tokio::time::sleep;
use vim_rs::types::convert::CastInto as _;
use std::env;
use std::time::Duration;
use snippets::connect;
use vim_rs::vim_retrievable;
use vim_rs::core::tasks::TaskTracker;
use vim_rs::mo::{SearchIndex, VirtualMachine};
use vim_rs::types::enums::VirtualDeviceConfigSpecOperationEnum;
use vim_rs::types::structs::{
VirtualDeviceConfigSpec, VirtualMachineConfigSpec,
};
use vim_rs::types::traits::{VirtualDeviceTrait, VirtualEthernetCardTrait};
vim_retrievable!(
struct Vm: VirtualMachine {
name = "name",
devices = "config.hardware.device",
}
);
async fn toggle_wol() -> Result<()> {
// Connect to vCenter
let client = connect(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")).await?;
info!("Connected to {}", client.service_content().about.full_name);
let task_tracker = TaskTracker::new(client.clone());
// Get VM inventory path from environment
let vm_path = env::var("VM_INVENTORY_PATH")
.context("VM_INVENTORY_PATH env var not set (e.g., '/Datacenter/vm/MyVM')")?;
// Find the VM using SearchIndex
let search_index = SearchIndex::new(
client.clone(),
&client
.service_content()
.search_index
.as_ref()
.context("SearchIndex not available")?
.value,
);
info!("Searching for VM at path: {}", vm_path);
let vm_moref = search_index
.find_by_inventory_path(&vm_path)
.await?
.context("VM not found at specified inventory path")?;
info!("Found VM: {} ({})", vm_path, vm_moref.value);
// Retrieve VM devices using PropertyCollector
let retriever = vim_rs::core::pc_retrieve::ObjectRetriever::new(client.clone())?;
let vms: Vec<Vm> = retriever
.retrieve_objects_from_list(&[vm_moref.clone()])
.await?;
let vm = vms
.into_iter()
.next()
.context("VM not found in retrieval results")?;
info!("VM name: {}", vm.name);
// Move devices out of the VM struct
let mut devices = vm.devices.context("No devices found on VM")?;
info!("Processing {} devices", devices.len());
// Find all ethernet cards and toggle wake_on_lan_enabled
let mut device_changes: Vec<VirtualDeviceConfigSpec> = Vec::new();
let mut nic_count = 0;
// Drain the devices vector to take ownership of each device
for device in devices.drain(..) {
let Ok(mut eth) : Result<Box<dyn VirtualEthernetCardTrait>,_> = device.into_box() else{
continue;
};
let current_wol = eth.wake_on_lan_enabled.unwrap_or(false);
eth.wake_on_lan_enabled = Some(!current_wol);
let device: Box<dyn VirtualDeviceTrait> = eth.into_box().expect("trait cast should work");
let type_: &'static str = device.data_type().into();
if current_wol {
info!("Wake-on-LAN will be disabled for the {} adapter with key {}", type_, device.key);
} else {
info!("Wake-on-LAN will be enabled for the {} adapter with key {}", type_, device.key);
}
device_changes.push(VirtualDeviceConfigSpec {
operation: Some(VirtualDeviceConfigSpecOperationEnum::Edit),
device,
..Default::default()
});
nic_count += 1;
}
if nic_count == 0 {
info!("No ethernet network adapters found on VM");
return Ok(());
}
info!(
"Found {} ethernet adapter(s), submitting reconfiguration",
nic_count
);
// Create VirtualMachineConfigSpec with device changes
// We only need to set device_change - all other fields default to None via Default trait
let config_spec = VirtualMachineConfigSpec {
device_change: Some(
device_changes
.into_iter()
.map(|spec| {
Box::new(spec) as Box<dyn vim_rs::types::traits::VirtualDeviceConfigSpecTrait>
})
.collect(),
),
..Default::default()
};
// Reconfigure the VM
let vm = VirtualMachine::new(client.clone(), &vm_moref.value);
info!("Calling reconfig_vm_task...");
let task_ref = vm.reconfig_vm_task(&config_spec).await?;
info!("Reconfigure task created: {}", task_ref.value);
// Wait for task completion
task_tracker.wait::<()>(task_ref).await?;
info!("✅ Task completed successfully");
info!("Successfully toggled Wake-on-LAN for {} NIC(s)", nic_count);
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
dotenvy::dotenv().ok();
env_logger::init();
toggle_wol().await?;
// Yield to run async drop cleanup
sleep(Duration::from_millis(100)).await;
Ok(())
}