Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c7a1650
feat: adding ProxyVM and HotAdd constants + CRD
Mar 24, 2026
4380e47
chore: propagating Proxy Name from main to migrate.go
Mar 24, 2026
5acb5cf
feat: adding RunCommandToWriter to execute data copy on storage fabri…
Mar 24, 2026
8c639a2
feat: adding HotAddCopy and Proxy VM name as an input for the migrati…
Mar 24, 2026
a1afdfa
chore: adding remaining CRD
Mar 24, 2026
12ac4d8
feat: adding HotAddCopy to UI
Mar 24, 2026
ba1c62a
feat: adding migrate logic for hotaddcopy
Mar 24, 2026
d782d66
feat(helper function - hotaddcopy): hotAddGetVCenterClient extracts V…
Mar 24, 2026
0225d38
fix: merge conflict fix
Mar 25, 2026
1038af9
feat(hotaddcopy): fucntion for linked copy VM
Mar 25, 2026
d92eb8f
feat(hotaddcopy): helper functions to delete linked copy VM and list …
Mar 25, 2026
172448a
feat(hotaddcopy): orchestration steps
Mar 25, 2026
c691d6f
feat(hotaddcopy): validate hot add copy and VMIP selection
Mar 25, 2026
92c849f
feat(hotaddcopy - orchestration): adding update disk info to start mi…
Mar 25, 2026
83c3555
feat(hotaddcopy - orchestration): adding create a linked VM and a def…
Mar 25, 2026
eab873d
feat(hotaddcopy): hotAddAddDiskTo VM - hot adds a disk to a running VM
Mar 25, 2026
ebe2745
feat(hotaddcopy): hotAddRemoveDiskFrom VM adding a function for the same
Mar 25, 2026
26c97cf
fix: adding HotAddCopy as a migration FormValue in the Rolling Migrat…
Mar 25, 2026
db21e16
feat(hotaddcopy - orchestration): adding disk backing path for linked…
Mar 26, 2026
88c93df
feat(hotaddcopy - orchestration): adding disk backing path for linked…
Mar 26, 2026
88a94b7
feat(hotaddcopy): hotAddDetectBlockDevice polls lsblk on the proxy VM…
Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion deploy/00crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,11 @@ spec:
- windowsGuest
- linuxGuest
type: string
proxyVMName:
description: |-
ProxyVMName is the name of the VM in vCenter that will receive the source disk
via SCSI HotAdd. Required when StorageCopyMethod is HotAddCopy.
type: string
source:
description: Source is the source details for the virtual machine
properties:
Expand All @@ -1351,10 +1356,11 @@ spec:
default: normal
description: |-
StorageCopyMethod indicates the method to use for storage migration
Valid values: "normal" (default), "StorageAcceleratedCopy"
Valid values: "normal" (default), "StorageAcceleratedCopy", "HotAddCopy"
enum:
- normal
- StorageAcceleratedCopy
- HotAddCopy
type: string
storageMapping:
description: |-
Expand Down
8 changes: 7 additions & 1 deletion deploy/installer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,11 @@ spec:
- windowsGuest
- linuxGuest
type: string
proxyVMName:
description: |-
ProxyVMName is the name of the VM in vCenter that will receive the source disk
via SCSI HotAdd. Required when StorageCopyMethod is HotAddCopy.
type: string
source:
description: Source is the source details for the virtual machine
properties:
Expand All @@ -1351,10 +1356,11 @@ spec:
default: normal
description: |-
StorageCopyMethod indicates the method to use for storage migration
Valid values: "normal" (default), "StorageAcceleratedCopy"
Valid values: "normal" (default), "StorageAcceleratedCopy", "HotAddCopy"
enum:
- normal
- StorageAcceleratedCopy
- HotAddCopy
type: string
storageMapping:
description: |-
Expand Down
8 changes: 6 additions & 2 deletions k8s/migration/api/v1alpha1/migrationtemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ type MigrationTemplateSpec struct {
// +optional
ArrayCredsMapping string `json:"arrayCredsMapping,omitempty"`
// StorageCopyMethod indicates the method to use for storage migration
// Valid values: "normal" (default), "StorageAcceleratedCopy"
// +kubebuilder:validation:Enum=normal;StorageAcceleratedCopy
// Valid values: "normal" (default), "StorageAcceleratedCopy", "HotAddCopy"
// +kubebuilder:validation:Enum=normal;StorageAcceleratedCopy;HotAddCopy
// +kubebuilder:default:=normal
// +optional
StorageCopyMethod string `json:"storageCopyMethod,omitempty"`
// ProxyVMName is the name of the VM in vCenter that will receive the source disk
// via SCSI HotAdd. Required when StorageCopyMethod is HotAddCopy.
// +optional
ProxyVMName string `json:"proxyVMName,omitempty"`
// Source is the source details for the virtual machine
Source MigrationTemplateSource `json:"source"`
// Destination is the destination details for the virtual machine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ spec:
- windowsGuest
- linuxGuest
type: string
proxyVMName:
description: |-
ProxyVMName is the name of the VM in vCenter that will receive the source disk
via SCSI HotAdd. Required when StorageCopyMethod is HotAddCopy.
type: string
source:
description: Source is the source details for the virtual machine
properties:
Expand All @@ -85,10 +90,11 @@ spec:
default: normal
description: |-
StorageCopyMethod indicates the method to use for storage migration
Valid values: "normal" (default), "StorageAcceleratedCopy"
Valid values: "normal" (default), "StorageAcceleratedCopy", "HotAddCopy"
enum:
- normal
- StorageAcceleratedCopy
- HotAddCopy
type: string
storageMapping:
description: |-
Expand Down
13 changes: 10 additions & 3 deletions k8s/migration/internal/controller/migrationplan_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ const VDDKDirectory = "/home/ubuntu/vmware-vix-disklib-distrib"
// StorageCopyMethod is the storage copy method value for Storage Accelerated copy
const StorageCopyMethod = "StorageAcceleratedCopy"

// HotAddCopyMethod is the storage copy method value for VMware HotAdd SCSI transport
const HotAddCopyMethod = "HotAddCopy"

// MigrationPlanReconciler reconciles a MigrationPlan object
type MigrationPlanReconciler struct {
client.Client
Expand Down Expand Up @@ -465,7 +468,8 @@ func GetVMwareMachineForVM(ctx context.Context, r *MigrationPlanReconciler, vm s
//nolint:gocyclo
func (r *MigrationPlanReconciler) ReconcileMigrationPlanJob(ctx context.Context,
migrationplan *vjailbreakv1alpha1.MigrationPlan,
scope *scope.MigrationPlanScope) (ctrl.Result, error) {
scope *scope.MigrationPlanScope,
) (ctrl.Result, error) {
totalVMs := 0
for _, group := range migrationplan.Spec.VirtualMachines {
totalVMs += len(group)
Expand Down Expand Up @@ -531,7 +535,6 @@ func (r *MigrationPlanReconciler) ReconcileMigrationPlanJob(ctx context.Context,
latest.Status.MigrationMessage = ""
return r.Status().Update(ctx, latest)
})

if err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to reset status for retry")
}
Expand Down Expand Up @@ -1555,10 +1558,14 @@ func (r *MigrationPlanReconciler) setOSFamilyAndStorageFields(
configMapData["OS_FAMILY"] = migrationtemplate.Spec.OSFamily
}

if migrationtemplate.Spec.StorageCopyMethod == StorageCopyMethod {
switch migrationtemplate.Spec.StorageCopyMethod {
case StorageCopyMethod:
configMapData["STORAGE_COPY_METHOD"] = StorageCopyMethod
configMapData["VENDOR_TYPE"] = arraycreds.Spec.VendorType
configMapData["ARRAY_CREDS_MAPPING"] = migrationtemplate.Spec.ArrayCredsMapping
case HotAddCopyMethod:
configMapData["STORAGE_COPY_METHOD"] = HotAddCopyMethod
configMapData["PROXY_VM_NAME"] = migrationtemplate.Spec.ProxyVMName
}

return nil
Expand Down
9 changes: 9 additions & 0 deletions pkg/common/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,15 @@ const (
// StorageCopyMethod is the default value for storage copy method
StorageCopyMethod = "StorageAcceleratedCopy"

// HotAddCopy is the storage copy method that uses VMware HotAdd SCSI transport.
// A proxy VM on the same ESXi host or shared datastore receives the source disk
// directly via the SCSI fabric, bypassing the ESXi NFC management-network path.
// Beta: cold migration only, SCSI disks only.
HotAddCopy = "HotAddCopy"

// ProxyVMNameKey is the ConfigMap key for the proxy VM name used in HotAdd copy
ProxyVMNameKey = "PROXY_VM_NAME"

// MaxPowerOffRetryLimit is the max number of retries for power off status check
MaxPowerOffRetryLimit = 3

Expand Down
2 changes: 1 addition & 1 deletion ui/src/features/migration/MigrationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export interface FormValues extends Record<string, unknown> {
networkMappings?: { source: string; target: string }[]
storageMappings?: { source: string; target: string }[]
arrayCredsMappings?: { source: string; target: string }[]
storageCopyMethod?: 'normal' | 'StorageAcceleratedCopy'
storageCopyMethod?: 'normal' | 'StorageAcceleratedCopy' | 'HotAddCopy'
// Cluster selection fields
vmwareCluster?: string // Format: "credName:datacenter:clusterName"
pcdCluster?: string // PCD cluster ID
Expand Down
75 changes: 54 additions & 21 deletions ui/src/features/migration/NetworkAndStorageMappingStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
FormControlLabel,
Radio,
Alert,
Chip
Chip,
TextField
} from '@mui/material'
import { useEffect, useMemo, useCallback, useRef } from 'react'
import { ResourceMappingTableNew as ResourceMappingTable } from './components'
Expand All @@ -35,7 +36,8 @@ export interface ResourceMap {
// Storage copy method options
export const STORAGE_COPY_METHOD_OPTIONS = [
{ value: 'normal', label: 'Standard Copy' },
{ value: 'StorageAcceleratedCopy', label: 'Storage Accelerated Copy' }
{ value: 'StorageAcceleratedCopy', label: 'Storage Accelerated Copy' },
{ value: 'HotAddCopy', label: 'HotAdd Copy' }
] as const

export type StorageCopyMethod = (typeof STORAGE_COPY_METHOD_OPTIONS)[number]['value']
Expand All @@ -50,6 +52,7 @@ interface NetworkAndStorageMappingStepProps {
storageMappings?: ResourceMap[]
arrayCredsMappings?: ResourceMap[]
storageCopyMethod?: StorageCopyMethod
proxyVMName?: string
}
onChange: (key: string) => (value: any) => void
networkMappingError?: string
Expand Down Expand Up @@ -330,7 +333,7 @@ export default function NetworkAndStorageMappingStep({
value={option.value}
control={<Radio />}
label={
option.value === 'StorageAcceleratedCopy' ? (
option.value === 'StorageAcceleratedCopy' || option.value === 'HotAddCopy' ? (
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 1 }}>
<Box component="span">{option.label}</Box>
<Chip
Expand Down Expand Up @@ -360,24 +363,7 @@ export default function NetworkAndStorageMappingStep({
</RadioGroup>
</Box>

{storageCopyMethod === 'normal' ? (
<>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Select source and target storage to automatically create mappings. All storage
devices must be mapped in order to proceed.
</Typography>
<ResourceMappingTable
sourceItems={vmWareStorage}
targetItems={openstackStorage}
sourceLabel="VMware Datastore"
targetLabel="PCD Volume Type"
values={params.storageMappings || []}
onChange={(value) => onChange('storageMappings')(value)}
oneToManyMapping
fieldPrefix="storageMapping"
/>
</>
) : (
{storageCopyMethod === 'StorageAcceleratedCopy' ? (
<>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Map datastores to storage array credentials for storage array data copy.
Expand Down Expand Up @@ -413,6 +399,53 @@ export default function NetworkAndStorageMappingStep({
</>
)}
</>
) : storageCopyMethod === 'HotAddCopy' ? (
<>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
HotAdd attaches the source disk directly to a proxy VM on the same ESXi host,
bypassing the management network. SCSI disks only. Cold migration only (Beta).
</Typography>
<TextField
label="Proxy VM Name"
placeholder="e.g. vjailbreak-proxy"
value={params.proxyVMName || ''}
onChange={(e) => onChange('proxyVMName')(e.target.value)}
fullWidth
size="small"
helperText="Name of the VM in vCenter that will receive the source disk via SCSI HotAdd"
sx={{ mb: 2 }}
/>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Select source and target storage for Cinder volume creation:
</Typography>
<ResourceMappingTable
sourceItems={vmWareStorage}
targetItems={openstackStorage}
sourceLabel="VMware Datastore"
targetLabel="PCD Volume Type"
values={params.storageMappings || []}
onChange={(value) => onChange('storageMappings')(value)}
oneToManyMapping
fieldPrefix="storageMapping"
/>
</>
) : (
<>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Select source and target storage to automatically create mappings. All storage
devices must be mapped in order to proceed.
</Typography>
<ResourceMappingTable
sourceItems={vmWareStorage}
targetItems={openstackStorage}
sourceLabel="VMware Datastore"
targetLabel="PCD Volume Type"
values={params.storageMappings || []}
onChange={(value) => onChange('storageMappings')(value)}
oneToManyMapping
fieldPrefix="storageMapping"
/>
</>
)}

{storageMappingError && <FormHelperText error>{storageMappingError}</FormHelperText>}
Expand Down
23 changes: 15 additions & 8 deletions ui/src/features/migration/RollingMigrationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,8 @@ interface FormValues extends Record<string, unknown> {
cutoverEndTime?: string
postMigrationScript?: string
osFamily?: string
useGPU?: boolean
useFlavorless?: boolean
disconnectSourceNetwork?: boolean
fallbackToDHCP?: boolean
networkPersistence?: boolean
storageCopyMethod?: 'normal' | 'StorageAcceleratedCopy'
storageCopyMethod?: 'normal' | 'StorageAcceleratedCopy' | 'HotAddCopy'
proxyVMName?: string
}

type RollingMigrationRHFValues = {
Expand Down Expand Up @@ -1107,7 +1103,7 @@ export default function RollingMigrationFormDrawer({
const handleMappingsChange = (key: string) => (value: unknown) => {
markTouched('mapResources')

if (!Array.isArray(value) && key !== 'storageCopyMethod') {
if (!Array.isArray(value) && key !== 'storageCopyMethod' && key !== 'proxyVMName') {
return
}

Expand Down Expand Up @@ -1138,6 +1134,11 @@ export default function RollingMigrationFormDrawer({
getParamsUpdater('storageCopyMethod')(value)
}
break
case 'proxyVMName':
if (typeof value === 'string') {
getParamsUpdater('proxyVMName')(value)
}
break
default:
break
}
Expand Down Expand Up @@ -1212,6 +1213,7 @@ export default function RollingMigrationFormDrawer({
const storageCopyMethod = (params.storageCopyMethod || 'normal') as
| 'normal'
| 'StorageAcceleratedCopy'
| 'HotAddCopy'

if (selectedVMs.length > 0) {
if (
Expand Down Expand Up @@ -1392,6 +1394,10 @@ export default function RollingMigrationFormDrawer({
...(storageCopyMethod !== 'StorageAcceleratedCopy' &&
storageMappingResponse?.metadata?.name && {
storageMapping: storageMappingResponse.metadata.name
}),
...(storageCopyMethod === 'HotAddCopy' &&
params.proxyVMName && {
proxyVMName: params.proxyVMName
})
}
})
Expand Down Expand Up @@ -3560,7 +3566,8 @@ export default function RollingMigrationFormDrawer({
networkMappings: networkMappings,
storageMappings: storageMappings,
arrayCredsMappings: arrayCredsMappings,
storageCopyMethod: params.storageCopyMethod as any
storageCopyMethod: params.storageCopyMethod as any,
proxyVMName: params.proxyVMName
}}
onChange={handleMappingsChange}
networkMappingError={networkMappingError}
Expand Down
Loading
Loading