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

mendersoftware / deployments / 772934272

pending completion
772934272

Pull #824

gitlab-ci

Krzysztof Jaskiewicz
feat: track and show deployment total size
Pull Request #824: show artifact download size feature

26 of 28 new or added lines in 3 files covered. (92.86%)

35 existing lines in 1 file now uncovered.

6443 of 8226 relevant lines covered (78.32%)

74.2 hits per line

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

85.07
/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/mongo/migrate"
32
        mstore "github.com/mendersoftware/go-lib-micro/store"
33

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

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

49
const DefaultDocumentLimit = 20
50
const maxCountDocuments = int64(10000)
51

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

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

75
        // Indexes (version: 1.2.3)
76
        IndexArtifactNameDependsName = "artifactNameDepends"
77
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
78

79
        // Indexes (version: 1.2.4)
80
        IndexDeploymentStatus = "deploymentStatus"
81

82
        // Indexes 1.2.6
83
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
84

85
        // Indexes 1.2.13
86
        IndexArtifactProvidesName = "artifact_provides"
87

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

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

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

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

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

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

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

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

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

358
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
359

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

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

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

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

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

390
        StorageKeyDeviceDeploymentLogMessages = "messages"
391

392
        StorageKeyDeviceDeploymentAssignedImage   = "image"
393
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
394
                "." + StorageKeyId
395

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

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

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

433
        ArtifactDependsDeviceType = "device_type"
434
)
435

436
type DataStoreMongo struct {
437
        client *mongo.Client
438
}
439

440
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
545✔
441
        return &DataStoreMongo{
545✔
442
                client: client,
545✔
443
        }
545✔
444
}
545✔
445

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

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

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

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

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

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

488
        return client, nil
1✔
489
}
490

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

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

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

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

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

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

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

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

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

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

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

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

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

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

631
        return limit, nil
4✔
632
}
633

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

9✔
636
        dbname := mstore.DbNameForTenant(tenantId, DbName)
9✔
637

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

641
//images
642

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

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

651
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
652
        collImg := database.Collection(CollectionImages)
×
653

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

662
        return true, nil
×
663
}
664

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

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

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

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

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

689
        return true, nil
2✔
690
}
691

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

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

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

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

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

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

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

728
        return &image, nil
6✔
729
}
730

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

1✔
735
        if len(deviceType) == 0 {
1✔
736
                return nil, ErrImagesStorageInvalidDeviceType
×
737
        }
×
738

739
        if len(ids) == 0 {
1✔
740
                return nil, ErrImagesStorageInvalidID
×
741
        }
×
742

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

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

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

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

765
        return &image, nil
1✔
766
}
767

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

1✔
772
        var images []*model.Image
1✔
773

1✔
774
        if len(name) == 0 {
1✔
775
                return nil, ErrImagesStorageInvalidName
×
776
        }
×
777

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

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

794
        return images, nil
1✔
795
}
796

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

125✔
800
        if image == nil {
125✔
801
                return ErrImagesStorageInvalidImage
×
802
        }
×
803

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

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

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

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

833
        return nil
111✔
834
}
835

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

1✔
840
        if len(id) == 0 {
1✔
841
                return nil, ErrImagesStorageInvalidID
×
842
        }
×
843

844
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
845
        collImg := database.Collection(CollectionImages)
1✔
846
        projection := bson.M{
1✔
847
                StorageKeyImageDependsIdx:  0,
1✔
848
                StorageKeyImageProvidesIdx: 0,
1✔
849
        }
1✔
850
        findOptions := mopts.FindOne()
1✔
851
        findOptions.SetProjection(projection)
1✔
852

1✔
853
        var image model.Image
1✔
854
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
855
                Decode(&image); err != nil {
2✔
856
                if err == mongo.ErrNoDocuments {
2✔
857
                        return nil, nil
1✔
858
                }
1✔
859
                return nil, err
×
860
        }
861

862
        return &image, nil
1✔
863
}
864

865
// IsArtifactUnique checks if there is no artifact with the same artifactName
866
// supporting one of the device types from deviceTypesCompatible list.
867
// Returns true, nil if artifact is unique;
868
// false, nil if artifact is not unique;
869
// false, error in case of error.
870
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
871
        artifactName string, deviceTypesCompatible []string) (bool, error) {
11✔
872

11✔
873
        if len(artifactName) == 0 {
13✔
874
                return false, ErrImagesStorageInvalidArtifactName
2✔
875
        }
2✔
876

877
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
878
        collImg := database.Collection(CollectionImages)
9✔
879

9✔
880
        query := bson.M{
9✔
881
                "$and": []bson.M{
9✔
882
                        {
9✔
883
                                StorageKeyImageName: artifactName,
9✔
884
                        },
9✔
885
                        {
9✔
886
                                StorageKeyImageDeviceTypes: bson.M{
9✔
887
                                        "$in": deviceTypesCompatible},
9✔
888
                        },
9✔
889
                },
9✔
890
        }
9✔
891

9✔
892
        // do part of the job manually
9✔
893
        // if candidate images have any extra 'depends' - guaranteed non-overlap
9✔
894
        // otherwise it's a match
9✔
895
        cur, err := collImg.Find(ctx, query)
9✔
896
        if err != nil {
9✔
897
                return false, err
×
898
        }
×
899

900
        var images []model.Image
9✔
901
        err = cur.All(ctx, &images)
9✔
902
        if err != nil {
9✔
903
                return false, err
×
904
        }
×
905

906
        for _, i := range images {
11✔
907
                // the artifact already has same name and overlapping dev type
2✔
908
                // if there are no more depends than dev type - it's not unique
2✔
909
                if len(i.ArtifactMeta.Depends) == 1 {
4✔
910
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
4✔
911
                                return false, nil
2✔
912
                        }
2✔
913
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
914
                        return false, nil
×
915
                }
×
916
        }
917

918
        return true, nil
7✔
919
}
920

921
// Delete image specified by ID
922
// Noop on if not found.
923
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
924

1✔
925
        if len(id) == 0 {
1✔
926
                return ErrImagesStorageInvalidID
×
927
        }
×
928

929
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
930
        collImg := database.Collection(CollectionImages)
1✔
931

1✔
932
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
933
                if res.DeletedCount == 0 {
×
934
                        return nil
×
935
                }
×
936
                return err
×
937
        }
938

939
        return nil
1✔
940
}
941

942
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
47✔
943
        if filt != nil && filt.Sort != "" {
59✔
944
                sortParts := strings.SplitN(filt.Sort, ":", 2)
12✔
945
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
24✔
946
                        sortField := sortParts[0]
12✔
947
                        sortOrder := 1
12✔
948
                        if sortParts[1] == model.SortDirectionDescending {
20✔
949
                                sortOrder = -1
8✔
950
                        }
8✔
951
                        return sortField, sortOrder
12✔
952
                }
953
        }
954
        return "", 0
35✔
955
}
956

957
// ListImages lists all images
958
func (db *DataStoreMongo) ListImages(
959
        ctx context.Context,
960
        filt *model.ReleaseOrImageFilter,
961
) ([]*model.Image, int, error) {
29✔
962

29✔
963
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
964
        collImg := database.Collection(CollectionImages)
29✔
965

29✔
966
        filters := bson.M{}
29✔
967
        if filt != nil {
48✔
968
                if filt.Name != "" {
27✔
969
                        filters[StorageKeyImageName] = bson.M{
8✔
970
                                "$regex": primitive.Regex{
8✔
971
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
8✔
972
                                        Options: "i",
8✔
973
                                },
8✔
974
                        }
8✔
975
                }
8✔
976
                if filt.Description != "" {
23✔
977
                        filters[StorageKeyImageDescription] = bson.M{
4✔
978
                                "$regex": primitive.Regex{
4✔
979
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
980
                                        Options: "i",
4✔
981
                                },
4✔
982
                        }
4✔
983
                }
4✔
984
                if filt.DeviceType != "" {
21✔
985
                        filters[StorageKeyImageDeviceTypes] = bson.M{
2✔
986
                                "$regex": primitive.Regex{
2✔
987
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
988
                                        Options: "i",
2✔
989
                                },
2✔
990
                        }
2✔
991
                }
2✔
992

993
        }
994

995
        projection := bson.M{
29✔
996
                StorageKeyImageDependsIdx:  0,
29✔
997
                StorageKeyImageProvidesIdx: 0,
29✔
998
        }
29✔
999
        findOptions := &mopts.FindOptions{}
29✔
1000
        findOptions.SetProjection(projection)
29✔
1001
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
31✔
1002
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
2✔
1003
                findOptions.SetLimit(int64(filt.PerPage))
2✔
1004
        }
2✔
1005

1006
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
29✔
1007
        if sortField == "" || sortField == "name" {
54✔
1008
                sortField = StorageKeyImageName
25✔
1009
        }
25✔
1010
        if sortOrder == 0 {
52✔
1011
                sortOrder = 1
23✔
1012
        }
23✔
1013
        findOptions.SetSort(bson.D{
29✔
1014
                {Key: sortField, Value: sortOrder},
29✔
1015
                {Key: "_id", Value: sortOrder},
29✔
1016
        })
29✔
1017

29✔
1018
        cursor, err := collImg.Find(ctx, filters, findOptions)
29✔
1019
        if err != nil {
29✔
1020
                return nil, 0, err
×
1021
        }
×
1022

1023
        // NOTE: cursor.All closes the cursor before returning
1024
        var images []*model.Image
29✔
1025
        if err := cursor.All(ctx, &images); err != nil {
29✔
1026
                if err == mongo.ErrNoDocuments {
×
1027
                        return nil, 0, nil
×
1028
                }
×
1029
                return nil, 0, err
×
1030
        }
1031

1032
        count, err := collImg.CountDocuments(ctx, filters)
29✔
1033
        if err != nil {
29✔
1034
                return nil, -1, ErrDevicesCountFailed
×
1035
        }
×
1036

1037
        return images, int(count), nil
29✔
1038
}
1039

1040
// device deployment log
1041
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1042
        log model.DeploymentLog) error {
17✔
1043

17✔
1044
        if err := log.Validate(); err != nil {
23✔
1045
                return err
6✔
1046
        }
6✔
1047

1048
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1049
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1050

11✔
1051
        query := bson.D{
11✔
1052
                {Key: StorageKeyDeviceDeploymentDeviceId,
11✔
1053
                        Value: log.DeviceID},
11✔
1054
                {Key: StorageKeyDeviceDeploymentDeploymentID,
11✔
1055
                        Value: log.DeploymentID},
11✔
1056
        }
11✔
1057

11✔
1058
        // update log messages
11✔
1059
        // if the deployment log is already present than messages will be overwritten
11✔
1060
        update := bson.D{
11✔
1061
                {Key: "$set", Value: bson.M{
11✔
1062
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
11✔
1063
                }},
11✔
1064
        }
11✔
1065
        updateOptions := mopts.Update()
11✔
1066
        updateOptions.SetUpsert(true)
11✔
1067
        if _, err := collLogs.UpdateOne(
11✔
1068
                ctx, query, update, updateOptions); err != nil {
11✔
1069
                return err
×
1070
        }
×
1071

1072
        return nil
11✔
1073
}
1074

1075
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1076
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
11✔
1077

11✔
1078
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1079
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1080

11✔
1081
        query := bson.M{
11✔
1082
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
11✔
1083
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1084
        }
11✔
1085

11✔
1086
        var depl model.DeploymentLog
11✔
1087
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
15✔
1088
                if err == mongo.ErrNoDocuments {
8✔
1089
                        return nil, nil
4✔
1090
                }
4✔
1091
                return nil, err
×
1092
        }
1093

1094
        return &depl, nil
7✔
1095
}
1096

1097
// device deployments
1098

1099
// Insert persists device deployment object
1100
func (db *DataStoreMongo) InsertDeviceDeployment(
1101
        ctx context.Context,
1102
        deviceDeployment *model.DeviceDeployment,
1103
        incrementDeviceCount bool,
1104
) error {
53✔
1105
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
53✔
1106
        c := database.Collection(CollectionDevices)
53✔
1107

53✔
1108
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
53✔
1109
                return err
×
1110
        }
×
1111

1112
        if incrementDeviceCount {
106✔
1113
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
53✔
1114
                if err != nil {
53✔
1115
                        return err
×
1116
                }
×
1117
        }
1118

1119
        return nil
53✔
1120
}
1121

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

78✔
1127
        if len(deployments) == 0 {
102✔
1128
                return nil
24✔
1129
        }
24✔
1130

1131
        deviceCountIncrements := make(map[string]int)
54✔
1132

54✔
1133
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
54✔
1134
        var list []interface{}
54✔
1135
        for _, deployment := range deployments {
186✔
1136

132✔
1137
                if deployment == nil {
134✔
1138
                        return ErrStorageInvalidDeviceDeployment
2✔
1139
                }
2✔
1140

1141
                if err := deployment.Validate(); err != nil {
134✔
1142
                        return errors.Wrap(err, "Validating device deployment")
4✔
1143
                }
4✔
1144

1145
                list = append(list, deployment)
126✔
1146
                deviceCountIncrements[deployment.DeploymentId]++
126✔
1147
        }
1148

1149
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
48✔
1150
        collDevs := database.Collection(CollectionDevices)
48✔
1151

48✔
1152
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
48✔
1153
                return err
×
1154
        }
×
1155

1156
        for deploymentID := range deviceCountIncrements {
104✔
1157
                err := db.IncrementDeploymentDeviceCount(
56✔
1158
                        ctx,
56✔
1159
                        deploymentID,
56✔
1160
                        deviceCountIncrements[deploymentID],
56✔
1161
                )
56✔
1162
                if err != nil {
56✔
1163
                        return err
×
1164
                }
×
1165
        }
1166

1167
        return nil
48✔
1168
}
1169

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

1✔
1174
        // Verify ID formatting
1✔
1175
        if len(imageID) == 0 {
1✔
1176
                return false, ErrStorageInvalidID
×
1177
        }
×
1178

1179
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1180

1✔
1181
        if len(statuses) > 0 {
2✔
1182
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1183
                        "$in": statuses,
1✔
1184
                }
1✔
1185
        }
1✔
1186

1187
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1188
        collDevs := database.Collection(CollectionDevices)
1✔
1189

1✔
1190
        // if found at least one then image in active deployment
1✔
1191
        var tmp interface{}
1✔
1192
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1193
                if err == mongo.ErrNoDocuments {
2✔
1194
                        return false, nil
1✔
1195
                }
1✔
1196
                return false, err
×
1197
        }
1198

1199
        return true, nil
×
1200
}
1201

1202
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1203
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1204
        ctx context.Context,
1205
        deviceID string,
1206
) (*model.DeviceDeployment, error) {
11✔
1207

11✔
1208
        // Verify ID formatting
11✔
1209
        if len(deviceID) == 0 {
13✔
1210
                return nil, ErrStorageInvalidID
2✔
1211
        }
2✔
1212

1213
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1214
        collDevs := database.Collection(CollectionDevices)
9✔
1215

9✔
1216
        // Device should know only about deployments that are not finished
9✔
1217
        query := bson.D{
9✔
1218
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
9✔
1219
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1220
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1221
                        {Key: "$exists", Value: false},
9✔
1222
                }},
9✔
1223
        }
9✔
1224

9✔
1225
        // Find the oldest one by sorting the creation timestamp
9✔
1226
        // in ascending order.
9✔
1227
        findOptions := mopts.FindOne()
9✔
1228
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
9✔
1229

9✔
1230
        // Select only the oldest one that have not been finished yet.
9✔
1231
        deployment := new(model.DeviceDeployment)
9✔
1232
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1233
                Decode(deployment); err != nil {
14✔
1234
                if err == mongo.ErrNoDocuments {
8✔
1235
                        return nil, nil
3✔
1236
                }
3✔
1237
                return nil, err
2✔
1238
        }
1239

1240
        return deployment, nil
5✔
1241
}
1242

1243
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1244
// matching device id that has not finished yet.
1245
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1246
        ctx context.Context,
1247
        deviceID string,
1248
) (*model.DeviceDeployment, error) {
11✔
1249

11✔
1250
        // Verify ID formatting
11✔
1251
        if len(deviceID) == 0 {
13✔
1252
                return nil, ErrStorageInvalidID
2✔
1253
        }
2✔
1254

1255
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1256
        collDevs := database.Collection(CollectionDevices)
9✔
1257

9✔
1258
        query := bson.D{
9✔
1259
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
9✔
1260
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1261
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1262
                        {Key: "$exists", Value: false},
9✔
1263
                }},
9✔
1264
        }
9✔
1265

9✔
1266
        // Find the latest one by sorting by the creation timestamp
9✔
1267
        // in ascending order.
9✔
1268
        findOptions := mopts.FindOne()
9✔
1269
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
9✔
1270

9✔
1271
        // Select only the latest one that have not been finished yet.
9✔
1272
        var deployment *model.DeviceDeployment
9✔
1273
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1274
                Decode(&deployment); err != nil {
14✔
1275
                if err == mongo.ErrNoDocuments {
8✔
1276
                        return nil, nil
3✔
1277
                }
3✔
1278
                return nil, err
2✔
1279
        }
1280

1281
        return deployment, nil
5✔
1282
}
1283

1284
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1285
        ctx context.Context,
1286
        deviceID string,
1287
        deploymentID string,
1288
        ddState model.DeviceDeploymentState,
1289
) (model.DeviceDeploymentStatus, error) {
19✔
1290

19✔
1291
        // Verify ID formatting
19✔
1292
        if len(deviceID) == 0 ||
19✔
1293
                len(deploymentID) == 0 {
23✔
1294
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
4✔
1295
        }
4✔
1296

1297
        if err := ddState.Validate(); err != nil {
17✔
1298
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
2✔
1299
        }
2✔
1300

1301
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1302
        collDevs := database.Collection(CollectionDevices)
13✔
1303

13✔
1304
        // Device should know only about deployments that are not finished
13✔
1305
        query := bson.D{
13✔
1306
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1307
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1308
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1309
                        {Key: "$exists", Value: false},
13✔
1310
                }},
13✔
1311
        }
13✔
1312

13✔
1313
        // update status field
13✔
1314
        set := bson.M{
13✔
1315
                StorageKeyDeviceDeploymentStatus: ddState.Status,
13✔
1316
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
13✔
1317
        }
13✔
1318
        // and finish time if provided
13✔
1319
        if ddState.FinishTime != nil {
16✔
1320
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
3✔
1321
        }
3✔
1322

1323
        if len(ddState.SubState) > 0 {
15✔
1324
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1325
        }
2✔
1326

1327
        update := bson.D{
13✔
1328
                {Key: "$set", Value: set},
13✔
1329
        }
13✔
1330

13✔
1331
        var old model.DeviceDeployment
13✔
1332

13✔
1333
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
13✔
1334
                Decode(&old); err != nil {
17✔
1335
                if err == mongo.ErrNoDocuments {
8✔
1336
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
4✔
1337
                }
4✔
1338
                return model.DeviceDeploymentStatusNull, err
×
1339

1340
        }
1341

1342
        return old.Status, nil
9✔
1343
}
1344

1345
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1346
        deviceID string, deploymentID string, log bool) error {
13✔
1347

13✔
1348
        // Verify ID formatting
13✔
1349
        if len(deviceID) == 0 ||
13✔
1350
                len(deploymentID) == 0 {
17✔
1351
                return ErrStorageInvalidID
4✔
1352
        }
4✔
1353

1354
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1355
        collDevs := database.Collection(CollectionDevices)
9✔
1356

9✔
1357
        selector := bson.D{
9✔
1358
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1359
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1360
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1361
                        {Key: "$exists", Value: false},
9✔
1362
                }},
9✔
1363
        }
9✔
1364

9✔
1365
        update := bson.D{
9✔
1366
                {Key: "$set", Value: bson.M{
9✔
1367
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
9✔
1368
        }
9✔
1369

9✔
1370
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
9✔
1371
                return err
×
1372
        } else if res.MatchedCount == 0 {
13✔
1373
                return ErrStorageNotFound
4✔
1374
        }
4✔
1375

1376
        return nil
5✔
1377
}
1378

1379
// SaveDeviceDeploymentRequest saves device deployment request
1380
// with the device deployment object
1381
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1382
        ctx context.Context,
1383
        ID string,
1384
        request *model.DeploymentNextRequest,
1385
) error {
7✔
1386

7✔
1387
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1388
        collDevs := database.Collection(CollectionDevices)
7✔
1389

7✔
1390
        res, err := collDevs.UpdateOne(
7✔
1391
                ctx,
7✔
1392
                bson.D{{Key: StorageKeyId, Value: ID}},
7✔
1393
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
7✔
1394
        )
7✔
1395
        if err != nil {
7✔
1396
                return err
×
1397
        } else if res.MatchedCount == 0 {
9✔
1398
                return ErrStorageNotFound
2✔
1399
        }
2✔
1400
        return nil
5✔
1401
}
1402

1403
// AssignArtifact assigns artifact to the device deployment
1404
func (db *DataStoreMongo) AssignArtifact(
1405
        ctx context.Context,
1406
        deviceID string,
1407
        deploymentID string,
1408
        artifact *model.Image,
1409
) error {
1✔
1410

1✔
1411
        // Verify ID formatting
1✔
1412
        if len(deviceID) == 0 ||
1✔
1413
                len(deploymentID) == 0 {
1✔
1414
                return ErrStorageInvalidID
×
1415
        }
×
1416

1417
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1418
        collDevs := database.Collection(CollectionDevices)
1✔
1419

1✔
1420
        selector := bson.D{
1✔
1421
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1422
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1423
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1424
                        {Key: "$exists", Value: false},
1✔
1425
                }},
1✔
1426
        }
1✔
1427

1✔
1428
        update := bson.D{
1✔
1429
                {Key: "$set", Value: bson.M{
1✔
1430
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1431
                }},
1✔
1432
        }
1✔
1433

1✔
1434
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1435
                return err
×
1436
        } else if res.MatchedCount == 0 {
1✔
1437
                return ErrStorageNotFound
×
1438
        }
×
1439

1440
        return nil
1✔
1441
}
1442

1443
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1444
        id string) (model.Stats, error) {
11✔
1445

11✔
1446
        if len(id) == 0 {
11✔
1447
                return nil, ErrStorageInvalidID
×
1448
        }
×
1449

1450
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1451
        collDevs := database.Collection(CollectionDevices)
11✔
1452

11✔
1453
        match := bson.D{
11✔
1454
                {Key: "$match", Value: bson.M{
11✔
1455
                        StorageKeyDeviceDeploymentDeploymentID: id,
11✔
1456
                        StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1457
                                {Key: "$exists", Value: false},
11✔
1458
                        },
11✔
1459
                }},
11✔
1460
        }
11✔
1461
        group := bson.D{
11✔
1462
                {Key: "$group", Value: bson.D{
11✔
1463
                        {Key: "_id",
11✔
1464
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
11✔
1465
                        {Key: "count",
11✔
1466
                                Value: bson.M{"$sum": 1}}},
11✔
1467
                },
11✔
1468
        }
11✔
1469
        pipeline := []bson.D{
11✔
1470
                match,
11✔
1471
                group,
11✔
1472
        }
11✔
1473
        var results []struct {
11✔
1474
                Status model.DeviceDeploymentStatus `bson:"_id"`
11✔
1475
                Count  int
11✔
1476
        }
11✔
1477
        cursor, err := collDevs.Aggregate(ctx, pipeline)
11✔
1478
        if err != nil {
11✔
1479
                return nil, err
×
1480
        }
×
1481
        if err := cursor.All(ctx, &results); err != nil {
11✔
1482
                if err == mongo.ErrNoDocuments {
×
1483
                        return nil, nil
×
1484
                }
×
1485
                return nil, err
×
1486
        }
1487

1488
        raw := model.NewDeviceDeploymentStats()
11✔
1489
        for _, res := range results {
32✔
1490
                raw.Set(res.Status, res.Count)
21✔
1491
        }
21✔
1492
        return raw, nil
11✔
1493
}
1494

1495
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1496
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1497
        deploymentID string) ([]model.DeviceDeployment, error) {
11✔
1498

11✔
1499
        statuses := []model.DeviceDeployment{}
11✔
1500
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1501
        collDevs := database.Collection(CollectionDevices)
11✔
1502

11✔
1503
        query := bson.M{
11✔
1504
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1505
                StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1506
                        {Key: "$exists", Value: false},
11✔
1507
                },
11✔
1508
        }
11✔
1509

11✔
1510
        cursor, err := collDevs.Find(ctx, query)
11✔
1511
        if err != nil {
11✔
1512
                return nil, err
×
1513
        }
×
1514

1515
        if err = cursor.All(ctx, &statuses); err != nil {
11✔
1516
                if err == mongo.ErrNoDocuments {
×
1517
                        return nil, nil
×
1518
                }
×
1519
                return nil, err
×
1520
        }
1521

1522
        return statuses, nil
11✔
1523
}
1524

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

29✔
1528
        statuses := []model.DeviceDeployment{}
29✔
1529
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1530
        collDevs := database.Collection(CollectionDevices)
29✔
1531

29✔
1532
        query := bson.D{
29✔
1533
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
29✔
1534
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
29✔
1535
                        {Key: "$exists", Value: false},
29✔
1536
                }},
29✔
1537
        }
29✔
1538
        if q.Status != nil {
37✔
1539
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
10✔
1540
                        query = append(query, bson.E{
2✔
1541
                                Key: "status", Value: bson.D{{
2✔
1542
                                        Key:   "$gte",
2✔
1543
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1544
                                }, {
2✔
1545
                                        Key:   "$lte",
2✔
1546
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1547
                                }},
2✔
1548
                        })
2✔
1549
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
8✔
1550
                        query = append(query, bson.E{
×
1551
                                Key: "status", Value: bson.D{{
×
1552
                                        Key:   "$gte",
×
1553
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1554
                                }, {
×
1555
                                        Key:   "$lte",
×
1556
                                        Value: model.DeviceDeploymentStatusPending,
×
1557
                                }},
×
1558
                        })
×
1559
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1560
                        query = append(query, bson.E{
2✔
1561
                                Key: "status", Value: bson.D{{
2✔
1562
                                        Key: "$in",
2✔
1563
                                        Value: []model.DeviceDeploymentStatus{
2✔
1564
                                                model.DeviceDeploymentStatusFailure,
2✔
1565
                                                model.DeviceDeploymentStatusAborted,
2✔
1566
                                                model.DeviceDeploymentStatusSuccess,
2✔
1567
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1568
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1569
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1570
                                        },
2✔
1571
                                }},
2✔
1572
                        })
2✔
1573
                } else {
6✔
1574
                        var status model.DeviceDeploymentStatus
4✔
1575
                        err := status.UnmarshalText([]byte(*q.Status))
4✔
1576
                        if err != nil {
6✔
1577
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1578
                        }
2✔
1579
                        query = append(query, bson.E{
2✔
1580
                                Key: "status", Value: status,
2✔
1581
                        })
2✔
1582
                }
1583
        }
1584

1585
        options := mopts.Find()
27✔
1586
        sortFieldQuery := bson.D{
27✔
1587
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
27✔
1588
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
27✔
1589
        }
27✔
1590
        options.SetSort(sortFieldQuery)
27✔
1591
        if q.Skip > 0 {
32✔
1592
                options.SetSkip(int64(q.Skip))
5✔
1593
        }
5✔
1594
        if q.Limit > 0 {
36✔
1595
                options.SetLimit(int64(q.Limit))
9✔
1596
        } else {
27✔
1597
                options.SetLimit(DefaultDocumentLimit)
18✔
1598
        }
18✔
1599

1600
        cursor, err := collDevs.Find(ctx, query, options)
27✔
1601
        if err != nil {
29✔
1602
                return nil, -1, err
2✔
1603
        }
2✔
1604

1605
        if err = cursor.All(ctx, &statuses); err != nil {
25✔
1606
                if err == mongo.ErrNoDocuments {
×
1607
                        return nil, -1, nil
×
1608
                }
×
1609
                return nil, -1, err
×
1610
        }
1611

1612
        count, err := collDevs.CountDocuments(ctx, query)
25✔
1613
        if err != nil {
25✔
1614
                return nil, -1, ErrDevicesCountFailed
×
1615
        }
×
1616

1617
        return statuses, int(count), nil
25✔
1618
}
1619

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

20✔
1623
        statuses := []model.DeviceDeployment{}
20✔
1624
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
20✔
1625
        collDevs := database.Collection(CollectionDevices)
20✔
1626

20✔
1627
        query := bson.D{}
20✔
1628
        if q.DeviceID != "" {
38✔
1629
                query = append(query, bson.E{
18✔
1630
                        Key:   StorageKeyDeviceDeploymentDeviceId,
18✔
1631
                        Value: q.DeviceID,
18✔
1632
                })
18✔
1633
        } else if len(q.IDs) > 0 {
22✔
1634
                query = append(query, bson.E{
2✔
1635
                        Key: StorageKeyId,
2✔
1636
                        Value: bson.D{{
2✔
1637
                                Key:   "$in",
2✔
1638
                                Value: q.IDs,
2✔
1639
                        }},
2✔
1640
                })
2✔
1641
        }
2✔
1642

1643
        if q.Status != nil {
36✔
1644
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
18✔
1645
                        query = append(query, bson.E{
2✔
1646
                                Key: "status", Value: bson.D{{
2✔
1647
                                        Key:   "$gte",
2✔
1648
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1649
                                }, {
2✔
1650
                                        Key:   "$lte",
2✔
1651
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1652
                                }},
2✔
1653
                        })
2✔
1654
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
18✔
1655
                        query = append(query, bson.E{
2✔
1656
                                Key: "status", Value: bson.D{{
2✔
1657
                                        Key:   "$gte",
2✔
1658
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1659
                                }, {
2✔
1660
                                        Key:   "$lte",
2✔
1661
                                        Value: model.DeviceDeploymentStatusPending,
2✔
1662
                                }},
2✔
1663
                        })
2✔
1664
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
16✔
1665
                        query = append(query, bson.E{
2✔
1666
                                Key: "status", Value: bson.D{{
2✔
1667
                                        Key: "$in",
2✔
1668
                                        Value: []model.DeviceDeploymentStatus{
2✔
1669
                                                model.DeviceDeploymentStatusFailure,
2✔
1670
                                                model.DeviceDeploymentStatusAborted,
2✔
1671
                                                model.DeviceDeploymentStatusSuccess,
2✔
1672
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1673
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1674
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1675
                                        },
2✔
1676
                                }},
2✔
1677
                        })
2✔
1678
                } else {
12✔
1679
                        var status model.DeviceDeploymentStatus
10✔
1680
                        err := status.UnmarshalText([]byte(*q.Status))
10✔
1681
                        if err != nil {
12✔
1682
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1683
                        }
2✔
1684
                        query = append(query, bson.E{
8✔
1685
                                Key: "status", Value: status,
8✔
1686
                        })
8✔
1687
                }
1688
        }
1689

1690
        options := mopts.Find()
18✔
1691
        sortFieldQuery := bson.D{
18✔
1692
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
18✔
1693
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
18✔
1694
        }
18✔
1695
        options.SetSort(sortFieldQuery)
18✔
1696
        if q.Skip > 0 {
20✔
1697
                options.SetSkip(int64(q.Skip))
2✔
1698
        }
2✔
1699
        if q.Limit > 0 {
36✔
1700
                options.SetLimit(int64(q.Limit))
18✔
1701
        } else {
18✔
1702
                options.SetLimit(DefaultDocumentLimit)
×
1703
        }
×
1704

1705
        cursor, err := collDevs.Find(ctx, query, options)
18✔
1706
        if err != nil {
18✔
1707
                return nil, -1, err
×
1708
        }
×
1709

1710
        if err = cursor.All(ctx, &statuses); err != nil {
18✔
1711
                if err == mongo.ErrNoDocuments {
×
1712
                        return nil, 0, nil
×
1713
                }
×
1714
                return nil, -1, err
×
1715
        }
1716

1717
        maxCount := maxCountDocuments
18✔
1718
        countOptions := &mopts.CountOptions{
18✔
1719
                Limit: &maxCount,
18✔
1720
        }
18✔
1721
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
18✔
1722
        if err != nil {
18✔
1723
                return nil, -1, ErrDevicesCountFailed
×
1724
        }
×
1725

1726
        return statuses, int(count), nil
18✔
1727
}
1728

1729
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1730
// `deviceID`, false otherwise. In case of errors returns false and an error
1731
// that occurred
1732
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1733
        deploymentID string, deviceID string) (bool, error) {
13✔
1734

13✔
1735
        var dep model.DeviceDeployment
13✔
1736
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1737
        collDevs := database.Collection(CollectionDevices)
13✔
1738

13✔
1739
        query := bson.D{
13✔
1740
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1741
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1742
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1743
                        {Key: "$exists", Value: false},
13✔
1744
                }},
13✔
1745
        }
13✔
1746

13✔
1747
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
19✔
1748
                if err == mongo.ErrNoDocuments {
12✔
1749
                        return false, nil
6✔
1750
                } else {
6✔
1751
                        return false, err
×
1752
                }
×
1753
        }
1754

1755
        return true, nil
7✔
1756
}
1757

1758
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1759
        deploymentId string) error {
5✔
1760

5✔
1761
        if len(deploymentId) == 0 {
7✔
1762
                return ErrStorageInvalidID
2✔
1763
        }
2✔
1764

1765
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1766
        collDevs := database.Collection(CollectionDevices)
3✔
1767
        selector := bson.M{
3✔
1768
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
3✔
1769
                StorageKeyDeviceDeploymentActive:       true,
3✔
1770
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1771
                        {Key: "$exists", Value: false},
3✔
1772
                },
3✔
1773
        }
3✔
1774

3✔
1775
        update := bson.M{
3✔
1776
                "$set": bson.M{
3✔
1777
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
3✔
1778
                        StorageKeyDeviceDeploymentActive: false,
3✔
1779
                },
3✔
1780
        }
3✔
1781

3✔
1782
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
3✔
1783
                return err
×
1784
        }
×
1785

1786
        return nil
3✔
1787
}
1788

1789
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
1790
        deviceID string) error {
4✔
1791
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1792
        collDevs := database.Collection(CollectionDevices)
4✔
1793
        selector := bson.M{
4✔
1794
                StorageKeyDeviceDeploymentDeviceId: deviceID,
4✔
1795
                StorageKeyDeviceDeploymentActive:   false,
4✔
1796
                StorageKeyDeviceDeploymentDeleted: bson.M{
4✔
1797
                        "$exists": false,
4✔
1798
                },
4✔
1799
        }
4✔
1800

4✔
1801
        now := time.Now()
4✔
1802
        update := bson.M{
4✔
1803
                "$set": bson.M{
4✔
1804
                        StorageKeyDeviceDeploymentDeleted: &now,
4✔
1805
                },
4✔
1806
        }
4✔
1807

4✔
1808
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
4✔
1809
                return err
×
1810
        }
×
1811

1812
        return nil
4✔
1813
}
1814

1815
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
1816
        deviceId string) error {
4✔
1817

4✔
1818
        if len(deviceId) == 0 {
6✔
1819
                return ErrStorageInvalidID
2✔
1820
        }
2✔
1821

1822
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1823
        collDevs := database.Collection(CollectionDevices)
2✔
1824
        selector := bson.M{
2✔
1825
                StorageKeyDeviceDeploymentDeviceId: deviceId,
2✔
1826
                StorageKeyDeviceDeploymentActive:   true,
2✔
1827
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
1828
                        {Key: "$exists", Value: false},
2✔
1829
                },
2✔
1830
        }
2✔
1831

2✔
1832
        update := bson.M{
2✔
1833
                "$set": bson.M{
2✔
1834
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
2✔
1835
                        StorageKeyDeviceDeploymentActive: false,
2✔
1836
                },
2✔
1837
        }
2✔
1838

2✔
1839
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
1840
                return err
×
1841
        }
×
1842

1843
        return nil
2✔
1844
}
1845

1846
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
1847
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
1848

1✔
1849
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1850
        collDevs := database.Collection(CollectionDevices)
1✔
1851

1✔
1852
        filter := bson.M{
1✔
1853
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1854
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
1855
        }
1✔
1856
        if !includeDeleted {
2✔
1857
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
1858
                        {Key: "$exists", Value: false},
1✔
1859
                }
1✔
1860
        }
1✔
1861

1862
        opts := &mopts.FindOneOptions{}
1✔
1863
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
1864

1✔
1865
        var dd model.DeviceDeployment
1✔
1866
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
1867
                if err == mongo.ErrNoDocuments {
2✔
1868
                        return nil, ErrStorageNotFound
1✔
1869
                }
1✔
1870
                return nil, err
×
1871
        }
1872

1873
        return &dd, nil
1✔
1874
}
1875

1876
func (db *DataStoreMongo) GetDeviceDeployments(
1877
        ctx context.Context,
1878
        skip int,
1879
        limit int,
1880
        deviceID string,
1881
        active *bool,
1882
        includeDeleted bool,
1883
) ([]model.DeviceDeployment, error) {
8✔
1884

8✔
1885
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
1886
        collDevs := database.Collection(CollectionDevices)
8✔
1887

8✔
1888
        filter := bson.M{}
8✔
1889
        if !includeDeleted {
12✔
1890
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
4✔
1891
                        {Key: "$exists", Value: false},
4✔
1892
                }
4✔
1893
        }
4✔
1894
        if deviceID != "" {
10✔
1895
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
2✔
1896
        }
2✔
1897
        if active != nil {
10✔
1898
                filter[StorageKeyDeviceDeploymentActive] = *active
2✔
1899
        }
2✔
1900

1901
        opts := &mopts.FindOptions{}
8✔
1902
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
8✔
1903
        if skip > 0 {
10✔
1904
                opts.SetSkip(int64(skip))
2✔
1905
        }
2✔
1906
        if limit > 0 {
10✔
1907
                opts.SetLimit(int64(limit))
2✔
1908
        }
2✔
1909

1910
        var deviceDeployments []model.DeviceDeployment
8✔
1911
        cursor, err := collDevs.Find(ctx, filter, opts)
8✔
1912
        if err != nil {
8✔
1913
                return nil, err
×
1914
        }
×
1915
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
8✔
1916
                return nil, err
×
1917
        }
×
1918

1919
        return deviceDeployments, nil
8✔
1920
}
1921

1922
// deployments
1923

1924
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
1925
        indexes ...mongo.IndexModel) error {
691✔
1926
        ctx := context.Background()
691✔
1927
        dataBase := db.client.Database(dbName)
691✔
1928

691✔
1929
        coll := dataBase.Collection(collName)
691✔
1930
        idxView := coll.Indexes()
691✔
1931
        _, err := idxView.CreateMany(ctx, indexes)
691✔
1932
        return err
691✔
1933
}
691✔
1934

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

32✔
1938
        var idx bson.M
32✔
1939
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
32✔
1940
        collDpl := database.Collection(CollectionDeployments)
32✔
1941
        idxView := collDpl.Indexes()
32✔
1942

32✔
1943
        cursor, err := idxView.List(ctx)
32✔
1944
        if err != nil {
32✔
1945
                // check failed, assume indexing is not there
×
1946
                return false
×
1947
        }
×
1948

1949
        has := map[string]bool{}
32✔
1950
        for cursor.Next(ctx) {
92✔
1951
                if err = cursor.Decode(&idx); err != nil {
60✔
1952
                        continue
×
1953
                }
1954
                if _, ok := idx["weights"]; ok {
90✔
1955
                        // text index
30✔
1956
                        for k := range idx["weights"].(bson.M) {
90✔
1957
                                has[k] = true
60✔
1958
                        }
60✔
1959
                } else {
30✔
1960
                        for i := range idx["key"].(bson.M) {
60✔
1961
                                has[i] = true
30✔
1962
                        }
30✔
1963

1964
                }
1965
        }
1966
        if err != nil {
32✔
1967
                return false
×
1968
        }
×
1969

1970
        for _, key := range StorageIndexes.Keys.(bson.D) {
94✔
1971
                _, ok := has[key.Key]
62✔
1972
                if !ok {
64✔
1973
                        return false
2✔
1974
                }
2✔
1975
        }
1976

1977
        return true
30✔
1978
}
1979

1980
// Insert persists object
1981
func (db *DataStoreMongo) InsertDeployment(
1982
        ctx context.Context,
1983
        deployment *model.Deployment,
1984
) error {
419✔
1985

419✔
1986
        if deployment == nil {
421✔
1987
                return ErrDeploymentStorageInvalidDeployment
2✔
1988
        }
2✔
1989

1990
        if err := deployment.Validate(); err != nil {
420✔
1991
                return err
3✔
1992
        }
3✔
1993

1994
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
415✔
1995
        collDpl := database.Collection(CollectionDeployments)
415✔
1996

415✔
1997
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
416✔
1998
                return err
1✔
1999
        }
1✔
2000
        return nil
415✔
2001
}
2002

2003
// Delete removed entry by ID
2004
// Noop on ID not found
2005
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
8✔
2006

8✔
2007
        if len(id) == 0 {
10✔
2008
                return ErrStorageInvalidID
2✔
2009
        }
2✔
2010

2011
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2012
        collDpl := database.Collection(CollectionDeployments)
6✔
2013

6✔
2014
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
6✔
2015
                return err
×
2016
        }
×
2017

2018
        return nil
6✔
2019
}
2020

2021
func (db *DataStoreMongo) FindDeploymentByID(
2022
        ctx context.Context,
2023
        id string,
2024
) (*model.Deployment, error) {
19✔
2025

19✔
2026
        if len(id) == 0 {
21✔
2027
                return nil, ErrStorageInvalidID
2✔
2028
        }
2✔
2029

2030
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
17✔
2031
        collDpl := database.Collection(CollectionDeployments)
17✔
2032

17✔
2033
        deployment := new(model.Deployment)
17✔
2034
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
17✔
2035
                Decode(deployment); err != nil {
23✔
2036
                if err == mongo.ErrNoDocuments {
12✔
2037
                        return nil, nil
6✔
2038
                }
6✔
2039
                return nil, err
×
2040
        }
2041

2042
        return deployment, nil
11✔
2043
}
2044

2045
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2046
        ctx context.Context,
2047
        ids ...string,
2048
) (deploymentStats []*model.DeploymentStats, err error) {
4✔
2049

4✔
2050
        if len(ids) == 0 {
4✔
2051
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2052
        }
×
2053

2054
        for _, id := range ids {
12✔
2055
                if len(id) == 0 {
8✔
2056
                        return nil, ErrStorageInvalidID
×
2057
                }
×
2058
        }
2059

2060
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2061
        collDpl := database.Collection(CollectionDeployments)
4✔
2062

4✔
2063
        query := bson.M{
4✔
2064
                "_id": bson.M{
4✔
2065
                        "$in": ids,
4✔
2066
                },
4✔
2067
        }
4✔
2068
        statsProjection := &mopts.FindOptions{
4✔
2069
                Projection: bson.M{"stats": 1},
4✔
2070
        }
4✔
2071

4✔
2072
        results, err := collDpl.Find(
4✔
2073
                ctx,
4✔
2074
                query,
4✔
2075
                statsProjection,
4✔
2076
        )
4✔
2077
        if err != nil {
4✔
2078
                return nil, err
×
2079
        }
×
2080

2081
        for results.Next(context.Background()) {
12✔
2082
                depl := new(model.DeploymentStats)
8✔
2083
                if err = results.Decode(&depl); err != nil {
8✔
2084
                        if err == mongo.ErrNoDocuments {
×
2085
                                return nil, nil
×
2086
                        }
×
2087
                        return nil, err
×
2088
                }
2089
                deploymentStats = append(deploymentStats, depl)
8✔
2090
        }
2091

2092
        return deploymentStats, nil
4✔
2093
}
2094

2095
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2096
        id string) (*model.Deployment, error) {
15✔
2097

15✔
2098
        if len(id) == 0 {
17✔
2099
                return nil, ErrStorageInvalidID
2✔
2100
        }
2✔
2101

2102
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
2103
        collDpl := database.Collection(CollectionDeployments)
13✔
2104

13✔
2105
        var deployment *model.Deployment
13✔
2106
        filter := bson.D{
13✔
2107
                {Key: "_id", Value: id},
13✔
2108
                {Key: StorageKeyDeploymentFinished, Value: nil},
13✔
2109
        }
13✔
2110
        if err := collDpl.FindOne(ctx, filter).
13✔
2111
                Decode(&deployment); err != nil {
22✔
2112
                if err == mongo.ErrNoDocuments {
18✔
2113
                        return nil, nil
9✔
2114
                }
9✔
2115
                return nil, err
×
2116
        }
2117

2118
        return deployment, nil
5✔
2119
}
2120

2121
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2122
        ctx context.Context,
2123
        deploymentID string,
2124
        increment int,
2125
) error {
109✔
2126
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
109✔
2127
        collection := database.Collection(CollectionDeployments)
109✔
2128

109✔
2129
        filter := bson.M{
109✔
2130
                "_id": deploymentID,
109✔
2131
                StorageKeyDeploymentDeviceCount: bson.M{
109✔
2132
                        "$ne": nil,
109✔
2133
                },
109✔
2134
        }
109✔
2135

109✔
2136
        update := bson.M{
109✔
2137
                "$inc": bson.M{
109✔
2138
                        StorageKeyDeploymentDeviceCount: increment,
109✔
2139
                },
109✔
2140
        }
109✔
2141

109✔
2142
        _, err := collection.UpdateOne(ctx, filter, update)
109✔
2143
        return err
109✔
2144
}
109✔
2145

2146
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2147
        ctx context.Context,
2148
        deploymentID string,
2149
        count int,
2150
) error {
6✔
2151
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2152
        collection := database.Collection(CollectionDeployments)
6✔
2153

6✔
2154
        filter := bson.M{
6✔
2155
                "_id": deploymentID,
6✔
2156
                StorageKeyDeploymentDeviceCount: bson.M{
6✔
2157
                        "$eq": nil,
6✔
2158
                },
6✔
2159
        }
6✔
2160

6✔
2161
        update := bson.M{
6✔
2162
                "$set": bson.M{
6✔
2163
                        StorageKeyDeploymentDeviceCount: count,
6✔
2164
                },
6✔
2165
        }
6✔
2166

6✔
2167
        _, err := collection.UpdateOne(ctx, filter, update)
6✔
2168
        return err
6✔
2169
}
6✔
2170

2171
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2172
        id string) (int, error) {
6✔
2173

6✔
2174
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2175
        collDevs := database.Collection(CollectionDevices)
6✔
2176

6✔
2177
        filter := bson.M{
6✔
2178
                StorageKeyDeviceDeploymentDeploymentID: id,
6✔
2179
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
2180
                        {Key: "$exists", Value: false},
6✔
2181
                },
6✔
2182
        }
6✔
2183

6✔
2184
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
6✔
2185
        if err != nil {
6✔
2186
                return 0, err
×
2187
        }
×
2188

2189
        return int(deviceCount), nil
6✔
2190
}
2191

2192
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2193
        id string, stats model.Stats) error {
11✔
2194

11✔
2195
        if len(id) == 0 {
13✔
2196
                return ErrStorageInvalidID
2✔
2197
        }
2✔
2198

2199
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2200
        collDpl := database.Collection(CollectionDeployments)
9✔
2201

9✔
2202
        deployment, err := model.NewDeployment()
9✔
2203
        if err != nil {
9✔
2204
                return errors.Wrap(err, "failed to create deployment")
×
2205
        }
×
2206

2207
        deployment.Stats = stats
9✔
2208
        var update bson.M
9✔
2209
        if deployment.IsFinished() {
9✔
2210
                now := time.Now()
×
2211

×
2212
                update = bson.M{
×
2213
                        "$set": bson.M{
×
2214
                                StorageKeyDeploymentStats:    stats,
×
2215
                                StorageKeyDeploymentFinished: &now,
×
2216
                        },
×
2217
                }
×
2218
        } else {
9✔
2219
                update = bson.M{
9✔
2220
                        "$set": bson.M{
9✔
2221
                                StorageKeyDeploymentStats: stats,
9✔
2222
                        },
9✔
2223
                }
9✔
2224
        }
9✔
2225

2226
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
9✔
2227
        if res != nil && res.MatchedCount == 0 {
13✔
2228
                return ErrStorageInvalidID
4✔
2229
        }
4✔
2230
        return err
5✔
2231
}
2232

2233
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2234
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
15✔
2235

15✔
2236
        if len(id) == 0 {
17✔
2237
                return ErrStorageInvalidID
2✔
2238
        }
2✔
2239

2240
        if _, err := stateTo.MarshalText(); err != nil {
13✔
2241
                return ErrStorageInvalidInput
×
2242
        }
×
2243

2244
        // does not need any extra operations
2245
        // following query won't handle this case well and increase the state_to value
2246
        if stateFrom == stateTo {
15✔
2247
                return nil
2✔
2248
        }
2✔
2249

2250
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2251
        collDpl := database.Collection(CollectionDeployments)
11✔
2252

11✔
2253
        var update bson.M
11✔
2254

11✔
2255
        if stateFrom == model.DeviceDeploymentStatusNull {
14✔
2256
                // note dot notation on embedded document
3✔
2257
                update = bson.M{
3✔
2258
                        "$inc": bson.M{
3✔
2259
                                "stats." + stateTo.String(): 1,
3✔
2260
                        },
3✔
2261
                }
3✔
2262
        } else {
12✔
2263
                // note dot notation on embedded document
9✔
2264
                update = bson.M{
9✔
2265
                        "$inc": bson.M{
9✔
2266
                                "stats." + stateFrom.String(): -1,
9✔
2267
                                "stats." + stateTo.String():   1,
9✔
2268
                        },
9✔
2269
                }
9✔
2270
        }
9✔
2271

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

11✔
2274
        if res != nil && res.MatchedCount == 0 {
13✔
2275
                return ErrStorageInvalidID
2✔
2276
        }
2✔
2277

2278
        return err
9✔
2279
}
2280

2281
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2282
        ctx context.Context,
2283
        deploymentID string,
2284
        increment int64,
2285
) error {
1✔
2286
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2287
        collection := database.Collection(CollectionDeployments)
1✔
2288

1✔
2289
        filter := bson.M{
1✔
2290
                "_id": deploymentID,
1✔
2291
                StorageKeyDeploymentTotalSize: bson.M{
1✔
2292
                        "$ne": nil,
1✔
2293
                },
1✔
2294
        }
1✔
2295

1✔
2296
        update := bson.M{
1✔
2297
                "$inc": bson.M{
1✔
2298
                        StorageKeyDeploymentTotalSize: increment,
1✔
2299
                },
1✔
2300
        }
1✔
2301

1✔
2302
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2303
        return err
1✔
2304
}
1✔
2305

2306
func (db *DataStoreMongo) Find(ctx context.Context,
2307
        match model.Query) ([]*model.Deployment, int64, error) {
67✔
2308

67✔
2309
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
67✔
2310
        collDpl := database.Collection(CollectionDeployments)
67✔
2311

67✔
2312
        andq := []bson.M{}
67✔
2313

67✔
2314
        // filter by IDs
67✔
2315
        if len(match.IDs) > 0 {
67✔
2316
                tq := bson.M{
×
2317
                        "_id": bson.M{
×
2318
                                "$in": match.IDs,
×
2319
                        },
×
UNCOV
2320
                }
×
UNCOV
2321
                andq = append(andq, tq)
×
UNCOV
2322
        }
×
2323

2324
        // build deployment by name part of the query
2325
        if match.SearchText != "" {
99✔
2326
                // we must have indexing for text search
32✔
2327
                if !db.hasIndexing(ctx, db.client) {
34✔
2328
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
2✔
2329
                }
2✔
2330

2331
                tq := bson.M{
30✔
2332
                        "$text": bson.M{
30✔
2333
                                "$search": match.SearchText,
30✔
2334
                        },
30✔
2335
                }
30✔
2336

30✔
2337
                andq = append(andq, tq)
30✔
2338
        }
2339

2340
        // build deployment by status part of the query
2341
        if match.Status != model.StatusQueryAny {
85✔
2342
                var status model.DeploymentStatus
20✔
2343
                if match.Status == model.StatusQueryPending {
24✔
2344
                        status = model.DeploymentStatusPending
4✔
2345
                } else if match.Status == model.StatusQueryInProgress {
28✔
2346
                        status = model.DeploymentStatusInProgress
8✔
2347
                } else {
16✔
2348
                        status = model.DeploymentStatusFinished
8✔
2349
                }
8✔
2350
                stq := bson.M{StorageKeyDeploymentStatus: status}
20✔
2351
                andq = append(andq, stq)
20✔
2352
        }
2353

2354
        // build deployment by type part of the query
2355
        if match.Type != "" {
69✔
2356
                if match.Type == model.DeploymentTypeConfiguration {
8✔
2357
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
4✔
2358
                } else if match.Type == model.DeploymentTypeSoftware {
4✔
2359
                        andq = append(andq, bson.M{
×
2360
                                "$or": []bson.M{
×
2361
                                        {StorageKeyDeploymentType: match.Type},
×
2362
                                        {StorageKeyDeploymentType: ""},
×
UNCOV
2363
                                },
×
UNCOV
2364
                        })
×
UNCOV
2365
                }
×
2366
        }
2367

2368
        query := bson.M{}
65✔
2369
        if len(andq) != 0 {
111✔
2370
                // use search criteria if any
46✔
2371
                query = bson.M{
46✔
2372
                        "$and": andq,
46✔
2373
                }
46✔
2374
        }
46✔
2375

2376
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
65✔
2377
                query["created"] = bson.M{
×
UNCOV
2378
                        "$gte": match.CreatedAfter,
×
2379
                        "$lte": match.CreatedBefore,
×
2380
                }
×
2381
        } else if match.CreatedAfter != nil {
65✔
UNCOV
2382
                query["created"] = bson.M{
×
2383
                        "$gte": match.CreatedAfter,
×
2384
                }
×
2385
        } else if match.CreatedBefore != nil {
65✔
2386
                query["created"] = bson.M{
×
UNCOV
2387
                        "$lte": match.CreatedBefore,
×
UNCOV
2388
                }
×
UNCOV
2389
        }
×
2390

2391
        options := db.findOptions(match)
65✔
2392

65✔
2393
        var deployments []*model.Deployment
65✔
2394
        cursor, err := collDpl.Find(ctx, query, options)
65✔
2395
        if err != nil {
65✔
2396
                return nil, 0, err
×
2397
        }
×
2398
        if err := cursor.All(ctx, &deployments); err != nil {
65✔
UNCOV
2399
                return nil, 0, err
×
UNCOV
2400
        }
×
2401
        // Count documents if we didn't find all already.
2402
        count := int64(0)
65✔
2403
        if !match.DisableCount {
130✔
2404
                count = int64(len(deployments))
65✔
2405
                if count >= int64(match.Limit) {
129✔
2406
                        count, err = collDpl.CountDocuments(ctx, query)
64✔
2407
                        if err != nil {
64✔
UNCOV
2408
                                return nil, 0, err
×
UNCOV
2409
                        }
×
2410
                } else {
1✔
2411
                        // Don't forget to add the skipped documents
1✔
2412
                        count += int64(match.Skip)
1✔
2413
                }
1✔
2414
        }
2415

2416
        return deployments, count, nil
65✔
2417
}
2418

2419
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
65✔
2420
        options := &mopts.FindOptions{}
65✔
2421
        if match.Sort == model.SortDirectionAscending {
67✔
2422
                options.SetSort(bson.D{{Key: "created", Value: 1}})
2✔
2423
        } else {
65✔
2424
                options.SetSort(bson.D{{Key: "created", Value: -1}})
63✔
2425
        }
63✔
2426
        if match.Skip > 0 {
69✔
2427
                options.SetSkip(int64(match.Skip))
4✔
2428
        }
4✔
2429
        if match.Limit > 0 {
74✔
2430
                options.SetLimit(int64(match.Limit))
9✔
2431
        }
9✔
2432
        return options
65✔
2433
}
2434

2435
// FindNewerActiveDeployments finds active deployments which were created
2436
// after createdAfter
2437
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2438
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
9✔
2439

9✔
2440
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2441
        c := database.Collection(CollectionDeployments)
9✔
2442

9✔
2443
        queryFilters := make([]bson.M, 0)
9✔
2444
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
9✔
2445
        queryFilters = append(queryFilters,
9✔
2446
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
9✔
2447
        findQuery := bson.M{}
9✔
2448
        findQuery["$and"] = queryFilters
9✔
2449

9✔
2450
        findOptions := &mopts.FindOptions{}
9✔
2451
        findOptions.SetSkip(int64(skip))
9✔
2452
        findOptions.SetLimit(int64(limit))
9✔
2453

9✔
2454
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
9✔
2455
        cursor, err := c.Find(ctx, findQuery, findOptions)
9✔
2456
        if err != nil {
9✔
UNCOV
2457
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2458
        }
×
2459
        defer cursor.Close(ctx)
9✔
2460

9✔
2461
        var deployments []*model.Deployment
9✔
2462

9✔
2463
        if err = cursor.All(ctx, &deployments); err != nil {
9✔
UNCOV
2464
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2465
        }
×
2466

2467
        return deployments, nil
9✔
2468
}
2469

2470
// SetDeploymentStatus simply sets the status field
2471
// optionally sets 'finished time' if deployment is indeed finished
2472
func (db *DataStoreMongo) SetDeploymentStatus(
2473
        ctx context.Context,
2474
        id string,
2475
        status model.DeploymentStatus,
2476
        now time.Time,
2477
) error {
11✔
2478
        if len(id) == 0 {
11✔
UNCOV
2479
                return ErrStorageInvalidID
×
UNCOV
2480
        }
×
2481

2482
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2483
        collDpl := database.Collection(CollectionDeployments)
11✔
2484

11✔
2485
        var update bson.M
11✔
2486
        if status == model.DeploymentStatusFinished {
14✔
2487
                update = bson.M{
3✔
2488
                        "$set": bson.M{
3✔
2489
                                StorageKeyDeploymentActive:   false,
3✔
2490
                                StorageKeyDeploymentStatus:   status,
3✔
2491
                                StorageKeyDeploymentFinished: &now,
3✔
2492
                        },
3✔
2493
                }
3✔
2494
        } else {
12✔
2495
                update = bson.M{
9✔
2496
                        "$set": bson.M{
9✔
2497
                                StorageKeyDeploymentActive: true,
9✔
2498
                                StorageKeyDeploymentStatus: status,
9✔
2499
                        },
9✔
2500
                }
9✔
2501
        }
9✔
2502

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

11✔
2505
        if res != nil && res.MatchedCount == 0 {
13✔
2506
                return ErrStorageInvalidID
2✔
2507
        }
2✔
2508

2509
        return err
9✔
2510
}
2511

2512
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2513
// given artifact
2514
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2515
        id string) (bool, error) {
7✔
2516

7✔
2517
        if len(id) == 0 {
7✔
UNCOV
2518
                return false, ErrStorageInvalidID
×
UNCOV
2519
        }
×
2520

2521
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2522
        collDpl := database.Collection(CollectionDeployments)
7✔
2523

7✔
2524
        var tmp interface{}
7✔
2525
        query := bson.D{
7✔
2526
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2527
                {Key: StorageKeyDeploymentArtifacts, Value: id},
7✔
2528
        }
7✔
2529
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
12✔
2530
                if err == mongo.ErrNoDocuments {
10✔
2531
                        return false, nil
5✔
2532
                }
5✔
UNCOV
2533
                return false, err
×
2534
        }
2535

2536
        return true, nil
3✔
2537
}
2538

2539
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2540
// given artifact
2541
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2542
        artifactName string) (bool, error) {
7✔
2543

7✔
2544
        if len(artifactName) == 0 {
7✔
UNCOV
2545
                return false, ErrImagesStorageInvalidArtifactName
×
UNCOV
2546
        }
×
2547

2548
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2549
        collDpl := database.Collection(CollectionDeployments)
7✔
2550

7✔
2551
        var tmp interface{}
7✔
2552
        query := bson.D{
7✔
2553
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2554
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
7✔
2555
        }
7✔
2556

7✔
2557
        projection := bson.M{
7✔
2558
                "_id": 1,
7✔
2559
        }
7✔
2560
        findOptions := mopts.FindOne()
7✔
2561
        findOptions.SetProjection(projection)
7✔
2562

7✔
2563
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
12✔
2564
                if err == mongo.ErrNoDocuments {
10✔
2565
                        return false, nil
5✔
2566
                }
5✔
UNCOV
2567
                return false, err
×
2568
        }
2569

2570
        return true, nil
2✔
2571
}
2572

2573
// ExistByArtifactId check if there is any deployment that uses give artifact
2574
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2575
        id string) (bool, error) {
×
2576

×
UNCOV
2577
        if len(id) == 0 {
×
2578
                return false, ErrStorageInvalidID
×
2579
        }
×
2580

2581
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2582
        collDpl := database.Collection(CollectionDeployments)
×
2583

×
2584
        var tmp interface{}
×
2585
        query := bson.D{
×
2586
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2587
        }
×
2588
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2589
                if err == mongo.ErrNoDocuments {
×
UNCOV
2590
                        return false, nil
×
UNCOV
2591
                }
×
2592
                return false, err
×
2593
        }
2594

UNCOV
2595
        return true, nil
×
2596
}
2597

2598
// Per-tenant storage settings
2599
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
3✔
2600
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2601
        collection := database.Collection(CollectionStorageSettings)
3✔
2602

3✔
2603
        settings := new(model.StorageSettings)
3✔
2604
        // supposed that it's only one document in the collection
3✔
2605
        query := bson.M{
3✔
2606
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2607
        }
3✔
2608
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
4✔
2609
                if err == mongo.ErrNoDocuments {
2✔
2610
                        return nil, nil
1✔
2611
                }
1✔
UNCOV
2612
                return nil, err
×
2613
        }
2614

2615
        return settings, nil
3✔
2616
}
2617

2618
func (db *DataStoreMongo) SetStorageSettings(
2619
        ctx context.Context,
2620
        storageSettings *model.StorageSettings,
2621
) error {
3✔
2622
        var err error
3✔
2623
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2624
        collection := database.Collection(CollectionStorageSettings)
3✔
2625

3✔
2626
        filter := bson.M{
3✔
2627
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2628
        }
3✔
2629
        if storageSettings != nil {
6✔
2630
                replaceOptions := mopts.Replace()
3✔
2631
                replaceOptions.SetUpsert(true)
3✔
2632
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
3✔
2633
        } else {
4✔
2634
                _, err = collection.DeleteOne(ctx, filter)
1✔
2635
        }
1✔
2636

2637
        return err
3✔
2638
}
2639

2640
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2641
        ctx context.Context,
2642
        artifactName string,
2643
        artifactIDs []string,
2644
) error {
2✔
2645
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2646
        collDpl := database.Collection(CollectionDeployments)
2✔
2647

2✔
2648
        query := bson.D{
2✔
2649
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2650
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
2✔
2651
        }
2✔
2652
        update := bson.M{
2✔
2653
                "$set": bson.M{
2✔
2654
                        StorageKeyDeploymentArtifacts: artifactIDs,
2✔
2655
                },
2✔
2656
        }
2✔
2657

2✔
2658
        _, err := collDpl.UpdateMany(ctx, query, update)
2✔
2659
        return err
2✔
2660
}
2✔
2661

UNCOV
2662
func (db *DataStoreMongo) GetTenantDbs() ([]string, error) {
×
UNCOV
2663
        return migrate.GetTenantDbs(context.Background(), db.client, mstore.IsTenantDb(DbName))
×
UNCOV
2664
}
×
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