File: warnings.go

package info (click to toggle)
incus 6.0.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,392 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (222 lines) | stat: -rw-r--r-- 6,661 bytes parent folder | download | duplicates (3)
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
}