1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
//go:build linux && cgo && !agent
package db
import (
"context"
"errors"
"fmt"
"net/http"
"slices"
"time"
"github.com/google/uuid"
"github.com/lxc/incus/v6/internal/server/db/cluster"
"github.com/lxc/incus/v6/internal/server/db/warningtype"
"github.com/lxc/incus/v6/shared/api"
)
var warningCreate = cluster.RegisterStmt(`
INSERT INTO warnings (node_id, project_id, entity_type_code, entity_id, uuid, type_code, status, first_seen_date, last_seen_date, updated_date, last_message, count)
VALUES ((SELECT nodes.id FROM nodes WHERE nodes.name = ?), (SELECT projects.id FROM projects WHERE projects.name = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
// UpsertWarningLocalNode creates or updates a warning for the local member. Returns error if no local member name.
func (c *ClusterTx) UpsertWarningLocalNode(ctx context.Context, projectName string, entityTypeCode int, entityID int, typeCode warningtype.Type, message string) error {
localName, err := c.GetLocalNodeName(ctx)
if err != nil {
return fmt.Errorf("Failed getting local member name: %w", err)
}
if localName == "" {
return errors.New("Local member name not available")
}
return c.UpsertWarning(ctx, localName, projectName, entityTypeCode, entityID, typeCode, message)
}
// UpsertWarning creates or updates a warning.
func (c *ClusterTx) UpsertWarning(ctx context.Context, nodeName string, projectName string, entityTypeCode int, entityID int, typeCode warningtype.Type, message string) error {
// Validate
_, err := c.GetURIFromEntity(ctx, entityTypeCode, entityID)
if err != nil {
return fmt.Errorf("Failed to get URI for entity ID %d with entity type code %d: %w", entityID, entityTypeCode, err)
}
_, ok := warningtype.TypeNames[typeCode]
if !ok {
return fmt.Errorf("Unknown warning type code %d", typeCode)
}
now := time.Now().UTC()
filter := cluster.WarningFilter{
TypeCode: &typeCode,
Node: &nodeName,
Project: &projectName,
EntityTypeCode: &entityTypeCode,
EntityID: &entityID,
}
warnings, err := cluster.GetWarnings(ctx, c.tx, filter)
if err != nil {
return fmt.Errorf("Failed to retrieve warnings: %w", err)
}
if len(warnings) > 1 {
// This shouldn't happen
return fmt.Errorf("More than one warnings (%d) match the criteria: typeCode: %d, nodeName: %q, projectName: %q, entityTypeCode: %d, entityID: %d", len(warnings), typeCode, nodeName, projectName, entityTypeCode, entityID)
} else if len(warnings) == 1 {
// If there is a historical warning that was previously automatically resolved and the same
// warning has now reoccurred then set the status back to warningtype.StatusNew so it shows as
// a current active warning.
newStatus := warnings[0].Status
if newStatus == warningtype.StatusResolved {
newStatus = warningtype.StatusNew
}
err = c.UpdateWarningState(warnings[0].UUID, message, newStatus)
} else {
warning := cluster.Warning{
Node: nodeName,
Project: projectName,
EntityTypeCode: entityTypeCode,
EntityID: entityID,
UUID: uuid.New().String(),
TypeCode: typeCode,
Status: warningtype.StatusNew,
FirstSeenDate: now,
LastSeenDate: now,
UpdatedDate: time.Time{}.UTC(),
LastMessage: message,
Count: 1,
}
_, err = c.createWarning(ctx, warning)
}
if err != nil {
return err
}
return nil
}
// UpdateWarningStatus updates the status of the warning with the given UUID.
func (c *ClusterTx) UpdateWarningStatus(UUID string, status warningtype.Status) error {
str := "UPDATE warnings SET status=?, updated_date=? WHERE uuid=?"
res, err := c.tx.Exec(str, status, time.Now(), UUID)
if err != nil {
return fmt.Errorf("Failed to update warning status for warning %q: %w", UUID, err)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return fmt.Errorf("Failed to get affected rows to update warning status %q: %w", UUID, err)
}
if rowsAffected == 0 {
return api.StatusErrorf(http.StatusNotFound, "Warning not found")
}
return nil
}
// UpdateWarningState updates the warning message and status with the given ID.
func (c *ClusterTx) UpdateWarningState(UUID string, message string, status warningtype.Status) error {
str := "UPDATE warnings SET last_message=?, last_seen_date=?, updated_date=?, status = ?, count=count+1 WHERE uuid=?"
now := time.Now()
res, err := c.tx.Exec(str, message, now, now, status, UUID)
if err != nil {
return fmt.Errorf("Failed to update warning %q: %w", UUID, err)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return fmt.Errorf("Failed to get affected rows to update warning state %q: %w", UUID, err)
}
if rowsAffected == 0 {
return api.StatusErrorf(http.StatusNotFound, "Warning not found")
}
return nil
}
// createWarning adds a new warning to the database.
func (c *ClusterTx) createWarning(ctx context.Context, object cluster.Warning) (int64, error) {
// Check if a warning with the same key exists.
exists, err := cluster.WarningExists(ctx, c.tx, object.UUID)
if err != nil {
return -1, fmt.Errorf("Failed to check for duplicates: %w", err)
}
if exists {
return -1, errors.New("This warning already exists")
}
args := make([]any, 12)
// Populate the statement arguments.
if object.Node != "" {
// Ensure node exists
_, err = c.GetNodeByName(ctx, object.Node)
if err != nil {
return -1, fmt.Errorf("Failed to get node: %w", err)
}
args[0] = object.Node
}
if object.Project != "" {
// Ensure project exists
projects, err := cluster.GetProjectNames(context.Background(), c.tx)
if err != nil {
return -1, fmt.Errorf("Failed to get project names: %w", err)
}
if !slices.Contains(projects, object.Project) {
return -1, fmt.Errorf("Unknown project %q", object.Project)
}
args[1] = object.Project
}
if object.EntityTypeCode != -1 {
args[2] = object.EntityTypeCode
}
if object.EntityID != -1 {
args[3] = object.EntityID
}
args[4] = object.UUID
args[5] = object.TypeCode
args[6] = object.Status
args[7] = object.FirstSeenDate
args[8] = object.LastSeenDate
args[9] = object.UpdatedDate
args[10] = object.LastMessage
args[11] = object.Count
// Prepared statement to use.
stmt, err := cluster.Stmt(c.tx, warningCreate)
if err != nil {
return -1, fmt.Errorf("Failed to get \"warningCreate\" prepared statement: %w", err)
}
// Execute the statement.
result, err := stmt.Exec(args...)
if err != nil {
return -1, fmt.Errorf("Failed to create warning: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
return -1, fmt.Errorf("Failed to fetch warning ID: %w", err)
}
return id, nil
}
|