package controller
import (
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
cache "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
runtime "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
util "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployutil "github.com/openshift/origin/pkg/deploy/util"
"github.com/golang/glog"
)
// DeploymentConfigChangeController watches for changes to DeploymentConfigs and regenerates them only
// when detecting a change to the PodTemplate of a DeploymentConfig containing a ConfigChange trigger.
type DeploymentConfigChangeController struct {
ChangeStrategy ChangeStrategy
NextDeploymentConfig func() *deployapi.DeploymentConfig
DeploymentStore cache.Store
Codec runtime.Codec
// Stop is an optional channel that controls when the controller exits
Stop <-chan struct{}
}
// ChangeStrategy knows how to generate and update DeploymentConfigs.
type ChangeStrategy interface {
GenerateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error)
UpdateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
}
// Run watches for config change events.
func (dc *DeploymentConfigChangeController) Run() {
go util.Until(func() { dc.HandleDeploymentConfig() }, 0, dc.Stop)
}
// HandleDeploymentConfig handles the next DeploymentConfig change that happens.
func (dc *DeploymentConfigChangeController) HandleDeploymentConfig() {
config := dc.NextDeploymentConfig()
hasChangeTrigger := false
for _, trigger := range config.Triggers {
if trigger.Type == deployapi.DeploymentTriggerOnConfigChange {
hasChangeTrigger = true
break
}
}
if !hasChangeTrigger {
glog.V(4).Infof("Config has no change trigger; skipping")
return
}
if config.LatestVersion == 0 {
glog.V(4).Infof("Creating new deployment for config %v", config.Name)
dc.generateDeployment(config, nil)
return
}
latestDeploymentID := deployutil.LatestDeploymentIDForConfig(config)
obj, exists := dc.DeploymentStore.Get(latestDeploymentID)
if !exists {
glog.V(4).Info("Ignoring config change due to lack of existing deployment")
return
}
deployment := obj.(*kapi.ReplicationController)
if deployedConfig, err := deployutil.DecodeDeploymentConfig(deployment, dc.Codec); err == nil {
if deployutil.PodSpecsEqual(config.Template.ControllerTemplate.Template.Spec, deployedConfig.Template.ControllerTemplate.Template.Spec) {
glog.V(4).Infof("Ignoring updated config %s with LatestVersion=%d because it matches deployed config %s", config.Name, config.LatestVersion, deployment.Name)
return
}
} else {
glog.V(0).Infof("Error decoding deploymentConfig from deployment %s: %v", deployment.Name, err)
}
dc.generateDeployment(config, deployment)
}
func (dc *DeploymentConfigChangeController) generateDeployment(config *deployapi.DeploymentConfig, deployment *kapi.ReplicationController) {
newConfig, err := dc.ChangeStrategy.GenerateDeploymentConfig(config.Namespace, config.Name)
if err != nil {
glog.V(2).Infof("Error generating new version of deploymentConfig %v: %#v", config.Name, err)
return
}
if deployment != nil {
glog.V(4).Infof("Updating config %s (LatestVersion: %d -> %d) to advance existing deployment %s", config.Name, config.LatestVersion, newConfig.LatestVersion, deployment.Name)
}
// set the trigger details for the new deployment config
causes := []*deployapi.DeploymentCause{}
causes = append(causes,
&deployapi.DeploymentCause{
Type: deployapi.DeploymentTriggerOnConfigChange,
})
newConfig.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.
if _, err = dc.ChangeStrategy.UpdateDeploymentConfig(config.Namespace, newConfig); err != nil {
glog.V(2).Infof("Error updating deploymentConfig %v: %#v", config.Name, err)
}
}