add vm inventory update endpoint
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-09-30 15:30:38 +10:00
parent 6a41528f41
commit 380707cf23
11 changed files with 254 additions and 23 deletions

View File

@@ -10,8 +10,8 @@ ALTER TABLE "Events" ADD COLUMN VmName TEXT;
-- +goose Down -- +goose Down
-- +goose StatementBegin -- +goose StatementBegin
ALTER TABLE "Events" DROP COLUMN VmName; ALTER TABLE "Events" DROP COLUMN VmName;
ALTER TABLE "Updates" DROP COLUMN ComputeResourceId; ALTER TABLE "Events" DROP COLUMN ComputeResourceId;
ALTER TABLE "Updates" DROP COLUMN DatacenterId; ALTER TABLE "Events" DROP COLUMN DatacenterId;
ALTER TABLE "Events" RENAME COLUMN ComputeResourceName to ComputeResource; ALTER TABLE "Events" RENAME COLUMN ComputeResourceName to ComputeResource;
ALTER TABLE "Events" RENAME COLUMN DatacenterName to Datacenter; ALTER TABLE "Events" RENAME COLUMN DatacenterName to Datacenter;
-- +goose StatementEnd -- +goose StatementEnd

View File

@@ -0,0 +1,18 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS "InventoryHistory" (
"Hid" INTEGER PRIMARY KEY AUTOINCREMENT,
"InventoryId" INTEGER,
"ReportDate" INTEGER,
"UpdateTime" INTEGER,
"PreviousVcpus" INTEGER,
"PreviousRam" INTEGER,
"PreviousResourcePool" TEXT,
"PreviousProvisionedDisk" REAL
)
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE "InventoryHistory";
-- +goose StatementEnd

View File

@@ -49,6 +49,17 @@ type Inventory struct {
VmUuid sql.NullString VmUuid sql.NullString
} }
type InventoryHistory struct {
Hid int64
InventoryId sql.NullInt64
ReportDate sql.NullInt64
UpdateTime sql.NullInt64
PreviousVcpus sql.NullInt64
PreviousRam sql.NullInt64
PreviousResourcePool sql.NullString
PreviousProvisionedDisk sql.NullFloat64
}
type Updates struct { type Updates struct {
Uid int64 Uid int64
InventoryId sql.NullInt64 InventoryId sql.NullInt64

View File

@@ -38,6 +38,11 @@ INSERT INTO "Inventory" (
) )
RETURNING *; RETURNING *;
-- name: InventoryUpdate :exec
UPDATE "Inventory"
SET "VmUuid" = sqlc.arg('uuid'), "SrmPlaceholder" = sqlc.arg('srmPlaceholder')
WHERE "Iid" = sqlc.arg('iid');
-- name: InventoryMarkDeleted :exec -- name: InventoryMarkDeleted :exec
UPDATE "Inventory" UPDATE "Inventory"
SET "DeletionTime" = sqlc.arg('deletionTime') SET "DeletionTime" = sqlc.arg('deletionTime')
@@ -91,4 +96,12 @@ ORDER BY "EventTime";
-- name: UpdateEventsProcessed :exec -- name: UpdateEventsProcessed :exec
UPDATE "Events" UPDATE "Events"
SET "Processed" = 1 SET "Processed" = 1
WHERE "Eid" = sqlc.arg('eid'); WHERE "Eid" = sqlc.arg('eid');
-- name: CreateInventoryHistory :one
INSERT INTO "InventoryHistory" (
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
) VALUES(
?, ?, ?, ?, ?, ?, ?
)
RETURNING *;

View File

@@ -165,6 +165,49 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
return i, err return i, err
} }
const createInventoryHistory = `-- name: CreateInventoryHistory :one
INSERT INTO "InventoryHistory" (
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
) VALUES(
?, ?, ?, ?, ?, ?, ?
)
RETURNING Hid, InventoryId, ReportDate, UpdateTime, PreviousVcpus, PreviousRam, PreviousResourcePool, PreviousProvisionedDisk
`
type CreateInventoryHistoryParams struct {
InventoryId sql.NullInt64
ReportDate sql.NullInt64
UpdateTime sql.NullInt64
PreviousVcpus sql.NullInt64
PreviousRam sql.NullInt64
PreviousResourcePool sql.NullString
PreviousProvisionedDisk sql.NullFloat64
}
func (q *Queries) CreateInventoryHistory(ctx context.Context, arg CreateInventoryHistoryParams) (InventoryHistory, error) {
row := q.db.QueryRowContext(ctx, createInventoryHistory,
arg.InventoryId,
arg.ReportDate,
arg.UpdateTime,
arg.PreviousVcpus,
arg.PreviousRam,
arg.PreviousResourcePool,
arg.PreviousProvisionedDisk,
)
var i InventoryHistory
err := row.Scan(
&i.Hid,
&i.InventoryId,
&i.ReportDate,
&i.UpdateTime,
&i.PreviousVcpus,
&i.PreviousRam,
&i.PreviousResourcePool,
&i.PreviousProvisionedDisk,
)
return i, err
}
const createUpdate = `-- name: CreateUpdate :one const createUpdate = `-- name: CreateUpdate :one
INSERT INTO "Updates" ( INSERT INTO "Updates" (
"InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName" "InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName"
@@ -606,6 +649,23 @@ func (q *Queries) InventoryMarkDeleted(ctx context.Context, arg InventoryMarkDel
return err return err
} }
const inventoryUpdate = `-- name: InventoryUpdate :exec
UPDATE "Inventory"
SET "VmUuid" = ?1, "SrmPlaceholder" = ?2
WHERE "Iid" = ?3
`
type InventoryUpdateParams struct {
Uuid sql.NullString
SrmPlaceholder interface{}
Iid int64
}
func (q *Queries) InventoryUpdate(ctx context.Context, arg InventoryUpdateParams) error {
_, err := q.db.ExecContext(ctx, inventoryUpdate, arg.Uuid, arg.SrmPlaceholder, arg.Iid)
return err
}
const listEvents = `-- name: ListEvents :many const listEvents = `-- name: ListEvents :many
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events" SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
ORDER BY "EventTime" ORDER BY "EventTime"

View File

@@ -114,16 +114,16 @@ func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vce
c.Logger.Debug("found VM") c.Logger.Debug("found VM")
if vmObject.Name == "DBRaaS_testVMTemplate" { /*
c.Logger.Debug("Found problematic VM") if vmObject.Name == "DBRaaS_testVMTemplate" {
//prettyPrint(vmObject) c.Logger.Debug("Found problematic VM")
} //prettyPrint(vmObject)
}
srmPlaceholder = "FALSE" // Default assumption */
//prettyPrint(vmObject)
// calculate VM properties we want to store // calculate VM properties we want to store
if vmObject.Config != nil { if vmObject.Config != nil {
// Skip any template VMs
if vmObject.Config.Template { if vmObject.Config.Template {
c.Logger.Debug("Not adding templates to inventory") c.Logger.Debug("Not adding templates to inventory")
return nil return nil
@@ -133,6 +133,7 @@ func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vce
numRam = vmObject.Config.Hardware.MemoryMB numRam = vmObject.Config.Hardware.MemoryMB
numVcpus = vmObject.Config.Hardware.NumCPU numVcpus = vmObject.Config.Hardware.NumCPU
srmPlaceholder = "FALSE" // Default assumption
// Calculate creation date // Calculate creation date
if vmObject.Config.CreateDate.IsZero() { if vmObject.Config.CreateDate.IsZero() {
@@ -156,16 +157,19 @@ func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vce
} }
totalDiskBytes += disk.CapacityInBytes totalDiskBytes += disk.CapacityInBytes
//totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
} }
} }
totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024) totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024)
c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB) c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
// Determine if the VM is a normal VM or an SRM placeholder // Determine if the VM is a normal VM or an SRM placeholder
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.Type == "com.vmware.vcDr" { if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
c.Logger.Debug("VM ManagedBy indicates managed by SRM") if vmObject.Config.ManagedBy.Type == "placeholderVm" {
srmPlaceholder = "TRUE" c.Logger.Debug("VM is a placeholder")
srmPlaceholder = "TRUE"
} else {
c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
}
} }
// Retrieve the full folder path of the VM // Retrieve the full folder path of the VM
@@ -179,10 +183,10 @@ func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vce
foundVmConfig = true foundVmConfig = true
} else { } else {
c.Logger.Error("Empty VM config") c.Logger.Warn("Empty VM config")
} }
c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState) //c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState)
if vmObject.Runtime.PowerState == "poweredOff" { if vmObject.Runtime.PowerState == "poweredOff" {
poweredOn = "FALSE" poweredOn = "FALSE"
} else { } else {
@@ -241,7 +245,6 @@ func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vce
} }
return nil return nil
} }
// prettyPrint comes from https://gist.github.com/sfate/9d45f6c5405dc4c9bf63bf95fe6d1a7c // prettyPrint comes from https://gist.github.com/sfate/9d45f6c5405dc4c9bf63bf95fe6d1a7c

View File

@@ -94,9 +94,13 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB) c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
// Determine if the VM is a normal VM or an SRM placeholder // Determine if the VM is a normal VM or an SRM placeholder
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.Type == "com.vmware.vcDr" { if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
c.Logger.Debug("VM ManagedBy indicates managed by SRM") if vmObject.Config.ManagedBy.Type == "placeholderVm" {
srmPlaceholder = "TRUE" c.Logger.Debug("VM is a placeholder")
srmPlaceholder = "TRUE"
} else {
c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
}
} }
if vmObject.Config.Template { if vmObject.Config.Template {

View File

@@ -209,7 +209,7 @@ func main() {
c.Start() c.Start()
// Start server // Start server
r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a) r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a, s)
svr := server.New( svr := server.New(
logger, logger,
c, c,

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"vctp/db" "vctp/db"
"vctp/internal/secrets" "vctp/internal/secrets"
"vctp/internal/settings"
"vctp/internal/vcenter" "vctp/internal/vcenter"
"github.com/a-h/templ" "github.com/a-h/templ"
@@ -20,6 +21,7 @@ type Handler struct {
GoVersion string GoVersion string
VcCreds *vcenter.VcenterLogin VcCreds *vcenter.VcenterLogin
Secret *secrets.Secrets Secret *secrets.Secrets
Settings *settings.Settings
} }
func (h *Handler) html(ctx context.Context, w http.ResponseWriter, status int, t templ.Component) { func (h *Handler) html(ctx context.Context, w http.ResponseWriter, status int, t templ.Component) {

View File

@@ -0,0 +1,114 @@
package handler
import (
"context"
"database/sql"
"fmt"
"net/http"
"vctp/db/queries"
"vctp/internal/vcenter"
)
// VmUpdate receives the CloudEvent for a VM modification or move
func (h *Handler) VmUpdateDetails(w http.ResponseWriter, r *http.Request) {
var matchFound bool
var inventoryId int64
var srmPlaceholder string
var vmUuid string
var dbUuid string
ctx := context.Background()
// reload settings in case vcenter list has changed
h.Settings.ReadYMLSettings()
for _, url := range h.Settings.Values.Settings.VcenterAddresses {
h.Logger.Debug("connecting to vcenter", "url", url)
vc := vcenter.New(h.Logger, h.VcCreds)
vc.Login(url)
// Get list of VMs from vcenter
vms, err := vc.GetAllVmReferences()
// Get list of VMs from inventory table
h.Logger.Debug("Querying inventory table")
results, err := h.Database.Queries().GetInventoryByVcenter(ctx, url)
if err != nil {
h.Logger.Error("Unable to query inventory table", "error", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Unable to query inventory table %s\n", err)
}
if len(results) == 0 {
h.Logger.Error("Empty inventory results")
}
// Iterate VMs from vcenter and see if they were in the database
for _, vm := range vms {
matchFound = false
inventoryId = 0
srmPlaceholder = "FALSE" // Default assumption
vmUuid = ""
for _, dbvm := range results {
if dbvm.VmId.String == vm.Reference().Value {
h.Logger.Debug("Found VM in database", "vm_name", dbvm.Name, "id", dbvm.VmId.String)
matchFound = true
inventoryId = dbvm.Iid
dbUuid = dbvm.VmUuid.String
break
}
}
if matchFound {
//h.Logger.Debug("Need to update VM in inventory table", "MoRef", vm.Reference())
vmObj, err := vc.ConvertObjToMoVM(vm)
if err != nil {
h.Logger.Error("Received error getting vm managedobject", "error", err)
continue
}
if vmObj.Config != nil {
vmUuid = vmObj.Config.Uuid
// Determine if the VM is a normal VM or an SRM placeholder
if vmObj.Config.ManagedBy != nil && vmObj.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
if vmObj.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", vmObj.Config.ManagedBy)
}
}
if srmPlaceholder == "TRUE" || vmUuid != dbUuid {
h.Logger.Debug("Need to update vm", "name", vmObj.Name, "srm_placeholder", srmPlaceholder, "uuid", vmUuid)
params := queries.InventoryUpdateParams{
Iid: inventoryId,
SrmPlaceholder: srmPlaceholder,
Uuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""},
}
h.Logger.Debug("database params", "params", params)
err := h.Database.Queries().InventoryUpdate(context.Background(), params)
if err != nil {
h.Logger.Error("Error received updating inventory for VM", "name", vmObj.Name, "error", err)
}
}
} else {
h.Logger.Warn("VM no longer present in vcenter or missing config values", "MoRef", vm.Reference())
}
}
}
}
h.Logger.Debug("Processed vm update successfully")
w.WriteHeader(http.StatusOK)
// TODO - return some JSON
fmt.Fprintf(w, "Processed vm update successfully")
}

View File

@@ -7,12 +7,13 @@ import (
"vctp/db" "vctp/db"
"vctp/dist" "vctp/dist"
"vctp/internal/secrets" "vctp/internal/secrets"
"vctp/internal/settings"
"vctp/internal/vcenter" "vctp/internal/vcenter"
"vctp/server/handler" "vctp/server/handler"
"vctp/server/middleware" "vctp/server/middleware"
) )
func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string, creds *vcenter.VcenterLogin, secret *secrets.Secrets) http.Handler { func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string, creds *vcenter.VcenterLogin, secret *secrets.Secrets, settings *settings.Settings) http.Handler {
h := &handler.Handler{ h := &handler.Handler{
Logger: logger, Logger: logger,
Database: database, Database: database,
@@ -21,6 +22,7 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st
GoVersion: goVersion, GoVersion: goVersion,
VcCreds: creds, VcCreds: creds,
Secret: secret, Secret: secret,
Settings: settings,
} }
mux := http.NewServeMux() mux := http.NewServeMux()
@@ -34,9 +36,13 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st
mux.HandleFunc("/api/import/vm", h.VmImport) mux.HandleFunc("/api/import/vm", h.VmImport)
// Use this when we need to manually remove a VM from the database to clean up // Use this when we need to manually remove a VM from the database to clean up
mux.HandleFunc("/api/inventory/vm/delete", h.VmCleanup) mux.HandleFunc("/api/inventory/vm/delete", h.VmCleanup)
// add missing data to VMs
mux.HandleFunc("/api/inventory/vm/update", h.VmUpdateDetails)
// temporary endpoint // temporary endpoint
//mux.HandleFunc("/api/cleanup/updates", h.UpdateCleanup) //mux.HandleFunc("/api/cleanup/updates", h.UpdateCleanup)
mux.HandleFunc("/api/cleanup/vcenter", h.VcCleanup) //mux.HandleFunc("/api/cleanup/vcenter", h.VcCleanup)
mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload) mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload)
mux.HandleFunc("/api/report/updates", h.UpdateReportDownload) mux.HandleFunc("/api/report/updates", h.UpdateReportDownload)