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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

48.22
/backend/services/deployments/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) {
1✔
112
        switch stat {
1✔
113
        case DeviceDeploymentStatusFailure:
1✔
114
                return []byte(DeviceDeploymentStatusFailureStr), nil
1✔
115
        case DeviceDeploymentStatusPauseBeforeInstall:
1✔
116
                return []byte(DeviceDeploymentStatusPauseBeforeInstallStr), nil
1✔
117
        case DeviceDeploymentStatusPauseBeforeCommit:
1✔
118
                return []byte(DeviceDeploymentStatusPauseBeforeCommitStr), nil
1✔
119
        case DeviceDeploymentStatusPauseBeforeReboot:
1✔
120
                return []byte(DeviceDeploymentStatusPauseBeforeRebootStr), nil
1✔
121
        case DeviceDeploymentStatusDownloading:
1✔
122
                return []byte(DeviceDeploymentStatusDownloadingStr), nil
1✔
123
        case DeviceDeploymentStatusInstalling:
1✔
124
                return []byte(DeviceDeploymentStatusInstallingStr), nil
1✔
125
        case DeviceDeploymentStatusRebooting:
1✔
126
                return []byte(DeviceDeploymentStatusRebootingStr), nil
1✔
127
        case DeviceDeploymentStatusPending:
1✔
128
                return []byte(DeviceDeploymentStatusPendingStr), nil
1✔
129
        case DeviceDeploymentStatusSuccess:
1✔
130
                return []byte(DeviceDeploymentStatusSuccessStr), nil
1✔
131
        case DeviceDeploymentStatusAborted:
1✔
132
                return []byte(DeviceDeploymentStatusAbortedStr), nil
1✔
133
        case DeviceDeploymentStatusNoArtifact:
1✔
134
                return []byte(DeviceDeploymentStatusNoArtifactStr), nil
1✔
135
        case DeviceDeploymentStatusAlreadyInst:
1✔
136
                return []byte(DeviceDeploymentStatusAlreadyInstStr), nil
1✔
137
        case DeviceDeploymentStatusDecommissioned:
1✔
138
                return []byte(DeviceDeploymentStatusDecommissionedStr), nil
1✔
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 {
1✔
148
        ret, err := stat.MarshalText()
1✔
149
        if err != nil {
1✔
150
                return "invalid"
×
151
        }
×
152
        return string(ret)
1✔
153
}
154

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

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

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

UNCOV
208
func (state DeviceDeploymentState) Validate() error {
×
UNCOV
209
        return validation.ValidateStruct(&state,
×
UNCOV
210
                validation.Field(&state.Status, validation.Required),
×
UNCOV
211
        )
×
UNCOV
212
}
×
213

214
type DeviceDeployment struct {
215
        // Active says whether the device's deployment status is in an active
216
        // state - in progress or pending.
217
        Active bool `json:"-" bson:"active"`
218

219
        // Internal field of initial creation of deployment
220
        Created *time.Time `json:"created" bson:"created"`
221

222
        // Internal field of start of the deployment (after /next call)
223
        Started *time.Time `json:"started,omitempty" bson:"started"`
224

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

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

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

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

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

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

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

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

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

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

256
func NewDeviceDeployment(deviceId, deploymentId string) *DeviceDeployment {
1✔
257

1✔
258
        now := time.Now()
1✔
259

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

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

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

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

302
func NewDeviceDeploymentStats() Stats {
1✔
303

1✔
304
        s := make(Stats, len(allStatuses))
1✔
305

1✔
306
        // populate statuses with 0s
1✔
307
        for _, k := range allStatuses {
2✔
308
                s[k.String()] = 0
1✔
309
        }
1✔
310

311
        return s
1✔
312
}
313

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

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

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

332
func IsDeviceDeploymentStatusFinished(status DeviceDeploymentStatus) bool {
1✔
333
        if status == DeviceDeploymentStatusFailure || status == DeviceDeploymentStatusSuccess ||
1✔
334
                status == DeviceDeploymentStatusNoArtifact || status == DeviceDeploymentStatusAlreadyInst ||
1✔
335
                status == DeviceDeploymentStatusAborted || status == DeviceDeploymentStatusDecommissioned {
2✔
336
                return true
1✔
337
        }
1✔
338
        return false
1✔
339
}
340

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

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

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

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

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

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

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