-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
controller.go
145 lines (122 loc) · 6.36 KB
/
controller.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
package configchange
import (
"fmt"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
// DeploymentConfigChangeController increments the version of a
// DeploymentConfig which has a config change trigger when a pod template
// change is detected.
//
// Use the DeploymentConfigChangeControllerFactory to create this controller.
type DeploymentConfigChangeController struct {
// changeStrategy knows how to generate and update DeploymentConfigs.
changeStrategy changeStrategy
// decodeConfig knows how to decode the deploymentConfig from a deployment's annotations.
decodeConfig func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error)
}
// fatalError is an error which can't be retried.
type fatalError string
func (e fatalError) Error() string {
return fmt.Sprintf("fatal error handling configuration: %s", string(e))
}
// Handle processes change triggers for config.
func (c *DeploymentConfigChangeController) Handle(config *deployapi.DeploymentConfig) error {
if !deployutil.HasChangeTrigger(config) {
glog.V(5).Infof("Ignoring DeploymentConfig %s; no change triggers detected", deployutil.LabelForDeploymentConfig(config))
return nil
}
if config.Status.LatestVersion == 0 {
_, _, err := c.generateDeployment(config)
if err != nil {
if kerrors.IsConflict(err) {
return fatalError(fmt.Sprintf("DeploymentConfig %s updated since retrieval; aborting trigger: %v", deployutil.LabelForDeploymentConfig(config), err))
}
glog.V(4).Infof("Couldn't create initial deployment for deploymentConfig %q: %v", deployutil.LabelForDeploymentConfig(config), err)
return nil
}
glog.V(4).Infof("Created initial deployment for deploymentConfig %q", deployutil.LabelForDeploymentConfig(config))
return nil
}
latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config)
deployment, err := c.changeStrategy.getDeployment(config.Namespace, latestDeploymentName)
if err != nil {
// If there's no deployment for the latest config, we have no basis of
// comparison. It's the responsibility of the deployment config controller
// to make the deployment for the config, so return early.
if kerrors.IsNotFound(err) {
glog.V(5).Infof("Ignoring change for DeploymentConfig %s; no existing Deployment found", deployutil.LabelForDeploymentConfig(config))
return nil
}
return fmt.Errorf("couldn't retrieve Deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err)
}
deployedConfig, err := c.decodeConfig(deployment)
if err != nil {
return fatalError(fmt.Sprintf("error decoding DeploymentConfig from Deployment %s for DeploymentConfig %s: %v", deployutil.LabelForDeployment(deployment), deployutil.LabelForDeploymentConfig(config), err))
}
// Detect template diffs, and return early if there aren't any changes.
if kapi.Semantic.DeepEqual(config.Spec.Template, deployedConfig.Spec.Template) {
glog.V(5).Infof("Ignoring DeploymentConfig change for %s (latestVersion=%d); same as Deployment %s", deployutil.LabelForDeploymentConfig(config), config.Status.LatestVersion, deployutil.LabelForDeployment(deployment))
return nil
}
// There was a template diff, so generate a new config version.
fromVersion, toVersion, err := c.generateDeployment(config)
if err != nil {
if kerrors.IsConflict(err) {
return fatalError(fmt.Sprintf("DeploymentConfig %s updated since retrieval; aborting trigger: %v", deployutil.LabelForDeploymentConfig(config), err))
}
return fmt.Errorf("couldn't generate deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err)
}
glog.V(4).Infof("Updated DeploymentConfig %s from version %d to %d for existing deployment %s", deployutil.LabelForDeploymentConfig(config), fromVersion, toVersion, deployutil.LabelForDeployment(deployment))
return nil
}
func (c *DeploymentConfigChangeController) generateDeployment(config *deployapi.DeploymentConfig) (int, int, error) {
newConfig, err := c.changeStrategy.generateDeploymentConfig(config.Namespace, config.Name)
if err != nil {
return config.Status.LatestVersion, 0, err
}
if newConfig.Status.LatestVersion == config.Status.LatestVersion {
newConfig.Status.LatestVersion++
}
// set the trigger details for the new deployment config
causes := []*deployapi.DeploymentCause{}
causes = append(causes,
&deployapi.DeploymentCause{
Type: deployapi.DeploymentTriggerOnConfigChange,
})
newConfig.Status.Details = &deployapi.DeploymentDetails{
Causes: causes,
}
// This update is atomic. If it fails because a newer resource was already persisted, that's
// okay - we can just ignore the update for the old resource and any changes to the more
// current config will be captured in future events.
updatedConfig, err := c.changeStrategy.updateDeploymentConfig(config.Namespace, newConfig)
if err != nil {
return config.Status.LatestVersion, newConfig.Status.LatestVersion, err
}
return config.Status.LatestVersion, updatedConfig.Status.LatestVersion, nil
}
// changeStrategy knows how to generate and update DeploymentConfigs.
type changeStrategy interface {
getDeployment(namespace, name string) (*kapi.ReplicationController, error)
generateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error)
updateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
}
// changeStrategyImpl is a pluggable changeStrategy.
type changeStrategyImpl struct {
getDeploymentFunc func(namespace, name string) (*kapi.ReplicationController, error)
generateDeploymentConfigFunc func(namespace, name string) (*deployapi.DeploymentConfig, error)
updateDeploymentConfigFunc func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
}
func (i *changeStrategyImpl) getDeployment(namespace, name string) (*kapi.ReplicationController, error) {
return i.getDeploymentFunc(namespace, name)
}
func (i *changeStrategyImpl) generateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error) {
return i.generateDeploymentConfigFunc(namespace, name)
}
func (i *changeStrategyImpl) updateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
return i.updateDeploymentConfigFunc(namespace, config)
}