Skip to content

Commit 36ab117

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

File tree

6 files changed

+173
-12
lines changed

6 files changed

+173
-12
lines changed

api/v1alpha1/condition_consts.go

Lines changed: 22 additions & 0 deletions
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

Lines changed: 22 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 55 additions & 10 deletions
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,25 @@ func GetCloudInitMetadata(vm *vmopv1.VirtualMachine,
114115
return string(metadataBytes), nil
115116
}
116117

118+
type ErrWithReason struct {
119+
error
120+
conditionReason string
121+
}
122+
117123
func GetCloudInitPrepCustSpec(
118124
cloudInitMetadata string,
119-
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *vimTypes.CustomizationSpec, error) {
125+
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *vimTypes.CustomizationSpec, *ErrWithReason) {
120126

121127
userdata := updateArgs.VMMetadata.Data["user-data"]
122128

123129
if userdata != "" {
124130
// Ensure the data is normalized first to plain-text.
125131
plainText, err := util.TryToDecodeBase64Gzip([]byte(userdata))
126132
if err != nil {
127-
return nil, nil, fmt.Errorf("decoding cloud-init prep userdata failed %v", err)
133+
return nil, nil, &ErrWithReason{
134+
error: fmt.Errorf("decoding cloud-init prep userdata failed %v", err),
135+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
136+
}
128137
}
129138
userdata = plainText
130139
}
@@ -147,17 +156,27 @@ func GetCloudInitPrepCustSpec(
147156
func GetCloudInitGuestInfoCustSpec(
148157
cloudInitMetadata string,
149158
config *vimTypes.VirtualMachineConfigInfo,
150-
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, error) {
159+
updateArgs VMUpdateArgs) (*vimTypes.VirtualMachineConfigSpec, *ErrWithReason) {
151160

152161
extraConfig := map[string]string{}
153162

154163
encodedMetadata, err := EncodeGzipBase64(cloudInitMetadata)
155164
if err != nil {
156-
return nil, fmt.Errorf("encoding cloud-init metadata failed %v", err)
165+
return nil, &ErrWithReason{
166+
error: fmt.Errorf("encoding cloud-init metadata failed %v", err),
167+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
168+
}
157169
}
158170
extraConfig[constants.CloudInitGuestInfoMetadata] = encodedMetadata
159171
extraConfig[constants.CloudInitGuestInfoMetadataEncoding] = "gzip+base64"
160172

173+
if len(encodedMetadata) > constants.CloudInitGuestInfoMaxSize {
174+
return nil, &ErrWithReason{
175+
error: fmt.Errorf("size of cloud-init guest info exceeds the maximum allowed size of 64KiB"),
176+
conditionReason: vmopv1.VirtualMachineGuestInfoSizeExceededReason,
177+
}
178+
}
179+
161180
var data string
162181
// Check for the 'user-data' key as per official contract and API documentation.
163182
// Additionally, To support the cluster bootstrap data supplied by CAPBK's secret,
@@ -173,16 +192,30 @@ func GetCloudInitGuestInfoCustSpec(
173192
// Ensure the data is normalized first to plain-text.
174193
plainText, err := util.TryToDecodeBase64Gzip([]byte(data))
175194
if err != nil {
176-
return nil, fmt.Errorf("decoding cloud-init userdata failed %v", err)
195+
return nil, &ErrWithReason{
196+
error: fmt.Errorf("decoding cloud-init userdata failed %v", err),
197+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
198+
}
177199
}
178200

179201
encodedUserdata, err := EncodeGzipBase64(plainText)
180202
if err != nil {
181-
return nil, fmt.Errorf("encoding cloud-init userdata failed %v", err)
203+
return nil, &ErrWithReason{
204+
error: fmt.Errorf("encoding cloud-init userdata failed %v", err),
205+
conditionReason: vmopv1.VirtualMachineMetadataInvalidReason,
206+
}
182207
}
183208

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

188221
configSpec := &vimTypes.VirtualMachineConfigSpec{}
@@ -245,23 +278,34 @@ func customizeCloudInit(
245278

246279
cloudInitMetadata, err := GetCloudInitMetadata(vmCtx.VM, netplan, updateArgs.VMMetadata.Data)
247280
if err != nil {
281+
conditions.MarkFalse(vmCtx.VM,
282+
vmopv1.VirtualMachineMetadataReadyCondition,
283+
vmopv1.VirtualMachineMetadataInvalidReason,
284+
vmopv1.ConditionSeverityError,
285+
err.Error())
248286
return nil, nil, err
249287
}
250288

251289
var configSpec *vimTypes.VirtualMachineConfigSpec
252290
var custSpec *vimTypes.CustomizationSpec
291+
var errWithReason *ErrWithReason
253292

254293
switch vmCtx.VM.Annotations[constants.CloudInitTypeAnnotation] {
255294
case constants.CloudInitTypeValueCloudInitPrep:
256-
configSpec, custSpec, err = GetCloudInitPrepCustSpec(cloudInitMetadata, updateArgs)
295+
configSpec, custSpec, errWithReason = GetCloudInitPrepCustSpec(cloudInitMetadata, updateArgs)
257296
case constants.CloudInitTypeValueGuestInfo, "":
258297
fallthrough
259298
default:
260-
configSpec, err = GetCloudInitGuestInfoCustSpec(cloudInitMetadata, config, updateArgs)
299+
configSpec, errWithReason = GetCloudInitGuestInfoCustSpec(cloudInitMetadata, config, updateArgs)
261300
}
262301

263-
if err != nil {
264-
return nil, nil, err
302+
if errWithReason != nil {
303+
conditions.MarkFalse(vmCtx.VM,
304+
vmopv1.VirtualMachineMetadataReadyCondition,
305+
errWithReason.conditionReason,
306+
vmopv1.ConditionSeverityError,
307+
errWithReason.Error())
308+
return nil, nil, errWithReason
265309
}
266310

267311
return configSpec, custSpec, nil
@@ -307,6 +351,7 @@ func (s *Session) customize(
307351
if err != nil {
308352
return err
309353
}
354+
conditions.MarkTrue(vmCtx.VM, vmopv1.VirtualMachineMetadataReadyCondition)
310355

311356
if configSpec != nil {
312357
defaultConfigSpec := &vimTypes.VirtualMachineConfigSpec{}

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

Lines changed: 54 additions & 0 deletions
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

Lines changed: 17 additions & 2 deletions
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)