• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / deployments / 843450222

pending completion
843450222

Pull #854

gitlab-ci

Alf-Rune Siqveland
chore: Add `--throttle` flag to `propagate-reporting` command
Pull Request #854: chore: Add `--throttle` flag to `propagate-reporting` command

8 of 11 new or added lines in 1 file covered. (72.73%)

434 existing lines in 4 files now uncovered.

6943 of 8758 relevant lines covered (79.28%)

70.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.54
/store/mongo/datastore_mongo.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package mongo
15

16
import (
17
        "context"
18
        "crypto/tls"
19
        "math"
20
        "regexp"
21
        "strings"
22
        "time"
23

24
        "github.com/pkg/errors"
25
        "go.mongodb.org/mongo-driver/bson"
26
        "go.mongodb.org/mongo-driver/bson/primitive"
27
        "go.mongodb.org/mongo-driver/mongo"
28
        mopts "go.mongodb.org/mongo-driver/mongo/options"
29

30
        "github.com/mendersoftware/go-lib-micro/config"
31
        "github.com/mendersoftware/go-lib-micro/identity"
32
        "github.com/mendersoftware/go-lib-micro/mongo/migrate"
33
        mstore "github.com/mendersoftware/go-lib-micro/store"
34

35
        dconfig "github.com/mendersoftware/deployments/config"
36
        "github.com/mendersoftware/deployments/model"
37
        "github.com/mendersoftware/deployments/store"
38
)
39

40
const (
41
        DatabaseName                   = "deployment_service"
42
        CollectionLimits               = "limits"
43
        CollectionImages               = "images"
44
        CollectionDeployments          = "deployments"
45
        CollectionDeviceDeploymentLogs = "devices.logs"
46
        CollectionDevices              = "devices"
47
        CollectionStorageSettings      = "settings"
48
        CollectionUploadIntents        = "uploads"
49
)
50

51
const DefaultDocumentLimit = 20
52
const maxCountDocuments = int64(10000)
53

54
// Internal status codes from
55
// https://github.com/mongodb/mongo/blob/4.4/src/mongo/base/error_codes.yml
56
const (
57
        errorCodeNamespaceNotFound = 26
58
        errorCodeIndexNotFound     = 27
59
)
60

61
var (
62
        // Indexes (version: 1.2.2)
63
        IndexUniqueNameAndDeviceTypeName          = "uniqueNameAndDeviceTypeIndex"
64
        IndexDeploymentArtifactName               = "deploymentArtifactNameIndex"
65
        IndexDeploymentDeviceStatusesName         = "deviceIdWithStatusByCreated"
66
        IndexDeploymentDeviceIdStatusName         = "devicesIdWithStatus"
67
        IndexDeploymentDeviceCreatedStatusName    = "devicesIdWithCreatedStatus"
68
        IndexDeploymentDeviceDeploymentIdName     = "devicesDeploymentId"
69
        IndexDeploymentStatusFinishedName         = "deploymentStatusFinished"
70
        IndexDeploymentStatusPendingName          = "deploymentStatusPending"
71
        IndexDeploymentCreatedName                = "deploymentCreated"
72
        IndexDeploymentDeviceStatusRebootingName  = "deploymentsDeviceStatusRebooting"
73
        IndexDeploymentDeviceStatusPendingName    = "deploymentsDeviceStatusPending"
74
        IndexDeploymentDeviceStatusInstallingName = "deploymentsDeviceStatusInstalling"
75
        IndexDeploymentDeviceStatusFinishedName   = "deploymentsFinished"
76

77
        // Indexes (version: 1.2.3)
78
        IndexArtifactNameDependsName = "artifactNameDepends"
79
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
80

81
        // Indexes (version: 1.2.4)
82
        IndexDeploymentStatus = "deploymentStatus"
83

84
        // Indexes 1.2.6
85
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
86

87
        // Indexes 1.2.13
88
        IndexArtifactProvidesName = "artifact_provides"
89

90
        _false         = false
91
        _true          = true
92
        StorageIndexes = mongo.IndexModel{
93
                // NOTE: Keys should be bson.D as element
94
                //       order matters!
95
                Keys: bson.D{
96
                        {Key: StorageKeyDeploymentName,
97
                                Value: "text"},
98
                        {Key: StorageKeyDeploymentArtifactName,
99
                                Value: "text"},
100
                },
101
                Options: &mopts.IndexOptions{
102
                        Background: &_false,
103
                        Name:       &IndexDeploymentArtifactName,
104
                },
105
        }
106
        StatusIndexes = mongo.IndexModel{
107
                Keys: bson.D{
108
                        {Key: StorageKeyDeviceDeploymentDeviceId,
109
                                Value: 1},
110
                        {Key: StorageKeyDeviceDeploymentStatus,
111
                                Value: 1},
112
                        {Key: StorageKeyDeploymentStatsCreated,
113
                                Value: 1},
114
                },
115
                Options: &mopts.IndexOptions{
116
                        Background: &_false,
117
                        Name:       &IndexDeploymentDeviceStatusesName,
118
                },
119
        }
120
        DeploymentStatusIndex = mongo.IndexModel{
121
                Keys: bson.D{
122
                        {Key: StorageKeyDeviceDeploymentStatus,
123
                                Value: 1},
124
                },
125
                Options: &mopts.IndexOptions{
126
                        Background: &_false,
127
                        Name:       &IndexDeploymentStatus,
128
                },
129
        }
130
        DeviceIDStatusIndexes = mongo.IndexModel{
131
                Keys: bson.D{
132
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
133
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
134
                },
135
                Options: &mopts.IndexOptions{
136
                        Background: &_false,
137
                        Name:       &IndexDeploymentDeviceIdStatusName,
138
                },
139
        }
140
        DeviceIDCreatedStatusIndex = mongo.IndexModel{
141
                Keys: bson.D{
142
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
143
                        {Key: StorageKeyDeploymentStatsCreated, Value: 1},
144
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
145
                },
146
                Options: &mopts.IndexOptions{
147
                        Background: &_false,
148
                        Name:       &IndexDeploymentDeviceCreatedStatusName,
149
                },
150
        }
151
        DeploymentIdIndexes = mongo.IndexModel{
152
                Keys: bson.D{
153
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
154
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
155
                },
156
                Options: &mopts.IndexOptions{
157
                        Background: &_false,
158
                        Name:       &IndexDeploymentDeviceDeploymentIdName,
159
                },
160
        }
161
        DeviceDeploymentIdStatus = mongo.IndexModel{
162
                Keys: bson.D{
163
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
164
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
165
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
166
                },
167
                Options: mopts.Index().
168
                        SetName(IndexDeviceDeploymentStatusName),
169
        }
170
        DeploymentStatusFinishedIndex = mongo.IndexModel{
171
                Keys: bson.D{
172
                        {Key: "stats.downloading", Value: 1},
173
                        {Key: "stats.installing", Value: 1},
174
                        {Key: "stats.pending", Value: 1},
175
                        {Key: "stats.rebooting", Value: 1},
176
                        {Key: "created", Value: -1},
177
                },
178
                Options: &mopts.IndexOptions{
179
                        Background: &_false,
180
                        Name:       &IndexDeploymentStatusFinishedName,
181
                },
182
        }
183
        DeploymentStatusPendingIndex = mongo.IndexModel{
184
                Keys: bson.D{
185
                        {Key: "stats.aborted", Value: 1},
186
                        {Key: "stats.already-installed", Value: 1},
187
                        {Key: "stats.decommissioned", Value: 1},
188
                        {Key: "stats.downloading", Value: 1},
189
                        {Key: "stats.failure", Value: 1},
190
                        {Key: "stats.installing", Value: 1},
191
                        {Key: "stats.noartifact", Value: 1},
192
                        {Key: "stats.rebooting", Value: 1},
193
                        {Key: "stats.success", Value: 1},
194
                        {Key: "created", Value: -1},
195
                },
196
                Options: &mopts.IndexOptions{
197
                        Background: &_false,
198
                        Name:       &IndexDeploymentStatusPendingName,
199
                },
200
        }
201
        DeploymentCreatedIndex = mongo.IndexModel{
202
                Keys: bson.D{
203
                        {Key: "created", Value: -1},
204
                },
205
                Options: &mopts.IndexOptions{
206
                        Background: &_false,
207
                        Name:       &IndexDeploymentCreatedName,
208
                },
209
        }
210
        DeploymentDeviceStatusRebootingIndex = mongo.IndexModel{
211
                Keys: bson.D{
212
                        {Key: "stats.rebooting", Value: 1},
213
                },
214
                Options: &mopts.IndexOptions{
215
                        Background: &_false,
216
                        Name:       &IndexDeploymentDeviceStatusRebootingName,
217
                },
218
        }
219
        DeploymentDeviceStatusPendingIndex = mongo.IndexModel{
220
                Keys: bson.D{
221
                        {Key: "stats.pending", Value: 1},
222
                },
223
                Options: &mopts.IndexOptions{
224
                        Background: &_false,
225
                        Name:       &IndexDeploymentDeviceStatusPendingName,
226
                },
227
        }
228
        DeploymentDeviceStatusInstallingIndex = mongo.IndexModel{
229
                Keys: bson.D{
230
                        {Key: "stats.installing", Value: 1},
231
                },
232
                Options: &mopts.IndexOptions{
233
                        Background: &_false,
234
                        Name:       &IndexDeploymentDeviceStatusInstallingName,
235
                },
236
        }
237
        DeploymentDeviceStatusFinishedIndex = mongo.IndexModel{
238
                Keys: bson.D{
239
                        {Key: "finished", Value: 1},
240
                },
241
                Options: &mopts.IndexOptions{
242
                        Background: &_false,
243
                        Name:       &IndexDeploymentDeviceStatusFinishedName,
244
                },
245
        }
246
        UniqueNameVersionIndex = mongo.IndexModel{
247
                Keys: bson.D{
248
                        {Key: StorageKeyImageName,
249
                                Value: 1},
250
                        {Key: StorageKeyImageDeviceTypes,
251
                                Value: 1},
252
                },
253
                Options: &mopts.IndexOptions{
254
                        Background: &_false,
255
                        Name:       &IndexUniqueNameAndDeviceTypeName,
256
                        Unique:     &_true,
257
                },
258
        }
259

260
        // 1.2.3
261
        IndexArtifactNameDepends = mongo.IndexModel{
262
                Keys: bson.D{
263
                        {Key: StorageKeyImageName,
264
                                Value: 1},
265
                        {Key: StorageKeyImageDependsIdx,
266
                                Value: 1},
267
                },
268
                Options: &mopts.IndexOptions{
269
                        Background: &_false,
270
                        Name:       &IndexArtifactNameDependsName,
271
                        Unique:     &_true,
272
                },
273
        }
274

275
        // Indexes 1.2.7
276
        IndexImageMetaDescription      = "image_meta_description"
277
        IndexImageMetaDescriptionModel = mongo.IndexModel{
278
                Keys: bson.D{
279
                        {Key: StorageKeyImageDescription, Value: 1},
280
                },
281
                Options: &mopts.IndexOptions{
282
                        Background: &_false,
283
                        Name:       &IndexImageMetaDescription,
284
                },
285
        }
286

287
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
288
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
289
                Keys: bson.D{
290
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
291
                },
292
                Options: &mopts.IndexOptions{
293
                        Background: &_false,
294
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
295
                },
296
        }
297

298
        // Indexes 1.2.8
299
        IndexDeploymentsActiveCreated      = "active_created"
300
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
301
                Keys: bson.D{
302
                        {Key: StorageKeyDeploymentCreated, Value: 1},
303
                },
304
                Options: &mopts.IndexOptions{
305
                        Background: &_false,
306
                        Name:       &IndexDeploymentsActiveCreated,
307
                        PartialFilterExpression: bson.M{
308
                                StorageKeyDeploymentActive: true,
309
                        },
310
                },
311
        }
312

313
        // Index 1.2.9
314
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
315
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
316
                Keys: bson.D{
317
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
318
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
319
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
320
                },
321
                Options: mopts.Index().
322
                        SetName(IndexDeviceDeploymentsActiveCreated),
323
        }
324

325
        // Index 1.2.11
326
        IndexDeviceDeploymentsLogs      = "devices_logs"
327
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
328
                Keys: bson.D{
329
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
330
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
331
                },
332
                Options: mopts.Index().
333
                        SetName(IndexDeviceDeploymentsLogs),
334
        }
335

336
        // 1.2.13
337
        IndexArtifactProvides = mongo.IndexModel{
338
                Keys: bson.D{
339
                        {Key: model.StorageKeyImageProvidesIdxKey,
340
                                Value: 1},
341
                        {Key: model.StorageKeyImageProvidesIdxValue,
342
                                Value: 1},
343
                },
344
                Options: &mopts.IndexOptions{
345
                        Background: &_false,
346
                        Sparse:     &_true,
347
                        Name:       &IndexArtifactProvidesName,
348
                },
349
        }
350
)
351

352
// Errors
353
var (
354
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
355
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
356
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
357
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
358
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
359

360
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
361

362
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
363
        ErrStorageInvalidID                   = errors.New("Invalid id")
364
        ErrStorageNotFound                    = errors.New("Not found")
365
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
366
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
367
        ErrStorageInvalidInput                = errors.New("invalid input")
368

369
        ErrLimitNotFound      = errors.New("limit not found")
370
        ErrDevicesCountFailed = errors.New("failed to count devices")
371
)
372

373
const (
374
        ErrMsgConflictingDepends = "An artifact with the same name has " +
375
                "conflicting depends"
376
)
377

378
// Database keys
379
const (
380
        // Need to be kept in sync with structure filed names
381
        StorageKeyId = "_id"
382

383
        StorageKeyImageProvides    = "meta_artifact.provides"
384
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
385
        StorageKeyImageDepends     = "meta_artifact.depends"
386
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
387
        StorageKeyImageSize        = "size"
388
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
389
        StorageKeyImageName        = "meta_artifact.name"
390
        StorageKeyImageDescription = "meta.description"
391

392
        StorageKeyDeviceDeploymentLogMessages = "messages"
393

394
        StorageKeyDeviceDeploymentAssignedImage   = "image"
395
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
396
                "." + StorageKeyId
397

398
        StorageKeyDeviceDeploymentActive         = "active"
399
        StorageKeyDeviceDeploymentCreated        = "created"
400
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
401
        StorageKeyDeviceDeploymentStatus         = "status"
402
        StorageKeyDeviceDeploymentSubState       = "substate"
403
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
404
        StorageKeyDeviceDeploymentFinished       = "finished"
405
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
406
        StorageKeyDeviceDeploymentArtifact       = "image"
407
        StorageKeyDeviceDeploymentRequest        = "request"
408
        StorageKeyDeviceDeploymentDeleted        = "deleted"
409

410
        StorageKeyDeploymentName         = "deploymentconstructor.name"
411
        StorageKeyDeploymentArtifactName = "deploymentconstructor.artifactname"
412
        StorageKeyDeploymentStats        = "stats"
413
        StorageKeyDeploymentActive       = "active"
414
        StorageKeyDeploymentStatus       = "status"
415
        StorageKeyDeploymentCreated      = "created"
416
        StorageKeyDeploymentStatsCreated = "created"
417
        StorageKeyDeploymentFinished     = "finished"
418
        StorageKeyDeploymentArtifacts    = "artifacts"
419
        StorageKeyDeploymentDeviceCount  = "device_count"
420
        StorageKeyDeploymentMaxDevices   = "max_devices"
421
        StorageKeyDeploymentType         = "type"
422
        StorageKeyDeploymentTotalSize    = "statistics.total_size"
423

424
        StorageKeyStorageSettingsDefaultID      = "settings"
425
        StorageKeyStorageSettingsBucket         = "bucket"
426
        StorageKeyStorageSettingsRegion         = "region"
427
        StorageKeyStorageSettingsKey            = "key"
428
        StorageKeyStorageSettingsSecret         = "secret"
429
        StorageKeyStorageSettingsURI            = "uri"
430
        StorageKeyStorageSettingsExternalURI    = "external_uri"
431
        StorageKeyStorageSettingsToken          = "token"
432
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
433
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
434

435
        ArtifactDependsDeviceType = "device_type"
436
)
437

438
type DataStoreMongo struct {
439
        client *mongo.Client
440
}
441

442
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
555✔
443
        return &DataStoreMongo{
555✔
444
                client: client,
555✔
445
        }
555✔
446
}
555✔
447

448
func NewMongoClient(ctx context.Context, c config.Reader) (*mongo.Client, error) {
1✔
449

1✔
450
        clientOptions := mopts.Client()
1✔
451
        mongoURL := c.GetString(dconfig.SettingMongo)
1✔
452
        if !strings.Contains(mongoURL, "://") {
1✔
UNCOV
453
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
UNCOV
454
                        mongoURL)
×
455
        }
×
456
        clientOptions.ApplyURI(mongoURL)
1✔
457

1✔
458
        username := c.GetString(dconfig.SettingDbUsername)
1✔
459
        if username != "" {
1✔
UNCOV
460
                credentials := mopts.Credential{
×
UNCOV
461
                        Username: c.GetString(dconfig.SettingDbUsername),
×
462
                }
×
463
                password := c.GetString(dconfig.SettingDbPassword)
×
464
                if password != "" {
×
465
                        credentials.Password = password
×
466
                        credentials.PasswordSet = true
×
467
                }
×
468
                clientOptions.SetAuth(credentials)
×
469
        }
470

471
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
UNCOV
472
                tlsConfig := &tls.Config{}
×
UNCOV
473
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
474
                clientOptions.SetTLSConfig(tlsConfig)
×
475
        }
×
476

477
        // Set 10s timeout
478
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1✔
479
        defer cancel()
1✔
480
        client, err := mongo.Connect(ctx, clientOptions)
1✔
481
        if err != nil {
1✔
UNCOV
482
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
UNCOV
483
        }
×
484

485
        // Validate connection
486
        if err = client.Ping(ctx, nil); err != nil {
1✔
UNCOV
487
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
UNCOV
488
        }
×
489

490
        return client, nil
1✔
491
}
492

493
func (db *DataStoreMongo) Ping(ctx context.Context) error {
2✔
494
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
2✔
495
        return res.Err()
2✔
496
}
2✔
497

498
func (db *DataStoreMongo) GetReleases(
499
        ctx context.Context,
500
        filt *model.ReleaseOrImageFilter,
501
) ([]model.Release, int, error) {
19✔
502
        var pipe []bson.D
19✔
503

19✔
504
        pipe = []bson.D{}
19✔
505
        if filt != nil && filt.Name != "" {
24✔
506
                pipe = append(pipe, bson.D{
5✔
507
                        {Key: "$match", Value: bson.M{
5✔
508
                                StorageKeyImageName: bson.M{
5✔
509
                                        "$regex": primitive.Regex{
5✔
510
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
5✔
511
                                                Options: "i",
5✔
512
                                        },
5✔
513
                                },
5✔
514
                        }},
5✔
515
                })
5✔
516
        }
5✔
517

518
        pipe = append(pipe, bson.D{
19✔
519
                // Remove (possibly expensive) sub-documents from pipeline
19✔
520
                {
19✔
521
                        Key: "$project",
19✔
522
                        Value: bson.M{
19✔
523
                                StorageKeyImageDependsIdx:  0,
19✔
524
                                StorageKeyImageProvidesIdx: 0,
19✔
525
                        },
19✔
526
                },
19✔
527
        })
19✔
528

19✔
529
        pipe = append(pipe, bson.D{
19✔
530
                {Key: "$group", Value: bson.D{
19✔
531
                        {Key: "_id", Value: "$" + StorageKeyImageName},
19✔
532
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
19✔
533
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
19✔
534
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
19✔
535
                }},
19✔
536
        })
19✔
537

19✔
538
        if filt != nil && filt.Description != "" {
23✔
539
                pipe = append(pipe, bson.D{
4✔
540
                        {Key: "$match", Value: bson.M{
4✔
541
                                "artifacts." + StorageKeyImageDescription: bson.M{
4✔
542
                                        "$regex": primitive.Regex{
4✔
543
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
544
                                                Options: "i",
4✔
545
                                        },
4✔
546
                                },
4✔
547
                        }},
4✔
548
                })
4✔
549
        }
4✔
550
        if filt != nil && filt.DeviceType != "" {
21✔
551
                pipe = append(pipe, bson.D{
2✔
552
                        {Key: "$match", Value: bson.M{
2✔
553
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
2✔
554
                                        "$regex": primitive.Regex{
2✔
555
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
556
                                                Options: "i",
2✔
557
                                        },
2✔
558
                                },
2✔
559
                        }},
2✔
560
                })
2✔
561
        }
2✔
562

563
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
19✔
564
        if sortField == "" {
32✔
565
                sortField = "name"
13✔
566
        }
13✔
567
        if sortOrder == 0 {
32✔
568
                sortOrder = 1
13✔
569
        }
13✔
570

571
        page := 1
19✔
572
        perPage := math.MaxInt64
19✔
573
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
21✔
574
                page = filt.Page
2✔
575
                perPage = filt.PerPage
2✔
576
        }
2✔
577
        pipe = append(pipe,
19✔
578
                bson.D{{Key: "$facet", Value: bson.D{
19✔
579
                        {Key: "results", Value: []bson.D{
19✔
580
                                {
19✔
581
                                        {Key: "$sort", Value: bson.D{
19✔
582
                                                {Key: sortField, Value: sortOrder},
19✔
583
                                                {Key: "_id", Value: 1},
19✔
584
                                        }},
19✔
585
                                },
19✔
586
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
19✔
587
                                {{Key: "$limit", Value: int64(perPage)}},
19✔
588
                        }},
19✔
589
                        {Key: "count", Value: []bson.D{
19✔
590
                                {{Key: "$count", Value: "count"}},
19✔
591
                        }},
19✔
592
                }}},
19✔
593
        )
19✔
594

19✔
595
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
19✔
596
        collImg := database.Collection(CollectionImages)
19✔
597

19✔
598
        cursor, err := collImg.Aggregate(ctx, pipe)
19✔
599
        if err != nil {
19✔
UNCOV
600
                return nil, 0, err
×
UNCOV
601
        }
×
602
        defer cursor.Close(ctx)
19✔
603

19✔
604
        result := struct {
19✔
605
                Results []model.Release       `bson:"results"`
19✔
606
                Count   []struct{ Count int } `bson:"count"`
19✔
607
        }{}
19✔
608
        if !cursor.Next(ctx) {
19✔
UNCOV
609
                return nil, 0, nil
×
610
        } else if err = cursor.Decode(&result); err != nil {
19✔
611
                return nil, 0, err
×
612
        } else if len(result.Count) == 0 {
22✔
613
                return []model.Release{}, 0, err
3✔
614
        }
3✔
615
        return result.Results, result.Count[0].Count, nil
17✔
616
}
617

618
// limits
619
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
8✔
620

8✔
621
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
622
        collLim := database.Collection(CollectionLimits)
8✔
623

8✔
624
        limit := new(model.Limit)
8✔
625
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
8✔
626
                Decode(limit); err != nil {
12✔
627
                if err == mongo.ErrNoDocuments {
8✔
628
                        return nil, ErrLimitNotFound
4✔
629
                }
4✔
UNCOV
630
                return nil, err
×
631
        }
632

633
        return limit, nil
4✔
634
}
635

636
func (db *DataStoreMongo) ProvisionTenant(ctx context.Context, tenantId string) error {
9✔
637

9✔
638
        dbname := mstore.DbNameForTenant(tenantId, DbName)
9✔
639

9✔
640
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
9✔
641
}
9✔
642

643
//images
644

645
// Exists checks if object with ID exists
UNCOV
646
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
UNCOV
647
        var result interface{}
×
648

×
649
        if len(id) == 0 {
×
650
                return false, ErrImagesStorageInvalidID
×
651
        }
×
652

653
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
UNCOV
654
        collImg := database.Collection(CollectionImages)
×
655

×
656
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
657
                Decode(&result); err != nil {
×
658
                if err == mongo.ErrNoDocuments {
×
659
                        return false, nil
×
660
                }
×
661
                return false, err
×
662
        }
663

UNCOV
664
        return true, nil
×
665
}
666

667
// Update provided Image
668
// Return false if not found
669
func (db *DataStoreMongo) Update(ctx context.Context,
670
        image *model.Image) (bool, error) {
2✔
671

2✔
672
        if err := image.Validate(); err != nil {
2✔
UNCOV
673
                return false, err
×
UNCOV
674
        }
×
675

676
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
677
        collImg := database.Collection(CollectionImages)
2✔
678

2✔
679
        // add special representation of artifact provides
2✔
680
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
2✔
681

2✔
682
        image.SetModified(time.Now())
2✔
683
        if res, err := collImg.ReplaceOne(
2✔
684
                ctx, bson.M{"_id": image.Id}, image,
2✔
685
        ); err != nil {
2✔
UNCOV
686
                return false, err
×
687
        } else if res.MatchedCount == 0 {
2✔
688
                return false, nil
×
UNCOV
689
        }
×
690

691
        return true, nil
2✔
692
}
693

694
// ImageByNameAndDeviceType finds image with specified application name and target device type
695
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
696
        name, deviceType string) (*model.Image, error) {
18✔
697

18✔
698
        if len(name) == 0 {
20✔
699
                return nil, ErrImagesStorageInvalidArtifactName
2✔
700
        }
2✔
701

702
        if len(deviceType) == 0 {
18✔
703
                return nil, ErrImagesStorageInvalidDeviceType
2✔
704
        }
2✔
705

706
        // equal to device type & software version (application name + version)
707
        query := bson.M{
14✔
708
                StorageKeyImageName:        name,
14✔
709
                StorageKeyImageDeviceTypes: deviceType,
14✔
710
        }
14✔
711

14✔
712
        // If multiple entries matches, pick the smallest one.
14✔
713
        findOpts := mopts.FindOne()
14✔
714
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
14✔
715

14✔
716
        dbName := mstore.DbFromContext(ctx, DatabaseName)
14✔
717
        database := db.client.Database(dbName)
14✔
718
        collImg := database.Collection(CollectionImages)
14✔
719

14✔
720
        // Both we lookup unique object, should be one or none.
14✔
721
        var image model.Image
14✔
722
        if err := collImg.FindOne(ctx, query, findOpts).
14✔
723
                Decode(&image); err != nil {
22✔
724
                if err == mongo.ErrNoDocuments {
16✔
725
                        return nil, nil
8✔
726
                }
8✔
UNCOV
727
                return nil, err
×
728
        }
729

730
        return &image, nil
6✔
731
}
732

733
// ImageByIdsAndDeviceType finds image with id from ids and target device type
734
func (db *DataStoreMongo) ImageByIdsAndDeviceType(ctx context.Context,
735
        ids []string, deviceType string) (*model.Image, error) {
1✔
736

1✔
737
        if len(deviceType) == 0 {
1✔
UNCOV
738
                return nil, ErrImagesStorageInvalidDeviceType
×
UNCOV
739
        }
×
740

741
        if len(ids) == 0 {
1✔
UNCOV
742
                return nil, ErrImagesStorageInvalidID
×
UNCOV
743
        }
×
744

745
        query := bson.D{
1✔
746
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
747
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
748
        }
1✔
749

1✔
750
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
751
        collImg := database.Collection(CollectionImages)
1✔
752

1✔
753
        // If multiple entries matches, pick the smallest one
1✔
754
        findOpts := mopts.FindOne()
1✔
755
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
756

1✔
757
        // Both we lookup unique object, should be one or none.
1✔
758
        var image model.Image
1✔
759
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
760
                Decode(&image); err != nil {
1✔
UNCOV
761
                if err == mongo.ErrNoDocuments {
×
UNCOV
762
                        return nil, nil
×
763
                }
×
764
                return nil, err
×
765
        }
766

767
        return &image, nil
1✔
768
}
769

770
// ImagesByName finds images with specified artifact name
771
func (db *DataStoreMongo) ImagesByName(
772
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
773

1✔
774
        var images []*model.Image
1✔
775

1✔
776
        if len(name) == 0 {
1✔
UNCOV
777
                return nil, ErrImagesStorageInvalidName
×
UNCOV
778
        }
×
779

780
        // equal to artifact name
781
        query := bson.M{
1✔
782
                StorageKeyImageName: name,
1✔
783
        }
1✔
784

1✔
785
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
786
        collImg := database.Collection(CollectionImages)
1✔
787
        cursor, err := collImg.Find(ctx, query)
1✔
788
        if err != nil {
1✔
UNCOV
789
                return nil, err
×
UNCOV
790
        }
×
791
        // Both we lookup unique object, should be one or none.
792
        if err = cursor.All(ctx, &images); err != nil {
1✔
UNCOV
793
                return nil, err
×
UNCOV
794
        }
×
795

796
        return images, nil
1✔
797
}
798

799
// Insert persists object
800
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
125✔
801

125✔
802
        if image == nil {
125✔
UNCOV
803
                return ErrImagesStorageInvalidImage
×
UNCOV
804
        }
×
805

806
        if err := image.Validate(); err != nil {
125✔
UNCOV
807
                return err
×
UNCOV
808
        }
×
809

810
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
125✔
811
        collImg := database.Collection(CollectionImages)
125✔
812

125✔
813
        // add special representation of artifact provides
125✔
814
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
125✔
815

125✔
816
        _, err := collImg.InsertOne(ctx, image)
125✔
817
        if err != nil {
139✔
818
                if except, ok := err.(mongo.WriteException); ok {
28✔
819
                        var conflicts string
14✔
820
                        if len(except.WriteErrors) > 0 {
28✔
821
                                err := except.WriteErrors[0]
14✔
822
                                yamlStart := strings.IndexByte(err.Message, '{')
14✔
823
                                if yamlStart != -1 {
28✔
824
                                        conflicts = err.Message[yamlStart:]
14✔
825
                                }
14✔
826
                        }
827
                        conflictErr := model.NewConflictError(
14✔
828
                                ErrMsgConflictingDepends,
14✔
829
                                conflicts,
14✔
830
                        )
14✔
831
                        return conflictErr
14✔
832
                }
833
        }
834

835
        return nil
111✔
836
}
837

838
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
3✔
839
        collUploads := db.client.
3✔
840
                Database(DatabaseName).
3✔
841
                Collection(CollectionUploadIntents)
3✔
842
        if idty := identity.FromContext(ctx); idty != nil {
4✔
843
                link.TenantID = idty.Tenant
1✔
844
        }
1✔
845
        _, err := collUploads.InsertOne(ctx, link)
3✔
846
        return err
3✔
847
}
848

849
func (db *DataStoreMongo) UpdateUploadIntentStatus(
850
        ctx context.Context,
851
        id string,
852
        from, to model.LinkStatus,
853
) error {
11✔
854
        collUploads := db.client.
11✔
855
                Database(DatabaseName).
11✔
856
                Collection(CollectionUploadIntents)
11✔
857
        q := bson.D{
11✔
858
                {Key: "_id", Value: id},
11✔
859
                {Key: "status", Value: from},
11✔
860
        }
11✔
861
        if idty := identity.FromContext(ctx); idty != nil {
20✔
862
                q = append(q, bson.E{
9✔
863
                        Key:   "tenant_id",
9✔
864
                        Value: idty.Tenant,
9✔
865
                })
9✔
866
        }
9✔
867
        update := bson.D{{
11✔
868
                Key: "updated_ts", Value: time.Now(),
11✔
869
        }}
11✔
870
        if from != to {
22✔
871
                update = append(update, bson.E{
11✔
872
                        Key: "status", Value: to,
11✔
873
                })
11✔
874
        }
11✔
875
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
11✔
876
                {Key: "$set", Value: update},
11✔
877
        })
11✔
878
        if err != nil {
13✔
879
                return err
2✔
880
        } else if res.MatchedCount == 0 {
15✔
881
                return store.ErrNotFound
4✔
882
        }
4✔
883
        return nil
5✔
884
}
885

886
func (db *DataStoreMongo) FindUploadLinks(
887
        ctx context.Context,
888
        expiredAt time.Time,
889
) (store.Iterator[model.UploadLink], error) {
2✔
890
        collUploads := db.client.
2✔
891
                Database(DatabaseName).
2✔
892
                Collection(CollectionUploadIntents)
2✔
893

2✔
894
        q := bson.D{{
2✔
895
                Key: "status",
2✔
896
                Value: bson.D{{
2✔
897
                        Key:   "$lt",
2✔
898
                        Value: model.LinkStatusProcessedBit,
2✔
899
                }},
2✔
900
        }, {
2✔
901
                Key: "expire",
2✔
902
                Value: bson.D{{
2✔
903
                        Key:   "$lt",
2✔
904
                        Value: expiredAt,
2✔
905
                }},
2✔
906
        }}
2✔
907
        cur, err := collUploads.Find(ctx, q)
2✔
908
        return IteratorFromCursor[model.UploadLink](cur), err
2✔
909
}
2✔
910

911
// FindImageByID search storage for image with ID, returns nil if not found
912
func (db *DataStoreMongo) FindImageByID(ctx context.Context,
913
        id string) (*model.Image, error) {
1✔
914

1✔
915
        if len(id) == 0 {
1✔
UNCOV
916
                return nil, ErrImagesStorageInvalidID
×
UNCOV
917
        }
×
918

919
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
920
        collImg := database.Collection(CollectionImages)
1✔
921
        projection := bson.M{
1✔
922
                StorageKeyImageDependsIdx:  0,
1✔
923
                StorageKeyImageProvidesIdx: 0,
1✔
924
        }
1✔
925
        findOptions := mopts.FindOne()
1✔
926
        findOptions.SetProjection(projection)
1✔
927

1✔
928
        var image model.Image
1✔
929
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
930
                Decode(&image); err != nil {
2✔
931
                if err == mongo.ErrNoDocuments {
2✔
932
                        return nil, nil
1✔
933
                }
1✔
UNCOV
934
                return nil, err
×
935
        }
936

937
        return &image, nil
1✔
938
}
939

940
// IsArtifactUnique checks if there is no artifact with the same artifactName
941
// supporting one of the device types from deviceTypesCompatible list.
942
// Returns true, nil if artifact is unique;
943
// false, nil if artifact is not unique;
944
// false, error in case of error.
945
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
946
        artifactName string, deviceTypesCompatible []string) (bool, error) {
11✔
947

11✔
948
        if len(artifactName) == 0 {
13✔
949
                return false, ErrImagesStorageInvalidArtifactName
2✔
950
        }
2✔
951

952
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
953
        collImg := database.Collection(CollectionImages)
9✔
954

9✔
955
        query := bson.M{
9✔
956
                "$and": []bson.M{
9✔
957
                        {
9✔
958
                                StorageKeyImageName: artifactName,
9✔
959
                        },
9✔
960
                        {
9✔
961
                                StorageKeyImageDeviceTypes: bson.M{
9✔
962
                                        "$in": deviceTypesCompatible},
9✔
963
                        },
9✔
964
                },
9✔
965
        }
9✔
966

9✔
967
        // do part of the job manually
9✔
968
        // if candidate images have any extra 'depends' - guaranteed non-overlap
9✔
969
        // otherwise it's a match
9✔
970
        cur, err := collImg.Find(ctx, query)
9✔
971
        if err != nil {
9✔
UNCOV
972
                return false, err
×
UNCOV
973
        }
×
974

975
        var images []model.Image
9✔
976
        err = cur.All(ctx, &images)
9✔
977
        if err != nil {
9✔
UNCOV
978
                return false, err
×
UNCOV
979
        }
×
980

981
        for _, i := range images {
11✔
982
                // the artifact already has same name and overlapping dev type
2✔
983
                // if there are no more depends than dev type - it's not unique
2✔
984
                if len(i.ArtifactMeta.Depends) == 1 {
4✔
985
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
4✔
986
                                return false, nil
2✔
987
                        }
2✔
UNCOV
988
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
UNCOV
989
                        return false, nil
×
990
                }
×
991
        }
992

993
        return true, nil
7✔
994
}
995

996
// Delete image specified by ID
997
// Noop on if not found.
998
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
999

1✔
1000
        if len(id) == 0 {
1✔
UNCOV
1001
                return ErrImagesStorageInvalidID
×
UNCOV
1002
        }
×
1003

1004
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1005
        collImg := database.Collection(CollectionImages)
1✔
1006

1✔
1007
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
UNCOV
1008
                if res.DeletedCount == 0 {
×
UNCOV
1009
                        return nil
×
1010
                }
×
1011
                return err
×
1012
        }
1013

1014
        return nil
1✔
1015
}
1016

1017
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
47✔
1018
        if filt != nil && filt.Sort != "" {
59✔
1019
                sortParts := strings.SplitN(filt.Sort, ":", 2)
12✔
1020
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
24✔
1021
                        sortField := sortParts[0]
12✔
1022
                        sortOrder := 1
12✔
1023
                        if sortParts[1] == model.SortDirectionDescending {
20✔
1024
                                sortOrder = -1
8✔
1025
                        }
8✔
1026
                        return sortField, sortOrder
12✔
1027
                }
1028
        }
1029
        return "", 0
35✔
1030
}
1031

1032
// ListImages lists all images
1033
func (db *DataStoreMongo) ListImages(
1034
        ctx context.Context,
1035
        filt *model.ReleaseOrImageFilter,
1036
) ([]*model.Image, int, error) {
29✔
1037

29✔
1038
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1039
        collImg := database.Collection(CollectionImages)
29✔
1040

29✔
1041
        filters := bson.M{}
29✔
1042
        if filt != nil {
48✔
1043
                if filt.Name != "" {
27✔
1044
                        filters[StorageKeyImageName] = bson.M{
8✔
1045
                                "$regex": primitive.Regex{
8✔
1046
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
8✔
1047
                                        Options: "i",
8✔
1048
                                },
8✔
1049
                        }
8✔
1050
                }
8✔
1051
                if filt.Description != "" {
23✔
1052
                        filters[StorageKeyImageDescription] = bson.M{
4✔
1053
                                "$regex": primitive.Regex{
4✔
1054
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
1055
                                        Options: "i",
4✔
1056
                                },
4✔
1057
                        }
4✔
1058
                }
4✔
1059
                if filt.DeviceType != "" {
21✔
1060
                        filters[StorageKeyImageDeviceTypes] = bson.M{
2✔
1061
                                "$regex": primitive.Regex{
2✔
1062
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
1063
                                        Options: "i",
2✔
1064
                                },
2✔
1065
                        }
2✔
1066
                }
2✔
1067

1068
        }
1069

1070
        projection := bson.M{
29✔
1071
                StorageKeyImageDependsIdx:  0,
29✔
1072
                StorageKeyImageProvidesIdx: 0,
29✔
1073
        }
29✔
1074
        findOptions := &mopts.FindOptions{}
29✔
1075
        findOptions.SetProjection(projection)
29✔
1076
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
31✔
1077
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
2✔
1078
                findOptions.SetLimit(int64(filt.PerPage))
2✔
1079
        }
2✔
1080

1081
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
29✔
1082
        if sortField == "" || sortField == "name" {
54✔
1083
                sortField = StorageKeyImageName
25✔
1084
        }
25✔
1085
        if sortOrder == 0 {
52✔
1086
                sortOrder = 1
23✔
1087
        }
23✔
1088
        findOptions.SetSort(bson.D{
29✔
1089
                {Key: sortField, Value: sortOrder},
29✔
1090
                {Key: "_id", Value: sortOrder},
29✔
1091
        })
29✔
1092

29✔
1093
        cursor, err := collImg.Find(ctx, filters, findOptions)
29✔
1094
        if err != nil {
29✔
UNCOV
1095
                return nil, 0, err
×
UNCOV
1096
        }
×
1097

1098
        // NOTE: cursor.All closes the cursor before returning
1099
        var images []*model.Image
29✔
1100
        if err := cursor.All(ctx, &images); err != nil {
29✔
UNCOV
1101
                if err == mongo.ErrNoDocuments {
×
UNCOV
1102
                        return nil, 0, nil
×
1103
                }
×
1104
                return nil, 0, err
×
1105
        }
1106

1107
        count, err := collImg.CountDocuments(ctx, filters)
29✔
1108
        if err != nil {
29✔
UNCOV
1109
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1110
        }
×
1111

1112
        return images, int(count), nil
29✔
1113
}
1114

1115
// device deployment log
1116
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1117
        log model.DeploymentLog) error {
17✔
1118

17✔
1119
        if err := log.Validate(); err != nil {
23✔
1120
                return err
6✔
1121
        }
6✔
1122

1123
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1124
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1125

11✔
1126
        query := bson.D{
11✔
1127
                {Key: StorageKeyDeviceDeploymentDeviceId,
11✔
1128
                        Value: log.DeviceID},
11✔
1129
                {Key: StorageKeyDeviceDeploymentDeploymentID,
11✔
1130
                        Value: log.DeploymentID},
11✔
1131
        }
11✔
1132

11✔
1133
        // update log messages
11✔
1134
        // if the deployment log is already present than messages will be overwritten
11✔
1135
        update := bson.D{
11✔
1136
                {Key: "$set", Value: bson.M{
11✔
1137
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
11✔
1138
                }},
11✔
1139
        }
11✔
1140
        updateOptions := mopts.Update()
11✔
1141
        updateOptions.SetUpsert(true)
11✔
1142
        if _, err := collLogs.UpdateOne(
11✔
1143
                ctx, query, update, updateOptions); err != nil {
11✔
UNCOV
1144
                return err
×
UNCOV
1145
        }
×
1146

1147
        return nil
11✔
1148
}
1149

1150
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1151
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
11✔
1152

11✔
1153
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1154
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1155

11✔
1156
        query := bson.M{
11✔
1157
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
11✔
1158
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1159
        }
11✔
1160

11✔
1161
        var depl model.DeploymentLog
11✔
1162
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
15✔
1163
                if err == mongo.ErrNoDocuments {
8✔
1164
                        return nil, nil
4✔
1165
                }
4✔
UNCOV
1166
                return nil, err
×
1167
        }
1168

1169
        return &depl, nil
7✔
1170
}
1171

1172
// device deployments
1173

1174
// Insert persists device deployment object
1175
func (db *DataStoreMongo) InsertDeviceDeployment(
1176
        ctx context.Context,
1177
        deviceDeployment *model.DeviceDeployment,
1178
        incrementDeviceCount bool,
1179
) error {
53✔
1180
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
53✔
1181
        c := database.Collection(CollectionDevices)
53✔
1182

53✔
1183
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
53✔
UNCOV
1184
                return err
×
UNCOV
1185
        }
×
1186

1187
        if incrementDeviceCount {
106✔
1188
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
53✔
1189
                if err != nil {
53✔
UNCOV
1190
                        return err
×
UNCOV
1191
                }
×
1192
        }
1193

1194
        return nil
53✔
1195
}
1196

1197
// InsertMany stores multiple device deployment objects.
1198
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1199
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1200
        deployments ...*model.DeviceDeployment) error {
78✔
1201

78✔
1202
        if len(deployments) == 0 {
102✔
1203
                return nil
24✔
1204
        }
24✔
1205

1206
        deviceCountIncrements := make(map[string]int)
54✔
1207

54✔
1208
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
54✔
1209
        var list []interface{}
54✔
1210
        for _, deployment := range deployments {
186✔
1211

132✔
1212
                if deployment == nil {
134✔
1213
                        return ErrStorageInvalidDeviceDeployment
2✔
1214
                }
2✔
1215

1216
                if err := deployment.Validate(); err != nil {
134✔
1217
                        return errors.Wrap(err, "Validating device deployment")
4✔
1218
                }
4✔
1219

1220
                list = append(list, deployment)
126✔
1221
                deviceCountIncrements[deployment.DeploymentId]++
126✔
1222
        }
1223

1224
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
48✔
1225
        collDevs := database.Collection(CollectionDevices)
48✔
1226

48✔
1227
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
48✔
UNCOV
1228
                return err
×
UNCOV
1229
        }
×
1230

1231
        for deploymentID := range deviceCountIncrements {
104✔
1232
                err := db.IncrementDeploymentDeviceCount(
56✔
1233
                        ctx,
56✔
1234
                        deploymentID,
56✔
1235
                        deviceCountIncrements[deploymentID],
56✔
1236
                )
56✔
1237
                if err != nil {
56✔
UNCOV
1238
                        return err
×
UNCOV
1239
                }
×
1240
        }
1241

1242
        return nil
48✔
1243
}
1244

1245
// ExistAssignedImageWithIDAndStatuses checks if image is used by deployment with specified status.
1246
func (db *DataStoreMongo) ExistAssignedImageWithIDAndStatuses(ctx context.Context,
1247
        imageID string, statuses ...model.DeviceDeploymentStatus) (bool, error) {
1✔
1248

1✔
1249
        // Verify ID formatting
1✔
1250
        if len(imageID) == 0 {
1✔
UNCOV
1251
                return false, ErrStorageInvalidID
×
UNCOV
1252
        }
×
1253

1254
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1255

1✔
1256
        if len(statuses) > 0 {
2✔
1257
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1258
                        "$in": statuses,
1✔
1259
                }
1✔
1260
        }
1✔
1261

1262
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1263
        collDevs := database.Collection(CollectionDevices)
1✔
1264

1✔
1265
        // if found at least one then image in active deployment
1✔
1266
        var tmp interface{}
1✔
1267
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1268
                if err == mongo.ErrNoDocuments {
2✔
1269
                        return false, nil
1✔
1270
                }
1✔
UNCOV
1271
                return false, err
×
1272
        }
1273

UNCOV
1274
        return true, nil
×
1275
}
1276

1277
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1278
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1279
        ctx context.Context,
1280
        deviceID string,
1281
) (*model.DeviceDeployment, error) {
11✔
1282

11✔
1283
        // Verify ID formatting
11✔
1284
        if len(deviceID) == 0 {
13✔
1285
                return nil, ErrStorageInvalidID
2✔
1286
        }
2✔
1287

1288
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1289
        collDevs := database.Collection(CollectionDevices)
9✔
1290

9✔
1291
        // Device should know only about deployments that are not finished
9✔
1292
        query := bson.D{
9✔
1293
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
9✔
1294
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1295
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1296
                        {Key: "$exists", Value: false},
9✔
1297
                }},
9✔
1298
        }
9✔
1299

9✔
1300
        // Find the oldest one by sorting the creation timestamp
9✔
1301
        // in ascending order.
9✔
1302
        findOptions := mopts.FindOne()
9✔
1303
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
9✔
1304

9✔
1305
        // Select only the oldest one that have not been finished yet.
9✔
1306
        deployment := new(model.DeviceDeployment)
9✔
1307
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1308
                Decode(deployment); err != nil {
14✔
1309
                if err == mongo.ErrNoDocuments {
8✔
1310
                        return nil, nil
3✔
1311
                }
3✔
1312
                return nil, err
2✔
1313
        }
1314

1315
        return deployment, nil
5✔
1316
}
1317

1318
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1319
// matching device id that has not finished yet.
1320
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1321
        ctx context.Context,
1322
        deviceID string,
1323
) (*model.DeviceDeployment, error) {
11✔
1324

11✔
1325
        // Verify ID formatting
11✔
1326
        if len(deviceID) == 0 {
13✔
1327
                return nil, ErrStorageInvalidID
2✔
1328
        }
2✔
1329

1330
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1331
        collDevs := database.Collection(CollectionDevices)
9✔
1332

9✔
1333
        query := bson.D{
9✔
1334
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
9✔
1335
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1336
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1337
                        {Key: "$exists", Value: false},
9✔
1338
                }},
9✔
1339
        }
9✔
1340

9✔
1341
        // Find the latest one by sorting by the creation timestamp
9✔
1342
        // in ascending order.
9✔
1343
        findOptions := mopts.FindOne()
9✔
1344
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
9✔
1345

9✔
1346
        // Select only the latest one that have not been finished yet.
9✔
1347
        var deployment *model.DeviceDeployment
9✔
1348
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1349
                Decode(&deployment); err != nil {
14✔
1350
                if err == mongo.ErrNoDocuments {
8✔
1351
                        return nil, nil
3✔
1352
                }
3✔
1353
                return nil, err
2✔
1354
        }
1355

1356
        return deployment, nil
5✔
1357
}
1358

1359
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1360
        ctx context.Context,
1361
        deviceID string,
1362
        deploymentID string,
1363
        ddState model.DeviceDeploymentState,
1364
) (model.DeviceDeploymentStatus, error) {
19✔
1365

19✔
1366
        // Verify ID formatting
19✔
1367
        if len(deviceID) == 0 ||
19✔
1368
                len(deploymentID) == 0 {
23✔
1369
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
4✔
1370
        }
4✔
1371

1372
        if err := ddState.Validate(); err != nil {
17✔
1373
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
2✔
1374
        }
2✔
1375

1376
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1377
        collDevs := database.Collection(CollectionDevices)
13✔
1378

13✔
1379
        // Device should know only about deployments that are not finished
13✔
1380
        query := bson.D{
13✔
1381
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1382
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1383
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1384
                        {Key: "$exists", Value: false},
13✔
1385
                }},
13✔
1386
        }
13✔
1387

13✔
1388
        // update status field
13✔
1389
        set := bson.M{
13✔
1390
                StorageKeyDeviceDeploymentStatus: ddState.Status,
13✔
1391
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
13✔
1392
        }
13✔
1393
        // and finish time if provided
13✔
1394
        if ddState.FinishTime != nil {
16✔
1395
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
3✔
1396
        }
3✔
1397

1398
        if len(ddState.SubState) > 0 {
15✔
1399
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1400
        }
2✔
1401

1402
        update := bson.D{
13✔
1403
                {Key: "$set", Value: set},
13✔
1404
        }
13✔
1405

13✔
1406
        var old model.DeviceDeployment
13✔
1407

13✔
1408
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
13✔
1409
                Decode(&old); err != nil {
17✔
1410
                if err == mongo.ErrNoDocuments {
8✔
1411
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
4✔
1412
                }
4✔
UNCOV
1413
                return model.DeviceDeploymentStatusNull, err
×
1414

1415
        }
1416

1417
        return old.Status, nil
9✔
1418
}
1419

1420
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1421
        deviceID string, deploymentID string, log bool) error {
13✔
1422

13✔
1423
        // Verify ID formatting
13✔
1424
        if len(deviceID) == 0 ||
13✔
1425
                len(deploymentID) == 0 {
17✔
1426
                return ErrStorageInvalidID
4✔
1427
        }
4✔
1428

1429
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1430
        collDevs := database.Collection(CollectionDevices)
9✔
1431

9✔
1432
        selector := bson.D{
9✔
1433
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1434
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1435
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1436
                        {Key: "$exists", Value: false},
9✔
1437
                }},
9✔
1438
        }
9✔
1439

9✔
1440
        update := bson.D{
9✔
1441
                {Key: "$set", Value: bson.M{
9✔
1442
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
9✔
1443
        }
9✔
1444

9✔
1445
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
9✔
UNCOV
1446
                return err
×
1447
        } else if res.MatchedCount == 0 {
13✔
1448
                return ErrStorageNotFound
4✔
1449
        }
4✔
1450

1451
        return nil
5✔
1452
}
1453

1454
// SaveDeviceDeploymentRequest saves device deployment request
1455
// with the device deployment object
1456
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1457
        ctx context.Context,
1458
        ID string,
1459
        request *model.DeploymentNextRequest,
1460
) error {
7✔
1461

7✔
1462
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1463
        collDevs := database.Collection(CollectionDevices)
7✔
1464

7✔
1465
        res, err := collDevs.UpdateOne(
7✔
1466
                ctx,
7✔
1467
                bson.D{{Key: StorageKeyId, Value: ID}},
7✔
1468
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
7✔
1469
        )
7✔
1470
        if err != nil {
7✔
UNCOV
1471
                return err
×
1472
        } else if res.MatchedCount == 0 {
9✔
1473
                return ErrStorageNotFound
2✔
1474
        }
2✔
1475
        return nil
5✔
1476
}
1477

1478
// AssignArtifact assigns artifact to the device deployment
1479
func (db *DataStoreMongo) AssignArtifact(
1480
        ctx context.Context,
1481
        deviceID string,
1482
        deploymentID string,
1483
        artifact *model.Image,
1484
) error {
1✔
1485

1✔
1486
        // Verify ID formatting
1✔
1487
        if len(deviceID) == 0 ||
1✔
1488
                len(deploymentID) == 0 {
1✔
UNCOV
1489
                return ErrStorageInvalidID
×
UNCOV
1490
        }
×
1491

1492
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1493
        collDevs := database.Collection(CollectionDevices)
1✔
1494

1✔
1495
        selector := bson.D{
1✔
1496
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1497
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1498
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1499
                        {Key: "$exists", Value: false},
1✔
1500
                }},
1✔
1501
        }
1✔
1502

1✔
1503
        update := bson.D{
1✔
1504
                {Key: "$set", Value: bson.M{
1✔
1505
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1506
                }},
1✔
1507
        }
1✔
1508

1✔
1509
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
UNCOV
1510
                return err
×
1511
        } else if res.MatchedCount == 0 {
1✔
1512
                return ErrStorageNotFound
×
UNCOV
1513
        }
×
1514

1515
        return nil
1✔
1516
}
1517

1518
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1519
        id string) (model.Stats, error) {
11✔
1520

11✔
1521
        if len(id) == 0 {
11✔
UNCOV
1522
                return nil, ErrStorageInvalidID
×
UNCOV
1523
        }
×
1524

1525
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1526
        collDevs := database.Collection(CollectionDevices)
11✔
1527

11✔
1528
        match := bson.D{
11✔
1529
                {Key: "$match", Value: bson.M{
11✔
1530
                        StorageKeyDeviceDeploymentDeploymentID: id,
11✔
1531
                        StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1532
                                {Key: "$exists", Value: false},
11✔
1533
                        },
11✔
1534
                }},
11✔
1535
        }
11✔
1536
        group := bson.D{
11✔
1537
                {Key: "$group", Value: bson.D{
11✔
1538
                        {Key: "_id",
11✔
1539
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
11✔
1540
                        {Key: "count",
11✔
1541
                                Value: bson.M{"$sum": 1}}},
11✔
1542
                },
11✔
1543
        }
11✔
1544
        pipeline := []bson.D{
11✔
1545
                match,
11✔
1546
                group,
11✔
1547
        }
11✔
1548
        var results []struct {
11✔
1549
                Status model.DeviceDeploymentStatus `bson:"_id"`
11✔
1550
                Count  int
11✔
1551
        }
11✔
1552
        cursor, err := collDevs.Aggregate(ctx, pipeline)
11✔
1553
        if err != nil {
11✔
UNCOV
1554
                return nil, err
×
UNCOV
1555
        }
×
1556
        if err := cursor.All(ctx, &results); err != nil {
11✔
1557
                if err == mongo.ErrNoDocuments {
×
UNCOV
1558
                        return nil, nil
×
1559
                }
×
1560
                return nil, err
×
1561
        }
1562

1563
        raw := model.NewDeviceDeploymentStats()
11✔
1564
        for _, res := range results {
32✔
1565
                raw.Set(res.Status, res.Count)
21✔
1566
        }
21✔
1567
        return raw, nil
11✔
1568
}
1569

1570
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1571
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1572
        deploymentID string) ([]model.DeviceDeployment, error) {
11✔
1573

11✔
1574
        statuses := []model.DeviceDeployment{}
11✔
1575
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1576
        collDevs := database.Collection(CollectionDevices)
11✔
1577

11✔
1578
        query := bson.M{
11✔
1579
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1580
                StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1581
                        {Key: "$exists", Value: false},
11✔
1582
                },
11✔
1583
        }
11✔
1584

11✔
1585
        cursor, err := collDevs.Find(ctx, query)
11✔
1586
        if err != nil {
11✔
UNCOV
1587
                return nil, err
×
UNCOV
1588
        }
×
1589

1590
        if err = cursor.All(ctx, &statuses); err != nil {
11✔
UNCOV
1591
                if err == mongo.ErrNoDocuments {
×
UNCOV
1592
                        return nil, nil
×
1593
                }
×
1594
                return nil, err
×
1595
        }
1596

1597
        return statuses, nil
11✔
1598
}
1599

1600
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1601
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
29✔
1602

29✔
1603
        statuses := []model.DeviceDeployment{}
29✔
1604
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1605
        collDevs := database.Collection(CollectionDevices)
29✔
1606

29✔
1607
        query := bson.D{
29✔
1608
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
29✔
1609
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
29✔
1610
                        {Key: "$exists", Value: false},
29✔
1611
                }},
29✔
1612
        }
29✔
1613
        if q.Status != nil {
37✔
1614
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
10✔
1615
                        query = append(query, bson.E{
2✔
1616
                                Key: "status", Value: bson.D{{
2✔
1617
                                        Key:   "$gte",
2✔
1618
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1619
                                }, {
2✔
1620
                                        Key:   "$lte",
2✔
1621
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1622
                                }},
2✔
1623
                        })
2✔
1624
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
8✔
UNCOV
1625
                        query = append(query, bson.E{
×
UNCOV
1626
                                Key: "status", Value: bson.D{{
×
1627
                                        Key:   "$gte",
×
1628
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1629
                                }, {
×
1630
                                        Key:   "$lte",
×
1631
                                        Value: model.DeviceDeploymentStatusPending,
×
1632
                                }},
×
1633
                        })
×
1634
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1635
                        query = append(query, bson.E{
2✔
1636
                                Key: "status", Value: bson.D{{
2✔
1637
                                        Key: "$in",
2✔
1638
                                        Value: []model.DeviceDeploymentStatus{
2✔
1639
                                                model.DeviceDeploymentStatusFailure,
2✔
1640
                                                model.DeviceDeploymentStatusAborted,
2✔
1641
                                                model.DeviceDeploymentStatusSuccess,
2✔
1642
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1643
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1644
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1645
                                        },
2✔
1646
                                }},
2✔
1647
                        })
2✔
1648
                } else {
6✔
1649
                        var status model.DeviceDeploymentStatus
4✔
1650
                        err := status.UnmarshalText([]byte(*q.Status))
4✔
1651
                        if err != nil {
6✔
1652
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1653
                        }
2✔
1654
                        query = append(query, bson.E{
2✔
1655
                                Key: "status", Value: status,
2✔
1656
                        })
2✔
1657
                }
1658
        }
1659

1660
        options := mopts.Find()
27✔
1661
        sortFieldQuery := bson.D{
27✔
1662
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
27✔
1663
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
27✔
1664
        }
27✔
1665
        options.SetSort(sortFieldQuery)
27✔
1666
        if q.Skip > 0 {
32✔
1667
                options.SetSkip(int64(q.Skip))
5✔
1668
        }
5✔
1669
        if q.Limit > 0 {
36✔
1670
                options.SetLimit(int64(q.Limit))
9✔
1671
        } else {
27✔
1672
                options.SetLimit(DefaultDocumentLimit)
18✔
1673
        }
18✔
1674

1675
        cursor, err := collDevs.Find(ctx, query, options)
27✔
1676
        if err != nil {
29✔
1677
                return nil, -1, err
2✔
1678
        }
2✔
1679

1680
        if err = cursor.All(ctx, &statuses); err != nil {
25✔
UNCOV
1681
                if err == mongo.ErrNoDocuments {
×
UNCOV
1682
                        return nil, -1, nil
×
1683
                }
×
1684
                return nil, -1, err
×
1685
        }
1686

1687
        count, err := collDevs.CountDocuments(ctx, query)
25✔
1688
        if err != nil {
25✔
UNCOV
1689
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1690
        }
×
1691

1692
        return statuses, int(count), nil
25✔
1693
}
1694

1695
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1696
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
20✔
1697

20✔
1698
        statuses := []model.DeviceDeployment{}
20✔
1699
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
20✔
1700
        collDevs := database.Collection(CollectionDevices)
20✔
1701

20✔
1702
        query := bson.D{}
20✔
1703
        if q.DeviceID != "" {
38✔
1704
                query = append(query, bson.E{
18✔
1705
                        Key:   StorageKeyDeviceDeploymentDeviceId,
18✔
1706
                        Value: q.DeviceID,
18✔
1707
                })
18✔
1708
        } else if len(q.IDs) > 0 {
22✔
1709
                query = append(query, bson.E{
2✔
1710
                        Key: StorageKeyId,
2✔
1711
                        Value: bson.D{{
2✔
1712
                                Key:   "$in",
2✔
1713
                                Value: q.IDs,
2✔
1714
                        }},
2✔
1715
                })
2✔
1716
        }
2✔
1717

1718
        if q.Status != nil {
36✔
1719
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
18✔
1720
                        query = append(query, bson.E{
2✔
1721
                                Key: "status", Value: bson.D{{
2✔
1722
                                        Key:   "$gte",
2✔
1723
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1724
                                }, {
2✔
1725
                                        Key:   "$lte",
2✔
1726
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1727
                                }},
2✔
1728
                        })
2✔
1729
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
18✔
1730
                        query = append(query, bson.E{
2✔
1731
                                Key: "status", Value: bson.D{{
2✔
1732
                                        Key:   "$gte",
2✔
1733
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1734
                                }, {
2✔
1735
                                        Key:   "$lte",
2✔
1736
                                        Value: model.DeviceDeploymentStatusPending,
2✔
1737
                                }},
2✔
1738
                        })
2✔
1739
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
16✔
1740
                        query = append(query, bson.E{
2✔
1741
                                Key: "status", Value: bson.D{{
2✔
1742
                                        Key: "$in",
2✔
1743
                                        Value: []model.DeviceDeploymentStatus{
2✔
1744
                                                model.DeviceDeploymentStatusFailure,
2✔
1745
                                                model.DeviceDeploymentStatusAborted,
2✔
1746
                                                model.DeviceDeploymentStatusSuccess,
2✔
1747
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1748
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1749
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1750
                                        },
2✔
1751
                                }},
2✔
1752
                        })
2✔
1753
                } else {
12✔
1754
                        var status model.DeviceDeploymentStatus
10✔
1755
                        err := status.UnmarshalText([]byte(*q.Status))
10✔
1756
                        if err != nil {
12✔
1757
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1758
                        }
2✔
1759
                        query = append(query, bson.E{
8✔
1760
                                Key: "status", Value: status,
8✔
1761
                        })
8✔
1762
                }
1763
        }
1764

1765
        options := mopts.Find()
18✔
1766
        sortFieldQuery := bson.D{
18✔
1767
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
18✔
1768
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
18✔
1769
        }
18✔
1770
        options.SetSort(sortFieldQuery)
18✔
1771
        if q.Skip > 0 {
20✔
1772
                options.SetSkip(int64(q.Skip))
2✔
1773
        }
2✔
1774
        if q.Limit > 0 {
36✔
1775
                options.SetLimit(int64(q.Limit))
18✔
1776
        } else {
18✔
UNCOV
1777
                options.SetLimit(DefaultDocumentLimit)
×
UNCOV
1778
        }
×
1779

1780
        cursor, err := collDevs.Find(ctx, query, options)
18✔
1781
        if err != nil {
18✔
UNCOV
1782
                return nil, -1, err
×
UNCOV
1783
        }
×
1784

1785
        if err = cursor.All(ctx, &statuses); err != nil {
18✔
UNCOV
1786
                if err == mongo.ErrNoDocuments {
×
UNCOV
1787
                        return nil, 0, nil
×
1788
                }
×
1789
                return nil, -1, err
×
1790
        }
1791

1792
        maxCount := maxCountDocuments
18✔
1793
        countOptions := &mopts.CountOptions{
18✔
1794
                Limit: &maxCount,
18✔
1795
        }
18✔
1796
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
18✔
1797
        if err != nil {
18✔
UNCOV
1798
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1799
        }
×
1800

1801
        return statuses, int(count), nil
18✔
1802
}
1803

1804
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1805
// `deviceID`, false otherwise. In case of errors returns false and an error
1806
// that occurred
1807
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1808
        deploymentID string, deviceID string) (bool, error) {
13✔
1809

13✔
1810
        var dep model.DeviceDeployment
13✔
1811
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1812
        collDevs := database.Collection(CollectionDevices)
13✔
1813

13✔
1814
        query := bson.D{
13✔
1815
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1816
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1817
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1818
                        {Key: "$exists", Value: false},
13✔
1819
                }},
13✔
1820
        }
13✔
1821

13✔
1822
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
19✔
1823
                if err == mongo.ErrNoDocuments {
12✔
1824
                        return false, nil
6✔
1825
                } else {
6✔
UNCOV
1826
                        return false, err
×
UNCOV
1827
                }
×
1828
        }
1829

1830
        return true, nil
7✔
1831
}
1832

1833
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1834
        deploymentId string) error {
5✔
1835

5✔
1836
        if len(deploymentId) == 0 {
7✔
1837
                return ErrStorageInvalidID
2✔
1838
        }
2✔
1839

1840
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1841
        collDevs := database.Collection(CollectionDevices)
3✔
1842
        selector := bson.M{
3✔
1843
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
3✔
1844
                StorageKeyDeviceDeploymentActive:       true,
3✔
1845
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1846
                        {Key: "$exists", Value: false},
3✔
1847
                },
3✔
1848
        }
3✔
1849

3✔
1850
        update := bson.M{
3✔
1851
                "$set": bson.M{
3✔
1852
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
3✔
1853
                        StorageKeyDeviceDeploymentActive: false,
3✔
1854
                },
3✔
1855
        }
3✔
1856

3✔
1857
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
3✔
UNCOV
1858
                return err
×
UNCOV
1859
        }
×
1860

1861
        return nil
3✔
1862
}
1863

1864
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
1865
        deviceID string) error {
4✔
1866
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1867
        collDevs := database.Collection(CollectionDevices)
4✔
1868
        selector := bson.M{
4✔
1869
                StorageKeyDeviceDeploymentDeviceId: deviceID,
4✔
1870
                StorageKeyDeviceDeploymentActive:   false,
4✔
1871
                StorageKeyDeviceDeploymentDeleted: bson.M{
4✔
1872
                        "$exists": false,
4✔
1873
                },
4✔
1874
        }
4✔
1875

4✔
1876
        now := time.Now()
4✔
1877
        update := bson.M{
4✔
1878
                "$set": bson.M{
4✔
1879
                        StorageKeyDeviceDeploymentDeleted: &now,
4✔
1880
                },
4✔
1881
        }
4✔
1882

4✔
1883
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
4✔
UNCOV
1884
                return err
×
UNCOV
1885
        }
×
1886

1887
        return nil
4✔
1888
}
1889

1890
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
1891
        deviceId string) error {
4✔
1892

4✔
1893
        if len(deviceId) == 0 {
6✔
1894
                return ErrStorageInvalidID
2✔
1895
        }
2✔
1896

1897
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1898
        collDevs := database.Collection(CollectionDevices)
2✔
1899
        selector := bson.M{
2✔
1900
                StorageKeyDeviceDeploymentDeviceId: deviceId,
2✔
1901
                StorageKeyDeviceDeploymentActive:   true,
2✔
1902
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
1903
                        {Key: "$exists", Value: false},
2✔
1904
                },
2✔
1905
        }
2✔
1906

2✔
1907
        update := bson.M{
2✔
1908
                "$set": bson.M{
2✔
1909
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
2✔
1910
                        StorageKeyDeviceDeploymentActive: false,
2✔
1911
                },
2✔
1912
        }
2✔
1913

2✔
1914
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
UNCOV
1915
                return err
×
UNCOV
1916
        }
×
1917

1918
        return nil
2✔
1919
}
1920

1921
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
1922
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
1923

1✔
1924
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1925
        collDevs := database.Collection(CollectionDevices)
1✔
1926

1✔
1927
        filter := bson.M{
1✔
1928
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1929
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
1930
        }
1✔
1931
        if !includeDeleted {
2✔
1932
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
1933
                        {Key: "$exists", Value: false},
1✔
1934
                }
1✔
1935
        }
1✔
1936

1937
        opts := &mopts.FindOneOptions{}
1✔
1938
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
1939

1✔
1940
        var dd model.DeviceDeployment
1✔
1941
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
1942
                if err == mongo.ErrNoDocuments {
2✔
1943
                        return nil, ErrStorageNotFound
1✔
1944
                }
1✔
UNCOV
1945
                return nil, err
×
1946
        }
1947

1948
        return &dd, nil
1✔
1949
}
1950

1951
func (db *DataStoreMongo) GetDeviceDeployments(
1952
        ctx context.Context,
1953
        skip int,
1954
        limit int,
1955
        deviceID string,
1956
        active *bool,
1957
        includeDeleted bool,
1958
) ([]model.DeviceDeployment, error) {
8✔
1959

8✔
1960
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
1961
        collDevs := database.Collection(CollectionDevices)
8✔
1962

8✔
1963
        filter := bson.M{}
8✔
1964
        if !includeDeleted {
12✔
1965
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
4✔
1966
                        {Key: "$exists", Value: false},
4✔
1967
                }
4✔
1968
        }
4✔
1969
        if deviceID != "" {
10✔
1970
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
2✔
1971
        }
2✔
1972
        if active != nil {
10✔
1973
                filter[StorageKeyDeviceDeploymentActive] = *active
2✔
1974
        }
2✔
1975

1976
        opts := &mopts.FindOptions{}
8✔
1977
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
8✔
1978
        if skip > 0 {
10✔
1979
                opts.SetSkip(int64(skip))
2✔
1980
        }
2✔
1981
        if limit > 0 {
10✔
1982
                opts.SetLimit(int64(limit))
2✔
1983
        }
2✔
1984

1985
        var deviceDeployments []model.DeviceDeployment
8✔
1986
        cursor, err := collDevs.Find(ctx, filter, opts)
8✔
1987
        if err != nil {
8✔
UNCOV
1988
                return nil, err
×
UNCOV
1989
        }
×
1990
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
8✔
UNCOV
1991
                return nil, err
×
UNCOV
1992
        }
×
1993

1994
        return deviceDeployments, nil
8✔
1995
}
1996

1997
// deployments
1998

1999
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2000
        indexes ...mongo.IndexModel) error {
691✔
2001
        ctx := context.Background()
691✔
2002
        dataBase := db.client.Database(dbName)
691✔
2003

691✔
2004
        coll := dataBase.Collection(collName)
691✔
2005
        idxView := coll.Indexes()
691✔
2006
        _, err := idxView.CreateMany(ctx, indexes)
691✔
2007
        return err
691✔
2008
}
691✔
2009

2010
// return true if required indexing was set up
2011
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
32✔
2012

32✔
2013
        var idx bson.M
32✔
2014
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
32✔
2015
        collDpl := database.Collection(CollectionDeployments)
32✔
2016
        idxView := collDpl.Indexes()
32✔
2017

32✔
2018
        cursor, err := idxView.List(ctx)
32✔
2019
        if err != nil {
32✔
UNCOV
2020
                // check failed, assume indexing is not there
×
UNCOV
2021
                return false
×
UNCOV
2022
        }
×
2023

2024
        has := map[string]bool{}
32✔
2025
        for cursor.Next(ctx) {
92✔
2026
                if err = cursor.Decode(&idx); err != nil {
60✔
2027
                        continue
×
2028
                }
2029
                if _, ok := idx["weights"]; ok {
90✔
2030
                        // text index
30✔
2031
                        for k := range idx["weights"].(bson.M) {
90✔
2032
                                has[k] = true
60✔
2033
                        }
60✔
2034
                } else {
30✔
2035
                        for i := range idx["key"].(bson.M) {
60✔
2036
                                has[i] = true
30✔
2037
                        }
30✔
2038

2039
                }
2040
        }
2041
        if err != nil {
32✔
UNCOV
2042
                return false
×
UNCOV
2043
        }
×
2044

2045
        for _, key := range StorageIndexes.Keys.(bson.D) {
94✔
2046
                _, ok := has[key.Key]
62✔
2047
                if !ok {
64✔
2048
                        return false
2✔
2049
                }
2✔
2050
        }
2051

2052
        return true
30✔
2053
}
2054

2055
// Insert persists object
2056
func (db *DataStoreMongo) InsertDeployment(
2057
        ctx context.Context,
2058
        deployment *model.Deployment,
2059
) error {
419✔
2060

419✔
2061
        if deployment == nil {
421✔
2062
                return ErrDeploymentStorageInvalidDeployment
2✔
2063
        }
2✔
2064

2065
        if err := deployment.Validate(); err != nil {
420✔
2066
                return err
3✔
2067
        }
3✔
2068

2069
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
415✔
2070
        collDpl := database.Collection(CollectionDeployments)
415✔
2071

415✔
2072
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
416✔
2073
                return err
1✔
2074
        }
1✔
2075
        return nil
415✔
2076
}
2077

2078
// Delete removed entry by ID
2079
// Noop on ID not found
2080
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
8✔
2081

8✔
2082
        if len(id) == 0 {
10✔
2083
                return ErrStorageInvalidID
2✔
2084
        }
2✔
2085

2086
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2087
        collDpl := database.Collection(CollectionDeployments)
6✔
2088

6✔
2089
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
6✔
UNCOV
2090
                return err
×
UNCOV
2091
        }
×
2092

2093
        return nil
6✔
2094
}
2095

2096
func (db *DataStoreMongo) FindDeploymentByID(
2097
        ctx context.Context,
2098
        id string,
2099
) (*model.Deployment, error) {
19✔
2100

19✔
2101
        if len(id) == 0 {
21✔
2102
                return nil, ErrStorageInvalidID
2✔
2103
        }
2✔
2104

2105
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
17✔
2106
        collDpl := database.Collection(CollectionDeployments)
17✔
2107

17✔
2108
        deployment := new(model.Deployment)
17✔
2109
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
17✔
2110
                Decode(deployment); err != nil {
23✔
2111
                if err == mongo.ErrNoDocuments {
12✔
2112
                        return nil, nil
6✔
2113
                }
6✔
UNCOV
2114
                return nil, err
×
2115
        }
2116

2117
        return deployment, nil
11✔
2118
}
2119

2120
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2121
        ctx context.Context,
2122
        ids ...string,
2123
) (deploymentStats []*model.DeploymentStats, err error) {
4✔
2124

4✔
2125
        if len(ids) == 0 {
4✔
UNCOV
2126
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
UNCOV
2127
        }
×
2128

2129
        for _, id := range ids {
12✔
2130
                if len(id) == 0 {
8✔
UNCOV
2131
                        return nil, ErrStorageInvalidID
×
2132
                }
×
2133
        }
2134

2135
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2136
        collDpl := database.Collection(CollectionDeployments)
4✔
2137

4✔
2138
        query := bson.M{
4✔
2139
                "_id": bson.M{
4✔
2140
                        "$in": ids,
4✔
2141
                },
4✔
2142
        }
4✔
2143
        statsProjection := &mopts.FindOptions{
4✔
2144
                Projection: bson.M{"stats": 1},
4✔
2145
        }
4✔
2146

4✔
2147
        results, err := collDpl.Find(
4✔
2148
                ctx,
4✔
2149
                query,
4✔
2150
                statsProjection,
4✔
2151
        )
4✔
2152
        if err != nil {
4✔
UNCOV
2153
                return nil, err
×
UNCOV
2154
        }
×
2155

2156
        for results.Next(context.Background()) {
12✔
2157
                depl := new(model.DeploymentStats)
8✔
2158
                if err = results.Decode(&depl); err != nil {
8✔
2159
                        if err == mongo.ErrNoDocuments {
×
2160
                                return nil, nil
×
UNCOV
2161
                        }
×
UNCOV
2162
                        return nil, err
×
2163
                }
2164
                deploymentStats = append(deploymentStats, depl)
8✔
2165
        }
2166

2167
        return deploymentStats, nil
4✔
2168
}
2169

2170
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2171
        id string) (*model.Deployment, error) {
15✔
2172

15✔
2173
        if len(id) == 0 {
17✔
2174
                return nil, ErrStorageInvalidID
2✔
2175
        }
2✔
2176

2177
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
2178
        collDpl := database.Collection(CollectionDeployments)
13✔
2179

13✔
2180
        var deployment *model.Deployment
13✔
2181
        filter := bson.D{
13✔
2182
                {Key: "_id", Value: id},
13✔
2183
                {Key: StorageKeyDeploymentFinished, Value: nil},
13✔
2184
        }
13✔
2185
        if err := collDpl.FindOne(ctx, filter).
13✔
2186
                Decode(&deployment); err != nil {
22✔
2187
                if err == mongo.ErrNoDocuments {
18✔
2188
                        return nil, nil
9✔
2189
                }
9✔
UNCOV
2190
                return nil, err
×
2191
        }
2192

2193
        return deployment, nil
5✔
2194
}
2195

2196
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2197
        ctx context.Context,
2198
        deploymentID string,
2199
        increment int,
2200
) error {
109✔
2201
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
109✔
2202
        collection := database.Collection(CollectionDeployments)
109✔
2203

109✔
2204
        filter := bson.M{
109✔
2205
                "_id": deploymentID,
109✔
2206
                StorageKeyDeploymentDeviceCount: bson.M{
109✔
2207
                        "$ne": nil,
109✔
2208
                },
109✔
2209
        }
109✔
2210

109✔
2211
        update := bson.M{
109✔
2212
                "$inc": bson.M{
109✔
2213
                        StorageKeyDeploymentDeviceCount: increment,
109✔
2214
                },
109✔
2215
        }
109✔
2216

109✔
2217
        _, err := collection.UpdateOne(ctx, filter, update)
109✔
2218
        return err
109✔
2219
}
109✔
2220

2221
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2222
        ctx context.Context,
2223
        deploymentID string,
2224
        count int,
2225
) error {
6✔
2226
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2227
        collection := database.Collection(CollectionDeployments)
6✔
2228

6✔
2229
        filter := bson.M{
6✔
2230
                "_id": deploymentID,
6✔
2231
                StorageKeyDeploymentDeviceCount: bson.M{
6✔
2232
                        "$eq": nil,
6✔
2233
                },
6✔
2234
        }
6✔
2235

6✔
2236
        update := bson.M{
6✔
2237
                "$set": bson.M{
6✔
2238
                        StorageKeyDeploymentDeviceCount: count,
6✔
2239
                },
6✔
2240
        }
6✔
2241

6✔
2242
        _, err := collection.UpdateOne(ctx, filter, update)
6✔
2243
        return err
6✔
2244
}
6✔
2245

2246
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2247
        id string) (int, error) {
6✔
2248

6✔
2249
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2250
        collDevs := database.Collection(CollectionDevices)
6✔
2251

6✔
2252
        filter := bson.M{
6✔
2253
                StorageKeyDeviceDeploymentDeploymentID: id,
6✔
2254
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
2255
                        {Key: "$exists", Value: false},
6✔
2256
                },
6✔
2257
        }
6✔
2258

6✔
2259
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
6✔
2260
        if err != nil {
6✔
UNCOV
2261
                return 0, err
×
UNCOV
2262
        }
×
2263

2264
        return int(deviceCount), nil
6✔
2265
}
2266

2267
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2268
        id string, stats model.Stats) error {
11✔
2269

11✔
2270
        if len(id) == 0 {
13✔
2271
                return ErrStorageInvalidID
2✔
2272
        }
2✔
2273

2274
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2275
        collDpl := database.Collection(CollectionDeployments)
9✔
2276

9✔
2277
        deployment, err := model.NewDeployment()
9✔
2278
        if err != nil {
9✔
UNCOV
2279
                return errors.Wrap(err, "failed to create deployment")
×
UNCOV
2280
        }
×
2281

2282
        deployment.Stats = stats
9✔
2283
        var update bson.M
9✔
2284
        if deployment.IsFinished() {
9✔
2285
                now := time.Now()
×
2286

×
UNCOV
2287
                update = bson.M{
×
UNCOV
2288
                        "$set": bson.M{
×
UNCOV
2289
                                StorageKeyDeploymentStats:    stats,
×
UNCOV
2290
                                StorageKeyDeploymentFinished: &now,
×
2291
                        },
×
2292
                }
×
2293
        } else {
9✔
2294
                update = bson.M{
9✔
2295
                        "$set": bson.M{
9✔
2296
                                StorageKeyDeploymentStats: stats,
9✔
2297
                        },
9✔
2298
                }
9✔
2299
        }
9✔
2300

2301
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
9✔
2302
        if res != nil && res.MatchedCount == 0 {
13✔
2303
                return ErrStorageInvalidID
4✔
2304
        }
4✔
2305
        return err
5✔
2306
}
2307

2308
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2309
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
15✔
2310

15✔
2311
        if len(id) == 0 {
17✔
2312
                return ErrStorageInvalidID
2✔
2313
        }
2✔
2314

2315
        if _, err := stateTo.MarshalText(); err != nil {
13✔
UNCOV
2316
                return ErrStorageInvalidInput
×
UNCOV
2317
        }
×
2318

2319
        // does not need any extra operations
2320
        // following query won't handle this case well and increase the state_to value
2321
        if stateFrom == stateTo {
15✔
2322
                return nil
2✔
2323
        }
2✔
2324

2325
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2326
        collDpl := database.Collection(CollectionDeployments)
11✔
2327

11✔
2328
        var update bson.M
11✔
2329

11✔
2330
        if stateFrom == model.DeviceDeploymentStatusNull {
14✔
2331
                // note dot notation on embedded document
3✔
2332
                update = bson.M{
3✔
2333
                        "$inc": bson.M{
3✔
2334
                                "stats." + stateTo.String(): 1,
3✔
2335
                        },
3✔
2336
                }
3✔
2337
        } else {
12✔
2338
                // note dot notation on embedded document
9✔
2339
                update = bson.M{
9✔
2340
                        "$inc": bson.M{
9✔
2341
                                "stats." + stateFrom.String(): -1,
9✔
2342
                                "stats." + stateTo.String():   1,
9✔
2343
                        },
9✔
2344
                }
9✔
2345
        }
9✔
2346

2347
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2348

11✔
2349
        if res != nil && res.MatchedCount == 0 {
13✔
2350
                return ErrStorageInvalidID
2✔
2351
        }
2✔
2352

2353
        return err
9✔
2354
}
2355

2356
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2357
        ctx context.Context,
2358
        deploymentID string,
2359
        increment int64,
2360
) error {
5✔
2361
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2362
        collection := database.Collection(CollectionDeployments)
5✔
2363

5✔
2364
        filter := bson.M{
5✔
2365
                "_id": deploymentID,
5✔
2366
        }
5✔
2367

5✔
2368
        update := bson.M{
5✔
2369
                "$inc": bson.M{
5✔
2370
                        StorageKeyDeploymentTotalSize: increment,
5✔
2371
                },
5✔
2372
        }
5✔
2373

5✔
2374
        _, err := collection.UpdateOne(ctx, filter, update)
5✔
2375
        return err
5✔
2376
}
5✔
2377

2378
func (db *DataStoreMongo) Find(ctx context.Context,
2379
        match model.Query) ([]*model.Deployment, int64, error) {
71✔
2380

71✔
2381
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
71✔
2382
        collDpl := database.Collection(CollectionDeployments)
71✔
2383

71✔
2384
        andq := []bson.M{}
71✔
2385

71✔
2386
        // filter by IDs
71✔
2387
        if len(match.IDs) > 0 {
71✔
UNCOV
2388
                tq := bson.M{
×
UNCOV
2389
                        "_id": bson.M{
×
UNCOV
2390
                                "$in": match.IDs,
×
UNCOV
2391
                        },
×
UNCOV
2392
                }
×
UNCOV
2393
                andq = append(andq, tq)
×
2394
        }
×
2395

2396
        // build deployment by name part of the query
2397
        if match.SearchText != "" {
103✔
2398
                // we must have indexing for text search
32✔
2399
                if !db.hasIndexing(ctx, db.client) {
34✔
2400
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
2✔
2401
                }
2✔
2402

2403
                tq := bson.M{
30✔
2404
                        "$text": bson.M{
30✔
2405
                                "$search": match.SearchText,
30✔
2406
                        },
30✔
2407
                }
30✔
2408

30✔
2409
                andq = append(andq, tq)
30✔
2410
        }
2411

2412
        // build deployment by status part of the query
2413
        if match.Status != model.StatusQueryAny {
89✔
2414
                var status model.DeploymentStatus
20✔
2415
                if match.Status == model.StatusQueryPending {
24✔
2416
                        status = model.DeploymentStatusPending
4✔
2417
                } else if match.Status == model.StatusQueryInProgress {
28✔
2418
                        status = model.DeploymentStatusInProgress
8✔
2419
                } else {
16✔
2420
                        status = model.DeploymentStatusFinished
8✔
2421
                }
8✔
2422
                stq := bson.M{StorageKeyDeploymentStatus: status}
20✔
2423
                andq = append(andq, stq)
20✔
2424
        }
2425

2426
        // build deployment by type part of the query
2427
        if match.Type != "" {
73✔
2428
                if match.Type == model.DeploymentTypeConfiguration {
8✔
2429
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
4✔
2430
                } else if match.Type == model.DeploymentTypeSoftware {
4✔
UNCOV
2431
                        andq = append(andq, bson.M{
×
UNCOV
2432
                                "$or": []bson.M{
×
UNCOV
2433
                                        {StorageKeyDeploymentType: match.Type},
×
UNCOV
2434
                                        {StorageKeyDeploymentType: ""},
×
UNCOV
2435
                                },
×
UNCOV
2436
                        })
×
2437
                }
×
2438
        }
2439

2440
        query := bson.M{}
69✔
2441
        if len(andq) != 0 {
115✔
2442
                // use search criteria if any
46✔
2443
                query = bson.M{
46✔
2444
                        "$and": andq,
46✔
2445
                }
46✔
2446
        }
46✔
2447

2448
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
69✔
UNCOV
2449
                query["created"] = bson.M{
×
UNCOV
2450
                        "$gte": match.CreatedAfter,
×
UNCOV
2451
                        "$lte": match.CreatedBefore,
×
UNCOV
2452
                }
×
2453
        } else if match.CreatedAfter != nil {
69✔
UNCOV
2454
                query["created"] = bson.M{
×
2455
                        "$gte": match.CreatedAfter,
×
2456
                }
×
2457
        } else if match.CreatedBefore != nil {
69✔
2458
                query["created"] = bson.M{
×
UNCOV
2459
                        "$lte": match.CreatedBefore,
×
2460
                }
×
2461
        }
×
2462

2463
        options := db.findOptions(match)
69✔
2464

69✔
2465
        var deployments []*model.Deployment
69✔
2466
        cursor, err := collDpl.Find(ctx, query, options)
69✔
2467
        if err != nil {
69✔
UNCOV
2468
                return nil, 0, err
×
UNCOV
2469
        }
×
2470
        if err := cursor.All(ctx, &deployments); err != nil {
69✔
UNCOV
2471
                return nil, 0, err
×
UNCOV
2472
        }
×
2473
        // Count documents if we didn't find all already.
2474
        count := int64(0)
69✔
2475
        if !match.DisableCount {
138✔
2476
                count = int64(len(deployments))
69✔
2477
                if count >= int64(match.Limit) {
137✔
2478
                        count, err = collDpl.CountDocuments(ctx, query)
68✔
2479
                        if err != nil {
68✔
UNCOV
2480
                                return nil, 0, err
×
UNCOV
2481
                        }
×
2482
                } else {
1✔
2483
                        // Don't forget to add the skipped documents
1✔
2484
                        count += int64(match.Skip)
1✔
2485
                }
1✔
2486
        }
2487

2488
        return deployments, count, nil
69✔
2489
}
2490

2491
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
69✔
2492
        options := &mopts.FindOptions{}
69✔
2493
        if match.Sort == model.SortDirectionAscending {
71✔
2494
                options.SetSort(bson.D{{Key: "created", Value: 1}})
2✔
2495
        } else {
69✔
2496
                options.SetSort(bson.D{{Key: "created", Value: -1}})
67✔
2497
        }
67✔
2498
        if match.Skip > 0 {
73✔
2499
                options.SetSkip(int64(match.Skip))
4✔
2500
        }
4✔
2501
        if match.Limit > 0 {
78✔
2502
                options.SetLimit(int64(match.Limit))
9✔
2503
        }
9✔
2504
        return options
69✔
2505
}
2506

2507
// FindNewerActiveDeployments finds active deployments which were created
2508
// after createdAfter
2509
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2510
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
9✔
2511

9✔
2512
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2513
        c := database.Collection(CollectionDeployments)
9✔
2514

9✔
2515
        queryFilters := make([]bson.M, 0)
9✔
2516
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
9✔
2517
        queryFilters = append(queryFilters,
9✔
2518
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
9✔
2519
        findQuery := bson.M{}
9✔
2520
        findQuery["$and"] = queryFilters
9✔
2521

9✔
2522
        findOptions := &mopts.FindOptions{}
9✔
2523
        findOptions.SetSkip(int64(skip))
9✔
2524
        findOptions.SetLimit(int64(limit))
9✔
2525

9✔
2526
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
9✔
2527
        cursor, err := c.Find(ctx, findQuery, findOptions)
9✔
2528
        if err != nil {
9✔
UNCOV
2529
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2530
        }
×
2531
        defer cursor.Close(ctx)
9✔
2532

9✔
2533
        var deployments []*model.Deployment
9✔
2534

9✔
2535
        if err = cursor.All(ctx, &deployments); err != nil {
9✔
2536
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2537
        }
×
2538

2539
        return deployments, nil
9✔
2540
}
2541

2542
// SetDeploymentStatus simply sets the status field
2543
// optionally sets 'finished time' if deployment is indeed finished
2544
func (db *DataStoreMongo) SetDeploymentStatus(
2545
        ctx context.Context,
2546
        id string,
2547
        status model.DeploymentStatus,
2548
        now time.Time,
2549
) error {
11✔
2550
        if len(id) == 0 {
11✔
UNCOV
2551
                return ErrStorageInvalidID
×
UNCOV
2552
        }
×
2553

2554
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2555
        collDpl := database.Collection(CollectionDeployments)
11✔
2556

11✔
2557
        var update bson.M
11✔
2558
        if status == model.DeploymentStatusFinished {
14✔
2559
                update = bson.M{
3✔
2560
                        "$set": bson.M{
3✔
2561
                                StorageKeyDeploymentActive:   false,
3✔
2562
                                StorageKeyDeploymentStatus:   status,
3✔
2563
                                StorageKeyDeploymentFinished: &now,
3✔
2564
                        },
3✔
2565
                }
3✔
2566
        } else {
12✔
2567
                update = bson.M{
9✔
2568
                        "$set": bson.M{
9✔
2569
                                StorageKeyDeploymentActive: true,
9✔
2570
                                StorageKeyDeploymentStatus: status,
9✔
2571
                        },
9✔
2572
                }
9✔
2573
        }
9✔
2574

2575
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2576

11✔
2577
        if res != nil && res.MatchedCount == 0 {
13✔
2578
                return ErrStorageInvalidID
2✔
2579
        }
2✔
2580

2581
        return err
9✔
2582
}
2583

2584
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2585
// given artifact
2586
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2587
        id string) (bool, error) {
7✔
2588

7✔
2589
        if len(id) == 0 {
7✔
UNCOV
2590
                return false, ErrStorageInvalidID
×
UNCOV
2591
        }
×
2592

2593
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2594
        collDpl := database.Collection(CollectionDeployments)
7✔
2595

7✔
2596
        var tmp interface{}
7✔
2597
        query := bson.D{
7✔
2598
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2599
                {Key: StorageKeyDeploymentArtifacts, Value: id},
7✔
2600
        }
7✔
2601
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
12✔
2602
                if err == mongo.ErrNoDocuments {
10✔
2603
                        return false, nil
5✔
2604
                }
5✔
UNCOV
2605
                return false, err
×
2606
        }
2607

2608
        return true, nil
3✔
2609
}
2610

2611
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2612
// given artifact
2613
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2614
        artifactName string) (bool, error) {
7✔
2615

7✔
2616
        if len(artifactName) == 0 {
7✔
UNCOV
2617
                return false, ErrImagesStorageInvalidArtifactName
×
UNCOV
2618
        }
×
2619

2620
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2621
        collDpl := database.Collection(CollectionDeployments)
7✔
2622

7✔
2623
        var tmp interface{}
7✔
2624
        query := bson.D{
7✔
2625
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2626
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
7✔
2627
        }
7✔
2628

7✔
2629
        projection := bson.M{
7✔
2630
                "_id": 1,
7✔
2631
        }
7✔
2632
        findOptions := mopts.FindOne()
7✔
2633
        findOptions.SetProjection(projection)
7✔
2634

7✔
2635
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
12✔
2636
                if err == mongo.ErrNoDocuments {
10✔
2637
                        return false, nil
5✔
2638
                }
5✔
UNCOV
2639
                return false, err
×
2640
        }
2641

2642
        return true, nil
2✔
2643
}
2644

2645
// ExistByArtifactId check if there is any deployment that uses give artifact
2646
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
UNCOV
2647
        id string) (bool, error) {
×
UNCOV
2648

×
UNCOV
2649
        if len(id) == 0 {
×
UNCOV
2650
                return false, ErrStorageInvalidID
×
UNCOV
2651
        }
×
2652

2653
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2654
        collDpl := database.Collection(CollectionDeployments)
×
2655

×
2656
        var tmp interface{}
×
2657
        query := bson.D{
×
UNCOV
2658
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2659
        }
×
2660
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2661
                if err == mongo.ErrNoDocuments {
×
2662
                        return false, nil
×
2663
                }
×
2664
                return false, err
×
2665
        }
2666

2667
        return true, nil
×
2668
}
2669

2670
// Per-tenant storage settings
2671
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
3✔
2672
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2673
        collection := database.Collection(CollectionStorageSettings)
3✔
2674

3✔
2675
        settings := new(model.StorageSettings)
3✔
2676
        // supposed that it's only one document in the collection
3✔
2677
        query := bson.M{
3✔
2678
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2679
        }
3✔
2680
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
4✔
2681
                if err == mongo.ErrNoDocuments {
2✔
2682
                        return nil, nil
1✔
2683
                }
1✔
UNCOV
2684
                return nil, err
×
2685
        }
2686

2687
        return settings, nil
3✔
2688
}
2689

2690
func (db *DataStoreMongo) SetStorageSettings(
2691
        ctx context.Context,
2692
        storageSettings *model.StorageSettings,
2693
) error {
3✔
2694
        var err error
3✔
2695
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2696
        collection := database.Collection(CollectionStorageSettings)
3✔
2697

3✔
2698
        filter := bson.M{
3✔
2699
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2700
        }
3✔
2701
        if storageSettings != nil {
6✔
2702
                replaceOptions := mopts.Replace()
3✔
2703
                replaceOptions.SetUpsert(true)
3✔
2704
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
3✔
2705
        } else {
4✔
2706
                _, err = collection.DeleteOne(ctx, filter)
1✔
2707
        }
1✔
2708

2709
        return err
3✔
2710
}
2711

2712
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2713
        ctx context.Context,
2714
        artifactName string,
2715
        artifactIDs []string,
2716
) error {
2✔
2717
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2718
        collDpl := database.Collection(CollectionDeployments)
2✔
2719

2✔
2720
        query := bson.D{
2✔
2721
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2722
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
2✔
2723
        }
2✔
2724
        update := bson.M{
2✔
2725
                "$set": bson.M{
2✔
2726
                        StorageKeyDeploymentArtifacts: artifactIDs,
2✔
2727
                },
2✔
2728
        }
2✔
2729

2✔
2730
        _, err := collDpl.UpdateMany(ctx, query, update)
2✔
2731
        return err
2✔
2732
}
2✔
2733

UNCOV
2734
func (db *DataStoreMongo) GetTenantDbs() ([]string, error) {
×
UNCOV
2735
        return migrate.GetTenantDbs(context.Background(), db.client, mstore.IsTenantDb(DbName))
×
UNCOV
2736
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc