Can resize disk image when adding a new disk image to a VM
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		@@ -394,6 +394,40 @@ impl DiskFileInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get disk virtual size, if available
 | 
				
			||||||
 | 
					    pub fn virtual_size(&self) -> Option<FileSize> {
 | 
				
			||||||
 | 
					        match self.format {
 | 
				
			||||||
 | 
					            DiskFileFormat::Raw { .. } => Some(self.file_size),
 | 
				
			||||||
 | 
					            DiskFileFormat::QCow2 { virtual_size } => Some(virtual_size),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Resize disk
 | 
				
			||||||
 | 
					    pub fn resize(&self, new_size: FileSize) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
 | 
				
			||||||
 | 
					        cmd.arg("resize")
 | 
				
			||||||
 | 
					            .arg("-f")
 | 
				
			||||||
 | 
					            .arg(match self.format {
 | 
				
			||||||
 | 
					                DiskFileFormat::QCow2 { .. } => "qcow2",
 | 
				
			||||||
 | 
					                DiskFileFormat::Raw { .. } => "raw",
 | 
				
			||||||
 | 
					                f => anyhow::bail!("Unsupported disk format for resize: {f:?}"),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .arg(&self.file_path)
 | 
				
			||||||
 | 
					            .arg(new_size.as_bytes().to_string());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output = cmd.output()?;
 | 
				
			||||||
 | 
					        if !output.status.success() {
 | 
				
			||||||
 | 
					            anyhow::bail!(
 | 
				
			||||||
 | 
					                "{} info failed, status: {}, stderr: {}",
 | 
				
			||||||
 | 
					                constants::PROGRAM_QEMU_IMAGE,
 | 
				
			||||||
 | 
					                output.status,
 | 
				
			||||||
 | 
					                String::from_utf8_lossy(&output.stderr)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,9 @@ pub struct VMFileDisk {
 | 
				
			|||||||
    /// When creating a new disk, specify the disk image template to use
 | 
					    /// When creating a new disk, specify the disk image template to use
 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
    pub from_image: Option<String>,
 | 
					    pub from_image: Option<String>,
 | 
				
			||||||
 | 
					    /// Set this variable to true to resize disk image
 | 
				
			||||||
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
 | 
					    pub resize: Option<bool>,
 | 
				
			||||||
    /// Set this variable to true to delete the disk
 | 
					    /// Set this variable to true to delete the disk
 | 
				
			||||||
    pub delete: bool,
 | 
					    pub delete: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -78,6 +81,7 @@ impl VMFileDisk {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            delete: false,
 | 
					            delete: false,
 | 
				
			||||||
            from_image: None,
 | 
					            from_image: None,
 | 
				
			||||||
 | 
					            resize: None,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -144,28 +148,40 @@ impl VMFileDisk {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if file.exists() {
 | 
					        if file.exists() {
 | 
				
			||||||
            log::debug!("File {file:?} does not exists, so it was not touched");
 | 
					            log::debug!("File {file:?} does not exists, so it was not touched");
 | 
				
			||||||
            return Ok(());
 | 
					        }
 | 
				
			||||||
 | 
					        // Create disk if required
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            // Determine file format
 | 
				
			||||||
 | 
					            let format = match self.format {
 | 
				
			||||||
 | 
					                VMDiskFormat::Raw { is_sparse } => DiskFileFormat::Raw { is_sparse },
 | 
				
			||||||
 | 
					                VMDiskFormat::QCow2 => DiskFileFormat::QCow2 {
 | 
				
			||||||
 | 
					                    virtual_size: self.size,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create / Restore disk file
 | 
				
			||||||
 | 
					            match &self.from_image {
 | 
				
			||||||
 | 
					                // Create disk file
 | 
				
			||||||
 | 
					                None => {
 | 
				
			||||||
 | 
					                    DiskFileInfo::create(&file, format, self.size)?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Restore disk image template
 | 
				
			||||||
 | 
					                Some(disk_img) => {
 | 
				
			||||||
 | 
					                    let src_file =
 | 
				
			||||||
 | 
					                        DiskFileInfo::load_file(&AppConfig::get().disk_images_file_path(disk_img))?;
 | 
				
			||||||
 | 
					                    src_file.convert(&file, format)?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let format = match self.format {
 | 
					        // Resize disk file if requested
 | 
				
			||||||
            VMDiskFormat::Raw { is_sparse } => DiskFileFormat::Raw { is_sparse },
 | 
					        if self.resize == Some(true) {
 | 
				
			||||||
            VMDiskFormat::QCow2 => DiskFileFormat::QCow2 {
 | 
					            let disk = DiskFileInfo::load_file(&file)?;
 | 
				
			||||||
                virtual_size: self.size,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create / Restore disk file
 | 
					            // Can only increase disk size
 | 
				
			||||||
        match &self.from_image {
 | 
					            if let Err(e) = disk.resize(self.size) {
 | 
				
			||||||
            // Create disk file
 | 
					                log::error!("Failed to resize disk file {}: {e:?}", self.name);
 | 
				
			||||||
            None => {
 | 
					 | 
				
			||||||
                DiskFileInfo::create(&file, format, self.size)?;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Restore disk image template
 | 
					 | 
				
			||||||
            Some(disk_img) => {
 | 
					 | 
				
			||||||
                let src_file =
 | 
					 | 
				
			||||||
                    DiskFileInfo::load_file(&AppConfig::get().disk_images_file_path(disk_img))?;
 | 
					 | 
				
			||||||
                src_file.convert(&file, format)?;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,9 @@ export interface BaseFileVMDisk {
 | 
				
			|||||||
  // For new disk only
 | 
					  // For new disk only
 | 
				
			||||||
  from_image?: string;
 | 
					  from_image?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Resize disk image after clone
 | 
				
			||||||
 | 
					  resize?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // application attributes
 | 
					  // application attributes
 | 
				
			||||||
  new?: boolean;
 | 
					  new?: boolean;
 | 
				
			||||||
  deleteType?: "keepfile" | "deletefile";
 | 
					  deleteType?: "keepfile" | "deletefile";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -218,24 +218,40 @@ function DiskInfo(p: {
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <TextInput
 | 
					      {/* Resize disk image */}
 | 
				
			||||||
        editable={true}
 | 
					      {!!p.disk.from_image && (
 | 
				
			||||||
        label="Disk size (GB)"
 | 
					        <CheckboxInput
 | 
				
			||||||
        size={{
 | 
					          editable
 | 
				
			||||||
          min:
 | 
					          checked={p.disk.resize}
 | 
				
			||||||
            ServerApi.Config.constraints.disk_size.min / (1000 * 1000 * 1000),
 | 
					          label="Resize disk file"
 | 
				
			||||||
          max:
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
            ServerApi.Config.constraints.disk_size.max / (1000 * 1000 * 1000),
 | 
					            p.disk.resize = v;
 | 
				
			||||||
        }}
 | 
					            p.onChange?.();
 | 
				
			||||||
        value={(p.disk.size / (1000 * 1000 * 1000)).toString()}
 | 
					          }}
 | 
				
			||||||
        onValueChange={(v) => {
 | 
					        />
 | 
				
			||||||
          p.disk.size = Number(v ?? "0") * 1000 * 1000 * 1000;
 | 
					      )}
 | 
				
			||||||
          p.onChange?.();
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        type="number"
 | 
					 | 
				
			||||||
        disabled={!!p.disk.from_image}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Disk size */}
 | 
				
			||||||
 | 
					      {(!p.disk.from_image || p.disk.resize === true) && (
 | 
				
			||||||
 | 
					        <TextInput
 | 
				
			||||||
 | 
					          editable={true}
 | 
				
			||||||
 | 
					          label="Disk size (GB)"
 | 
				
			||||||
 | 
					          size={{
 | 
				
			||||||
 | 
					            min:
 | 
				
			||||||
 | 
					              ServerApi.Config.constraints.disk_size.min / (1000 * 1000 * 1000),
 | 
				
			||||||
 | 
					            max:
 | 
				
			||||||
 | 
					              ServerApi.Config.constraints.disk_size.max / (1000 * 1000 * 1000),
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          value={(p.disk.size / (1000 * 1000 * 1000)).toString()}
 | 
				
			||||||
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
 | 
					            p.disk.size = Number(v ?? "0") * 1000 * 1000 * 1000;
 | 
				
			||||||
 | 
					            p.onChange?.();
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          type="number"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Disk image selection */}
 | 
				
			||||||
      <DiskImageSelect
 | 
					      <DiskImageSelect
 | 
				
			||||||
        label="Use disk image as template"
 | 
					        label="Use disk image as template"
 | 
				
			||||||
        list={p.diskImagesList}
 | 
					        list={p.diskImagesList}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user