From c122e775a3ce0890402ae87db287a29e44eaf8a5 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Mon, 16 Sep 2024 15:04:31 +1000 Subject: [PATCH] capture new disk size --- db/migrations/20240916045259_updates_disk.sql | 9 ++ db/queries/models.go | 19 +-- db/queries/query.sql | 4 +- db/queries/query.sql.go | 25 ++-- server/handler/vmDelete.go | 2 +- server/handler/vmModify.go | 44 +++++- server/models/models.go | 134 ++++++++++++++++++ 7 files changed, 212 insertions(+), 25 deletions(-) create mode 100644 db/migrations/20240916045259_updates_disk.sql diff --git a/db/migrations/20240916045259_updates_disk.sql b/db/migrations/20240916045259_updates_disk.sql new file mode 100644 index 0000000..1ad4e64 --- /dev/null +++ b/db/migrations/20240916045259_updates_disk.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN NewProvisionedDisk REAL; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN NewProvisionedDisk; +-- +goose StatementEnd diff --git a/db/queries/models.go b/db/queries/models.go index 0e04384..2ecc789 100644 --- a/db/queries/models.go +++ b/db/queries/models.go @@ -49,13 +49,14 @@ type Inventory struct { } type Updates struct { - Uid int64 - InventoryId sql.NullInt64 - UpdateTime sql.NullInt64 - UpdateType string - NewVcpus sql.NullInt64 - NewRam sql.NullInt64 - NewResourcePool sql.NullString - EventKey sql.NullString - EventId sql.NullString + Uid int64 + InventoryId sql.NullInt64 + UpdateTime sql.NullInt64 + UpdateType string + NewVcpus sql.NullInt64 + NewRam sql.NullInt64 + NewResourcePool sql.NullString + EventKey sql.NullString + EventId sql.NullString + NewProvisionedDisk sql.NullFloat64 } diff --git a/db/queries/query.sql b/db/queries/query.sql index 61d7a67..7460eac 100644 --- a/db/queries/query.sql +++ b/db/queries/query.sql @@ -29,9 +29,9 @@ WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName'); -- name: CreateUpdate :one INSERT INTO "Updates" ( - "InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool" + "InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk" ) VALUES( - ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ? ) RETURNING *; diff --git a/db/queries/query.sql.go b/db/queries/query.sql.go index 965d665..ec43c0d 100644 --- a/db/queries/query.sql.go +++ b/db/queries/query.sql.go @@ -146,22 +146,23 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams const createUpdate = `-- name: CreateUpdate :one INSERT INTO "Updates" ( - "InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool" + "InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk" ) VALUES( - ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ? ) -RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId +RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk ` type CreateUpdateParams struct { - InventoryId sql.NullInt64 - EventKey sql.NullString - EventId sql.NullString - UpdateTime sql.NullInt64 - UpdateType string - NewVcpus sql.NullInt64 - NewRam sql.NullInt64 - NewResourcePool sql.NullString + InventoryId sql.NullInt64 + EventKey sql.NullString + EventId sql.NullString + UpdateTime sql.NullInt64 + UpdateType string + NewVcpus sql.NullInt64 + NewRam sql.NullInt64 + NewResourcePool sql.NullString + NewProvisionedDisk sql.NullFloat64 } func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Updates, error) { @@ -174,6 +175,7 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd arg.NewVcpus, arg.NewRam, arg.NewResourcePool, + arg.NewProvisionedDisk, ) var i Updates err := row.Scan( @@ -186,6 +188,7 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd &i.NewResourcePool, &i.EventKey, &i.EventId, + &i.NewProvisionedDisk, ) return i, err } diff --git a/server/handler/vmDelete.go b/server/handler/vmDelete.go index 0bdc33b..3a34802 100644 --- a/server/handler/vmDelete.go +++ b/server/handler/vmDelete.go @@ -32,11 +32,11 @@ func (h *Handler) VmDelete(w http.ResponseWriter, r *http.Request) { var event models.CloudEventReceived if err := json.Unmarshal(reqBody, &event); err != nil { h.Logger.Error("unable to decode json", "error", err) + prettyPrint(event) http.Error(w, "Invalid JSON body", http.StatusBadRequest) return } else { h.Logger.Debug("successfully decoded JSON") - prettyPrint(event) } // Use the event CreatedTime to update the DeletionTime column in the VM inventory table diff --git a/server/handler/vmModify.go b/server/handler/vmModify.go index 38dd582..4c63260 100644 --- a/server/handler/vmModify.go +++ b/server/handler/vmModify.go @@ -12,7 +12,10 @@ import ( "strings" "time" "vctp/db/queries" + "vctp/internal/vcenter" models "vctp/server/models" + + "github.com/vmware/govmomi/vim25/types" ) // VmModify receives the CloudEvent for a VM modification or move @@ -65,6 +68,7 @@ func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { } else { found = true params.NewVcpus = sql.NullInt64{Int64: i, Valid: i > 0} + params.UpdateType = "reconfigure" } case "config.hardware.memoryMB": i, err := strconv.ParseInt(change["newValue"], 10, 64) @@ -73,6 +77,7 @@ func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { } else { found = true params.NewRam = sql.NullInt64{Int64: i, Valid: i > 0} + params.UpdateType = "reconfigure" } } @@ -82,6 +87,10 @@ func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { // TODO - recalculate total disk size h.Logger.Debug("Detected config change for VM disk") + found = true + params.UpdateType = "diskchange" + diskSize := h.calculateNewDiskSize(event) + params.NewProvisionedDisk = sql.NullFloat64{Float64: diskSize, Valid: diskSize > 0} } } @@ -117,7 +126,6 @@ func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { params.EventId = sql.NullString{String: event.CloudEvent.ID, Valid: event.CloudEvent.ID != ""} params.EventKey = sql.NullString{String: strconv.Itoa(event.CloudEvent.Data.Key), Valid: event.CloudEvent.Data.Key > 0} params.UpdateTime = sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0} - params.UpdateType = "reconfigure" // Create the Update database record result, err := h.Database.Queries().CreateUpdate(context.Background(), params) @@ -135,7 +143,7 @@ func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { } else { w.WriteHeader(http.StatusAccepted) fmt.Fprintf(w, "Processed update event but no config changes were of interest\n") - prettyPrint(configChanges) + prettyPrint(event.CloudEvent.Data.ConfigSpec) } } } @@ -179,3 +187,35 @@ func (h *Handler) processConfigChanges(configChanges string) []map[string]string return result } + +func (h *Handler) calculateNewDiskSize(event models.CloudEventReceived) float64 { + var diskSize float64 + h.Logger.Debug("connecting to vcenter") + vc := vcenter.New(h.Logger) + vc.Login(event.CloudEvent.Source) + + vmObject, err := vc.FindVMByIDWithDatacenter(event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Datacenter.Value) + + if err != nil { + h.Logger.Error("Can't locate vm in vCenter", "vmID", event.CloudEvent.Data.VM.VM.Value, "error", err) + } else { + if vmObject.Vm.Config != nil { + h.Logger.Debug("Found VM with config, calculating new total disk size", "vmID", event.CloudEvent.Data.VM.VM.Value) + // Calculate the total disk allocated in GB + for _, device := range vmObject.Vm.Config.Hardware.Device { + if disk, ok := device.(*types.VirtualDisk); ok { + diskSize += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB + } + } + } + } + + err = vc.Logout() + if err != nil { + h.Logger.Error("unable to logout of vcenter", "error", err) + } + + h.Logger.Debug("Calculated new disk size", "value", diskSize) + + return diskSize +} diff --git a/server/models/models.go b/server/models/models.go index 64c25b7..8ba38ac 100644 --- a/server/models/models.go +++ b/server/models/models.go @@ -2,6 +2,7 @@ package models import ( "encoding/json" + "time" ) type CloudEventReceived struct { @@ -66,3 +67,136 @@ type CloudEventReceived struct { type ConfigChangesReceived struct { Modified string `json:"modified"` } + +type ConfigSpec struct { + AlternateGuestName string `json:"AlternateGuestName"` + Annotation string `json:"Annotation"` + BootOptions any `json:"BootOptions"` + ChangeTrackingEnabled any `json:"ChangeTrackingEnabled"` + ChangeVersion string `json:"ChangeVersion"` + ConsolePreferences any `json:"ConsolePreferences"` + CPUAffinity any `json:"CpuAffinity"` + CPUAllocation any `json:"CpuAllocation"` + CPUFeatureMask any `json:"CpuFeatureMask"` + CPUHotAddEnabled any `json:"CpuHotAddEnabled"` + CPUHotRemoveEnabled any `json:"CpuHotRemoveEnabled"` + CreateDate time.Time `json:"CreateDate"` + Crypto any `json:"Crypto"` + DeviceChange []struct { + Backing any `json:"Backing"` + Device struct { + Backing struct { + BackingObjectID string `json:"BackingObjectId"` + ChangeID string `json:"ChangeId"` + ContentID string `json:"ContentId"` + Datastore struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"Datastore"` + DeltaDiskFormat string `json:"DeltaDiskFormat"` + DeltaDiskFormatVariant string `json:"DeltaDiskFormatVariant"` + DeltaGrainSize int `json:"DeltaGrainSize"` + DigestEnabled any `json:"DigestEnabled"` + DiskMode string `json:"DiskMode"` + EagerlyScrub bool `json:"EagerlyScrub"` + FileName string `json:"FileName"` + KeyID any `json:"KeyId"` + Parent any `json:"Parent"` + Sharing string `json:"Sharing"` + Split any `json:"Split"` + ThinProvisioned bool `json:"ThinProvisioned"` + UUID string `json:"Uuid"` + WriteThrough any `json:"WriteThrough"` + } `json:"Backing"` + CapacityInBytes int `json:"CapacityInBytes"` + CapacityInKB int `json:"CapacityInKB"` + Connectable any `json:"Connectable"` + ControllerKey int `json:"ControllerKey"` + DeviceInfo any `json:"DeviceInfo"` + DiskObjectID string `json:"DiskObjectId"` + Iofilter any `json:"Iofilter"` + Key int `json:"Key"` + NativeUnmanagedLinkedClone any `json:"NativeUnmanagedLinkedClone"` + Shares any `json:"Shares"` + SlotInfo any `json:"SlotInfo"` + StorageIOAllocation struct { + Limit int `json:"Limit"` + Reservation any `json:"Reservation"` + Shares struct { + Level string `json:"Level"` + Shares int `json:"Shares"` + } `json:"Shares"` + } `json:"StorageIOAllocation"` + UnitNumber int `json:"UnitNumber"` + VDiskID any `json:"VDiskId"` + VFlashCacheConfigInfo any `json:"VFlashCacheConfigInfo"` + } `json:"Device"` + FileOperation string `json:"FileOperation"` + Operation string `json:"Operation"` + Profile []struct { + ProfileData struct { + ExtensionKey string `json:"ExtensionKey"` + ObjectData time.Time `json:"ObjectData"` + } `json:"ProfileData"` + ProfileID string `json:"ProfileId"` + ProfileParams any `json:"ProfileParams"` + ReplicationSpec any `json:"ReplicationSpec"` + } `json:"Profile"` + } `json:"DeviceChange"` + ExtraConfig any `json:"ExtraConfig"` + Files struct { + FtMetadataDirectory string `json:"FtMetadataDirectory"` + LogDirectory string `json:"LogDirectory"` + SnapshotDirectory string `json:"SnapshotDirectory"` + SuspendDirectory string `json:"SuspendDirectory"` + VMPathName string `json:"VmPathName"` + } `json:"Files"` + Firmware string `json:"Firmware"` + Flags any `json:"Flags"` + FtInfo any `json:"FtInfo"` + GuestAutoLockEnabled any `json:"GuestAutoLockEnabled"` + GuestID string `json:"GuestId"` + GuestMonitoringModeInfo any `json:"GuestMonitoringModeInfo"` + InstanceUUID string `json:"InstanceUuid"` + LatencySensitivity any `json:"LatencySensitivity"` + LocationID string `json:"LocationId"` + ManagedBy any `json:"ManagedBy"` + MaxMksConnections int `json:"MaxMksConnections"` + MemoryAffinity any `json:"MemoryAffinity"` + MemoryAllocation any `json:"MemoryAllocation"` + MemoryHotAddEnabled any `json:"MemoryHotAddEnabled"` + MemoryMB int `json:"MemoryMB"` + MemoryReservationLockedToMax any `json:"MemoryReservationLockedToMax"` + MessageBusTunnelEnabled any `json:"MessageBusTunnelEnabled"` + MigrateEncryption string `json:"MigrateEncryption"` + Name string `json:"Name"` + NestedHVEnabled any `json:"NestedHVEnabled"` + NetworkShaper any `json:"NetworkShaper"` + NpivDesiredNodeWwns int `json:"NpivDesiredNodeWwns"` + NpivDesiredPortWwns int `json:"NpivDesiredPortWwns"` + NpivNodeWorldWideName any `json:"NpivNodeWorldWideName"` + NpivOnNonRdmDisks any `json:"NpivOnNonRdmDisks"` + NpivPortWorldWideName any `json:"NpivPortWorldWideName"` + NpivTemporaryDisabled any `json:"NpivTemporaryDisabled"` + NpivWorldWideNameOp string `json:"NpivWorldWideNameOp"` + NpivWorldWideNameType string `json:"NpivWorldWideNameType"` + NumCPUs int `json:"NumCPUs"` + NumCoresPerSocket int `json:"NumCoresPerSocket"` + PowerOpInfo any `json:"PowerOpInfo"` + RepConfig any `json:"RepConfig"` + ScheduledHardwareUpgradeInfo any `json:"ScheduledHardwareUpgradeInfo"` + SevEnabled any `json:"SevEnabled"` + SgxInfo any `json:"SgxInfo"` + SwapPlacement string `json:"SwapPlacement"` + Tools any `json:"Tools"` + UUID string `json:"Uuid"` + VAppConfig any `json:"VAppConfig"` + VAppConfigRemoved any `json:"VAppConfigRemoved"` + VAssertsEnabled any `json:"VAssertsEnabled"` + VPMCEnabled any `json:"VPMCEnabled"` + VcpuConfig any `json:"VcpuConfig"` + Version string `json:"Version"` + VirtualICH7MPresent any `json:"VirtualICH7MPresent"` + VirtualSMCPresent any `json:"VirtualSMCPresent"` + VMProfile any `json:"VmProfile"` +}