Skip to content

Commit d642d7a

Browse files
committed
Add vsphere reconciliation logic
1 parent 333d5e1 commit d642d7a

File tree

2 files changed

+590
-0
lines changed

2 files changed

+590
-0
lines changed

pkg/controller/vsphere/reconciler.go

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
package vsphere
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
machinev1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1"
8+
vsphereapi "github.com/openshift/machine-api-operator/pkg/apis/vsphereprovider/v1alpha1"
9+
machinecontroller "github.com/openshift/machine-api-operator/pkg/controller/machine"
10+
"github.com/pkg/errors"
11+
"github.com/vmware/govmomi/object"
12+
"github.com/vmware/govmomi/vim25/types"
13+
"k8s.io/klog"
14+
)
15+
16+
const (
17+
minMemMB = 2048
18+
minCPU = 2
19+
diskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndConsolidate)
20+
)
21+
22+
// Reconciler runs the logic to reconciles a machine resource towards its desired state
23+
type Reconciler struct {
24+
*machineScope
25+
}
26+
27+
func newReconciler(scope *machineScope) *Reconciler {
28+
return &Reconciler{
29+
machineScope: scope,
30+
}
31+
}
32+
33+
// create creates machine if it does not exists.
34+
func (r *Reconciler) create() error {
35+
if err := validateMachine(*r.machine, *r.providerSpec); err != nil {
36+
return fmt.Errorf("%v: failed validating machine provider spec: %v", r.machine.GetName(), err)
37+
}
38+
39+
_, err := findVM(r.machineScope)
40+
if err != nil {
41+
if !IsNotFound(err) {
42+
return err
43+
}
44+
if r.machineScope.session.IsVC() {
45+
klog.Infof("%v: cloning", r.machine.GetName())
46+
return clone(r.machineScope)
47+
}
48+
return fmt.Errorf("%v: not connected to a vCenter", r.machine.GetName())
49+
}
50+
51+
return nil
52+
}
53+
54+
// update finds a vm and reconciles the machine resource status against it.
55+
func (r *Reconciler) update() error {
56+
if err := validateMachine(*r.machine, *r.providerSpec); err != nil {
57+
return fmt.Errorf("%v: failed validating machine provider spec: %v", r.machine.GetName(), err)
58+
}
59+
60+
vmRef, err := findVM(r.machineScope)
61+
if err != nil {
62+
if !IsNotFound(err) {
63+
return err
64+
}
65+
if r.machineScope.session.IsVC() {
66+
return errors.Wrap(err, "catastrophic, machine not found on update")
67+
}
68+
}
69+
70+
vm := &virtualMachine{
71+
Context: r.machineScope.Context,
72+
Obj: object.NewVirtualMachine(r.machineScope.session.Client.Client, vmRef),
73+
Ref: vmRef,
74+
}
75+
76+
// TODO: we won't always want to reconcile power state
77+
// but as per comment in clone() function, powering on right on creation might be problematic
78+
if ok, err := vm.reconcilePowerState(); err != nil || !ok {
79+
return err
80+
}
81+
return r.reconcileMachineWithCloudState(vm)
82+
}
83+
84+
// exists returns true if machine exists.
85+
func (r *Reconciler) exists() (bool, error) {
86+
if err := validateMachine(*r.machine, *r.providerSpec); err != nil {
87+
return false, fmt.Errorf("%v: failed validating machine provider spec: %v", r.machine.GetName(), err)
88+
}
89+
90+
if _, err := findVM(r.machineScope); err != nil {
91+
if !IsNotFound(err) {
92+
return false, err
93+
}
94+
klog.Infof("%v: does not exist", r.machine.GetName())
95+
return false, nil
96+
}
97+
klog.Infof("%v: already exists", r.machine.GetName())
98+
return true, nil
99+
}
100+
101+
func (r *Reconciler) delete() error {
102+
// TODO: implement
103+
return nil
104+
}
105+
106+
// reconcileMachineWithCloudState reconcile machineSpec and status with the latest cloud state
107+
func (r *Reconciler) reconcileMachineWithCloudState(vm *virtualMachine) error {
108+
klog.V(3).Infof("%v: reconciling machine with cloud state", r.machine.GetName())
109+
// TODO: reconcile providerID
110+
// TODO: reconcile task
111+
return r.reconcileNetwork(vm)
112+
}
113+
114+
func (r *Reconciler) reconcileNetwork(vm *virtualMachine) error {
115+
// TODO: implement
116+
return nil
117+
}
118+
119+
func validateMachine(machine machinev1.Machine, providerSpec vsphereapi.VSphereMachineProviderSpec) error {
120+
if machine.Labels[machinev1.MachineClusterIDLabel] == "" {
121+
return machinecontroller.InvalidMachineConfiguration("%v: missing %q label", machine.GetName(), machinev1.MachineClusterIDLabel)
122+
}
123+
124+
return nil
125+
}
126+
127+
func findVM(s *machineScope) (types.ManagedObjectReference, error) {
128+
uuid := string(s.machine.UID)
129+
objRef, err := s.GetSession().FindRefByInstanceUUID(s, uuid)
130+
if err != nil {
131+
return types.ManagedObjectReference{}, err
132+
}
133+
if objRef == nil {
134+
return types.ManagedObjectReference{}, errNotFound{instanceUUID: true, uuid: uuid}
135+
}
136+
return objRef.Reference(), nil
137+
}
138+
139+
// errNotFound is returned by the findVM function when a VM is not found.
140+
type errNotFound struct {
141+
instanceUUID bool
142+
uuid string
143+
}
144+
145+
func (e errNotFound) Error() string {
146+
if e.instanceUUID {
147+
return fmt.Sprintf("vm with instance uuid %s not found", e.uuid)
148+
}
149+
return fmt.Sprintf("vm with bios uuid %s not found", e.uuid)
150+
}
151+
152+
func IsNotFound(err error) bool {
153+
switch err.(type) {
154+
case errNotFound, *errNotFound:
155+
return true
156+
default:
157+
return false
158+
}
159+
}
160+
161+
func clone(s *machineScope) error {
162+
vmTemplate, err := s.GetSession().FindVM(*s, s.providerSpec.Template)
163+
if err != nil {
164+
return err
165+
}
166+
167+
var folderPath, datastorePath, resourcepoolPath string
168+
if s.providerSpec.Workspace != nil {
169+
folderPath = s.providerSpec.Workspace.Folder
170+
datastorePath = s.providerSpec.Workspace.Datastore
171+
resourcepoolPath = s.providerSpec.Workspace.ResourcePool
172+
}
173+
174+
folder, err := s.GetSession().Finder.FolderOrDefault(s, folderPath)
175+
if err != nil {
176+
return errors.Wrapf(err, "unable to get folder for %q", folderPath)
177+
}
178+
179+
datastore, err := s.GetSession().Finder.DatastoreOrDefault(s, datastorePath)
180+
if err != nil {
181+
return errors.Wrapf(err, "unable to get datastore for %q", datastorePath)
182+
}
183+
184+
resourcepool, err := s.GetSession().Finder.ResourcePoolOrDefault(s, resourcepoolPath)
185+
if err != nil {
186+
return errors.Wrapf(err, "unable to get resource pool for %q", resourcepool)
187+
}
188+
189+
numCPUs := s.providerSpec.NumCPUs
190+
if numCPUs < minCPU {
191+
numCPUs = minCPU
192+
}
193+
numCoresPerSocket := s.providerSpec.NumCoresPerSocket
194+
if numCoresPerSocket == 0 {
195+
numCoresPerSocket = numCPUs
196+
}
197+
memMiB := s.providerSpec.MemoryMiB
198+
if memMiB == 0 {
199+
memMiB = minMemMB
200+
}
201+
202+
spec := types.VirtualMachineCloneSpec{
203+
Config: &types.VirtualMachineConfigSpec{
204+
Annotation: s.machine.GetName(),
205+
// Assign the clone's InstanceUUID the value of the Kubernetes Machine
206+
// object's UID. This allows lookup of the cloned VM prior to knowing
207+
// the VM's UUID.
208+
InstanceUuid: string(s.machine.UID),
209+
Flags: newVMFlagInfo(),
210+
// TODO: set userData
211+
//ExtraConfig: extraConfig,
212+
// TODO: set devices
213+
//DeviceChange: deviceSpecs,
214+
NumCPUs: numCPUs,
215+
NumCoresPerSocket: numCoresPerSocket,
216+
MemoryMB: memMiB,
217+
},
218+
Location: types.VirtualMachineRelocateSpec{
219+
Datastore: types.NewReference(datastore.Reference()),
220+
DiskMoveType: diskMoveType,
221+
Folder: types.NewReference(folder.Reference()),
222+
Pool: types.NewReference(resourcepool.Reference()),
223+
},
224+
// This is implicit, but making it explicit as it is important to not
225+
// power the VM on before its virtual hardware is created and the MAC
226+
// address(es) used to build and inject the VM with cloud-init metadata
227+
// are generated.
228+
PowerOn: false,
229+
}
230+
231+
task, err := vmTemplate.Clone(s, folder, s.machine.GetName(), spec)
232+
if err != nil {
233+
return errors.Wrapf(err, "error triggering clone op for machine %v", s)
234+
}
235+
236+
// TODO: store task in providerStatus/conditions?
237+
klog.V(3).Infof("%v: running task: %v", s.machine.GetName(), task.Name())
238+
return nil
239+
}
240+
241+
func newVMFlagInfo() *types.VirtualMachineFlagInfo {
242+
diskUUIDEnabled := true
243+
return &types.VirtualMachineFlagInfo{
244+
DiskUuidEnabled: &diskUUIDEnabled,
245+
}
246+
}
247+
248+
type virtualMachine struct {
249+
context.Context
250+
Ref types.ManagedObjectReference
251+
Obj *object.VirtualMachine
252+
}
253+
254+
func (vm *virtualMachine) reconcilePowerState() (bool, error) {
255+
powerState, err := vm.getPowerState()
256+
if err != nil {
257+
return false, err
258+
}
259+
switch powerState {
260+
case types.VirtualMachinePowerStatePoweredOff:
261+
klog.Infof("powering on")
262+
_, err := vm.powerOnVM()
263+
if err != nil {
264+
return false, errors.Wrapf(err, "failed to trigger power on op for vm %q", vm)
265+
}
266+
// TODO: store task in providerStatus/conditions?
267+
klog.Infof("requeue to wait for power on state")
268+
return false, nil
269+
case types.VirtualMachinePowerStatePoweredOn:
270+
klog.Infof("powered on")
271+
default:
272+
return false, errors.Errorf("unexpected power state %q for vm %q", powerState, vm)
273+
}
274+
275+
return true, nil
276+
}
277+
278+
func (vm *virtualMachine) powerOnVM() (string, error) {
279+
task, err := vm.Obj.PowerOn(vm.Context)
280+
if err != nil {
281+
return "", err
282+
}
283+
return task.Reference().Value, nil
284+
}
285+
286+
func (vm *virtualMachine) powerOffVM() (string, error) {
287+
task, err := vm.Obj.PowerOff(vm.Context)
288+
if err != nil {
289+
return "", err
290+
}
291+
return task.Reference().Value, nil
292+
}
293+
294+
func (vm *virtualMachine) getPowerState() (types.VirtualMachinePowerState, error) {
295+
powerState, err := vm.Obj.PowerState(vm.Context)
296+
if err != nil {
297+
return "", err
298+
}
299+
300+
switch powerState {
301+
case types.VirtualMachinePowerStatePoweredOn:
302+
return types.VirtualMachinePowerStatePoweredOn, nil
303+
case types.VirtualMachinePowerStatePoweredOff:
304+
return types.VirtualMachinePowerStatePoweredOff, nil
305+
case types.VirtualMachinePowerStateSuspended:
306+
return types.VirtualMachinePowerStateSuspended, nil
307+
default:
308+
return "", errors.Errorf("unexpected power state %q for vm %v", powerState, vm)
309+
}
310+
}

0 commit comments

Comments
 (0)