diff --git a/api/core/v1alpha2/virtual_disk.go b/api/core/v1alpha2/virtual_disk.go index 97fe2cd37..8f776f8cb 100644 --- a/api/core/v1alpha2/virtual_disk.go +++ b/api/core/v1alpha2/virtual_disk.go @@ -50,10 +50,20 @@ type VirtualDiskStatus struct { UploadCommand string `json:"uploadCommand,omitempty"` Phase DiskPhase `json:"phase"` AttachedToVirtualMachines []AttachedVirtualMachine `json:"attachedToVirtualMachines,omitempty"` + Stats VirtualDiskStats `json:"stats"` Conditions []metav1.Condition `json:"conditions,omitempty"` ObservedGeneration int64 `json:"observedGeneration,omitempty"` } +type VirtualDiskStats struct { + CreationDuration VirtualDiskStatsCreationDuration `json:"creationDuration"` +} + +type VirtualDiskStatsCreationDuration struct { + WaitingForDependencies *metav1.Duration `json:"waitingForDependencies,omitempty"` + Provisioning *metav1.Duration `json:"provisioning,omitempty"` +} + type AttachedVirtualMachine struct { Name string `json:"name"` } diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 388d31bd6..2b3c25e4e 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -728,6 +728,49 @@ func (in *VirtualDiskSpec) DeepCopy() *VirtualDiskSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualDiskStats) DeepCopyInto(out *VirtualDiskStats) { + *out = *in + in.CreationDuration.DeepCopyInto(&out.CreationDuration) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskStats. +func (in *VirtualDiskStats) DeepCopy() *VirtualDiskStats { + if in == nil { + return nil + } + out := new(VirtualDiskStats) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualDiskStatsCreationDuration) DeepCopyInto(out *VirtualDiskStatsCreationDuration) { + *out = *in + if in.WaitingForDependencies != nil { + in, out := &in.WaitingForDependencies, &out.WaitingForDependencies + *out = new(v1.Duration) + **out = **in + } + if in.Provisioning != nil { + in, out := &in.Provisioning, &out.Provisioning + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskStatsCreationDuration. +func (in *VirtualDiskStatsCreationDuration) DeepCopy() *VirtualDiskStatsCreationDuration { + if in == nil { + return nil + } + out := new(VirtualDiskStatsCreationDuration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualDiskStatus) DeepCopyInto(out *VirtualDiskStatus) { *out = *in @@ -738,6 +781,7 @@ func (in *VirtualDiskStatus) DeepCopyInto(out *VirtualDiskStatus) { *out = make([]AttachedVirtualMachine, len(*in)) copy(*out, *in) } + in.Stats.DeepCopyInto(&out.Stats) if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) 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 00b18a775..456eee9f0 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -66,6 +66,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskObjectRef": schema_virtualization_api_core_v1alpha2_VirtualDiskObjectRef(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskPersistentVolumeClaim": schema_virtualization_api_core_v1alpha2_VirtualDiskPersistentVolumeClaim(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSpec": schema_virtualization_api_core_v1alpha2_VirtualDiskSpec(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStats": schema_virtualization_api_core_v1alpha2_VirtualDiskStats(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStatsCreationDuration": schema_virtualization_api_core_v1alpha2_VirtualDiskStatsCreationDuration(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStatus": schema_virtualization_api_core_v1alpha2_VirtualDiskStatus(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualImage": schema_virtualization_api_core_v1alpha2_VirtualImage(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualImageDataSource": schema_virtualization_api_core_v1alpha2_VirtualImageDataSource(ref), @@ -1807,6 +1809,51 @@ func schema_virtualization_api_core_v1alpha2_VirtualDiskSpec(ref common.Referenc } } +func schema_virtualization_api_core_v1alpha2_VirtualDiskStats(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "creationDuration": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStatsCreationDuration"), + }, + }, + }, + Required: []string{"creationDuration"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStatsCreationDuration"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualDiskStatsCreationDuration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "waitingForDependencies": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "provisioning": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + func schema_virtualization_api_core_v1alpha2_VirtualDiskStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1863,6 +1910,12 @@ func schema_virtualization_api_core_v1alpha2_VirtualDiskStatus(ref common.Refere }, }, }, + "stats": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStats"), + }, + }, "conditions": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, @@ -1883,11 +1936,11 @@ func schema_virtualization_api_core_v1alpha2_VirtualDiskStatus(ref common.Refere }, }, }, - Required: []string{"downloadSpeed", "target", "phase"}, + Required: []string{"downloadSpeed", "target", "phase", "stats"}, }, }, Dependencies: []string{ - "github.com/deckhouse/virtualization/api/core/v1alpha2.AttachedVirtualMachine", "github.com/deckhouse/virtualization/api/core/v1alpha2.DiskTarget", "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskDownloadSpeed", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/deckhouse/virtualization/api/core/v1alpha2.AttachedVirtualMachine", "github.com/deckhouse/virtualization/api/core/v1alpha2.DiskTarget", "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskDownloadSpeed", "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStats", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/crds/doc-ru-virtualdisk.yaml b/crds/doc-ru-virtualdisk.yaml index 2e65df764..3eaa05a3a 100644 --- a/crds/doc-ru-virtualdisk.yaml +++ b/crds/doc-ru-virtualdisk.yaml @@ -112,7 +112,21 @@ spec: description: Тип условия. attachedToVirtualMachines: description: | - Список виртуальных машин, использующих этот диск + Список виртуальных машин, использующих этот диск. + stats: + description: | + Статистика по виртуальному диску. + properties: + creationDuration: + description: | + Время создания виртуального диска. + properties: + waitingForDependencies: + description: | + Длительность ожидания зависимостей для создания виртуального диска. + provisioning: + description: | + Длительность создания ресурса (копирование/загрузка/создание диска). capacity: description: | Емкость PVC в человекочитаемом формате. @@ -137,7 +151,7 @@ spec: Текущее состояние ресурса `VirtualDisk`: * Pending — ресурс был создан и находится в очереди ожидания. - * Provisioning — идет процесс создания ресурса (копирование/загрузка/создание образа). + * Provisioning — идет процесс создания ресурса (копирование/загрузка/создание диска). * WaitForUserUpload — ожидание загрузки образа пользователем. Путь для загрузки образа указывается в `.status.uploadCommand`. * Ready — ресурс создан и готов к использованию. * Resizing — идет процесс увеличения размера диска. diff --git a/crds/virtualdisk.yaml b/crds/virtualdisk.yaml index a57e31072..d9902309d 100644 --- a/crds/virtualdisk.yaml +++ b/crds/virtualdisk.yaml @@ -298,6 +298,25 @@ spec: properties: name: type: string + stats: + type: object + description: "VirtualDisk statistics" + properties: + creationDuration: + type: object + description: | + The waiting time for the virtual disk creation. + properties: + waitingForDependencies: + type: string + description: | + The waiting time for dependent resources. + nullable: true + provisioning: + type: string + description: | + The waiting time for the virtual disk to be provisioned (copying/downloading/filling the PVC). + nullable: true observedGeneration: type: integer description: | diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/stats.go b/images/virtualization-artifact/pkg/controller/vd/internal/stats.go new file mode 100644 index 000000000..990474d1c --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/stats.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" +) + +type StatsHandler struct{} + +func NewStatsHandler() *StatsHandler { + return &StatsHandler{} +} + +func (h StatsHandler) Handle(_ context.Context, vd *virtv2.VirtualDisk) (reconcile.Result, error) { + sinceCreation := time.Since(vd.CreationTimestamp.Time).Truncate(time.Second) + + datasourceReady, _ := service.GetCondition(vdcondition.DatasourceReadyType, vd.Status.Conditions) + if datasourceReady.Status == metav1.ConditionTrue && vd.Status.Stats.CreationDuration.WaitingForDependencies == nil { + vd.Status.Stats.CreationDuration.WaitingForDependencies = &metav1.Duration{ + Duration: sinceCreation, + } + } + + ready, _ := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + if ready.Status == metav1.ConditionTrue && vd.Status.Stats.CreationDuration.Provisioning == nil { + duration := sinceCreation + + if vd.Status.Stats.CreationDuration.WaitingForDependencies != nil { + duration -= vd.Status.Stats.CreationDuration.WaitingForDependencies.Duration + } + + vd.Status.Stats.CreationDuration.Provisioning = &metav1.Duration{ + Duration: duration, + } + } + + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go index 5195d46b0..7a4565f03 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go @@ -76,6 +76,7 @@ func NewController( internal.NewResizingHandler(logger, disk), internal.NewDeletionHandler(sources), internal.NewAttacheeHandler(mgr.GetClient()), + internal.NewStatsHandler(), ) vdController, err := controller.New(ControllerName, mgr, controller.Options{Reconciler: reconciler}) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go b/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go index cf07ee840..d06237ecf 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go @@ -244,7 +244,7 @@ func (h *LifeCycleHandler) syncStats(current, changed *virtv2.VirtualMachine, kv } var empty virtv1.VirtualMachineInstanceGuestOSInfo if kvvmi != nil && empty == current.Status.GuestOSInfo && empty != kvvmi.Status.GuestOSInfo && !pt.Timestamp.IsZero() { - launchTimeDuration.GuestOSAgentStarting = &metav1.Duration{Duration: time.Now().Truncate(1 * time.Second).Sub(pt.Timestamp.Time)} + launchTimeDuration.GuestOSAgentStarting = &metav1.Duration{Duration: time.Now().Truncate(time.Second).Sub(pt.Timestamp.Time)} } } } @@ -296,7 +296,7 @@ type PhaseTransitions struct { } func NewPhaseTransitions(phaseTransitions []virtv2.VirtualMachinePhaseTransitionTimestamp, oldPhase, newPhase virtv2.MachinePhase) PhaseTransitions { - now := metav1.NewTime(time.Now().Truncate(1 * time.Second)) + now := metav1.NewTime(time.Now().Truncate(time.Second)) phasesTransitionsMap := make(map[virtv2.MachinePhase]virtv2.VirtualMachinePhaseTransitionTimestamp, len(phaseTransitions)) for _, pt := range phaseTransitions {