From e155f4e5e4f5268731028a65cc9d7a423d5c5883 Mon Sep 17 00:00:00 2001 From: Dmitry Rakitin Date: Wed, 18 Sep 2024 12:21:38 +0200 Subject: [PATCH] fix(vmbda): allow wffc hotplugs 1. vmbda condition BlockDeviceReady waits until the disk is wffc or ready, after which it performs a hot plug to the virtual machine (from this moment the disk is considered attached to the virtual machine) 2. vd does not allow editing the spec if the disk is attached to the virtual machine or Ready 3. vmbda CRD gen2 4. vmbda uses condition builder to manage conditions Signed-off-by: Isteb4k --- .../virtual_machine_block_disk_attachment.go | 47 +++- api/core/v1alpha2/vmbdacondition/condition.go | 30 ++- api/core/v1alpha2/zz_generated.deepcopy.go | 9 +- .../generated/openapi/zz_generated.openapi.go | 59 +++-- api/scripts/update-codegen.sh | 2 +- ...-virtualmachineblockdeviceattachments.yaml | 4 +- .../virtualmachineblockdeviceattachments.yaml | 226 +++++++++++------- .../pkg/controller/conditions/builder.go | 30 ++- .../validators/spec_changes_validator.go | 10 + .../vmbda/internal/block_device_ready.go | 81 ++++--- .../controller/vmbda/internal/life_cycle.go | 125 +++++----- .../vmbda/internal/virtual_machine_ready.go | 58 ++--- 12 files changed, 431 insertions(+), 250 deletions(-) diff --git a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go b/api/core/v1alpha2/virtual_machine_block_disk_attachment.go index 623f9532b..67da465ad 100644 --- a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go +++ b/api/core/v1alpha2/virtual_machine_block_disk_attachment.go @@ -23,7 +23,15 @@ const ( VMBDAResource = "virtualmachineblockdeviceattachments" ) -// VirtualMachineBlockDeviceAttachment is a disk ready to be bound by a VM +// VirtualMachineBlockDeviceAttachment provides a hot plug for connecting a disk to a virtual machine. +// +// +kubebuilder:object:root=true +// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization} +// +kubebuilder:subresource:status +// +kubebuilder:resource:categories={all,virtualization},scope=Namespaced,shortName={vmbda,vmbdas},singular=virtualmachineblockdeviceattachment +// +kubebuilder:printcolumn:name="PHASE",type="string",JSONPath=".status.phase",description="VirtualMachineBlockDeviceAttachment phase." +// +kubebuilder:printcolumn:name="VIRTUAL MACHINE NAME",type="string",JSONPath=".status.virtualMachineName",description="The name of the virtual machine to which this disk is attached." +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp",description="Time of creation resource." // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type VirtualMachineBlockDeviceAttachment struct { @@ -31,10 +39,10 @@ type VirtualMachineBlockDeviceAttachment struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec VirtualMachineBlockDeviceAttachmentSpec `json:"spec"` - Status VirtualMachineBlockDeviceAttachmentStatus `json:"status"` + Status VirtualMachineBlockDeviceAttachmentStatus `json:"status,omitempty"` } -// VirtualMachineBlockDeviceAttachmentList contains a list of VirtualMachineBlockDeviceAttachment +// VirtualMachineBlockDeviceAttachmentList contains a list of VirtualMachineBlockDeviceAttachment. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type VirtualMachineBlockDeviceAttachmentList struct { metav1.TypeMeta `json:",inline"` @@ -45,27 +53,46 @@ type VirtualMachineBlockDeviceAttachmentList struct { } type VirtualMachineBlockDeviceAttachmentSpec struct { + // The name of the virtual machine to which the disk or image should be connected. VirtualMachineName string `json:"virtualMachineName"` BlockDeviceRef VMBDAObjectRef `json:"blockDeviceRef"` } +type VirtualMachineBlockDeviceAttachmentStatus struct { + Phase BlockDeviceAttachmentPhase `json:"phase,omitempty"` + // The name of the virtual machine to which this disk is attached. + VirtualMachineName string `json:"virtualMachineName,omitempty"` + // Contains details of the current state of this API Resource. + Conditions []metav1.Condition `json:"conditions,omitempty"` + // The generation last processed by the controller + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + type VMBDAObjectRef struct { + // The type of the block device. Options are: + // * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode. Kind VMBDAObjectRefKind `json:"kind,omitempty"` - Name string `json:"name,omitempty"` + // The name of block device to attach. + Name string `json:"name,omitempty"` } +// VMBDAObjectRefKind defines the type of the block device. +// +// +kubebuilder:validation:Enum={VirtualDisk} type VMBDAObjectRefKind string const ( VMBDAObjectRefKindVirtualDisk VMBDAObjectRefKind = "VirtualDisk" ) -type VirtualMachineBlockDeviceAttachmentStatus struct { - Phase BlockDeviceAttachmentPhase `json:"phase,omitempty"` - Conditions []metav1.Condition `json:"conditions,omitempty"` - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - +// BlockDeviceAttachmentPhase defines current status of resource: +// * Pending — the resource has been created and is on a waiting queue. +// * InProgress — the disk is in the process of being attached. +// * Attached — the disk is attached to virtual machine. +// * Failed — there was a problem with attaching the disk. +// * Terminating — the process of resource deletion is in progress. +// +// +kubebuilder:validation:Enum={Pending,InProgress,Attached,Failed,Terminating} type BlockDeviceAttachmentPhase string const ( diff --git a/api/core/v1alpha2/vmbdacondition/condition.go b/api/core/v1alpha2/vmbdacondition/condition.go index fd0955b6e..01def54d0 100644 --- a/api/core/v1alpha2/vmbdacondition/condition.go +++ b/api/core/v1alpha2/vmbdacondition/condition.go @@ -17,7 +17,7 @@ limitations under the License. package vmbdacondition // Type represents the various condition types for the `VirtualMachineBlockDeviceAttachment`. -type Type = string +type Type string const ( // BlockDeviceReadyType indicates that the block device (for example, a `VirtualDisk`) is ready to be hot-plugged. @@ -30,24 +30,30 @@ const ( type ( // BlockDeviceReadyReason represents the various reasons for the `BlockDeviceReady` condition type. - BlockDeviceReadyReason = string + BlockDeviceReadyReason string // VirtualMachineReadyReason represents the various reasons for the `VirtualMachineReady` condition type. - VirtualMachineReadyReason = string + VirtualMachineReadyReason string // AttachedReason represents the various reasons for the `Attached` condition type. - AttachedReason = string + AttachedReason string ) const ( + // BlockDeviceReadyUnknown represents unknown condition state. + BlockDeviceReadyUnknown BlockDeviceReadyReason = "Unknown" // BlockDeviceReady signifies that the block device is ready to be hot-plugged, allowing the hot-plug process to start. BlockDeviceReady BlockDeviceReadyReason = "BlockDeviceReady" // BlockDeviceNotReady signifies that the block device is not ready, preventing the hot-plug process from starting. BlockDeviceNotReady BlockDeviceReadyReason = "BlockDeviceNotReady" + // VirtualMachineReadyUnknown represents unknown condition state. + VirtualMachineReadyUnknown VirtualMachineReadyReason = "Unknown" // VirtualMachineReady signifies that the virtual machine is ready for hot-plugging a disk, allowing the hot-plug process to start. VirtualMachineReady VirtualMachineReadyReason = "VirtualMachineReady" // VirtualMachineNotReady signifies that the virtual machine is not ready, preventing the hot-plug process from starting. VirtualMachineNotReady VirtualMachineReadyReason = "VirtualMachineNotReady" + // AttachedUnknown represents unknown condition state. + AttachedUnknown BlockDeviceReadyReason = "Unknown" // Attached signifies that the virtual disk is successfully hot-plugged into the virtual machine. Attached AttachedReason = "Attached" // NotAttached signifies that the virtual disk is not yet hot-plugged into the virtual machine. @@ -60,3 +66,19 @@ const ( // Only the one that was created or started sooner can be processed. Conflict AttachedReason = "Conflict" ) + +func (t Type) String() string { + return string(t) +} + +func (t BlockDeviceReadyReason) String() string { + return string(t) +} + +func (t VirtualMachineReadyReason) String() string { + return string(t) +} + +func (t AttachedReason) String() string { + return string(t) +} diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 8353f833c..57d8adb66 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -2004,7 +2004,7 @@ func (in *VirtualMachineOperation) DeepCopyInto(out *VirtualMachineOperation) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -2078,6 +2078,13 @@ func (in *VirtualMachineOperationSpec) DeepCopy() *VirtualMachineOperationSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineOperationStatus) DeepCopyInto(out *VirtualMachineOperationStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go index c51fbfbd9..78fd93ce8 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -1985,14 +1985,16 @@ func schema_virtualization_api_core_v1alpha2_VMBDAObjectRef(ref common.Reference Properties: map[string]spec.Schema{ "kind": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "The type of the block device. Options are: * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode.", + Type: []string{"string"}, + Format: "", }, }, "name": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "The name of block device to attach.", + Type: []string{"string"}, + Format: "", }, }, }, @@ -3040,7 +3042,7 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "VirtualMachineBlockDeviceAttachment is a disk ready to be bound by a VM", + Description: "VirtualMachineBlockDeviceAttachment provides a hot plug for connecting a disk to a virtual machine.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -3076,7 +3078,7 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment }, }, }, - Required: []string{"spec", "status"}, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -3088,7 +3090,7 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "VirtualMachineBlockDeviceAttachmentList contains a list of VirtualMachineBlockDeviceAttachment", + Description: "VirtualMachineBlockDeviceAttachmentList contains a list of VirtualMachineBlockDeviceAttachment.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -3142,9 +3144,10 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment Properties: map[string]spec.Schema{ "virtualMachineName": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "The name of the virtual machine to which the disk or image should be connected.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, "blockDeviceRef": { @@ -3174,9 +3177,17 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment Format: "", }, }, + "virtualMachineName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the virtual machine to which this disk is attached.", + Type: []string{"string"}, + Format: "", + }, + }, "conditions": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, + Description: "Contains details of the current state of this API Resource.", + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -3189,8 +3200,9 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineBlockDeviceAttachment }, "observedGeneration": { SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int64", + Description: "The generation last processed by the controller", + Type: []string{"integer"}, + Format: "int64", }, }, }, @@ -4066,22 +4078,31 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineOperationStatus(ref c Format: "", }, }, - "failureReason": { + "conditions": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, }, }, - "failureMessage": { + "observedGeneration": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Type: []string{"integer"}, + Format: "int64", }, }, }, Required: []string{"phase"}, }, }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/api/scripts/update-codegen.sh b/api/scripts/update-codegen.sh index ee41a36f1..a2b67a2dd 100755 --- a/api/scripts/update-codegen.sh +++ b/api/scripts/update-codegen.sh @@ -32,7 +32,7 @@ function source::settings { MODULE="github.com/deckhouse/virtualization/api" PREFIX_GROUP="virtualization.deckhouse.io_" # TODO: Temporary filter until all CRDs become auto-generated. - ALLOWED_RESOURCE_GEN_CRD=("VirtualMachineClass" "ExampleKind1" "ExampleKind2") + ALLOWED_RESOURCE_GEN_CRD=("VirtualMachineClass" "VirtualMachineBlockDeviceAttachment" "ExampleKind1" "ExampleKind2") source "${CODEGEN_PKG}/kube_codegen.sh" } diff --git a/crds/doc-ru-virtualmachineblockdeviceattachments.yaml b/crds/doc-ru-virtualmachineblockdeviceattachments.yaml index f3dd2e94a..5722fd590 100644 --- a/crds/doc-ru-virtualmachineblockdeviceattachments.yaml +++ b/crds/doc-ru-virtualmachineblockdeviceattachments.yaml @@ -15,7 +15,6 @@ spec: kind: description: | Тип блочного устройства. Возможны следующие варианты: - * `VirtualDisk` — использовать `VirtualDisk` в качестве диска. Этот тип всегда монтируется в режиме RW. name: description: | @@ -46,12 +45,11 @@ spec: phase: description: | Фаза ресурса: - * Pending — ресурс был создан и находится в очереди ожидания. * InProgress — диск в процессе подключения к ВМ. * Attached — диск подключен к ВМ. * Failed — возникла проблема с подключением диска. - * Terminating - ресурс находится в процессе удаления. + * Terminating — ресурс находится в процессе удаления. virtualMachineName: description: | Имя виртуальной машины, к которой подключен этот диск. diff --git a/crds/virtualmachineblockdeviceattachments.yaml b/crds/virtualmachineblockdeviceattachments.yaml index 8f066312a..c1ca36828 100644 --- a/crds/virtualmachineblockdeviceattachments.yaml +++ b/crds/virtualmachineblockdeviceattachments.yaml @@ -1,137 +1,191 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: virtualmachineblockdeviceattachments.virtualization.deckhouse.io + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 labels: heritage: deckhouse module: virtualization + name: virtualmachineblockdeviceattachments.virtualization.deckhouse.io spec: group: virtualization.deckhouse.io - scope: Namespaced names: categories: - all - virtualization - plural: virtualmachineblockdeviceattachments - singular: virtualmachineblockdeviceattachment - listKind: VirtualMachineBlockDeviceAttachmentList kind: VirtualMachineBlockDeviceAttachment + listKind: VirtualMachineBlockDeviceAttachmentList + plural: virtualmachineblockdeviceattachments shortNames: - vmbda - vmbdas - preserveUnknownFields: false + singular: virtualmachineblockdeviceattachment + scope: Namespaced versions: - - name: v1alpha2 - served: true - storage: true + - additionalPrinterColumns: + - description: VirtualMachineBlockDeviceAttachment phase. + jsonPath: .status.phase + name: PHASE + type: string + - description: The name of the virtual machine to which this disk is attached. + jsonPath: .status.virtualMachineName + name: VIRTUAL MACHINE NAME + type: string + - description: Time of creation resource. + jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha2 schema: openAPIV3Schema: - type: object - description: | - The resource provides a hot plug for connecting a disk to a virtual machine. - required: - - spec + description: + VirtualMachineBlockDeviceAttachment provides a hot plug for connecting + a disk to a virtual machine. properties: - spec: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: type: object - required: - - virtualMachineName - - blockDeviceRef + spec: properties: - virtualMachineName: - type: string - description: | - The name of the virtual machine to which the disk or image is connected. blockDeviceRef: - type: object - description: | - The block device that will be connected as a hot plug disk to the virtual machine. - required: ["kind", "name"] properties: kind: - type: string - enum: - - "VirtualDisk" - description: | + description: |- The type of the block device. Options are: - - * `VirtualDisk` — Use `VirtualDisk` as the disk. This type is always mounted in RW mode. - name: + * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode. + enum: + - VirtualDisk type: string + name: description: The name of block device to attach. - status: + type: string + type: object + virtualMachineName: + description: + The name of the virtual machine to which the disk or + image should be connected. + type: string + required: + - blockDeviceRef + - virtualMachineName type: object + status: properties: conditions: - description: | - The latest available observations of an object's current state. - type: array + description: Contains details of the current state of this API Resource. items: - type: object + description: + "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: - lastProbeTime: - description: Last time the condition was checked. - format: date-time - type: string lastTransitionTime: - description: Last time the condition transit from one status to another. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: Human readable message indicating details about last transition. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer reason: - description: (brief) reason for the condition's last transition. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string status: - description: Status of the condition, one of True, False, Unknown. + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown type: string - enum: ["True", "False", "Unknown"] type: - description: Type of condition. + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string required: + - lastTransitionTime + - message + - reason - status - type - virtualMachineName: - type: string - description: | - The name of the virtual machine to which this disk is attached. - phase: - type: string - description: | - Represents the current phase of resource: - - * Pending - the resource has been created and is on a waiting queue. - * InProgress - the disk is in the process of being attached. - * Attached - the disk is attached to virtual machine. - * Failed - there was a problem with attaching the disk. - * Terminating - the process of resource deletion is in progress. - enum: - - "Pending" - - "InProgress" - - "Attached" - - "Failed" - - "Terminating" + type: object + type: array observedGeneration: + description: The generation last processed by the controller + format: int64 type: integer - description: | - The generation last processed by the controller. - additionalPrinterColumns: - - name: Phase - type: string - jsonPath: .status.phase - - name: virtualMachineName - type: string - jsonPath: .status.virtualMachineName - priority: 1 - - name: FailureReason - type: string - jsonPath: .status.failureReason - priority: 1 - - name: FailureMessage - type: string - jsonPath: .status.failureMessage - priority: 1 + phase: + description: |- + BlockDeviceAttachmentPhase defines current status of resource: + * Pending — the resource has been created and is on a waiting queue. + * InProgress — the disk is in the process of being attached. + * Attached — the disk is attached to virtual machine. + * Failed — there was a problem with attaching the disk. + * Terminating — the process of resource deletion is in progress. + enum: + - Pending + - InProgress + - Attached + - Failed + - Terminating + type: string + virtualMachineName: + description: + The name of the virtual machine to which this disk is + attached. + type: string + type: object + required: + - spec + type: object + served: true + storage: true subresources: status: {} diff --git a/images/virtualization-artifact/pkg/controller/conditions/builder.go b/images/virtualization-artifact/pkg/controller/conditions/builder.go index 3c2c41cdb..141a289fd 100644 --- a/images/virtualization-artifact/pkg/controller/conditions/builder.go +++ b/images/virtualization-artifact/pkg/controller/conditions/builder.go @@ -25,17 +25,37 @@ type Conder interface { Condition() metav1.Condition } +func HasCondition(conditionType Stringer, conditions []metav1.Condition) bool { + for _, condition := range conditions { + if condition.Type == conditionType.String() { + return true + } + } + + return false +} + func SetCondition(c Conder, conditions *[]metav1.Condition) { meta.SetStatusCondition(conditions, c.Condition()) } +func GetCondition(condType Stringer, conditions []metav1.Condition) (metav1.Condition, bool) { + for _, condition := range conditions { + if condition.Type == condType.String() { + return condition, true + } + } + + return metav1.Condition{}, false +} + func NewConditionBuilder(conditionType Stringer) *ConditionBuilder { - return &ConditionBuilder{conditionType: conditionType.String()} + return &ConditionBuilder{conditionType: conditionType} } type ConditionBuilder struct { status metav1.ConditionStatus - conditionType string + conditionType Stringer reason string message string generation int64 @@ -43,7 +63,7 @@ type ConditionBuilder struct { func (c *ConditionBuilder) Condition() metav1.Condition { return metav1.Condition{ - Type: c.conditionType, + Type: c.conditionType.String(), Status: c.status, Reason: c.reason, LastTransitionTime: metav1.Now(), @@ -77,3 +97,7 @@ func (c *ConditionBuilder) Clone() *ConditionBuilder { *out = *c return out } + +func (c *ConditionBuilder) GetType() Stringer { + return c.conditionType +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validators/spec_changes_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validators/spec_changes_validator.go index 16f58ba74..201f705ef 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validators/spec_changes_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validators/spec_changes_validator.go @@ -55,5 +55,15 @@ func (v *SpecChangesValidator) ValidateUpdate(_ context.Context, oldVD, newVD *v } } + if len(newVD.Status.AttachedToVirtualMachines) > 0 { + if !reflect.DeepEqual(oldVD.Spec.DataSource, newVD.Spec.DataSource) { + return nil, fmt.Errorf("VirtualDisk has already been attached to the virtual machine: data source cannot be changed after disk is attached") + } + + if !reflect.DeepEqual(oldVD.Spec.PersistentVolumeClaim.StorageClass, newVD.Spec.PersistentVolumeClaim.StorageClass) { + return nil, fmt.Errorf("VirtualDisk has already been attached to the virtual machine: storage class cannot be changed after disk is attached") + } + } + return nil, nil } diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go index 941906ac6..8b1f2b662 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" @@ -42,20 +43,15 @@ func NewBlockDeviceReadyHandler(attachment *service.AttachmentService) *BlockDev } func (h BlockDeviceReadyHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachineBlockDeviceAttachment) (reconcile.Result, error) { - condition, ok := service.GetCondition(vmbdacondition.BlockDeviceReadyType, vmbda.Status.Conditions) - if !ok { - condition = metav1.Condition{ - Type: vmbdacondition.BlockDeviceReadyType, - Status: metav1.ConditionUnknown, - } - } + cb := conditions.NewConditionBuilder(vmbdacondition.BlockDeviceReadyType) + defer func() { conditions.SetCondition(cb.Generation(vmbda.Generation), &vmbda.Status.Conditions) }() - defer func() { service.SetCondition(condition, &vmbda.Status.Conditions) }() + if !conditions.HasCondition(cb.GetType(), vmbda.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.BlockDeviceReadyUnknown) + } if vmbda.DeletionTimestamp != nil { - condition.Status = metav1.ConditionUnknown - condition.Reason = "" - condition.Message = "" + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.BlockDeviceReadyUnknown) return reconcile.Result{}, nil } @@ -72,32 +68,45 @@ func (h BlockDeviceReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Virtu } if vd == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = fmt.Sprintf("VirtualDisk %s not found.", vdKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("VirtualDisk %q not found.", vdKey.String())) return reconcile.Result{}, nil } if vd.Generation != vd.Status.ObservedGeneration { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = fmt.Sprintf("Waiting for the VirtualDisk %s to be observed in its latest state generation.", vdKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Waiting for the VirtualDisk %q to be observed in its latest state generation.", vdKey.String())) return reconcile.Result{}, nil } - var diskReadyCondition metav1.Condition - diskReadyCondition, ok = service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) - if !ok || diskReadyCondition.Status != metav1.ConditionTrue { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = fmt.Sprintf("VirtualDisk %s is not Ready: waiting for the VirtualDisk to be Ready.", vdKey.String()) + if vd.Status.Phase != virtv2.DiskReady && vd.Status.Phase != virtv2.DiskWaitForFirstConsumer { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("VirtualDisk %q is not ready to be attached to the virtual machine: waiting for the VirtualDisk to be ready for attachment.", vdKey.String())) return reconcile.Result{}, nil } + if vd.Status.Phase == virtv2.DiskReady { + diskReadyCondition, _ := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + if diskReadyCondition.Status != metav1.ConditionTrue { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("VirtualDisk %q is not Ready: waiting for the VirtualDisk to be Ready.", vdKey.String())) + return reconcile.Result{}, nil + } + } + if vd.Status.Target.PersistentVolumeClaim == "" { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = "Waiting until VirtualDisk has associated PersistentVolumeClaim name." + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message("Waiting until VirtualDisk has associated PersistentVolumeClaim name.") return reconcile.Result{}, nil } @@ -107,22 +116,22 @@ func (h BlockDeviceReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Virtu } if pvc == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = fmt.Sprintf("Underlying PersistentVolumeClaim %s not found.", vd.Status.Target) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Underlying PersistentVolumeClaim %q not found.", vd.Status.Target)) return reconcile.Result{}, nil } - if pvc.Status.Phase != corev1.ClaimBound { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.BlockDeviceNotReady - condition.Message = fmt.Sprintf("Underlying PersistentVolumeClaim %s not bound.", vd.Status.Target) + if vd.Status.Phase == virtv2.DiskReady && pvc.Status.Phase != corev1.ClaimBound { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Underlying PersistentVolumeClaim %q not bound.", vd.Status.Target)) return reconcile.Result{}, nil } - condition.Status = metav1.ConditionTrue - condition.Reason = vmbdacondition.BlockDeviceReady - condition.Message = "" + cb.Status(metav1.ConditionTrue).Reason(vmbdacondition.BlockDeviceReady) return reconcile.Result{}, nil default: return reconcile.Result{}, fmt.Errorf("unknown block device kind %s", vmbda.Spec.BlockDeviceRef.Kind) diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go index dceaf5b6e..b8e41d866 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go @@ -25,6 +25,7 @@ import ( virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -46,15 +47,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi // TODO protect vd. - condition, ok := service.GetCondition(vmbdacondition.AttachedType, vmbda.Status.Conditions) - if !ok { - condition = metav1.Condition{ - Type: vmbdacondition.AttachedType, - Status: metav1.ConditionUnknown, - } - } + cb := conditions.NewConditionBuilder(vmbdacondition.AttachedType) + defer func() { conditions.SetCondition(cb.Generation(vmbda.Generation), &vmbda.Status.Conditions) }() - defer func() { service.SetCondition(condition, &vmbda.Status.Conditions) }() + if !conditions.HasCondition(cb.GetType(), vmbda.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.AttachedUnknown) + } vd, err := h.attacher.GetVirtualDisk(ctx, vmbda.Spec.BlockDeviceRef.Name, vmbda.Namespace) if err != nil { @@ -88,9 +86,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi } vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseTerminating - condition.Status = metav1.ConditionUnknown - condition.Reason = "" - condition.Message = "" + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.BlockDeviceReadyUnknown) return reconcile.Result{}, nil } @@ -106,13 +102,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi } vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseFailed - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.Conflict - condition.Message = fmt.Sprintf( - "Another VirtualMachineBlockDeviceAttachment %s/%s already exists "+ - "with the same block device %q for hot-plugging.", - vmbda.Namespace, conflictWithName, vmbda.Spec.BlockDeviceRef.Name, - ) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.Conflict). + Message(fmt.Sprintf( + "Another VirtualMachineBlockDeviceAttachment %s/%s already exists "+ + "with the same block device %q for hot-plugging.", + vmbda.Namespace, conflictWithName, vmbda.Spec.BlockDeviceRef.Name, + )) return reconcile.Result{}, nil } @@ -121,45 +118,50 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending } - blockDeviceReady, ok := service.GetCondition(vmbdacondition.BlockDeviceReadyType, vmbda.Status.Conditions) - if !ok || blockDeviceReady.Status != metav1.ConditionTrue { + blockDeviceReady, _ := conditions.GetCondition(vmbdacondition.BlockDeviceReadyType, vmbda.Status.Conditions) + if blockDeviceReady.Status != metav1.ConditionTrue { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = "Waiting for block device to be ready." + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message("Waiting for block device to be ready.") return reconcile.Result{}, nil } - virtualMachineReady, ok := service.GetCondition(vmbdacondition.VirtualMachineReadyType, vmbda.Status.Conditions) - if !ok || virtualMachineReady.Status != metav1.ConditionTrue { + virtualMachineReady, _ := conditions.GetCondition(vmbdacondition.VirtualMachineReadyType, vmbda.Status.Conditions) + if virtualMachineReady.Status != metav1.ConditionTrue { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = "Waiting for virtual machine to be ready." + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message("Waiting for virtual machine to be ready.") return reconcile.Result{}, nil } if vd == nil { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = fmt.Sprintf("VirtualDisk %s not found.", vmbda.Spec.BlockDeviceRef.Name) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(fmt.Sprintf("VirtualDisk %q not found.", vmbda.Spec.BlockDeviceRef.Name)) return reconcile.Result{}, nil } if vm == nil { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = fmt.Sprintf("VirtualMachine %s not found.", vmbda.Spec.VirtualMachineName) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(fmt.Sprintf("VirtualMachine %q not found.", vmbda.Spec.VirtualMachineName)) return reconcile.Result{}, nil } if kvvm == nil { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = fmt.Sprintf("InternalVirtualizationVirtualMachine %s not found.", vm.Name) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(fmt.Sprintf("InternalVirtualizationVirtualMachine %q not found.", vm.Name)) return reconcile.Result{}, nil } @@ -170,9 +172,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi if kvvmi == nil { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = fmt.Sprintf("InternalVirtualizationVirtualMachineInstance %s not found.", vm.Name) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(fmt.Sprintf("InternalVirtualizationVirtualMachineInstance %q not found.", vm.Name)) return reconcile.Result{}, nil } @@ -183,9 +186,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi if err != nil { if errors.Is(err, service.ErrVolumeStatusNotReady) { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseInProgress - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.AttachmentRequestSent - condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.AttachmentRequestSent). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, nil } @@ -196,9 +200,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi log.Info("Hot plug is completed and disk is attached") vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseAttached - condition.Status = metav1.ConditionTrue - condition.Reason = vmbdacondition.Attached - condition.Message = "" + cb.Status(metav1.ConditionTrue).Reason(vmbdacondition.Attached) + + vmbda.Status.VirtualMachineName = vm.Name + return reconcile.Result{}, nil } @@ -213,33 +218,37 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi } vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseInProgress - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.AttachmentRequestSent - condition.Message = "Attachment request has sent: attachment is in progress." + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.AttachmentRequestSent). + Message("Attachment request has sent: attachment is in progress.") return reconcile.Result{}, nil case errors.Is(err, service.ErrDiskIsSpecAttached): log.Info("VirtualDisk is already attached to the virtual machine spec") vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseFailed - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.Conflict - condition.Message = service.CapitalizeFirstLetter(err.Error()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.Conflict). + Message(service.CapitalizeFirstLetter(err.Error())) return reconcile.Result{}, nil case errors.Is(err, service.ErrHotPlugRequestAlreadySent): log.Info("Attachment request sent: attachment is in progress.") vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseInProgress - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.AttachmentRequestSent - condition.Message = "Attachment request sent: attachment is in progress." + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.AttachmentRequestSent). + Message("Attachment request sent: attachment is in progress.") return reconcile.Result{}, nil case errors.Is(err, service.ErrVirtualMachineWaitsForRestartApproval): log.Info("Virtual machine waits for restart approval") vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = service.CapitalizeFirstLetter(err.Error()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(service.CapitalizeFirstLetter(err.Error())) return reconcile.Result{}, nil default: return reconcile.Result{}, err diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/virtual_machine_ready.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/virtual_machine_ready.go index b40645e4c..9bf1f8f5e 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/virtual_machine_ready.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/virtual_machine_ready.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vmbdacondition" @@ -40,20 +41,15 @@ func NewVirtualMachineReadyHandler(attachment *service.AttachmentService) *Virtu } func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachineBlockDeviceAttachment) (reconcile.Result, error) { - condition, ok := service.GetCondition(vmbdacondition.VirtualMachineReadyType, vmbda.Status.Conditions) - if !ok { - condition = metav1.Condition{ - Type: vmbdacondition.VirtualMachineReadyType, - Status: metav1.ConditionUnknown, - } - } + cb := conditions.NewConditionBuilder(vmbdacondition.VirtualMachineReadyType) + defer func() { conditions.SetCondition(cb.Generation(vmbda.Generation), &vmbda.Status.Conditions) }() - defer func() { service.SetCondition(condition, &vmbda.Status.Conditions) }() + if !conditions.HasCondition(cb.GetType(), vmbda.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.VirtualMachineReadyUnknown) + } if vmbda.DeletionTimestamp != nil { - condition.Status = metav1.ConditionUnknown - condition.Reason = "" - condition.Message = "" + cb.Status(metav1.ConditionUnknown).Reason(vmbdacondition.VirtualMachineReadyUnknown) return reconcile.Result{}, nil } @@ -68,9 +64,10 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Vi } if vm == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.VirtualMachineNotReady - condition.Message = fmt.Sprintf("VirtualMachine %s not found.", vmKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.VirtualMachineNotReady). + Message(fmt.Sprintf("VirtualMachine %q not found.", vmKey.String())) return reconcile.Result{}, nil } @@ -79,14 +76,16 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Vi // OK. case virtv2.MachineStopping, virtv2.MachineStopped, virtv2.MachineStarting: vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.NotAttached - condition.Message = fmt.Sprintf("VirtualMachine %s is %s: waiting for the VirtualMachine to be Running.", vm.Name, vm.Status.Phase) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.NotAttached). + Message(fmt.Sprintf("VirtualMachine %q is %s: waiting for the VirtualMachine to be Running.", vm.Name, vm.Status.Phase)) return reconcile.Result{}, nil default: - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.VirtualMachineNotReady - condition.Message = fmt.Sprintf("Waiting for the VirtualMachine %s to be Running.", vmKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.VirtualMachineNotReady). + Message(fmt.Sprintf("Waiting for the VirtualMachine %q to be Running.", vmKey.String())) return reconcile.Result{}, nil } @@ -96,9 +95,10 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Vi } if kvvm == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.VirtualMachineNotReady - condition.Message = fmt.Sprintf("VirtualMachine %s Running, but underlying InternalVirtualizationVirtualMachine not found.", vmKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.VirtualMachineNotReady). + Message(fmt.Sprintf("VirtualMachine %q Running, but underlying InternalVirtualizationVirtualMachine not found.", vmKey.String())) return reconcile.Result{}, nil } @@ -108,14 +108,14 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Vi } if kvvmi == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmbdacondition.VirtualMachineNotReady - condition.Message = fmt.Sprintf("VirtualMachine %s Running, but underlying InternalVirtualizationVirtualMachineInstance not found.", vmKey.String()) + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.VirtualMachineNotReady). + Message(fmt.Sprintf("VirtualMachine %q Running, but underlying InternalVirtualizationVirtualMachineInstance not found.", vmKey.String())) return reconcile.Result{}, nil } - condition.Status = metav1.ConditionTrue - condition.Reason = vmbdacondition.VirtualMachineReady - condition.Message = "" + cb.Status(metav1.ConditionTrue).Reason(vmbdacondition.VirtualMachineReady) + return reconcile.Result{}, nil }