/
deployment.go
270 lines (237 loc) · 8.48 KB
/
deployment.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/*
Copyright 2019 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
stringer "github.com/openebs/maya/pkg/apis/stringer/v1alpha1"
"github.com/pkg/errors"
extnv1beta1 "k8s.io/api/extensions/v1beta1"
)
// Predicate abstracts conditional logic w.r.t the deployment instance
//
// NOTE:
// predicate is a functional approach versus traditional approach to mix
// conditions such as *if-else* within blocks of business logic
//
// NOTE:
// predicate approach enables clear separation of conditionals from
// imperatives i.e. actions that form the business logic
type Predicate func(*Deploy) bool
// Deploy is the wrapper over k8s deployment object
type Deploy struct {
// kubernetes deployment instance
object *extnv1beta1.Deployment
}
// Builder enables building an instance of
// deployment
type Builder struct {
deployment *Deploy // kubernetes deployment instance
checks []Predicate // predicate list for deploy
errors []error
}
// PredicateName type is wrapper over string.
// It is used to refer predicate and status msg.
type PredicateName string
const (
// PredicateProgressDeadlineExceeded refer to
// predicate IsProgressDeadlineExceeded.
PredicateProgressDeadlineExceeded PredicateName = "ProgressDeadlineExceeded"
// PredicateNotSpecSynced refer to predicate IsNotSpecSynced
PredicateNotSpecSynced PredicateName = "NotSpecSynced"
// PredicateOlderReplicaActive refer to predicate IsOlderReplicaActive
PredicateOlderReplicaActive PredicateName = "OlderReplicaActive"
// PredicateTerminationInProgress refer to predicate IsTerminationInProgress
PredicateTerminationInProgress PredicateName = "TerminationInProgress"
// PredicateUpdateInProgress refer to predicate IsUpdateInProgress.
PredicateUpdateInProgress PredicateName = "UpdateInProgress"
)
// String implements the stringer interface
func (d *Deploy) String() string {
return stringer.Yaml("deployment", d.object)
}
// GoString implements the goStringer interface
func (d *Deploy) GoString() string {
return d.String()
}
// NewBuilder returns a new instance of builder meant for deployment
func NewBuilder() *Builder {
return &Builder{
deployment: &Deploy{
object: &extnv1beta1.Deployment{},
},
}
}
// NewBuilderForAPIObject returns a new instance of builder
// for a given deployment object
func NewBuilderForAPIObject(deployment *extnv1beta1.Deployment) *Builder {
b := NewBuilder()
if deployment != nil {
b.deployment.object = deployment
} else {
b.errors = append(b.errors,
errors.New("nil deployment object given to get builder instance"))
}
return b
}
// Build returns a deployment instance
func (b *Builder) Build() (*Deploy, error) {
err := b.validate()
if err != nil {
return nil, errors.Wrapf(err,
"failed to build a deployment instance: %s",
b.deployment.object)
}
return b.deployment, nil
}
func (b *Builder) validate() error {
if len(b.errors) != 0 {
return errors.Errorf("failed to validate: build errors were found: %v", b.errors)
}
return nil
}
// AddCheck adds the predicate as a condition to be validated
// against the deployment instance
func (b *Builder) AddCheck(p Predicate) *Builder {
b.checks = append(b.checks, p)
return b
}
// AddChecks adds the provided predicates as conditions to be
// validated against the deployment instance
func (b *Builder) AddChecks(p []Predicate) *Builder {
for _, check := range p {
b.AddCheck(check)
}
return b
}
// IsRollout range over rolloutChecks map and check status of each predicate
// also it generates status message from rolloutStatuses using predicate key
func (d *Deploy) IsRollout() (PredicateName, bool) {
for pk, p := range RolloutChecks {
if p(d) {
return pk, false
}
}
return "", true
}
// FailedRollout returns rollout status message for fail condition
func (d *Deploy) FailedRollout(name PredicateName) *RolloutOutput {
return &RolloutOutput{
Message: RolloutStatuses[name](d),
IsRolledout: false,
}
}
// SuccessRollout returns rollout status message for success condition
func (d *Deploy) SuccessRollout() *RolloutOutput {
return &RolloutOutput{
Message: "deployment successfully rolled out",
IsRolledout: true,
}
}
// RolloutStatus returns rollout message of deployment instance
func (d *Deploy) RolloutStatus() (op *RolloutOutput, err error) {
pk, ok := d.IsRollout()
if ok {
return d.SuccessRollout(), nil
}
return d.FailedRollout(pk), nil
}
// RolloutStatusRaw returns rollout message of deployment instance
// in byte format
func (d *Deploy) RolloutStatusRaw() (op []byte, err error) {
message, err := d.RolloutStatus()
if err != nil {
return nil, err
}
return NewRollout(
withOutputObject(message)).
Raw()
}
// IsProgressDeadlineExceeded is used to check update is timed out or not.
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
// it is not rolled out.
func IsProgressDeadlineExceeded() Predicate {
return func(d *Deploy) bool {
return d.IsProgressDeadlineExceeded()
}
}
// IsProgressDeadlineExceeded is used to check update is timed out or not.
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
// it is not rolled out.
func (d *Deploy) IsProgressDeadlineExceeded() bool {
for _, cond := range d.object.Status.Conditions {
if cond.Type == extnv1beta1.DeploymentProgressing &&
cond.Reason == "ProgressDeadlineExceeded" {
return true
}
}
return false
}
// IsOlderReplicaActive check if older replica's are still active or not
// if Status.UpdatedReplicas < *Spec.Replicas then some of the replicas
// are updated and some of them are not.
func IsOlderReplicaActive() Predicate {
return func(d *Deploy) bool {
return d.IsOlderReplicaActive()
}
}
// IsOlderReplicaActive check if older replica's are still active or not
// if Status.UpdatedReplicas < *Spec.Replicas then some of the replicas
// are updated and some of them are not.
func (d *Deploy) IsOlderReplicaActive() bool {
return d.object.Spec.Replicas != nil && d.object.Status.UpdatedReplicas < *d.object.Spec.Replicas
}
// IsTerminationInProgress checks for older replicas are waiting to
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
// some of the older replicas are in running state because newer
// replicas are not in running state. It waits for newer replica to
// come into running state then terminate.
func IsTerminationInProgress() Predicate {
return func(d *Deploy) bool {
return d.IsTerminationInProgress()
}
}
// IsTerminationInProgress checks for older replicas are waiting to
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
// some of the older replicas are in running state because newer
// replicas are not in running state. It waits for newer replica to
// come into running state then terminate.
func (d *Deploy) IsTerminationInProgress() bool {
return d.object.Status.Replicas > d.object.Status.UpdatedReplicas
}
// IsUpdateInProgress Checks if all the replicas are updated or not. If
// Status.AvailableReplicas < Status.UpdatedReplicas then all the older
// replicas are not there but there are less number of availableReplicas
func IsUpdateInProgress() Predicate {
return func(d *Deploy) bool {
return d.IsUpdateInProgress()
}
}
// IsUpdateInProgress Checks if all the replicas are updated or not. If
// Status.AvailableReplicas < Status.UpdatedReplicas then all the older
// replicas are not there but there are less number of availableReplicas
func (d *Deploy) IsUpdateInProgress() bool {
return d.object.Status.AvailableReplicas < d.object.Status.UpdatedReplicas
}
// IsNotSyncSpec compare generation in status and spec and check if deployment
// spec is synced or not. If Generation <= Status.ObservedGeneration then
// deployment spec is not updated yet.
func IsNotSyncSpec() Predicate {
return func(d *Deploy) bool {
return d.IsNotSyncSpec()
}
}
// IsNotSyncSpec compare generation in status and spec and check if deployment
// spec is synced or not. If Generation <= Status.ObservedGeneration then
// deployment spec is not updated yet.
func (d *Deploy) IsNotSyncSpec() bool {
return d.object.Generation > d.object.Status.ObservedGeneration
}