From 1cb36be02c1c2af345b4117de96c28fdd5c59c25 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Sun, 15 Sep 2024 10:51:48 +1000 Subject: [PATCH] updates --- db/local.go | 4 + internal/tasks/processEvents.go | 70 ++++++++++++--- internal/vcenter/vcenter.go | 96 ++++++++++++++++++--- main.go | 5 +- server/handler/vmDelete.go | 21 +++++ server/handler/vmImport.go | 21 +++++ server/handler/{vmUpdate.go => vmModify.go} | 4 +- server/models/models.go | 4 + server/router/router.go | 4 +- server/server.go | 2 +- 10 files changed, 203 insertions(+), 28 deletions(-) create mode 100644 server/handler/vmDelete.go create mode 100644 server/handler/vmImport.go rename server/handler/{vmUpdate.go => vmModify.go} (75%) diff --git a/db/local.go b/db/local.go index 5bfdeab..a064e15 100644 --- a/db/local.go +++ b/db/local.go @@ -2,6 +2,7 @@ package db import ( "database/sql" + "fmt" "log/slog" "vctp/db/queries" @@ -36,6 +37,8 @@ func (d *LocalDB) Logger() *slog.Logger { } func (d *LocalDB) Close() error { + fmt.Println("Shutting database") + d.logger.Debug("test") return d.db.Close() } @@ -79,6 +82,7 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) { } for _, pragma := range pragmas { + logger.Debug("Setting pragma", "pragma", pragma) _, err := db.Exec(pragma) if err != nil { logger.Error("failed to execute pragma statement", "stmt", pragma, "error", err) diff --git a/internal/tasks/processEvents.go b/internal/tasks/processEvents.go index 7b0cb28..9da54d3 100644 --- a/internal/tasks/processEvents.go +++ b/internal/tasks/processEvents.go @@ -2,10 +2,14 @@ package tasks import ( "context" + "database/sql" "log/slog" "time" "vctp/db" + "vctp/db/queries" "vctp/internal/vcenter" + + "github.com/vmware/govmomi/vim25/types" ) // Handler handles requests. @@ -18,10 +22,11 @@ type CronTask struct { func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error { var ( //unixTimestamp int64 - numVcpus int32 - numRam int32 - datacenter string - foundVm bool + numVcpus int32 + numRam int32 + totalDiskGB float64 + srmPlaceholder int + foundVm bool ) logger.Debug("Started Events processing", "time", time.Now()) @@ -43,7 +48,7 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error { vc := vcenter.New(c.Logger) vc.Login(evt.Source) - datacenter = evt.DatacenterName.String + //datacenter = evt.DatacenterName.String vmObject, err := vc.FindVMByIDWithDatacenter(evt.VmId.String, evt.DatacenterId.String) if err != nil { @@ -52,8 +57,10 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error { c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String) numRam = 0 numVcpus = 0 + totalDiskGB = 0 } else { c.Logger.Debug("found VM") + srmPlaceholder = 0 // Default assumption //prettyPrint(vmObject) // calculate VM properties we want to store @@ -61,6 +68,20 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error { numRam = vmObject.Vm.Config.Hardware.MemoryMB //numVcpus = vmObject.Vm.Config.Hardware.NumCPU * vmObject.Vm.Config.Hardware.NumCoresPerSocket numVcpus = vmObject.Vm.Config.Hardware.NumCPU + + // Calculate the total disk allocated in GB + for _, device := range vmObject.Vm.Config.Hardware.Device { + if disk, ok := device.(*types.VirtualDisk); ok { + totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB + } + } + + // Determine if the VM is a normal VM or an SRM placeholder + if vmObject.Vm.Config.ManagedBy != nil && vmObject.Vm.Config.ManagedBy.Type == "com.vmware.vcDr" { + c.Logger.Debug("VM ManagedBy indicates managed by SRM") + srmPlaceholder = 1 + } + foundVm = true } else { c.Logger.Error("Empty VM config") @@ -73,17 +94,44 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error { } if foundVm { - c.Logger.Debug("Simulate adding to Inventory", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", datacenter) + c.Logger.Debug("Simulate adding to Inventory", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", evt.DatacenterId.String) - // mark this event as processed - err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid) + params := queries.CreateInventoryParams{ + Name: vmObject.Vm.Name, + Vcenter: evt.Source, + EventId: 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: vmObject.FolderPath, Valid: vmObject.FolderPath != ""}, + ResourcePool: sql.NullString{String: vmObject.ResourcePool, Valid: vmObject.ResourcePool != ""}, + SrmPlaceholder: sql.NullInt64{Int64: int64(srmPlaceholder), Valid: true}, + } + + c.Logger.Debug("database params", "params", params) + + // Insert the new inventory record into the database + result, err := c.Database.Queries().CreateInventory(ctx, params) if err != nil { - c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err) + c.Logger.Error("unable to perform database insert", "error", err) } else { - c.Logger.Debug("Marked event as processed", "event_id", evt.Eid) + c.Logger.Debug("created database record", "insert_result", result) + + // mark this event as processed + err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid) + if err != nil { + c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err) + } else { + c.Logger.Debug("Marked event as processed", "event_id", evt.Eid) + } } } else { - c.Logger.Debug("Not simulate adding to Inventory due to missing vcenter config property", "vm_name", evt.VmName.String) + c.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", evt.VmName.String) } } diff --git a/internal/vcenter/vcenter.go b/internal/vcenter/vcenter.go index b316020..8331fed 100644 --- a/internal/vcenter/vcenter.go +++ b/internal/vcenter/vcenter.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/url" "os" + "path" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" @@ -24,7 +25,9 @@ type Vcenter struct { } type VmProperties struct { - Vm mo.VirtualMachine + Vm mo.VirtualMachine + ResourcePool string + FolderPath string //Datacenter string } @@ -177,6 +180,8 @@ func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) { func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmProperties, error) { //var dcName string var err error + resourcePool := "" + vmFolderPath := "" v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID) finder := find.NewFinder(v.client.Client, true) @@ -196,14 +201,6 @@ func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmPropert // Use finder.SetDatacenter to set the datacenter finder.SetDatacenter(datacenter) - /* - dcName, err = datacenter.ObjectName(v.ctx) - if err != nil { - v.Logger.Error("Couldn't find the name of the datacenter", "error", err) - dcName = "" - } - */ - // Create a ManagedObjectReference for the VM vmRef := types.ManagedObjectReference{ Type: "VirtualMachine", @@ -215,18 +212,93 @@ func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmPropert err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm) if err == nil { v.Logger.Debug("Found VM") + + // Retrieve the resource pool the VM is in + if vm.ResourcePool != nil { + rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool) + rpName, err := rp.ObjectName(v.ctx) + if err != nil { + v.Logger.Error("failed to get resource pool name", "error", err) + } else { + v.Logger.Debug("Found resource pool name", "rp_name", rpName) + resourcePool = rpName + } + + } + + // Retrieve the full folder path of the VM + folderPath, err := v.getVMFolderPath(vm) + if err != nil { + v.Logger.Error("failed to get vm folder path", "error", err) + } else { + v.Logger.Debug("Found vm folder path", "folder_path", folderPath) + vmFolderPath = folderPath + } + return &VmProperties{ //Datacenter: dcName, - Vm: vm, + Vm: vm, + ResourcePool: resourcePool, + FolderPath: vmFolderPath, }, nil } else if _, ok := err.(*find.NotFoundError); !ok { // If the error is not a NotFoundError, return it //return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err) v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID) + return nil, nil } else { return nil, fmt.Errorf("failed to retrieve VM: %w", err) } - v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID) - return nil, nil + //v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID) + //return nil, nil +} + +// Helper function to retrieve the full folder path for the VM +func (v *Vcenter) getVMFolderPath(vm mo.VirtualMachine) (string, error) { + //finder := find.NewFinder(v.client.Client, true) + + v.Logger.Debug("Commencing vm folder path search") + + // Start from the VM's parent + parentRef := vm.Parent + if parentRef == nil { + return "", fmt.Errorf("no parent found for the VM") + } + + // Traverse the folder hierarchy to build the full folder path + folderPath := "" + v.Logger.Debug("parent is", "parent", parentRef) + + for parentRef.Type != "Datacenter" { + // Retrieve the parent object + //parentObj, err := finder.ObjectReference(v.ctx, *parentRef) + //if err != nil { + // return "", fmt.Errorf("failed to find parent object in inventory: %w", err) + //} + + // Retrieve the folder name + + var parentObj mo.Folder + err := v.client.RetrieveOne(v.ctx, *parentRef, nil, &parentObj) + if err != nil { + v.Logger.Error("Failed to get object for parent reference", "ref", parentRef) + break + } + + // Prepend the folder name to the path + folderPath = path.Join("/", parentObj.Name, folderPath) + + // Move up to the next parent + //if folder, ok := parentObj.(*object.Folder); ok { + if parentObj.Parent != nil { + parentRef = parentObj.Parent + v.Logger.Debug("Parent uplevel is", "ref", parentRef) + } else { + return "", fmt.Errorf("unexpected parent type: %s", parentObj.Reference().Type) + } + //break + } + + return folderPath, nil } diff --git a/main.go b/main.go index 893a301..b1a2272 100644 --- a/main.go +++ b/main.go @@ -47,10 +47,11 @@ func main() { os.Exit(1) } defer database.Close() + //defer database.DB().Close() if err = db.Migrate(database); err != nil { logger.Error("failed to migrate database", "error", err) - return + os.Exit(1) } // Prepare the task scheduler @@ -150,4 +151,6 @@ func main() { svr.SetPrivateKey(tlsKeyFilename) svr.StartAndWait() + + os.Exit(0) } diff --git a/server/handler/vmDelete.go b/server/handler/vmDelete.go new file mode 100644 index 0000000..50ecaa5 --- /dev/null +++ b/server/handler/vmDelete.go @@ -0,0 +1,21 @@ +package handler + +import ( + "fmt" + "io" + "net/http" +) + +// VmUpdate receives the CloudEvent for a VM modification or move +func (h *Handler) VmDelete(w http.ResponseWriter, r *http.Request) { + reqBody, err := io.ReadAll(r.Body) + if err != nil { + fmt.Fprintf(w, "Invalid data received") + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Logger.Debug("received delete request", "body", string(reqBody)) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Delete Request (%d): %v\n", len(reqBody), string(reqBody)) +} diff --git a/server/handler/vmImport.go b/server/handler/vmImport.go new file mode 100644 index 0000000..d938f1b --- /dev/null +++ b/server/handler/vmImport.go @@ -0,0 +1,21 @@ +package handler + +import ( + "fmt" + "io" + "net/http" +) + +// VmImport is used for bulk import of existing VMs +func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) { + reqBody, err := io.ReadAll(r.Body) + if err != nil { + fmt.Fprintf(w, "Invalid data received") + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Logger.Debug("received import request", "body", string(reqBody)) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Import Request (%d): %v\n", len(reqBody), string(reqBody)) +} diff --git a/server/handler/vmUpdate.go b/server/handler/vmModify.go similarity index 75% rename from server/handler/vmUpdate.go rename to server/handler/vmModify.go index 20d7a70..0385b10 100644 --- a/server/handler/vmUpdate.go +++ b/server/handler/vmModify.go @@ -6,8 +6,8 @@ import ( "net/http" ) -// VmUpdate receives the CloudEvent for a VM modification or move -func (h *Handler) VmUpdate(w http.ResponseWriter, r *http.Request) { +// VmModify receives the CloudEvent for a VM modification or move +func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) { reqBody, err := io.ReadAll(r.Body) if err != nil { fmt.Fprintf(w, "Invalid data received") diff --git a/server/models/models.go b/server/models/models.go index 241c363..871e30a 100644 --- a/server/models/models.go +++ b/server/models/models.go @@ -55,6 +55,10 @@ type CloudEventReceived struct { Value string `json:"Value"` } `json:"Vm"` } `json:"Vm"` + ConfigSpec any `json:"configSpec"` + ConfigChanges struct { + Modified string `json:"modified"` + } `json:"configChanges"` } `json:"data"` } `json:"cloudEvent"` } diff --git a/server/router/router.go b/server/router/router.go index c36b3e0..0d1debd 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -23,7 +23,9 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir)))) mux.HandleFunc("/", h.Home) mux.HandleFunc("/api/event/vm/create", h.VmCreate) - mux.HandleFunc("/api/event/vm/update", h.VmUpdate) + mux.HandleFunc("/api/event/vm/modify", h.VmModify) + mux.HandleFunc("/api/event/vm/delete", h.VmDelete) + mux.HandleFunc("/api/import/vm", h.VmImport) return middleware.NewLoggingMiddleware(logger, mux) } diff --git a/server/server.go b/server/server.go index af5827d..c92a5e6 100644 --- a/server/server.go +++ b/server/server.go @@ -152,5 +152,5 @@ func (s *Server) GracefulShutdown() { // <-ctx.Done() if your application should wait for other services // to finalize based on context cancellation. s.logger.Info("shutting down") - os.Exit(0) + //os.Exit(0) }