File: db_internal_test.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 (306 lines) | stat: -rw-r--r-- 9,886 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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//go:build linux && cgo && !agent

package db

import (
	"context"
	"database/sql"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/suite"

	"github.com/lxc/incus/v6/internal/server/db/cluster"
	"github.com/lxc/incus/v6/shared/api"
	"github.com/lxc/incus/v6/shared/logger"
)

const fixtures string = `
    INSERT INTO instances (node_id, name, architecture, type, project_id, description) VALUES (1, 'thename', 1, 1, 1, '');
    INSERT INTO profiles (name, project_id, description) VALUES ('theprofile', 1, '');
    INSERT INTO instances_profiles (instance_id, profile_id) VALUES (1, 2);
    INSERT INTO instances_config (instance_id, key, value) VALUES (1, 'thekey', 'thevalue');
    INSERT INTO instances_devices (instance_id, name, type) VALUES (1, 'somename', 1);
    INSERT INTO instances_devices_config (key, value, instance_device_id) VALUES ('configkey', 'configvalue', 1);
    INSERT INTO images (fingerprint, filename, size, architecture, creation_date, expiry_date, upload_date, auto_update, project_id) VALUES ('fingerprint', 'filename', 1024, 0,  1431547174,  1431547175,  1431547176, 1, 1);
    INSERT INTO images_aliases (name, image_id, description, project_id, description) VALUES ('somealias', 1, 'some description', 1, '');
    INSERT INTO images_properties (image_id, type, key, value) VALUES (1, 0, 'thekey', 'some value');
    INSERT INTO profiles_config (profile_id, key, value) VALUES (2, 'thekey', 'thevalue');
    INSERT INTO profiles_devices (profile_id, name, type) VALUES (2, 'devicename', 1);
    INSERT INTO profiles_devices_config (profile_device_id, key, value) VALUES (1, 'devicekey', 'devicevalue');
    `

type dbTestSuite struct {
	suite.Suite

	db      *Cluster
	cleanup func()
}

func (s *dbTestSuite) SetupTest() {
	s.db, s.cleanup = s.CreateTestDb()

	tx, commit := s.CreateTestTx()
	defer commit()

	_, err := tx.Exec(fixtures)
	s.Nil(err)
}

func (s *dbTestSuite) TearDownTest() {
	s.cleanup()
}

// Initialize a test in-memory DB.
func (s *dbTestSuite) CreateTestDb() (*Cluster, func()) {
	var err error

	// Setup logging if main() hasn't been called/when testing
	if logger.Log == nil {
		err = logger.InitLogger("", "", true, true, nil)
		s.Nil(err)
	}

	db, cleanup := NewTestCluster(s.T())
	return db, cleanup
}

// Enter a transaction on the test in-memory DB.
func (s *dbTestSuite) CreateTestTx() (*sql.Tx, func()) {
	tx, err := s.db.DB().Begin()
	s.Nil(err)
	commit := func() {
		s.Nil(tx.Commit())
	}

	return tx, commit
}

func TestDBTestSuite(t *testing.T) {
	suite.Run(t, &dbTestSuite{})
}

func (s *dbTestSuite) Test_deleting_a_container_cascades_on_related_tables() {
	var err error
	var count int
	var statements string

	// Drop the container we just created.
	statements = `DELETE FROM instances WHERE name = 'thename';`

	tx, commit := s.CreateTestTx()
	defer commit()

	_, err = tx.Exec(statements)
	s.Nil(err, "Error deleting container!")

	// Make sure there are 0 container_profiles entries left.
	statements = `SELECT count(*) FROM instances_profiles;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a container didn't delete the profile association!")

	// Make sure there are 0 containers_config entries left.
	statements = `SELECT count(*) FROM instances_config;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a container didn't delete the associated container_config!")

	// Make sure there are 0 containers_devices entries left.
	statements = `SELECT count(*) FROM instances_devices;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a container didn't delete the associated container_devices!")

	// Make sure there are 0 containers_devices_config entries left.
	statements = `SELECT count(*) FROM instances_devices_config;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a container didn't delete the associated container_devices_config!")
}

func (s *dbTestSuite) Test_deleting_a_profile_cascades_on_related_tables() {
	var err error
	var count int
	var statements string

	// Drop the profile we just created.
	statements = `DELETE FROM profiles WHERE name = 'theprofile';`

	tx, commit := s.CreateTestTx()
	defer commit()

	_, err = tx.Exec(statements)
	s.Nil(err)

	// Make sure there are 0 container_profiles entries left.
	statements = `SELECT count(*) FROM instances_profiles WHERE profile_id = 2;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a profile didn't delete the container association!")

	// Make sure there are 0 profiles_devices entries left.
	statements = `SELECT count(*) FROM profiles_devices WHERE profile_id == 2;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_devices!")

	// Make sure there are 0 profiles_config entries left.
	statements = `SELECT count(*) FROM profiles_config WHERE profile_id == 2;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_config! There are %d left")

	// Make sure there are 0 profiles_devices_config entries left.
	statements = `SELECT count(*) FROM profiles_devices_config WHERE profile_device_id == 3;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_devices_config!")
}

func (s *dbTestSuite) Test_deleting_an_image_cascades_on_related_tables() {
	var err error
	var count int
	var statements string

	// Drop the image we just created.
	statements = `DELETE FROM images;`

	tx, commit := s.CreateTestTx()
	defer commit()

	_, err = tx.Exec(statements)
	s.Nil(err)
	// Make sure there are 0 images_aliases entries left.
	statements = `SELECT count(*) FROM images_aliases;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting an image didn't delete the image alias association!")

	// Make sure there are 0 images_properties entries left.
	statements = `SELECT count(*) FROM images_properties;`
	err = tx.QueryRow(statements).Scan(&count)
	s.Nil(err)
	s.Equal(count, 0, "Deleting an image didn't delete the related images_properties!")
}

func (s *dbTestSuite) Test_ImageGet_finds_image_for_fingerprint() {
	var result *api.Image
	project := "default"

	err := s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		var err error
		_, result, err = tx.GetImage(ctx, "fingerprint", cluster.ImageFilter{Project: &project})
		return err
	})

	s.Nil(err)
	s.NotNil(result)
	s.Equal(result.Filename, "filename")
	s.Equal(result.CreatedAt.UTC(), time.Unix(1431547174, 0).UTC())
	s.Equal(result.ExpiresAt.UTC(), time.Unix(1431547175, 0).UTC())
	s.Equal(result.UploadedAt.UTC(), time.Unix(1431547176, 0).UTC())
}

func (s *dbTestSuite) Test_ImageGet_for_missing_fingerprint() {
	project := "default"

	err := s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		_, _, err := tx.GetImage(ctx, "unknown", cluster.ImageFilter{Project: &project})
		return err
	})

	s.True(api.StatusErrorCheck(err, http.StatusNotFound))
}

func (s *dbTestSuite) Test_ImageExists_true() {
	var exists bool

	err := s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		var err error
		exists, err = tx.ImageExists(ctx, "default", "fingerprint")
		return err
	})

	s.Nil(err)
	s.True(exists)
}

func (s *dbTestSuite) Test_ImageExists_false() {
	var exists bool

	err := s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		var err error
		exists, err = tx.ImageExists(ctx, "default", "foobar")
		return err
	})

	s.Nil(err)
	s.False(exists)
}

func (s *dbTestSuite) Test_GetImageAlias_alias_exists() {
	_ = s.db.Transaction(context.Background(), func(ctx context.Context, tx *ClusterTx) error {
		_, alias, err := tx.GetImageAlias(ctx, "default", "somealias", true)
		s.Nil(err)
		s.Equal(alias.Target, "fingerprint")

		return nil
	})
}

func (s *dbTestSuite) Test_GetImageAlias_alias_does_not_exists() {
	_ = s.db.Transaction(context.Background(), func(ctx context.Context, tx *ClusterTx) error {
		_, _, err := tx.GetImageAlias(ctx, "default", "whatever", true)
		s.True(api.StatusErrorCheck(err, http.StatusNotFound))

		return nil
	})
}

func (s *dbTestSuite) Test_CreateImageAlias() {
	_ = s.db.Transaction(context.Background(), func(ctx context.Context, tx *ClusterTx) error {
		err := tx.CreateImageAlias(ctx, "default", "Chaosphere", 1, "Someone will like the name")
		s.Nil(err)

		_, alias, err := tx.GetImageAlias(ctx, "default", "Chaosphere", true)
		s.Nil(err)
		s.Equal(alias.Target, "fingerprint")

		return nil
	})
}

func (s *dbTestSuite) Test_GetCachedImageSourceFingerprint() {
	project := "default"

	_ = s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		imageID, _, err := tx.GetImage(ctx, "fingerprint", cluster.ImageFilter{Project: &project})
		s.Nil(err)

		err = tx.CreateImageSource(ctx, imageID, "server.remote", "simplestreams", "", "test")
		s.Nil(err)

		fingerprint, err := tx.GetCachedImageSourceFingerprint(ctx, "server.remote", "simplestreams", "test", "container", 0)
		s.Nil(err)
		s.Equal(fingerprint, "fingerprint")
		return nil
	})
}

func (s *dbTestSuite) Test_GetCachedImageSourceFingerprint_no_match() {
	project := "default"

	_ = s.db.Transaction(context.TODO(), func(ctx context.Context, tx *ClusterTx) error {
		imageID, _, err := tx.GetImage(ctx, "fingerprint", cluster.ImageFilter{Project: &project})
		s.Nil(err)

		err = tx.CreateImageSource(ctx, imageID, "server.remote", "simplestreams", "", "test")
		s.Nil(err)

		_, err = tx.GetCachedImageSourceFingerprint(ctx, "server.remote", "incus", "test", "container", 0)
		s.True(api.StatusErrorCheck(err, http.StatusNotFound))
		return nil
	})
}