@@ -4,16 +4,20 @@ import (
4
4
"fmt"
5
5
"sort"
6
6
"strconv"
7
+ "strings"
7
8
8
9
"github.com/golang/glog"
9
10
10
11
kapi "k8s.io/kubernetes/pkg/api"
11
12
"k8s.io/kubernetes/pkg/api/errors"
12
- "k8s.io/kubernetes/pkg/client/record"
13
+ "k8s.io/kubernetes/pkg/fields"
14
+ "k8s.io/kubernetes/pkg/labels"
13
15
"k8s.io/kubernetes/pkg/util"
14
16
17
+ osclient "github.com/openshift/origin/pkg/client"
15
18
deployapi "github.com/openshift/origin/pkg/deploy/api"
16
19
deployutil "github.com/openshift/origin/pkg/deploy/util"
20
+ imageapi "github.com/openshift/origin/pkg/image/api"
17
21
)
18
22
19
23
// DeploymentConfigController is responsible for creating a new deployment when:
@@ -31,9 +35,10 @@ import (
31
35
type DeploymentConfigController struct {
32
36
// deploymentClient provides access to deployments.
33
37
deploymentClient deploymentClient
38
+ // osClient provides access to Origin resources
39
+ osClient osclient.Interface
34
40
// makeDeployment knows how to make a deployment from a config.
35
41
makeDeployment func (* deployapi.DeploymentConfig ) (* kapi.ReplicationController , error )
36
- recorder record.EventRecorder
37
42
}
38
43
39
44
// fatalError is an error which can't be retried.
@@ -43,33 +48,32 @@ type fatalError string
43
48
type transientError string
44
49
45
50
func (e fatalError ) Error () string {
46
- return fmt .Sprintf ("fatal error handling DeploymentConfig : %s" , string (e ))
51
+ return fmt .Sprintf ("fatal error handling deployment config : %s" , string (e ))
47
52
}
48
53
func (e transientError ) Error () string {
49
- return "transient error handling DeploymentConfig : " + string (e )
54
+ return "transient error handling deployment config : " + string (e )
50
55
}
51
56
52
57
// Handle processes config and creates a new deployment if necessary.
53
58
func (c * DeploymentConfigController ) Handle (config * deployapi.DeploymentConfig ) error {
59
+ // Inspect a deployment configuration every time the controller reconciles it
60
+ details , existingDeployments , latestDeploymentExists , err := c .findDetails (config )
61
+ if err != nil {
62
+ return err
63
+ }
64
+ config , err = c .updateDetails (config , details )
65
+ if err != nil {
66
+ return transientError (err .Error ())
67
+ }
68
+
54
69
// Only deploy when the version has advanced past 0.
55
70
if config .LatestVersion == 0 {
56
71
glog .V (5 ).Infof ("Waiting for first version of %s" , deployutil .LabelForDeploymentConfig (config ))
57
72
return nil
58
73
}
59
74
60
- // Check if any existing inflight deployments (any non-terminal state).
61
- existingDeployments , err := c .deploymentClient .listDeploymentsForConfig (config .Namespace , config .Name )
62
- if err != nil {
63
- return fmt .Errorf ("couldn't list Deployments for DeploymentConfig %s: %v" , deployutil .LabelForDeploymentConfig (config ), err )
64
- }
65
75
var inflightDeployment * kapi.ReplicationController
66
- latestDeploymentExists := false
67
76
for _ , deployment := range existingDeployments .Items {
68
- // check if this is the latest deployment
69
- // we'll return after we've dealt with the multiple-active-deployments case
70
- if deployutil .DeploymentVersionFor (& deployment ) == config .LatestVersion {
71
- latestDeploymentExists = true
72
- }
73
77
74
78
deploymentStatus := deployutil .DeploymentStatusFor (& deployment )
75
79
switch deploymentStatus {
@@ -93,9 +97,9 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig)
93
97
deploymentForCancellation .Annotations [deployapi .DeploymentCancelledAnnotation ] = deployapi .DeploymentCancelledAnnotationValue
94
98
deploymentForCancellation .Annotations [deployapi .DeploymentStatusReasonAnnotation ] = deployapi .DeploymentCancelledNewerDeploymentExists
95
99
if _ , err := c .deploymentClient .updateDeployment (deploymentForCancellation .Namespace , deploymentForCancellation ); err != nil {
96
- util .HandleError (fmt .Errorf ("couldn't cancel Deployment %s: %v" , deployutil .LabelForDeployment (deploymentForCancellation ), err ))
100
+ util .HandleError (fmt .Errorf ("couldn't cancel deployment %s: %v" , deployutil .LabelForDeployment (deploymentForCancellation ), err ))
97
101
}
98
- glog .V (4 ).Infof ("Cancelled Deployment %s for DeploymentConfig %s" , deployutil .LabelForDeployment (deploymentForCancellation ), deployutil .LabelForDeploymentConfig (config ))
102
+ glog .V (4 ).Infof ("Cancelled deployment %s for deployment config %s" , deployutil .LabelForDeployment (deploymentForCancellation ), deployutil .LabelForDeploymentConfig (config ))
99
103
}
100
104
}
101
105
@@ -107,22 +111,22 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig)
107
111
// check to see if there are inflight deployments
108
112
if inflightDeployment != nil {
109
113
// raise a transientError so that the deployment config can be re-queued
110
- glog .V (4 ).Infof ("Found previous inflight Deployment for %s - will requeue" , deployutil .LabelForDeploymentConfig (config ))
111
- return transientError (fmt .Sprintf ("found previous inflight Deployment for %s - requeuing" , deployutil .LabelForDeploymentConfig (config )))
114
+ glog .V (4 ).Infof ("Found previous inflight deployment for %s - will requeue" , deployutil .LabelForDeploymentConfig (config ))
115
+ return transientError (fmt .Sprintf ("found previous inflight deployment for %s - requeuing" , deployutil .LabelForDeploymentConfig (config )))
112
116
}
113
117
114
118
// Try and build a deployment for the config.
115
119
deployment , err := c .makeDeployment (config )
116
120
if err != nil {
117
- return fatalError (fmt .Sprintf ("couldn't make Deployment from (potentially invalid) DeploymentConfig %s: %v" , deployutil .LabelForDeploymentConfig (config ), err ))
121
+ return fatalError (fmt .Sprintf ("couldn't make deployment from (potentially invalid) deployment config %s: %v" , deployutil .LabelForDeploymentConfig (config ), err ))
118
122
}
119
123
120
124
// Compute the desired replicas for the deployment. Use the last completed
121
125
// deployment's current replica count, or the config template if there is no
122
126
// prior completed deployment available.
123
127
desiredReplicas := config .Template .ControllerTemplate .Replicas
124
128
if len (existingDeployments .Items ) > 0 {
125
- sort .Sort (deployutil .DeploymentsByLatestVersionDesc (existingDeployments .Items ))
129
+ sort .Sort (deployutil .ByLatestVersionDesc (existingDeployments .Items ))
126
130
for _ , existing := range existingDeployments .Items {
127
131
if deployutil .DeploymentStatusFor (& existing ) == deployapi .DeploymentStatusComplete {
128
132
desiredReplicas = existing .Spec .Replicas
@@ -135,19 +139,18 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig)
135
139
136
140
// Create the deployment.
137
141
if _ , err := c .deploymentClient .createDeployment (config .Namespace , deployment ); err == nil {
138
- glog .V (4 ).Infof ("Created Deployment for DeploymentConfig %s" , deployutil .LabelForDeploymentConfig (config ))
142
+ glog .V (4 ).Infof ("Created deployment for deployment config %s" , deployutil .LabelForDeploymentConfig (config ))
139
143
return nil
140
144
} else {
141
145
// If the deployment was already created, just move on. The cache could be stale, or another
142
146
// process could have already handled this update.
143
147
if errors .IsAlreadyExists (err ) {
144
- glog .V (4 ).Infof ("Deployment already exists for DeploymentConfig %s" , deployutil .LabelForDeploymentConfig (config ))
148
+ glog .V (4 ).Infof ("Deployment already exists for deployment config %s" , deployutil .LabelForDeploymentConfig (config ))
145
149
return nil
146
150
}
147
151
148
- // log an event if the deployment could not be created that the user can discover
149
- c .recorder .Eventf (config , "failedCreate" , "Error creating: %v" , err )
150
- return fmt .Errorf ("couldn't create Deployment for DeploymentConfig %s: %v" , deployutil .LabelForDeploymentConfig (config ), err )
152
+ glog .Warningf ("Cannot create latest deployment for deployment config %q: %v" , deployutil .LabelForDeploymentConfig (config ), err )
153
+ return fmt .Errorf ("couldn't create deployment for deployment config %s: %v" , deployutil .LabelForDeploymentConfig (config ), err )
151
154
}
152
155
}
153
156
@@ -178,3 +181,112 @@ func (i *deploymentClientImpl) listDeploymentsForConfig(namespace, configName st
178
181
func (i * deploymentClientImpl ) updateDeployment (namespace string , deployment * kapi.ReplicationController ) (* kapi.ReplicationController , error ) {
179
182
return i .updateDeploymentFunc (namespace , deployment )
180
183
}
184
+
185
+ // findDetails inspects the given deployment configuration for any failure causes
186
+ // and returns any found details about it
187
+ func (c * DeploymentConfigController ) findDetails (config * deployapi.DeploymentConfig ) (string , * kapi.ReplicationControllerList , bool , error ) {
188
+ // Check if any existing inflight deployments (any non-terminal state).
189
+ existingDeployments , err := c .deploymentClient .listDeploymentsForConfig (config .Namespace , config .Name )
190
+ if err != nil {
191
+ return "" , nil , false , fmt .Errorf ("couldn't list deployments for deployment config %q: %v" , deployutil .LabelForDeploymentConfig (config ), err )
192
+ }
193
+ // check if the latest deployment exists
194
+ // we'll return after we've dealt with the multiple-active-deployments case
195
+ latestDeploymentExists , latestDeploymentStatus := deployutil .LatestDeploymentInfo (config , existingDeployments )
196
+ if latestDeploymentExists && latestDeploymentStatus != deployapi .DeploymentStatusFailed {
197
+ // If the latest deployment exists and is not failed, clear the dc message
198
+ return "" , existingDeployments , latestDeploymentExists , nil
199
+ }
200
+
201
+ // Rest of the code will handle non-existing or failed latest deployment causes
202
+ // TODO: Inspect pod logs in case of failed latest
203
+
204
+ details := ""
205
+ allDetails := []string {}
206
+ willHaveImage , hasImageChangeTrigger := false , false
207
+ if config .Details != nil && len (config .Details .Message ) > 0 {
208
+ // Populate details with the previous message so that in case we stumble upon
209
+ // an unexpected client error, the message won't be overwritten
210
+ details = config .Details .Message
211
+ }
212
+ // Look into image change triggers and find out possible deployment failures such as
213
+ // missing image stream tags with or without build configurations pointing at them
214
+ for _ , trigger := range config .Triggers {
215
+ if trigger .Type != deployapi .DeploymentTriggerOnImageChange || trigger .ImageChangeParams == nil || ! trigger .ImageChangeParams .Automatic {
216
+ continue
217
+ }
218
+ hasImageChangeTrigger = true
219
+ name := trigger .ImageChangeParams .From .Name
220
+ tag := trigger .ImageChangeParams .Tag
221
+ istag := imageapi .JoinImageStreamTag (name , tag )
222
+
223
+ // Check if the image stream tag pointed by the trigger exists
224
+ if _ , err := c .osClient .ImageStreamTags (config .Namespace ).Get (name , tag ); err != nil {
225
+ if ! errors .IsNotFound (err ) {
226
+ glog .V (2 ).Infof ("Error while trying to get image stream tag %q: %v" , istag , err )
227
+ return details , existingDeployments , latestDeploymentExists , nil
228
+ }
229
+ // In case the image stream tag was not found, then it either doesn't exist or doesn't exist yet
230
+ // (a build configuration output points to it so it's going to be populated at some point in the
231
+ // future)
232
+ details = fmt .Sprintf ("The image trigger for image stream tag %s will have no effect because image stream tag %s does not exist." , istag , istag )
233
+ bcList , err := c .osClient .BuildConfigs (kapi .NamespaceAll ).List (labels .Everything (), fields .Everything ())
234
+ if err != nil {
235
+ glog .V (2 ).Infof ("Error while trying to list build configs: %v" , err )
236
+ return details , existingDeployments , latestDeploymentExists , nil
237
+ }
238
+
239
+ for _ , bc := range bcList .Items {
240
+ if bc .Spec .Output .To != nil && bc .Spec .Output .To .Kind == "ImageStreamTag" {
241
+ parts := strings .Split (bc .Spec .Output .To .Name , ":" )
242
+ if len (parts ) != 2 {
243
+ glog .V (2 ).Infof ("Invalid image stream tag: %q" , bc .Spec .Output .To .Name )
244
+ return details , existingDeployments , latestDeploymentExists , nil
245
+ }
246
+ if parts [0 ] == name && parts [1 ] == tag {
247
+ details = fmt .Sprintf ("The image trigger for image stream tag %s will have no effect because image stream tag %s does not exist. If image stream tag %s is expected, check buildconfig %s which produces image stream tag %s." , istag , istag , istag , bc .Name , istag )
248
+ break
249
+ }
250
+ }
251
+ }
252
+ // Try to see if the image stream exists, if not then the build will never be able to update the
253
+ // tag in question
254
+ if _ , err := c .osClient .ImageStreams (config .Namespace ).Get (name ); err != nil {
255
+ glog .V (2 ).Infof ("Error while trying to get image stream %q: %v" , name , err )
256
+ if errors .IsNotFound (err ) {
257
+ details = fmt .Sprintf ("The image trigger for image stream tag %s will have no effect because image stream %s does not exist." , istag , name )
258
+ }
259
+ }
260
+ } else {
261
+ willHaveImage = true
262
+ }
263
+ allDetails = append (allDetails , details )
264
+ }
265
+
266
+ if len (allDetails ) > 1 {
267
+ for i := range allDetails {
268
+ allDetails [i ] = fmt .Sprintf ("* %s" , allDetails [i ])
269
+ }
270
+ // Prepend multiple errors warning
271
+ multipleErrWarning := fmt .Sprintf ("Deployment config %q blocked by multiple errors:\n " , config .Name )
272
+ allDetails = append ([]string {multipleErrWarning }, allDetails ... )
273
+ }
274
+
275
+ if hasImageChangeTrigger && ! willHaveImage {
276
+ allDetails = append (allDetails , fmt .Sprintf ("Deployment config %q will never have an image." , config .Name ))
277
+ }
278
+
279
+ return strings .Join (allDetails , "\n " ), existingDeployments , latestDeploymentExists , nil
280
+ }
281
+
282
+ // updateDetails updates a deployment configuration with the provided details
283
+ func (c * DeploymentConfigController ) updateDetails (config * deployapi.DeploymentConfig , details string ) (* deployapi.DeploymentConfig , error ) {
284
+ if config .Details == nil {
285
+ config .Details = new (deployapi.DeploymentDetails )
286
+ }
287
+ if details != config .Details .Message {
288
+ config .Details .Message = details
289
+ return c .osClient .DeploymentConfigs (config .Namespace ).Update (config )
290
+ }
291
+ return config , nil
292
+ }
0 commit comments