@@ -363,6 +363,15 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error {
363363 // required: no
364364 // shortdesc: Only for VMs: Override the bus for the device
365365 "io.bus" : validate .Optional (validate .IsOneOf ("nvme" , "virtio-blk" , "virtio-scsi" , "auto" , "9p" , "virtiofs" , "usb" )),
366+
367+ // gendoc:generate(entity=devices, group=disk, key=attached)
368+ //
369+ // ---
370+ // type: bool
371+ // default: `true`
372+ // required: no
373+ // shortdesc: Only for VMs: Whether the disk is attached or ejected
374+ "attached" : validate .Optional (validate .IsBool ),
366375 }
367376
368377 err := d .config .Validate (rules )
@@ -570,6 +579,16 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error {
570579 return err
571580 }
572581
582+ if d .config ["attached" ] != "" {
583+ if instConf .Type () == instancetype .Container {
584+ return errors .New ("Attached configuration cannot be applied to containers" )
585+ } else if instConf .Type () == instancetype .Any {
586+ return errors .New ("Attached configuration cannot be applied to profiles" )
587+ } else if contentType != db .StoragePoolVolumeContentTypeISO {
588+ return errors .New ("Attached configuration can only be applied to ISO volumes" )
589+ }
590+ }
591+
573592 if contentType == db .StoragePoolVolumeContentTypeBlock {
574593 if instConf .Type () == instancetype .Container {
575594 return errors .New ("Custom block volumes cannot be used on containers" )
@@ -1038,6 +1057,9 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) {
10381057 opts = append (opts , fmt .Sprintf ("cache=%s" , d .config ["io.cache" ]))
10391058 }
10401059
1060+ // Setup the attached status.
1061+ attached := util .IsTrueOrEmpty (d .config ["attached" ])
1062+
10411063 // Add I/O limits if set.
10421064 var diskLimits * deviceConfig.DiskLimits
10431065 if d .config ["limits.read" ] != "" || d .config ["limits.write" ] != "" || d .config ["limits.max" ] != "" {
@@ -1094,10 +1116,11 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) {
10941116 // Encode the file descriptor and original isoPath into the DevPath field.
10951117 runConf .Mounts = []deviceConfig.MountEntryItem {
10961118 {
1097- DevPath : fmt .Sprintf ("%s:%d:%s" , DiskFileDescriptorMountPrefix , f .Fd (), isoPath ),
1098- DevName : d .name ,
1099- FSType : "iso9660" ,
1100- Opts : opts ,
1119+ DevPath : fmt .Sprintf ("%s:%d:%s" , DiskFileDescriptorMountPrefix , f .Fd (), isoPath ),
1120+ DevName : d .name ,
1121+ FSType : "iso9660" ,
1122+ Opts : opts ,
1123+ Attached : attached ,
11011124 },
11021125 }
11031126
@@ -1124,10 +1147,11 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) {
11241147 // Encode the file descriptor and original isoPath into the DevPath field.
11251148 runConf .Mounts = []deviceConfig.MountEntryItem {
11261149 {
1127- DevPath : fmt .Sprintf ("%s:%d:%s" , DiskFileDescriptorMountPrefix , f .Fd (), isoPath ),
1128- DevName : d .name ,
1129- FSType : "iso9660" ,
1130- Opts : opts ,
1150+ DevPath : fmt .Sprintf ("%s:%d:%s" , DiskFileDescriptorMountPrefix , f .Fd (), isoPath ),
1151+ DevName : d .name ,
1152+ FSType : "iso9660" ,
1153+ Opts : opts ,
1154+ Attached : attached ,
11311155 },
11321156 }
11331157
@@ -1142,19 +1166,21 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) {
11421166 clusterName , userName := d .cephCreds ()
11431167 runConf .Mounts = []deviceConfig.MountEntryItem {
11441168 {
1145- DevPath : DiskGetRBDFormat (clusterName , userName , fields [0 ], fields [1 ]),
1146- DevName : d .name ,
1147- Opts : opts ,
1148- Limits : diskLimits ,
1169+ DevPath : DiskGetRBDFormat (clusterName , userName , fields [0 ], fields [1 ]),
1170+ DevName : d .name ,
1171+ Opts : opts ,
1172+ Limits : diskLimits ,
1173+ Attached : attached ,
11491174 },
11501175 }
11511176 } else {
11521177 // Default to block device or image file passthrough first.
11531178 mount := deviceConfig.MountEntryItem {
1154- DevPath : d .config ["source" ],
1155- DevName : d .name ,
1156- Opts : opts ,
1157- Limits : diskLimits ,
1179+ DevPath : d .config ["source" ],
1180+ DevName : d .name ,
1181+ Opts : opts ,
1182+ Limits : diskLimits ,
1183+ Attached : attached ,
11581184 }
11591185
11601186 // Mount the pool volume and update srcPath to mount path so it can be recognised as dir
@@ -1208,10 +1234,11 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) {
12081234 }
12091235
12101236 mount := deviceConfig.MountEntryItem {
1211- DevPath : DiskGetRBDFormat (clusterName , userName , poolName , d .config ["source" ]),
1212- DevName : d .name ,
1213- Opts : opts ,
1214- Limits : diskLimits ,
1237+ DevPath : DiskGetRBDFormat (clusterName , userName , poolName , d .config ["source" ]),
1238+ DevName : d .name ,
1239+ Opts : opts ,
1240+ Limits : diskLimits ,
1241+ Attached : attached ,
12151242 }
12161243
12171244 if contentType == db .StoragePoolVolumeContentTypeISO {
@@ -1410,9 +1437,10 @@ func (d *disk) postStart() error {
14101437
14111438// Update applies configuration changes to a started device.
14121439func (d * disk ) Update (oldDevices deviceConfig.Devices , isRunning bool ) error {
1440+ expandedDevices := d .inst .ExpandedDevices ()
1441+
14131442 if internalInstance .IsRootDiskDevice (d .config ) {
14141443 // Make sure we have a valid root disk device (and only one).
1415- expandedDevices := d .inst .ExpandedDevices ()
14161444 newRootDiskDeviceKey , _ , err := internalInstance .GetRootDiskDevice (expandedDevices .CloneNative ())
14171445 if err != nil {
14181446 return fmt .Errorf ("Detect root disk device: %w" , err )
@@ -1486,7 +1514,7 @@ func (d *disk) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
14861514 }
14871515 }
14881516
1489- // Only apply IO limits if instance is running.
1517+ // Only apply IO limits and attach/detach logic if instance is running.
14901518 if isRunning {
14911519 runConf := deviceConfig.RunConfig {}
14921520
@@ -1518,6 +1546,20 @@ func (d *disk) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
15181546 Limits : diskLimits ,
15191547 },
15201548 }
1549+
1550+ oldAttached := util .IsTrueOrEmpty (oldDevices [d .name ]["attached" ])
1551+ newAttached := util .IsTrueOrEmpty (expandedDevices [d .name ]["attached" ])
1552+ if ! oldAttached && newAttached {
1553+ runConf .Mounts = append (runConf .Mounts , deviceConfig.MountEntryItem {
1554+ DevName : d .name ,
1555+ Attached : true ,
1556+ })
1557+ } else if oldAttached && ! newAttached {
1558+ runConf .Mounts = append (runConf .Mounts , deviceConfig.MountEntryItem {
1559+ DevName : d .name ,
1560+ Attached : false ,
1561+ })
1562+ }
15211563 }
15221564
15231565 err := d .inst .DeviceEventHandler (& runConf )
0 commit comments