Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 22 additions & 3 deletions deploy/00crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down Expand Up @@ -1202,6 +1208,12 @@ spec:
podRef:
description: PodRef is the name of the pod
type: string
powerOffTargetVm:
description: |-
PowerOffTargetVM specifies whether to leave the migrated VM powered off
in the target cloud instead of starting it. Useful when post-migration
tasks must run before the VM's first boot. Defaults to false.
type: boolean
vmName:
description: VMName is the name of the VM getting migrated from VMWare
to Openstack
Expand Down Expand Up @@ -1379,8 +1391,10 @@ spec:
- openstackRef
type: object
networkMapping:
description: NetworkMapping is the reference to the NetworkMapping
resource that defines source to destination network mappings
description: |-
NetworkMapping is the reference to the NetworkMapping resource that defines source to destination network mappings.
Optional: when the selected VMs have no attached VMware networks, no NetworkMapping is required and this field
may be empty. The controller will skip network reconciliation in that case.
type: string
osFamily:
description: OSFamily is the OS type of the virtual machine
Expand Down Expand Up @@ -1430,7 +1444,6 @@ spec:
type: string
required:
- destination
- networkMapping
- source
type: object
type: object
Expand Down Expand Up @@ -2383,6 +2396,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down
25 changes: 22 additions & 3 deletions deploy/installer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down Expand Up @@ -1202,6 +1208,12 @@ spec:
podRef:
description: PodRef is the name of the pod
type: string
powerOffTargetVm:
description: |-
PowerOffTargetVM specifies whether to leave the migrated VM powered off
in the target cloud instead of starting it. Useful when post-migration
tasks must run before the VM's first boot. Defaults to false.
type: boolean
vmName:
description: VMName is the name of the VM getting migrated from VMWare
to Openstack
Expand Down Expand Up @@ -1379,8 +1391,10 @@ spec:
- openstackRef
type: object
networkMapping:
description: NetworkMapping is the reference to the NetworkMapping
resource that defines source to destination network mappings
description: |-
NetworkMapping is the reference to the NetworkMapping resource that defines source to destination network mappings.
Optional: when the selected VMs have no attached VMware networks, no NetworkMapping is required and this field
may be empty. The controller will skip network reconciliation in that case.
type: string
osFamily:
description: OSFamily is the OS type of the virtual machine
Expand Down Expand Up @@ -1430,7 +1444,6 @@ spec:
type: string
required:
- destination
- networkMapping
- source
type: object
type: object
Expand Down Expand Up @@ -2383,6 +2396,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down
6 changes: 6 additions & 0 deletions k8s/migration/api/v1alpha1/migration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ type MigrationSpec struct {
// +optional
DisconnectSourceNetwork bool `json:"disconnectSourceNetwork,omitempty"`

// PowerOffTargetVM specifies whether to leave the migrated VM powered off
// in the target cloud instead of starting it. Useful when post-migration
// tasks must run before the VM's first boot. Defaults to false.
// +optional
PowerOffTargetVM bool `json:"powerOffTargetVm,omitempty"`

// NetworkOverrides is the JSON-serialized per-NIC overrides for IP and MAC preservation
// +optional
NetworkOverrides string `json:"networkOverrides,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions k8s/migration/api/v1alpha1/migrationplan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type MigrationPlanStrategy struct {
DisconnectSourceNetwork bool `json:"disconnectSourceNetwork,omitempty"`
// +kubebuilder:default:=false
ArrayOffload bool `json:"arrayOffload,omitempty"`
// PowerOffTargetVM, when true, leaves the migrated VM powered off in the
// target cloud instead of starting it after creation.
// +kubebuilder:default:=false
PowerOffTargetVM bool `json:"powerOffTargetVm,omitempty"`
}

// AdvancedOptions defines advanced configuration options for the migration process
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ spec:
podRef:
description: PodRef is the name of the pod
type: string
powerOffTargetVm:
description: |-
PowerOffTargetVM specifies whether to leave the migrated VM powered off
in the target cloud instead of starting it. Useful when post-migration
tasks must run before the VM's first boot. Defaults to false.
type: boolean
vmName:
description: VMName is the name of the VM getting migrated from VMWare
to Openstack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ spec:
performHealthChecks:
default: false
type: boolean
powerOffTargetVm:
default: false
description: |-
PowerOffTargetVM, when true, leaves the migrated VM powered off in the
target cloud instead of starting it after creation.
type: boolean
type:
enum:
- hot
Expand Down
2 changes: 2 additions & 0 deletions k8s/migration/internal/controller/migrationplan_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ func (r *MigrationPlanReconciler) CreateMigration(ctx context.Context,
// PodRef will be set in the migration controller
InitiateCutover: migrationplan.Spec.MigrationStrategy.AdminInitiatedCutOver,
DisconnectSourceNetwork: migrationplan.Spec.MigrationStrategy.DisconnectSourceNetwork,
PowerOffTargetVM: migrationplan.Spec.MigrationStrategy.PowerOffTargetVM,
NetworkOverrides: networkOverrides,
MigrationType: migrationplan.Spec.MigrationStrategy.Type,
},
Expand Down Expand Up @@ -1519,6 +1520,7 @@ func (r *MigrationPlanReconciler) buildBaseConfigMapData(
"REMOVE_VMWARE_TOOLS": strconv.FormatBool(migrationplan.Spec.AdvancedOptions.RemoveVMwareTools),
"ACKNOWLEDGE_NETWORK_CONFLICT_RISK": strconv.FormatBool(migrationplan.Spec.AdvancedOptions.AcknowledgeNetworkConflictRisk),
"DISCONNECT_SOURCE_NETWORK": strconv.FormatBool(migrationobj.Spec.DisconnectSourceNetwork),
"POWER_OFF_TARGET_VM": strconv.FormatBool(migrationobj.Spec.PowerOffTargetVM),
}
}

Expand Down
3 changes: 3 additions & 0 deletions v2v-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func main() {
Thumbprint: thumbprint,
Convert: migrationparams.OpenstackConvert,
DisconnectSourceNetwork: migrationparams.DisconnectSourceNetwork,
PowerOffTargetVM: migrationparams.PowerOffTargetVM,
Openstackclients: openstackclients,
Vcclient: vcclient,
VMops: vmops,
Expand Down Expand Up @@ -221,6 +222,7 @@ TYPE=%s
TARGET_FLAVOR_ID=%s
TARGET_AVAILABILITY_ZONE=%s
DISCONNECT_SOURCE_NETWORK=%s
POWER_OFF_TARGET_VM=%v
SECURITY_GROUPS=%s
SERVER_GROUP=%s
RDM_DISKS=%s
Expand All @@ -239,6 +241,7 @@ ACKNOWLEDGE_NETWORK_CONFLICT_RISK=%s`,
migrationparams.TARGET_FLAVOR_ID,
migrationparams.TargetAvailabilityZone,
migrationparams.DisconnectSourceNetwork,
migrationparams.PowerOffTargetVM,
migrationparams.SecurityGroups,
migrationparams.ServerGroup,
migrationparams.RDMDisks,
Expand Down
10 changes: 10 additions & 0 deletions v2v-helper/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Migrate struct {
Thumbprint string
Convert bool
DisconnectSourceNetwork bool
PowerOffTargetVM bool
Openstackclients openstack.OpenstackOperations
Vcclient vcenter.VCenterOperations
VMops vm.VMOperations
Expand Down Expand Up @@ -1637,6 +1638,15 @@ func (migobj *Migrate) CreateTargetInstance(ctx context.Context, vminfo vm.VMInf

migobj.logMessage(fmt.Sprintf("VM created successfully: ID: %s", newVM.ID))

if migobj.PowerOffTargetVM {
migobj.logMessage("PowerOffTargetVM is set: powering off the target VM and skipping health checks")
if err := openstackops.StopServer(ctx, newVM.ID, *vjailbreakSettings); err != nil {
return errors.Wrap(err, "failed to power off target VM")
}
migobj.logMessage("Target VM powered off successfully")
return nil
}

if migobj.PerformHealthChecks {
err = migobj.HealthCheck(vminfo, ipaddresses)
if err != nil {
Expand Down
53 changes: 53 additions & 0 deletions v2v-helper/migrate/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,59 @@ func TestCreateTargetInstance(t *testing.T) {
assert.NoError(t, err)
}

func TestCreateTargetInstance_PowerOffTargetVM(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()

mockOpenStackOps := openstack.NewMockOpenstackOperations(ctrl)
mockOpenStackOps.EXPECT().GetClosestFlavour(gomock.Any(), gomock.Any(), gomock.Any()).Return(&flavors.Flavor{
VCPUs: 2,
RAM: 2048,
}, nil).AnyTimes()
mockOpenStackOps.EXPECT().GetNetwork(gomock.Any(), gomock.Any()).Return(&networks.Network{}, nil).AnyTimes()
mockOpenStackOps.EXPECT().CreatePort(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&ports.Port{
MACAddress: "mac-address",
}, nil).AnyTimes()
mockOpenStackOps.EXPECT().CreateVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&servers.Server{}, nil).AnyTimes()
mockOpenStackOps.EXPECT().WaitUntilVMActive(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes()
mockOpenStackOps.EXPECT().GetFlavor(gomock.Any(), "flavor-id").Return(&flavors.Flavor{
VCPUs: 2,
RAM: 2048,
}, nil).AnyTimes()
mockOpenStackOps.EXPECT().GetSecurityGroupIDs(gomock.Any(), gomock.Any(), gomock.Any()).Return([]string{}, nil).AnyTimes()
// With PowerOffTargetVM set, CreateTargetInstance must call StopServer exactly once.
mockOpenStackOps.EXPECT().StopServer(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

inputvminfo := vm.VMInfo{
Name: "test-vm",
OSType: "linux",
Mac: []string{
"mac-address-1",
"mac-address-2",
},
}

fakeCtrlClient := ctrlfake.NewClientBuilder().WithObjects(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.VjailbreakSettingsConfigMapName,
Namespace: constants.NamespaceMigrationSystem,
},
Data: map[string]string{},
}).Build()

migobj := Migrate{
Openstackclients: mockOpenStackOps,
Networknames: []string{"network-name-1", "network-name-2"},
InPod: false,
TargetFlavorId: "flavor-id",
K8sClient: fakeCtrlClient,
PowerOffTargetVM: true,
}
err := migobj.CreateTargetInstance(ctx, inputvminfo, []string{"network-id-1", "network-id-2"}, []string{"port-id-1", "port-id-2"}, []string{"ip-address-1", "ip-address-2"}, -1)
assert.NoError(t, err)
}

func TestCreateTargetInstance_AdvancedMapping_Ports(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down
1 change: 1 addition & 0 deletions v2v-helper/openstack/openstackops.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type OpenstackOperations interface {
FindDevice(volumeID string) (string, error)
ManageExistingVolume(name string, ref map[string]interface{}, host string, volumeType string) (*volumes.Volume, error)
WaitUntilVMActive(ctx context.Context, vmID string) (bool, error)
StopServer(ctx context.Context, serverID string, vjailbreakSettings k8sutils.VjailbreakSettings) error
GetIsSimpleNetwork(ctx context.Context, networkID string) (bool, error)
// GetCinderVolumeServices returns Cinder volume services (Host, Status, State)
// Returns a slice of structs with these fields - defined in implementation package to avoid import cycles
Expand Down
14 changes: 14 additions & 0 deletions v2v-helper/openstack/openstackops_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions v2v-helper/pkg/utils/openstackopsutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,32 @@ func (osclient *OpenStackClients) WaitUntilVMActive(ctx context.Context, vmID st
return true, nil
}

// StopServer issues a power-off (stop) request for an OpenStack server and
// waits until it reaches the SHUTOFF state. Used when the migration is
// configured to leave the target VM powered off (see MigrationStrategy
// .powerOffTargetVm).
func (osclient *OpenStackClients) StopServer(ctx context.Context, serverID string, vjailbreakSettings k8sutils.VjailbreakSettings) error {
PrintLog(fmt.Sprintf("OPENSTACK API: Stopping server %s", serverID))
if err := servers.Stop(ctx, osclient.ComputeClient, serverID).ExtractErr(); err != nil {
return fmt.Errorf("failed to issue stop for server %s: %s", serverID, err)
}
for i := 0; i < vjailbreakSettings.VMActiveWaitRetryLimit; i++ {
result, err := servers.Get(ctx, osclient.ComputeClient, serverID).Extract()
if err != nil {
return fmt.Errorf("failed to get server status while waiting for shutoff: %s", err)
}
if result.Status == "SHUTOFF" {
PrintLog(fmt.Sprintf("Server %s is now powered off", serverID))
return nil
}
if result.Status == "ERROR" {
return fmt.Errorf("server %s went into ERROR state while stopping", serverID)
}
time.Sleep(time.Duration(vjailbreakSettings.VMActiveWaitIntervalSeconds) * time.Second)
}
return fmt.Errorf("server %s did not reach SHUTOFF after %d retries", serverID, vjailbreakSettings.VMActiveWaitRetryLimit)
}

// ManageExistingVolume manages an existing volume on the storage backend into Cinder
// Uses the manageable_volumes endpoint which is the standard Cinder manage API
func (osclient *OpenStackClients) ManageExistingVolume(name string, ref map[string]interface{}, host string, volumeType string) (*volumes.Volume, error) {
Expand Down
2 changes: 2 additions & 0 deletions v2v-helper/pkg/utils/vcenterutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type MigrationParams struct {
TargetAvailabilityZone string
VMwareMachineName string
DisconnectSourceNetwork bool
PowerOffTargetVM bool
SecurityGroups string
ServerGroup string
RDMDisks string
Expand Down Expand Up @@ -101,6 +102,7 @@ func GetMigrationParams(ctx context.Context, client client.Client) (*MigrationPa
TargetAvailabilityZone: string(configMap.Data["TARGET_AVAILABILITY_ZONE"]),
VMwareMachineName: string(configMap.Data["VMWARE_MACHINE_OBJECT_NAME"]),
DisconnectSourceNetwork: string(configMap.Data["DISCONNECT_SOURCE_NETWORK"]) == constants.TrueString,
PowerOffTargetVM: string(configMap.Data["POWER_OFF_TARGET_VM"]) == constants.TrueString,
SecurityGroups: string(configMap.Data["SECURITY_GROUPS"]),
ServerGroup: string(configMap.Data["SERVER_GROUP"]),
RDMDisks: string(configMap.Data["RDM_DISK_NAMES"]),
Expand Down