Skip to content

Commit 68a740a

Browse files
committed
Adds new condition to expose state of guestinfo
Signed-off-by: Sagar Muchhal <[email protected]>
1 parent 123dc00 commit 68a740a

File tree

6 files changed

+171
-8
lines changed

6 files changed

+171
-8
lines changed

api/v1alpha1/condition_consts.go

+22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@ const (
1111

1212
// Conditions and condition Reasons for the VirtualMachine object.
1313

14+
const (
15+
// VirtualMachineMetadataReadyCondition documents the state of the metadata passed to the VirtualMachine.
16+
VirtualMachineMetadataReadyCondition ConditionType = "VirtualMachineMetadataReady"
17+
18+
// VirtualMachineMetadataDuplicatedReason (Severity=Error) documents that the mutually exclusive Secret and ConfigMap are
19+
// specified in the VirtualMachineMetadata.
20+
//
21+
// This validation is performed by the validation webhook, defensively adding the reason.
22+
VirtualMachineMetadataDuplicatedReason = "VirtualMachineMetadataDuplicated"
23+
24+
// VirtualMachineMetadataNotFoundReason (Severity=Error) documents that the Secret or ConfigMap specified in the VirtualMachineSpec
25+
// cannot be found.
26+
VirtualMachineMetadataNotFoundReason = "VirtualMachineMetadataNotFound"
27+
28+
// VirtualMachineMetadataInvalidReason (Severity=Error) documents that the supplied Cloud Init metadata is invalid.
29+
VirtualMachineMetadataInvalidReason = "VirtualMachineMetadataInvalid"
30+
31+
// VirtualMachineGuestInfoSizeExceededReason (Severity=Error) documents that the supplied Cloud Init metadata exceeds the
32+
// maximum allowed capacity.
33+
VirtualMachineGuestInfoSizeExceededReason = "VirtualMachineGuestInfoSizeExceeded"
34+
)
35+
1436
const (
1537
// VirtualMachinePrereqReadyCondition documents that all of a VirtualMachine's prerequisites declared in the spec
1638
// (e.g. VirtualMachineClass) are satisfied.

api/v1alpha2/virtualmachine_types.go

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@ import (
1010
"github.com/vmware-tanzu/vm-operator/api/v1alpha2/common"
1111
)
1212

13+
const (
14+
// VirtualMachineMetadataReadyCondition documents the state of the metadata passed to the VirtualMachine.
15+
VirtualMachineMetadataReadyCondition = "VirtualMachineMetadataReady"
16+
17+
// VirtualMachineMetadataDuplicatedReason (Severity=Error) documents that the mutually exclusive Secret and ConfigMap are
18+
// specified in the VirtualMachineMetadata.
19+
//
20+
// This validation is performed by the validation webhook, defensively adding the reason.
21+
VirtualMachineMetadataDuplicatedReason = "VirtualMachineMetadataDuplicated"
22+
23+
// VirtualMachineMetadataNotFoundReason (Severity=Error) documents that the Secret or ConfigMap specified in the VirtualMachineSpec
24+
// cannot be found.
25+
VirtualMachineMetadataNotFoundReason = "VirtualMachineMetadataNotFound"
26+
27+
// VirtualMachineMetadataInvalidReason (Severity=Error) documents that the supplied Cloud Init metadata is invalid.
28+
VirtualMachineMetadataInvalidReason = "VirtualMachineMetadataInvalid"
29+
30+
// VirtualMachineGuestInfoSizeExceededReason (Severity=Error) documents that the supplied Cloud Init metadata exceeds the
31+
// maximum allowed capacity.
32+
VirtualMachineGuestInfoSizeExceededReason = "VirtualMachineGuestInfoSizeExceeded"
33+
)
34+
1335
const (
1436
// VirtualMachineConditionClassReady indicates that a referenced
1537
// VirtualMachineClass is ready.

pkg/vmprovider/providers/vsphere/constants/constants.go

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const (
7272
CloudInitGuestInfoUserdata = "guestinfo.userdata"
7373
CloudInitGuestInfoUserdataEncoding = "guestinfo.userdata.encoding"
7474

75+
// CloudInitGuestInfoMaxSize the maximum allowed size for cloud init metadata.
76+
CloudInitGuestInfoMaxSize = 64 * 1024
77+
7578
// InstanceStoragePVCNamePrefix prefix of auto-generated PVC names.
7679
InstanceStoragePVCNamePrefix = "instance-pvc-"
7780
// InstanceStorageLabelKey identifies resources related to instance storage.

pkg/vmprovider/providers/vsphere/session/session_vm_customization.go

+53-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
apiEquality "k8s.io/apimachinery/pkg/api/equality"
1818

1919
vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha1"
20+
"github.com/vmware-tanzu/vm-operator/pkg/conditions"
2021
"github.com/vmware-tanzu/vm-operator/pkg/context"
2122
"github.com/vmware-tanzu/vm-operator/pkg/lib"
2223
"github.com/vmware-tanzu/vm-operator/pkg/util"
@@ -114,17 +115,27 @@ func GetCloudInitMetadata(vm *vmopv1.VirtualMachine,
114115
return string(metadataBytes), nil
115116
}
116117

118+
// ErrWithReason encapsulates the error type as well as a human-readable reason that will be
119+
// used to set the Reason field on the Condition set on the object.
120+
type ErrWithReason struct {
121+
error
122+
conditionReason string
123+
}
124+
117125
func GetCloudInitPrepCustSpec(
118126
cloudInitMetadata string,
119-
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *vimTypes.CustomizationSpec, error) {
127+
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *vimTypes.CustomizationSpec, *ErrWithReason) {
120128

121129
userdata := updateArgs.VMMetadata.Data["user-data"]
122130

123131
if userdata != "" {
124132
// Ensure the data is normalized first to plain-text.
125133
plainText, err := util.TryToDecodeBase64Gzip([]byte(userdata))
126134
if err != nil {
127-
return nil, nil, fmt.Errorf("decoding cloud-init prep userdata failed %v", err)
135+
return nil, nil, &ErrWithReason{
136+
error: fmt.Errorf("decoding cloud-init prep userdata failed %v", err),
137+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
138+
}
128139
}
129140
userdata = plainText
130141
}
@@ -147,17 +158,27 @@ func GetCloudInitPrepCustSpec(
147158
func GetCloudInitGuestInfoCustSpec(
148159
cloudInitMetadata string,
149160
config *vimTypes.VirtualMachineConfigInfo,
150-
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, error) {
161+
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *ErrWithReason) {
151162

152163
extraConfig := map[string]string{}
153164

154165
encodedMetadata, err := EncodeGzipBase64(cloudInitMetadata)
155166
if err != nil {
156-
return nil, fmt.Errorf("encoding cloud-init metadata failed %v", err)
167+
return nil, &ErrWithReason{
168+
error: fmt.Errorf("encoding cloud-init metadata failed %v", err),
169+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
170+
}
157171
}
158172
extraConfig[constants.CloudInitGuestInfoMetadata] = encodedMetadata
159173
extraConfig[constants.CloudInitGuestInfoMetadataEncoding] = "gzip+base64"
160174

175+
if len(encodedMetadata) > constants.CloudInitGuestInfoMaxSize {
176+
return nil, &ErrWithReason{
177+
error: fmt.Errorf("size of cloud-init guest info exceeds the maximum allowed size of 64KiB"),
178+
conditionReason: vmopv1.VirtualMachineGuestInfoSizeExceededReason,
179+
}
180+
}
181+
161182
var data string
162183
// Check for the 'user-data' key as per official contract and API documentation.
163184
// Additionally, To support the cluster bootstrap data supplied by CAPBK's secret,
@@ -173,16 +194,30 @@ func GetCloudInitGuestInfoCustSpec(
173194
// Ensure the data is normalized first to plain-text.
174195
plainText, err := util.TryToDecodeBase64Gzip([]byte(data))
175196
if err != nil {
176-
return nil, fmt.Errorf("decoding cloud-init userdata failed %v", err)
197+
return nil, &ErrWithReason{
198+
error: fmt.Errorf("decoding cloud-init userdata failed %v", err),
199+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
200+
}
177201
}
178202

179203
encodedUserdata, err := EncodeGzipBase64(plainText)
180204
if err != nil {
181-
return nil, fmt.Errorf("encoding cloud-init userdata failed %v", err)
205+
return nil, &ErrWithReason{
206+
error: fmt.Errorf("encoding cloud-init userdata failed %v", err),
207+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
208+
}
182209
}
183210

184211
extraConfig[constants.CloudInitGuestInfoUserdata] = encodedUserdata
185212
extraConfig[constants.CloudInitGuestInfoUserdataEncoding] = "gzip+base64"
213+
214+
if len(encodedUserdata) > constants.CloudInitGuestInfoMaxSize ||
215+
len(encodedUserdata)+len(encodedMetadata) > constants.CloudInitGuestInfoMaxSize {
216+
return nil, &ErrWithReason{
217+
error: fmt.Errorf("size of cloud-init guest info exceeds the maximum allowed size of 64KiB"),
218+
conditionReason: vmopv1.VirtualMachineGuestInfoSizeExceededReason,
219+
}
220+
}
186221
}
187222

188223
configSpec := &vimTypes.VirtualMachineConfigSpec{}
@@ -245,6 +280,11 @@ func customizeCloudInit(
245280

246281
cloudInitMetadata, err := GetCloudInitMetadata(vmCtx.VM, netplan, updateArgs.VMMetadata.Data)
247282
if err != nil {
283+
conditions.MarkFalse(vmCtx.VM,
284+
vmopv1.VirtualMachineMetadataReadyCondition,
285+
vmopv1.VirtualMachineMetadataInvalidReason,
286+
vmopv1.ConditionSeverityError,
287+
err.Error())
248288
return nil, nil, err
249289
}
250290

@@ -261,6 +301,12 @@ func customizeCloudInit(
261301
}
262302

263303
if err != nil {
304+
errWithReason := err.(ErrWithReason)
305+
conditions.MarkFalse(vmCtx.VM,
306+
vmopv1.VirtualMachineMetadataReadyCondition,
307+
errWithReason.conditionReason,
308+
vmopv1.ConditionSeverityError,
309+
errWithReason.Error())
264310
return nil, nil, err
265311
}
266312

@@ -307,6 +353,7 @@ func (s *Session) customize(
307353
if err != nil {
308354
return err
309355
}
356+
conditions.MarkTrue(vmCtx.VM, vmopv1.VirtualMachineMetadataReadyCondition)
310357

311358
if configSpec != nil {
312359
defaultConfigSpec := &vimTypes.VirtualMachineConfigSpec{}

pkg/vmprovider/providers/vsphere/session/session_vm_customization_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
goctx "context"
88
"encoding/base64"
99
"fmt"
10+
"math/rand"
1011
"strings"
1112

1213
. "github.com/onsi/ginkgo"
@@ -402,6 +403,59 @@ var _ = Describe("Cloud-Init Customization", func() {
402403
})
403404
})
404405

406+
Context("With cloud init guest info exceeding the maximum size", func() {
407+
generateDataFunc := func(length int) string {
408+
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
409+
410+
sb := strings.Builder{}
411+
sb.Grow(length)
412+
for i := 0; i < length; i++ {
413+
sb.WriteByte(charset[rand.Intn(len(charset))]) //nolint:gosec
414+
}
415+
return sb.String()
416+
}
417+
418+
Context("With cloud init user data exceeding the maximum size", func() {
419+
BeforeEach(func() {
420+
// Doubling the input to the max allowed size to make sure the compressed and encoded
421+
// output is still above the allowed limit.
422+
data, err := session.EncodeGzipBase64(generateDataFunc(128 * 1000))
423+
Expect(err).ToNot(HaveOccurred())
424+
updateArgs.VMMetadata.Data["user-data"] = data
425+
})
426+
It("will return a limit exceeded error", func() {
427+
Expect(err).To(HaveOccurred())
428+
Expect(err.Error()).To(ContainSubstring("exceeds the maximum allowed size"))
429+
})
430+
})
431+
432+
Context("With cloud init metadata exceeding the maximum size", func() {
433+
BeforeEach(func() {
434+
// Doubling the input to the max allowed size to make sure the compressed and encoded
435+
// output is still above the allowed limit.
436+
data, err := session.EncodeGzipBase64(generateDataFunc(128 * 1000))
437+
Expect(err).ToNot(HaveOccurred())
438+
cloudInitMetadata = data
439+
})
440+
It("will return a limit exceeded error", func() {
441+
Expect(err).To(HaveOccurred())
442+
Expect(err.Error()).To(ContainSubstring("exceeds the maximum allowed size"))
443+
})
444+
})
445+
446+
Context("With cloud init metadata + userdata exceeding the maximum size", func() {
447+
BeforeEach(func() {
448+
data, err := session.EncodeGzipBase64(generateDataFunc(64 * 1000))
449+
Expect(err).ToNot(HaveOccurred())
450+
cloudInitMetadata = data
451+
updateArgs.VMMetadata.Data["user-data"] = data
452+
})
453+
It("will return a limit exceeded error", func() {
454+
Expect(err).To(HaveOccurred())
455+
Expect(err.Error()).To(ContainSubstring("exceeds the maximum allowed size"))
456+
})
457+
})
458+
})
405459
})
406460

407461
Context("GetCloudInitPrepCustSpec", func() {

pkg/vmprovider/providers/vsphere/vmprovider_vm_utils.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,25 @@ func GetVMMetadata(
228228
// The VM's MetaData ConfigMapName and SecretName are mutually exclusive. Validation webhook checks
229229
// this during create/update but double check it here.
230230
if metadata.ConfigMapName != "" && metadata.SecretName != "" {
231+
conditions.MarkFalse(vmCtx.VM,
232+
vmopv1.VirtualMachineMetadataReadyCondition,
233+
vmopv1.VirtualMachineMetadataDuplicatedReason,
234+
vmopv1.ConditionSeverityError,
235+
"Both ConfigMapName %s/%s and SecretName %s/%s are specified",
236+
metadata.ConfigMapName, vmCtx.VM.Namespace,
237+
metadata.SecretName, vmCtx.VM.Namespace)
231238
return vmMD, errors.New("invalid VM Metadata: both ConfigMapName and SecretName are specified")
232239
}
233240

234241
if metadata.ConfigMapName != "" {
235242
cm := &corev1.ConfigMap{}
236243
err := k8sClient.Get(vmCtx, ctrlclient.ObjectKey{Name: metadata.ConfigMapName, Namespace: vmCtx.VM.Namespace}, cm)
237244
if err != nil {
238-
// TODO: Condition
245+
conditions.MarkFalse(vmCtx.VM,
246+
vmopv1.VirtualMachineMetadataReadyCondition,
247+
vmopv1.VirtualMachineMetadataNotFoundReason,
248+
vmopv1.ConditionSeverityError,
249+
"Unable to get metadata ConfigMap %s/%s", metadata.ConfigMapName, vmCtx.VM.Namespace)
239250
return vmMD, errors.Wrap(err, "Failed to get VM Metadata ConfigMap")
240251
}
241252

@@ -245,7 +256,11 @@ func GetVMMetadata(
245256
secret := &corev1.Secret{}
246257
err := k8sClient.Get(vmCtx, ctrlclient.ObjectKey{Name: metadata.SecretName, Namespace: vmCtx.VM.Namespace}, secret)
247258
if err != nil {
248-
// TODO: Condition
259+
conditions.MarkFalse(vmCtx.VM,
260+
vmopv1.VirtualMachineMetadataReadyCondition,
261+
vmopv1.VirtualMachineMetadataNotFoundReason,
262+
vmopv1.ConditionSeverityError,
263+
"Unable to get metadata Secret %s/%s", metadata.ConfigMapName, vmCtx.VM.Namespace)
249264
return vmMD, errors.Wrap(err, "Failed to get VM Metadata Secret")
250265
}
251266

0 commit comments

Comments
 (0)