Skip to content

Commit e37a54a

Browse files
committed
Adds VsphereDeploymentZone reconciliation logic
Signed-off-by: Sagar Muchhal <[email protected]>
1 parent 57d1f68 commit e37a54a

19 files changed

+1492
-22
lines changed

api/v1alpha3/condition_consts.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,15 @@ const (
105105

106106
const (
107107
// VCenterAvailableCondition documents the connectivity with vcenter
108-
// for a given VSphereCluster
108+
// for a given resource
109109
VCenterAvailableCondition clusterv1.ConditionType = "VCenterAvailable"
110110

111-
// VCenterUnreachableReason (Severity=Error) documents a VSphereCluster controller detecting
112-
// issues with VCenter reachability;
111+
// VCenterUnreachableReason (Severity=Error) documents a controller detecting
112+
// issues with VCenter reachability
113113
VCenterUnreachableReason = "VCenterUnreachable"
114+
)
114115

116+
const (
115117
// CredentialsAvailableCondidtion is used by VSphereClusterIdentity when a credential secret is available and unused by other VSphereClusterIdentities
116118
CredentialsAvailableCondidtion clusterv1.ConditionType = "CredentialsAvailable"
117119

@@ -124,3 +126,48 @@ const (
124126
// SecretAlreadyInUseReason is used when another VSphereClusterIdentity is using the secret
125127
SecretAlreadyInUseReason = "SecretInUse"
126128
)
129+
130+
const (
131+
// PlacementConstraintMetCondition documents whether the placement constraint is configured correctly or not.
132+
PlacementConstraintMetCondition clusterv1.ConditionType = "PlacementConstraintMet"
133+
134+
// ResourcePoolNotFoundReason (Severity=Error) documents that the resource pool in the placement constraint
135+
// associated to the VSphereDeploymentZone is misconfigured.
136+
ResourcePoolNotFoundReason = "ResourcePoolNotFound"
137+
138+
// FolderNotFoundReason (Severity=Error) documents that the folder in the placement constraint
139+
// associated to the VSphereDeploymentZone is misconfigured.
140+
FolderNotFoundReason = "FolderNotFound"
141+
)
142+
143+
const (
144+
// VSphereFailureDomainValidatedCondition documents whether the failure domain for the deployment zone is configured correctly or not.
145+
VSphereFailureDomainValidatedCondition clusterv1.ConditionType = "VSphereFailureDomainValidated"
146+
147+
// RegionMisconfiguredReason (Severity=Error) documents that the region for the Failure Domain associated to
148+
// the VSphereDeploymentZone is misconfigured.
149+
RegionMisconfiguredReason = "FailureDomainRegionMisconfigured"
150+
151+
// ZoneMisconfiguredReason (Severity=Error) documents that the zone for the Failure Domain associated to
152+
// the VSphereDeploymentZone is misconfigured.
153+
ZoneMisconfiguredReason = "FailureDomainZoneMisconfigured"
154+
155+
// ComputeClusterNotFoundReason (Severity=Error) documents that the Compute Cluster for the Failure Domain
156+
// associated to the VSphereDeploymentZone cannot be found.
157+
ComputeClusterNotFoundReason = "ComputeClusterNotFound"
158+
159+
// HostsMisconfiguredReason (Severity=Error) documents that the VM & Host Group details for the Failure Domain
160+
// associated to the VSphereDeploymentZone are misconfigured.
161+
HostsMisconfiguredReason = "HostsMisconfigured"
162+
163+
// HostsAffinityMisconfiguredReason (Severity=Warning) documents that the VM & Host Group affinity rule for the FailureDomain is disabled.
164+
HostsAffinityMisconfiguredReason = "HostsAffinityMisconfigured"
165+
166+
// NetworkNotFoundReason (Severity=Error) documents that the networks in the topology for the Failure Domain
167+
// associated to the VSphereDeploymentZone are misconfigured.
168+
NetworkNotFoundReason = "NetworkNotFound"
169+
170+
// DatastoreNotFoundReason (Severity=Error) documents that the datastore in the topology for the Failure Domain
171+
// associated to the VSphereDeploymentZone is misconfigured.
172+
DatastoreNotFoundReason = "DatastoreNotFound"
173+
)

api/v1alpha3/vspheredeploymentzone_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ type VSphereDeploymentZone struct {
7777
Status VSphereDeploymentZoneStatus `json:"status,omitempty"`
7878
}
7979

80+
func (z *VSphereDeploymentZone) GetConditions() clusterv1.Conditions {
81+
return z.Status.Conditions
82+
}
83+
84+
func (z *VSphereDeploymentZone) SetConditions(conditions clusterv1.Conditions) {
85+
z.Status.Conditions = conditions
86+
}
87+
8088
// +kubebuilder:object:root=true
8189

8290
// VSphereDeploymentZoneList contains a list of VSphereDeploymentZone

config/rbac/role.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,38 @@ rules:
115115
- get
116116
- patch
117117
- update
118+
- apiGroups:
119+
- infrastructure.cluster.x-k8s.io
120+
resources:
121+
- vspheredeploymentzones
122+
verbs:
123+
- create
124+
- delete
125+
- get
126+
- list
127+
- patch
128+
- update
129+
- watch
130+
- apiGroups:
131+
- infrastructure.cluster.x-k8s.io
132+
resources:
133+
- vspheredeploymentzones/status
134+
verbs:
135+
- get
136+
- patch
137+
- update
138+
- apiGroups:
139+
- infrastructure.cluster.x-k8s.io
140+
resources:
141+
- vspherefailuredomains
142+
verbs:
143+
- create
144+
- delete
145+
- get
146+
- list
147+
- patch
148+
- update
149+
- watch
118150
- apiGroups:
119151
- infrastructure.cluster.x-k8s.io
120152
resources:
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
goctx "context"
21+
"fmt"
22+
"reflect"
23+
"strings"
24+
25+
"github.com/pkg/errors"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
"k8s.io/apimachinery/pkg/types"
28+
"k8s.io/utils/pointer"
29+
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
30+
"sigs.k8s.io/cluster-api/util/conditions"
31+
"sigs.k8s.io/cluster-api/util/patch"
32+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller"
35+
"sigs.k8s.io/controller-runtime/pkg/handler"
36+
"sigs.k8s.io/controller-runtime/pkg/manager"
37+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
38+
"sigs.k8s.io/controller-runtime/pkg/source"
39+
40+
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha3"
41+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/context"
42+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/identity"
43+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/record"
44+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/session"
45+
)
46+
47+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=vspheredeploymentzones,verbs=get;list;watch;create;update;patch;delete
48+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=vspheredeploymentzones/status,verbs=get;update;patch
49+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=vspherefailuredomains,verbs=get;list;watch;create;update;patch;delete
50+
51+
// AddVSphereDeploymentZoneControllerToManager adds the VSphereDeploymentZone controller to the provided manager.
52+
func AddVSphereDeploymentZoneControllerToManager(ctx *context.ControllerManagerContext, mgr manager.Manager) error {
53+
54+
var (
55+
controlledType = &infrav1.VSphereDeploymentZone{}
56+
controlledTypeName = reflect.TypeOf(controlledType).Elem().Name()
57+
controlledTypeGVK = infrav1.GroupVersion.WithKind(controlledTypeName)
58+
59+
controllerNameShort = fmt.Sprintf("%s-controller", strings.ToLower(controlledTypeName))
60+
controllerNameLong = fmt.Sprintf("%s/%s/%s", ctx.Namespace, ctx.Name, controllerNameShort)
61+
)
62+
63+
// Build the controller context.
64+
controllerContext := &context.ControllerContext{
65+
ControllerManagerContext: ctx,
66+
Name: controllerNameShort,
67+
Recorder: record.New(mgr.GetEventRecorderFor(controllerNameLong)),
68+
Logger: ctx.Logger.WithName(controllerNameShort),
69+
}
70+
reconciler := vsphereDeploymentZoneReconciler{ControllerContext: controllerContext}
71+
72+
return ctrl.NewControllerManagedBy(mgr).
73+
// Watch the controlled, infrastructure resource.
74+
For(controlledType).
75+
Watches(
76+
&source.Kind{Type: &infrav1.VSphereFailureDomain{}},
77+
&handler.EnqueueRequestsFromMapFunc{
78+
ToRequests: handler.ToRequestsFunc(reconciler.failureDomainsToDeploymentZones),
79+
},
80+
).
81+
// Watch a GenericEvent channel for the controlled resource.
82+
// This is useful when there are events outside of Kubernetes that
83+
// should cause a resource to be synchronized, such as a goroutine
84+
// waiting on some asynchronous, external task to complete.
85+
Watches(
86+
&source.Channel{Source: ctx.GetGenericEventChannelFor(controlledTypeGVK)},
87+
&handler.EnqueueRequestForObject{},
88+
).
89+
WithOptions(controller.Options{MaxConcurrentReconciles: ctx.MaxConcurrentReconciles}).
90+
Complete(reconciler)
91+
}
92+
93+
type vsphereDeploymentZoneReconciler struct {
94+
*context.ControllerContext
95+
}
96+
97+
func (r vsphereDeploymentZoneReconciler) Reconcile(request reconcile.Request) (_ reconcile.Result, reterr error) {
98+
logr := r.Logger.WithValues("vspheredeploymentzone", request.Name)
99+
// Fetch the VSphereDeploymentZone for this request.
100+
vsphereDeploymentZone := &infrav1.VSphereDeploymentZone{}
101+
if err := r.Client.Get(r, request.NamespacedName, vsphereDeploymentZone); err != nil {
102+
if apierrors.IsNotFound(err) {
103+
logr.V(4).Info("VSphereDeploymentZone not found, won't reconcile", "key", request.NamespacedName)
104+
return reconcile.Result{}, nil
105+
}
106+
return reconcile.Result{}, err
107+
}
108+
109+
failureDomain := &infrav1.VSphereFailureDomain{}
110+
failureDomainKey := client.ObjectKey{Name: vsphereDeploymentZone.Spec.FailureDomain}
111+
if err := r.Client.Get(r, failureDomainKey, failureDomain); err != nil {
112+
if apierrors.IsNotFound(err) {
113+
logr.V(4).Info("Failure Domain not found, won't reconcile", "key", failureDomainKey)
114+
return reconcile.Result{}, nil
115+
}
116+
return reconcile.Result{}, err
117+
}
118+
119+
patchHelper, err := patch.NewHelper(vsphereDeploymentZone, r.Client)
120+
if err != nil {
121+
return reconcile.Result{}, errors.Wrapf(
122+
err,
123+
"failed to init patch helper for %s %s/%s",
124+
vsphereDeploymentZone.GroupVersionKind(),
125+
vsphereDeploymentZone.Namespace,
126+
vsphereDeploymentZone.Name)
127+
}
128+
129+
vsphereDeploymentZoneContext := &context.VSphereDeploymentZoneContext{
130+
ControllerContext: r.ControllerContext,
131+
VSphereDeploymentZone: vsphereDeploymentZone,
132+
VSphereFailureDomain: failureDomain,
133+
Logger: logr,
134+
PatchHelper: patchHelper,
135+
}
136+
defer func() {
137+
if err := vsphereDeploymentZoneContext.Patch(); err != nil {
138+
if reterr == nil {
139+
reterr = err
140+
}
141+
logr.Error(err, "patch failed", "vsphereDeploymentZone", vsphereDeploymentZoneContext.String())
142+
}
143+
}()
144+
145+
if !vsphereDeploymentZone.DeletionTimestamp.IsZero() {
146+
return r.reconcileDelete(vsphereDeploymentZoneContext)
147+
}
148+
149+
return r.reconcileNormal(vsphereDeploymentZoneContext)
150+
}
151+
152+
func (r vsphereDeploymentZoneReconciler) reconcileNormal(ctx *context.VSphereDeploymentZoneContext) (reconcile.Result, error) {
153+
authSession, err := r.getVCenterSession(ctx)
154+
if err != nil {
155+
ctx.Logger.V(4).Error(err, "unable to create session")
156+
conditions.MarkFalse(ctx.VSphereDeploymentZone, infrav1.VCenterAvailableCondition, infrav1.VCenterUnreachableReason, clusterv1.ConditionSeverityError, err.Error())
157+
ctx.VSphereDeploymentZone.Status.Ready = pointer.BoolPtr(false)
158+
return reconcile.Result{}, errors.Wrapf(err, "unable to create auth session")
159+
}
160+
ctx.AuthSession = authSession
161+
conditions.MarkTrue(ctx.VSphereDeploymentZone, infrav1.VCenterAvailableCondition)
162+
163+
if err := r.reconcilePlacementConstraint(ctx); err != nil {
164+
ctx.VSphereDeploymentZone.Status.Ready = pointer.BoolPtr(false)
165+
return reconcile.Result{}, errors.Wrap(err, "placement constraint is misconfigured")
166+
}
167+
conditions.MarkTrue(ctx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition)
168+
169+
// reconcile the failure domain
170+
if err := r.reconcileFailureDomain(ctx); err != nil {
171+
ctx.Logger.V(4).Error(err, "failed to reconcile failure domain", "failureDomain", ctx.VSphereDeploymentZone.Spec.FailureDomain)
172+
ctx.VSphereDeploymentZone.Status.Ready = pointer.BoolPtr(false)
173+
return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile failure domain")
174+
}
175+
conditions.MarkTrue(ctx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition)
176+
177+
ctx.VSphereDeploymentZone.Status.Ready = pointer.BoolPtr(true)
178+
return reconcile.Result{}, nil
179+
}
180+
181+
func (r vsphereDeploymentZoneReconciler) reconcilePlacementConstraint(ctx *context.VSphereDeploymentZoneContext) error {
182+
placementConstraint := ctx.VSphereDeploymentZone.Spec.PlacementConstraint
183+
184+
if resourcePool := placementConstraint.ResourcePool; resourcePool != "" {
185+
if _, err := ctx.AuthSession.Finder.ResourcePool(ctx, resourcePool); err != nil {
186+
ctx.Logger.V(4).Error(err, "unable to find resource pool", "name", resourcePool)
187+
conditions.MarkFalse(ctx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition, infrav1.ResourcePoolNotFoundReason, clusterv1.ConditionSeverityError, "resource pool %s is misconfigured", resourcePool)
188+
return errors.Wrapf(err, "unable to find resource pool %s", resourcePool)
189+
}
190+
}
191+
192+
if folder := placementConstraint.Folder; folder != "" {
193+
if _, err := ctx.AuthSession.Finder.Folder(ctx, placementConstraint.Folder); err != nil {
194+
ctx.Logger.V(4).Error(err, "unable to find folder", "name", folder)
195+
conditions.MarkFalse(ctx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition, infrav1.FolderNotFoundReason, clusterv1.ConditionSeverityError, "datastore %s is misconfigured", folder)
196+
return errors.Wrapf(err, "unable to find folder %s", folder)
197+
}
198+
}
199+
return nil
200+
}
201+
202+
func (r vsphereDeploymentZoneReconciler) getVCenterSession(ctx *context.VSphereDeploymentZoneContext) (*session.Session, error) {
203+
clusterList := &infrav1.VSphereClusterList{}
204+
if err := r.Client.List(ctx, clusterList); err != nil {
205+
return nil, err
206+
}
207+
208+
for _, vsphereCluster := range clusterList.Items {
209+
if ctx.VSphereDeploymentZone.Spec.Server == vsphereCluster.Spec.Server && vsphereCluster.Spec.IdentityRef != nil {
210+
logger := ctx.Logger.WithValues("cluster", vsphereCluster.Name)
211+
212+
creds, err := identity.GetCredentials(ctx, r.Client, &vsphereCluster, r.Namespace) // nolint:scopelint
213+
if err != nil {
214+
logger.Error(err, "error retrieving credentials from IdentityRef")
215+
continue
216+
}
217+
logger.Info("using server credentials to create the authenticated session")
218+
return session.GetOrCreate(ctx, r.Logger, ctx.VSphereDeploymentZone.Spec.Server,
219+
ctx.VSphereFailureDomain.Spec.Topology.Datacenter, creds.Username, creds.Password, vsphereCluster.Spec.Thumbprint)
220+
}
221+
}
222+
223+
// Fallback to using credentials provided to the manager
224+
return session.GetOrCreate(ctx, r.Logger, ctx.VSphereDeploymentZone.Spec.Server,
225+
ctx.VSphereFailureDomain.Spec.Topology.Datacenter, ctx.Username, ctx.Password, "")
226+
}
227+
228+
func (r vsphereDeploymentZoneReconciler) reconcileDelete(ctx *context.VSphereDeploymentZoneContext) (reconcile.Result, error) {
229+
return reconcile.Result{}, nil
230+
}
231+
232+
func (r vsphereDeploymentZoneReconciler) failureDomainsToDeploymentZones(o handler.MapObject) []ctrl.Request {
233+
failureDomain, ok := o.Object.(*infrav1.VSphereFailureDomain)
234+
if !ok {
235+
r.Logger.Error(nil, fmt.Sprintf("expected a VSphereFailureDomain but got a %T", o))
236+
return nil
237+
}
238+
239+
var zones infrav1.VSphereDeploymentZoneList
240+
if err := r.Client.List(goctx.Background(), &zones); err != nil {
241+
return nil
242+
}
243+
244+
var requests []ctrl.Request
245+
for _, zone := range zones.Items {
246+
if zone.Spec.FailureDomain == failureDomain.Name {
247+
requests = append(requests, ctrl.Request{
248+
NamespacedName: types.NamespacedName{
249+
Name: zone.Name,
250+
},
251+
})
252+
}
253+
}
254+
return requests
255+
}

0 commit comments

Comments
 (0)