From f86ec3d615b199dc741b3529eb2b7ad9ac2a6807 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Tue, 15 Oct 2024 16:06:54 +1100 Subject: [PATCH] create vm in inventory when receiving modify event for vm we dont know about --- server/handler/vmModifyEvent.go | 253 ++++++++++++++++++++++++++++---- 1 file changed, 228 insertions(+), 25 deletions(-) diff --git a/server/handler/vmModifyEvent.go b/server/handler/vmModifyEvent.go index 552b5bc..3494160 100644 --- a/server/handler/vmModifyEvent.go +++ b/server/handler/vmModifyEvent.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -81,7 +82,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { configChanges = h.processConfigChanges(event.CloudEvent.Data.ConfigChanges.Modified) //prettyPrint(configChanges) - var found = false + var changeFound = false // Only interested in vCPU or ram changes currently for _, change := range configChanges { //fmt.Printf("Type: %s, New Value: %s\n", change["type"], change["newValue"]) @@ -91,7 +92,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { if err != nil { h.Logger.Error("Unable to convert new value to int64", "new_value", change["newValue"]) } else { - found = true + changeFound = true params.NewVcpus = sql.NullInt64{Int64: i, Valid: i > 0} params.UpdateType = "reconfigure" } @@ -100,12 +101,12 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { if err != nil { h.Logger.Error("Unable to convert new value to int64", "new_value", change["newValue"]) } else { - found = true + changeFound = true params.NewRam = sql.NullInt64{Int64: i, Valid: i > 0} params.UpdateType = "reconfigure" } case "config.managedBy": // This changes when a VM becomes a placeholder or vice versa - found = true + changeFound = true params.UpdateType = "srm" if change["newValue"] == "(extensionKey = \"com.vmware.vcDr\", type = \"placeholderVm\")" { @@ -159,7 +160,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { if strings.ToLower(matches[1]) == strings.ToLower(event.CloudEvent.Data.VM.Name) { h.Logger.Debug("This disk belongs to this VM") - found = true + changeFound = true diskChangeFound = true // don't need to keep searching through the rest of the backing devices in this VM @@ -183,7 +184,17 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { } // Only create a database record if we found one of the config changes we were interested in - if found { + if changeFound { + // Parse the datetime string to a time.Time object + eventTime, err := time.Parse(time.RFC3339, event.CloudEvent.Data.CreatedTime) + if err != nil { + h.Logger.Warn("unable to convert cloud event time to timestamp", "error", err) + unixTimestamp = time.Now().Unix() + } else { + // Convert to Unix timestamp + unixTimestamp = eventTime.Unix() + } + // lookup Iid from Inventory table for this VM // also figure out what to do if we didn't find an entry for this VM in the Inventory table. Create one? h.Logger.Debug("Checking inventory table for VM record") @@ -194,15 +205,48 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { invResult, err := h.Database.Queries().GetInventoryVmId(ctx, invParams) if err != nil { - h.Logger.Error("unable to find existing inventory record for this VM", "error", err) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(map[string]string{ - "status": "ERROR", - "message": fmt.Sprintf("Valid request but could not locate vm id '%s' and datacenter name '%s' within inventory table : %s", - event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err), - }) - return + if errors.Is(err, sql.ErrNoRows) { + // TODO Add a record to the inventory table for this VM + h.Logger.Info("Received VM modify event for a VM not currently in the inventory. Adding to inventory") + + iid, err := h.AddVmToInventory(event, ctx, unixTimestamp) + if err != nil { + h.Logger.Error("Received error adding VM to inventory", "error", err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{ + "status": "ERROR", + "message": fmt.Sprintf("Valid request but experienced error adding vm id '%s' in datacenter name '%s' to inventory table : %s", + event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err), + }) + return + } + + if iid > 0 { + params.InventoryId = sql.NullInt64{Int64: iid, Valid: iid > 0} + } else { + h.Logger.Error("Received zero for inventory id when adding VM to inventory") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{ + "status": "ERROR", + "message": fmt.Sprintf("Valid request but received zero result when adding vm id '%s' in datacenter name '%s' to inventory table", + event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name), + }) + return + } + } else { + h.Logger.Error("unable to find existing inventory record for this VM", "error", err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{ + "status": "ERROR", + "message": fmt.Sprintf("Valid request but could not locate vm id '%s' and datacenter name '%s' within inventory table : %s", + event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err), + }) + return + } + } else { params.InventoryId = sql.NullInt64{Int64: invResult.Iid, Valid: invResult.Iid > 0} } @@ -219,16 +263,6 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { return } - // Parse the datetime string to a time.Time object - eventTime, err := time.Parse(time.RFC3339, event.CloudEvent.Data.CreatedTime) - if err != nil { - h.Logger.Warn("unable to convert cloud event time to timestamp", "error", err) - unixTimestamp = time.Now().Unix() - } else { - // Convert to Unix timestamp - unixTimestamp = eventTime.Unix() - } - // populate other parameters for the Update database record params.Name = sql.NullString{String: event.CloudEvent.Data.VM.Name, Valid: event.CloudEvent.Data.VM.Name != ""} params.RawChangeString = []byte(event.CloudEvent.Data.ConfigChanges.Modified) @@ -356,3 +390,172 @@ func (h *Handler) calculateNewDiskSize(event models.CloudEventReceived) float64 return diskSize } + +// AddVmToInventory adds a vm from a received cloudevent and returns the inventoryid and any error message +func (h *Handler) AddVmToInventory(evt models.CloudEventReceived, ctx context.Context, unixTimestamp int64) (int64, error) { + var ( + numVcpus int32 + numRam int32 + totalDiskGB float64 + srmPlaceholder string + foundVm bool + isTemplate string + poweredOn string + folderPath string + rpName string + vmUuid string + ) + //c.Logger.Debug("connecting to vcenter") + vc := vcenter.New(h.Logger, h.VcCreds) + vc.Login(evt.CloudEvent.Source) + + //datacenter = evt.DatacenterName.String + vmObject, err := vc.FindVMByIDWithDatacenter(evt.CloudEvent.Data.VM.VM.Value, evt.CloudEvent.Data.Datacenter.Datacenter.Value) + + if err != nil { + h.Logger.Error("Can't locate vm in vCenter", "vmID", evt.CloudEvent.Data.VM.VM.Value, "error", err) + return 0, err + } else if vmObject == nil { + h.Logger.Debug("didn't find VM", "vm_id", evt.CloudEvent.Data.VM.VM.Value) + return 0, nil + + } + + //c.Logger.Debug("found VM") + srmPlaceholder = "FALSE" // Default assumption + //prettyPrint(vmObject) + + // calculate VM properties we want to store + if vmObject.Config != nil { + numRam = vmObject.Config.Hardware.MemoryMB + numVcpus = vmObject.Config.Hardware.NumCPU + vmUuid = vmObject.Config.Uuid + + var totalDiskBytes int64 + + // Calculate the total disk allocated in GB + for _, device := range vmObject.Config.Hardware.Device { + if disk, ok := device.(*types.VirtualDisk); ok { + + // Print the filename of the backing device + if _, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok { + //c.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName) + } else { + //c.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes) + } + + totalDiskBytes += disk.CapacityInBytes + //totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB + } + } + totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024) + h.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB) + + // Determine if the VM is a normal VM or an SRM placeholder + if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" { + if vmObject.Config.ManagedBy.Type == "placeholderVm" { + h.Logger.Debug("VM is a placeholder") + srmPlaceholder = "TRUE" + } else { + h.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy) + } + } + + if vmObject.Config.Template { + isTemplate = "TRUE" + } else { + isTemplate = "FALSE" + } + + // Retrieve the full folder path of the VM + folderPath, err = vc.GetVMFolderPath(*vmObject) + if err != nil { + h.Logger.Error("failed to get vm folder path", "error", err) + folderPath = "" + } else { + h.Logger.Debug("Found vm folder path", "folder_path", folderPath) + } + + // Retrieve the resource pool of the VM + rpName, _ = vc.GetVmResourcePool(*vmObject) + + foundVm = true + } else { + h.Logger.Error("Empty VM config") + } + + //c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState) + if vmObject.Runtime.PowerState == "poweredOff" { + poweredOn = "FALSE" + } else { + poweredOn = "TRUE" + } + + err = vc.Logout() + if err != nil { + h.Logger.Error("unable to logout of vcenter", "error", err) + } + + if foundVm { + e := evt.CloudEvent + h.Logger.Debug("Adding to Inventory table", "vm_name", e.Data.VM.Name, "vcpus", numVcpus, "ram", numRam, "dc", e.Data.Datacenter.Datacenter.Value) + + insertParams := queries.CreateInventoryParams{ + Name: e.Data.VM.Name, + Vcenter: evt.CloudEvent.Source, + CloudId: sql.NullString{String: e.ID, Valid: e.ID != ""}, + EventKey: sql.NullString{String: strconv.Itoa(e.Data.Key), Valid: e.Data.Key > 0}, + VmId: sql.NullString{String: e.Data.VM.VM.Value, Valid: e.Data.VM.VM.Value != ""}, + Datacenter: sql.NullString{String: e.Data.Datacenter.Name, Valid: e.Data.Datacenter.Name != ""}, + Cluster: sql.NullString{String: e.Data.ComputeResource.Name, Valid: e.Data.ComputeResource.Name != ""}, + CreationTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0}, + InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0}, + InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0}, + ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0}, + Folder: sql.NullString{String: folderPath, Valid: folderPath != ""}, + ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""}, + VmUuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""}, + SrmPlaceholder: srmPlaceholder, + IsTemplate: isTemplate, + PoweredOn: poweredOn, + } + + /* + params := queries.CreateInventoryParams{ + Name: vmObject.Name, + Vcenter: evt.Source, + CloudId: sql.NullString{String: evt.CloudId, Valid: evt.CloudId != ""}, + EventKey: sql.NullString{String: evt.EventKey.String, Valid: evt.EventKey.Valid}, + VmId: sql.NullString{String: evt.VmId.String, Valid: evt.VmId.Valid}, + Datacenter: sql.NullString{String: evt.DatacenterName.String, Valid: evt.DatacenterName.Valid}, + Cluster: sql.NullString{String: evt.ComputeResourceName.String, Valid: evt.ComputeResourceName.Valid}, + CreationTime: sql.NullInt64{Int64: evt.EventTime.Int64, Valid: evt.EventTime.Valid}, + InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0}, + InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0}, + ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0}, + Folder: sql.NullString{String: folderPath, Valid: folderPath != ""}, + ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""}, + VmUuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""}, + SrmPlaceholder: srmPlaceholder, + IsTemplate: isTemplate, + PoweredOn: poweredOn, + } + */ + + //c.Logger.Debug("database params", "params", params) + + // Insert the new inventory record into the database + record, err := h.Database.Queries().CreateInventory(ctx, insertParams) + if err != nil { + h.Logger.Error("unable to perform database insert", "error", err) + return 0, err + } else { + //c.Logger.Debug("created database record", "insert_result", result) + return record.Iid, nil + } + } else { + h.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", evt.CloudEvent.Data.VM.Name) + } + + return 0, nil +}