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

mendersoftware / deployments / 840356879

pending completion
840356879

Pull #849

gitlab-ci

Peter Grzybowski
feat: Store device deployments status separately
Pull Request #849: feat: Store device deployments status separately

40 of 42 new or added lines in 3 files covered. (95.24%)

19 existing lines in 1 file now uncovered.

0 of 0 relevant lines covered (NaN%)

0.0 hits per line

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

75.0
/model/device_deployment.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

15
package model
16

17
import (
18
        "encoding/json"
19
        "time"
20

21
        validation "github.com/go-ozzo/ozzo-validation/v4"
22
        "github.com/go-ozzo/ozzo-validation/v4/is"
23
        "github.com/google/uuid"
24
        "github.com/pkg/errors"
25
)
26

27
var (
28
        ErrDeviceDeploymentStatusMismatch = errors.New(
29
                "model active state does not match status",
30
        )
31
)
32

33
// DeviceDeploymentStatus is an enumerated type showing the status of a device within a deployment
34
type DeviceDeploymentStatus int32
35

36
// Deployment statuses
37
const (
38
        // The following statuses are distributed evenly by incrementing the
39
        // enum counter's second byte.
40
        // NOTE: when adding new statuses into the list, use the mean value between the
41
        //       neighbouring values and append the value AFTER the following list and extend
42
        //       the DeviceDeploymentStatus<type>Str constant and allStatuses variable as well
43
        //       as the MarshalText and UnmarshalText interface functions.
44
        //       See example below.
45
        // WARN: DO NOT CHANGE ANY OF THE FOLLOWING VALUES.
46
        DeviceDeploymentStatusNull DeviceDeploymentStatus = iota << 8 // i=0... {i * 2^8}
47
        DeviceDeploymentStatusFailure
48
        DeviceDeploymentStatusAborted
49
        DeviceDeploymentStatusPauseBeforeInstall
50
        DeviceDeploymentStatusPauseBeforeCommit
51
        DeviceDeploymentStatusPauseBeforeReboot
52
        DeviceDeploymentStatusDownloading
53
        DeviceDeploymentStatusInstalling
54
        DeviceDeploymentStatusRebooting
55
        DeviceDeploymentStatusPending
56
        DeviceDeploymentStatusSuccess
57
        DeviceDeploymentStatusNoArtifact
58
        DeviceDeploymentStatusAlreadyInst
59
        DeviceDeploymentStatusDecommissioned
60
        // DeviceDeploymentStatusNew = (DeviceDeploymentStatusSuccess +
61
        // DeviceDeploymentStatusNoArtifact) / 2
62

63
        DeviceDeploymentStatusActiveLow  = DeviceDeploymentStatusPauseBeforeInstall
64
        DeviceDeploymentStatusActiveHigh = DeviceDeploymentStatusPending
65

66
        DeviceDeploymentStatusFailureStr            = "failure"
67
        DeviceDeploymentStatusAbortedStr            = "aborted"
68
        DeviceDeploymentStatusPauseBeforeInstallStr = "pause_before_installing"
69
        DeviceDeploymentStatusPauseBeforeCommitStr  = "pause_before_committing"
70
        DeviceDeploymentStatusPauseBeforeRebootStr  = "pause_before_rebooting"
71
        DeviceDeploymentStatusDownloadingStr        = "downloading"
72
        DeviceDeploymentStatusInstallingStr         = "installing"
73
        DeviceDeploymentStatusRebootingStr          = "rebooting"
74
        DeviceDeploymentStatusPendingStr            = "pending"
75
        DeviceDeploymentStatusSuccessStr            = "success"
76
        DeviceDeploymentStatusNoArtifactStr         = "noartifact"
77
        DeviceDeploymentStatusAlreadyInstStr        = "already-installed"
78
        DeviceDeploymentStatusDecommissionedStr     = "decommissioned"
79
        // DeviceDeploymentStatusNew = "lorem-ipsum"
80
)
81

82
const (
83
        DeviceDeploymentStatusPauseStr    = "pause"
84
        DeviceDeploymentStatusActiveStr   = "active"
85
        DeviceDeploymentStatusFinishedStr = "finished"
86
)
87

88
func NewStatus(status string) DeviceDeploymentStatus {
×
89
        var stat DeviceDeploymentStatus
×
90
        _ = stat.UnmarshalText([]byte(status))
×
91
        return stat
×
92
}
×
93

94
var allStatuses = []DeviceDeploymentStatus{
95
        DeviceDeploymentStatusFailure,
96
        DeviceDeploymentStatusPauseBeforeInstall,
97
        DeviceDeploymentStatusPauseBeforeCommit,
98
        DeviceDeploymentStatusPauseBeforeReboot,
99
        DeviceDeploymentStatusDownloading,
100
        DeviceDeploymentStatusInstalling,
101
        DeviceDeploymentStatusRebooting,
102
        DeviceDeploymentStatusPending,
103
        DeviceDeploymentStatusSuccess,
104
        DeviceDeploymentStatusAborted,
105
        DeviceDeploymentStatusNoArtifact,
106
        DeviceDeploymentStatusAlreadyInst,
107
        DeviceDeploymentStatusDecommissioned,
108
        // DeviceDeploymentStatusNew
109
}
110

111
func (stat DeviceDeploymentStatus) MarshalText() ([]byte, error) {
27,001✔
112
        switch stat {
27,001✔
113
        case DeviceDeploymentStatusFailure:
2,077✔
114
                return []byte(DeviceDeploymentStatusFailureStr), nil
2,077✔
115
        case DeviceDeploymentStatusPauseBeforeInstall:
2,077✔
116
                return []byte(DeviceDeploymentStatusPauseBeforeInstallStr), nil
2,077✔
117
        case DeviceDeploymentStatusPauseBeforeCommit:
2,077✔
118
                return []byte(DeviceDeploymentStatusPauseBeforeCommitStr), nil
2,077✔
119
        case DeviceDeploymentStatusPauseBeforeReboot:
2,077✔
120
                return []byte(DeviceDeploymentStatusPauseBeforeRebootStr), nil
2,077✔
121
        case DeviceDeploymentStatusDownloading:
2,077✔
122
                return []byte(DeviceDeploymentStatusDownloadingStr), nil
2,077✔
123
        case DeviceDeploymentStatusInstalling:
2,077✔
124
                return []byte(DeviceDeploymentStatusInstallingStr), nil
2,077✔
125
        case DeviceDeploymentStatusRebooting:
2,077✔
126
                return []byte(DeviceDeploymentStatusRebootingStr), nil
2,077✔
127
        case DeviceDeploymentStatusPending:
2,093✔
128
                return []byte(DeviceDeploymentStatusPendingStr), nil
2,093✔
129
        case DeviceDeploymentStatusSuccess:
2,077✔
130
                return []byte(DeviceDeploymentStatusSuccessStr), nil
2,077✔
131
        case DeviceDeploymentStatusAborted:
2,077✔
132
                return []byte(DeviceDeploymentStatusAbortedStr), nil
2,077✔
133
        case DeviceDeploymentStatusNoArtifact:
2,077✔
134
                return []byte(DeviceDeploymentStatusNoArtifactStr), nil
2,077✔
135
        case DeviceDeploymentStatusAlreadyInst:
2,077✔
136
                return []byte(DeviceDeploymentStatusAlreadyInstStr), nil
2,077✔
137
        case DeviceDeploymentStatusDecommissioned:
2,073✔
138
                return []byte(DeviceDeploymentStatusDecommissionedStr), nil
2,073✔
139
        //case DeviceDeploymentStatusNew:
140
        //        return []byte(DeviceDeploymentStatusNewStr), nil
141
        case 0:
×
142
                return nil, errors.New("invalid status: variable not initialized")
×
143
        }
144
        return nil, errors.New("invalid status")
×
145
}
146

147
func (stat DeviceDeploymentStatus) String() string {
26,985✔
148
        ret, err := stat.MarshalText()
26,985✔
149
        if err != nil {
26,985✔
150
                return "invalid"
×
151
        }
×
152
        return string(ret)
26,985✔
153
}
154

155
func (stat *DeviceDeploymentStatus) UnmarshalText(b []byte) error {
5✔
156
        s := string(b)
5✔
157
        switch s {
5✔
158
        case DeviceDeploymentStatusFailureStr:
1✔
159
                *stat = DeviceDeploymentStatusFailure
1✔
160
        case DeviceDeploymentStatusPauseBeforeInstallStr:
1✔
161
                *stat = DeviceDeploymentStatusPauseBeforeInstall
1✔
162
        case DeviceDeploymentStatusPauseBeforeCommitStr:
1✔
163
                *stat = DeviceDeploymentStatusPauseBeforeCommit
1✔
164
        case DeviceDeploymentStatusPauseBeforeRebootStr:
1✔
165
                *stat = DeviceDeploymentStatusPauseBeforeReboot
1✔
166
        case DeviceDeploymentStatusDownloadingStr:
1✔
167
                *stat = DeviceDeploymentStatusDownloading
1✔
168
        case DeviceDeploymentStatusInstallingStr:
3✔
169
                *stat = DeviceDeploymentStatusInstalling
3✔
170
        case DeviceDeploymentStatusRebootingStr:
1✔
171
                *stat = DeviceDeploymentStatusRebooting
1✔
172
        case DeviceDeploymentStatusPendingStr:
×
173
                *stat = DeviceDeploymentStatusPending
×
174
        case DeviceDeploymentStatusSuccessStr:
1✔
175
                *stat = DeviceDeploymentStatusSuccess
1✔
176
        case DeviceDeploymentStatusAbortedStr:
3✔
177
                *stat = DeviceDeploymentStatusAborted
3✔
178
        case DeviceDeploymentStatusNoArtifactStr:
×
179
                *stat = DeviceDeploymentStatusNoArtifact
×
180
        case DeviceDeploymentStatusAlreadyInstStr:
×
181
                *stat = DeviceDeploymentStatusAlreadyInst
×
182
        case DeviceDeploymentStatusDecommissionedStr:
×
183
                *stat = DeviceDeploymentStatusDecommissioned
×
184
        //case DeviceDeploymentStatusNewStr:
185
        //        *stat = DeviceDeploymentStatusNew
186
        default:
×
187
                return errors.Errorf("invalid status for device '%s'", s)
×
188
        }
189
        return nil
5✔
190
}
191

192
func (stat DeviceDeploymentStatus) Active() bool {
3✔
193
        return stat >= DeviceDeploymentStatusActiveLow &&
3✔
194
                stat <= DeviceDeploymentStatusActiveHigh
3✔
195
}
3✔
196

197
func (stat DeviceDeploymentStatus) Successful() bool {
1✔
198
        return stat == DeviceDeploymentStatusSuccess
1✔
199
}
1✔
200

201
// DeviceDeploymentStatus is a helper type for reporting status changes through
202
// the layers
203
type DeviceDeploymentState struct {
204
        // status reported by device
205
        Status DeviceDeploymentStatus
206
        // substate reported by device
207
        SubState string `json:",omitempty" bson:",omitempty"`
208
        // finish time
209
        FinishTime *time.Time `json:",omitempty" bson:",omitempty"`
210
}
211

212
func (state DeviceDeploymentState) Validate() error {
1✔
213
        return validation.ValidateStruct(&state,
1✔
214
                validation.Field(&state.Status, validation.Required),
1✔
215
        )
1✔
216
}
1✔
217

218
type DeviceDeployment struct {
219
        // Active says whether the device's deployment status is in an active
220
        // state - in progress or pending.
221
        Active bool `json:"-" bson:"active"`
222

223
        // Internal field of initial creation of deployment
224
        Created *time.Time `json:"created" bson:"created"`
225

226
        // Update finish time
227
        Finished *time.Time `json:"finished,omitempty" bson:"finished,omitempty"`
228

229
        // Logical deletion time
230
        Deleted *time.Time `json:"deleted,omitempty" bson:"deleted,omitempty"`
231

232
        // Status
233
        Status DeviceDeploymentStatus `json:"status" bson:"status"`
234

235
        // Device id
236
        DeviceId string `json:"id" bson:"deviceid"`
237

238
        // Deployment id
239
        DeploymentId string `json:"-" bson:"deploymentid"`
240

241
        // ID
242
        Id string `json:"-" bson:"_id"`
243

244
        // Assigned software image
245
        Image *Image `json:"image,omitempty"`
246

247
        // deployments/next request from the device
248
        Request *DeploymentNextRequest `json:"-"`
249

250
        // Presence of deployment log
251
        IsLogAvailable bool `json:"log" bson:"log"`
252

253
        // Device reported substate
254
        SubState string `json:"substate,omitempty" bson:"substate,omitempty"`
255
}
256

257
func NewDeviceDeployment(deviceId, deploymentId string) *DeviceDeployment {
19✔
258

19✔
259
        now := time.Now()
19✔
260

19✔
261
        uid, err := uuid.NewRandom()
19✔
262
        if err != nil {
19✔
UNCOV
263
                panic(errors.Wrap(err, "failed to generate random uuid (v4)"))
×
264
        }
265
        id := uid.String()
19✔
266

19✔
267
        return &DeviceDeployment{
19✔
268
                Active:         true,
19✔
269
                Status:         DeviceDeploymentStatusPending,
19✔
270
                DeviceId:       deviceId,
19✔
271
                DeploymentId:   deploymentId,
19✔
272
                Id:             id,
19✔
273
                Created:        &now,
19✔
274
                IsLogAvailable: false,
19✔
275
        }
19✔
276
}
277

278
func (d DeviceDeployment) Validate() error {
16✔
279
        err := validation.ValidateStruct(&d,
16✔
280
                validation.Field(&d.Created, validation.Required),
16✔
281
                validation.Field(&d.Status, validation.Required, deviceDeploymentStatusValidator{}),
16✔
282
                validation.Field(&d.DeviceId, validation.Required),
16✔
283
                validation.Field(&d.DeploymentId, validation.Required, is.UUID),
16✔
284
                validation.Field(&d.Id, validation.Required, is.UUID),
16✔
285
        )
16✔
286
        if err != nil {
30✔
287
                return err
14✔
288
        }
14✔
289
        if d.Status.Active() {
4✔
290
                if !d.Active {
2✔
291
                        return ErrDeviceDeploymentStatusMismatch
×
UNCOV
292
                }
×
UNCOV
293
        } else if d.Active {
×
UNCOV
294
                return ErrDeviceDeploymentStatusMismatch
×
UNCOV
295
        }
×
296
        return nil
2✔
297
}
298

299
// Deployment statistics wrapper, each value carries a count of deployments
300
// aggregated by state.
301
type Stats map[string]int
302

303
func NewDeviceDeploymentStats() Stats {
2,073✔
304

2,073✔
305
        s := make(Stats, len(allStatuses))
2,073✔
306

2,073✔
307
        // populate statuses with 0s
2,073✔
308
        for _, k := range allStatuses {
29,010✔
309
                s[k.String()] = 0
26,937✔
310
        }
26,937✔
311

312
        return s
2,073✔
313
}
314

315
func (s Stats) Set(status DeviceDeploymentStatus, count int) {
25✔
316
        key := status.String()
25✔
317
        s[key] = count
25✔
318
}
25✔
319

320
func (s Stats) Inc(status DeviceDeploymentStatus) {
1✔
321
        var count int
1✔
322
        key := status.String()
1✔
323
        count = s[key]
1✔
324
        count++
1✔
325
        s[key] = count
1✔
326
}
1✔
327

UNCOV
328
func (s Stats) Get(status DeviceDeploymentStatus) int {
×
UNCOV
329
        key := status.String()
×
UNCOV
330
        return s[key]
×
UNCOV
331
}
×
332

333
func IsDeviceDeploymentStatusFinished(status DeviceDeploymentStatus) bool {
19✔
334
        if status == DeviceDeploymentStatusFailure || status == DeviceDeploymentStatusSuccess ||
19✔
335
                status == DeviceDeploymentStatusNoArtifact || status == DeviceDeploymentStatusAlreadyInst ||
19✔
336
                status == DeviceDeploymentStatusAborted || status == DeviceDeploymentStatusDecommissioned {
30✔
337
                return true
11✔
338
        }
11✔
339
        return false
9✔
340
}
341

342
// ActiveDeploymentStatuses lists statuses that represent deployment in active state (not finished).
343
func ActiveDeploymentStatuses() []DeviceDeploymentStatus {
1✔
344
        return []DeviceDeploymentStatus{
1✔
345
                DeviceDeploymentStatusPending,
1✔
346
                DeviceDeploymentStatusDownloading,
1✔
347
                DeviceDeploymentStatusInstalling,
1✔
348
                DeviceDeploymentStatusRebooting,
1✔
349
                DeviceDeploymentStatusPauseBeforeInstall,
1✔
350
                DeviceDeploymentStatusPauseBeforeCommit,
1✔
351
                DeviceDeploymentStatusPauseBeforeReboot,
1✔
352
        }
1✔
353
}
1✔
354

355
func InactiveDeploymentStatuses() []DeviceDeploymentStatus {
×
356
        return []DeviceDeploymentStatus{
×
357
                DeviceDeploymentStatusAlreadyInst,
×
358
                DeviceDeploymentStatusSuccess,
×
359
                DeviceDeploymentStatusFailure,
×
360
                DeviceDeploymentStatusNoArtifact,
×
361
                DeviceDeploymentStatusAlreadyInst,
×
UNCOV
362
                DeviceDeploymentStatusAborted,
×
UNCOV
363
                DeviceDeploymentStatusDecommissioned,
×
UNCOV
364
        }
×
UNCOV
365
}
×
366

367
// InstalledDeviceDeployment describes a deployment currently installed on the
368
// device, usually reported by a device
369
type InstalledDeviceDeployment struct {
370
        ArtifactName string            `json:"artifact_name"`
371
        DeviceType   string            `json:"device_type"`
372
        Provides     map[string]string `json:"artifact_provides,omitempty"`
373
}
374

375
// DeploymentNextRequest holds a deployments/next request
376
type DeploymentNextRequest struct {
377
        DeviceProvides   *InstalledDeviceDeployment `json:"device_provides"`
378
        UpdateControlMap bool                       `json:"update_control_map"`
379
}
380

381
func (i *DeploymentNextRequest) Validate() error {
×
382
        return validation.ValidateStruct(i,
×
383
                validation.Field(&i.DeviceProvides,
×
UNCOV
384
                        validation.Required,
×
UNCOV
385
                ),
×
UNCOV
386
        )
×
UNCOV
387
}
×
388

389
func (i *DeploymentNextRequest) String() string {
1✔
390
        j, err := json.Marshal(i)
1✔
391
        if err != nil {
1✔
UNCOV
392
                return "invalid request format"
×
UNCOV
393
        }
×
394
        return string(j)
1✔
395
}
396

397
func (i *InstalledDeviceDeployment) Validate() error {
1✔
398
        return validation.ValidateStruct(i,
1✔
399
                validation.Field(&i.ArtifactName,
1✔
400
                        validation.Required, lengthIn1To4096,
1✔
401
                ),
1✔
402
                validation.Field(&i.DeviceType,
1✔
403
                        validation.Required, lengthIn1To4096,
1✔
404
                ),
1✔
405
                validation.Field(&i.Provides, validation.Each(lengthIn1To4096)),
1✔
406
        )
1✔
407
}
1✔
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