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 { _, _, abort, 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 } if !abort { 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, abort, 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) } if !abort { 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, bool, error) { newConfig, err := c.changeStrategy.generateDeploymentConfig(config.Namespace, config.Name) if err != nil { return -1, -1, false, err } // The generator returns a cause only when there is an image change. If the configchange // controller detects an image change, it should just quit, otherwise it is racing with // the imagechange controller. if newConfig.Status.LatestVersion != config.Status.LatestVersion && deployutil.CauseFromAutomaticImageChange(newConfig) { return -1, -1, true, nil } 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, false, err } return config.Status.LatestVersion, updatedConfig.Status.LatestVersion, false, 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) }