1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

Restore immutable fields

This commit is contained in:
Ewout Prangsma 2018-02-20 17:12:05 +01:00
parent b9fc6db898
commit a90a14562b
No known key found for this signature in database
GPG key ID: 4DBAD380D93D0698
3 changed files with 150 additions and 9 deletions

View file

@ -361,6 +361,23 @@ func (s *ServerGroupSpec) SetDefaults(group ServerGroup, used bool, mode Deploym
}
}
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset.
func (s ServerGroupSpec) ResetImmutableFields(group ServerGroup, fieldPrefix string, target *ServerGroupSpec) []string {
var resetFields []string
if group == ServerGroupAgents {
if s.Count != target.Count {
target.Count = s.Count
resetFields = append(resetFields, fieldPrefix+".count")
}
}
if s.StorageClassName != target.StorageClassName {
target.StorageClassName = s.StorageClassName
resetFields = append(resetFields, fieldPrefix+".storageClassName")
}
return resetFields
}
// DeploymentSpec contains the spec part of a ArangoDeployment resource.
type DeploymentSpec struct {
Mode DeploymentMode `json:"mode,omitempty"`
@ -476,3 +493,37 @@ func (s *DeploymentSpec) Validate() error {
func (s DeploymentSpec) IsDevelopment() bool {
return s.Environment == EnvironmentDevelopment
}
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset.
// Field names are relative to `spec.`.
func (s DeploymentSpec) ResetImmutableFields(target *DeploymentSpec) []string {
var resetFields []string
if s.Mode != target.Mode {
target.Mode = s.Mode
resetFields = append(resetFields, "mode")
}
if s.StorageEngine != target.StorageEngine {
target.StorageEngine = s.StorageEngine
resetFields = append(resetFields, "storageEngine")
}
if l := s.Single.ResetImmutableFields(ServerGroupSingle, "single", &target.Single); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.Agents.ResetImmutableFields(ServerGroupAgents, "agents", &target.Agents); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.DBServers.ResetImmutableFields(ServerGroupDBServers, "dbservers", &target.DBServers); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.Coordinators.ResetImmutableFields(ServerGroupCoordinators, "coordinators", &target.Coordinators); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.SyncMasters.ResetImmutableFields(ServerGroupSyncMasters, "syncmasters", &target.SyncMasters); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.SyncWorkers.ResetImmutableFields(ServerGroupSyncWorkers, "syncworkers", &target.SyncWorkers); l != nil {
resetFields = append(resetFields, l...)
}
return resetFields
}

View file

@ -218,7 +218,36 @@ func (d *Deployment) run() {
// handleArangoDeploymentUpdatedEvent is called when the deployment is updated by the user.
func (d *Deployment) handleArangoDeploymentUpdatedEvent(event *deploymentEvent) error {
// TODO
log := d.deps.Log.With().Str("deployment", event.Deployment.GetName()).Logger()
newAPIObject := event.Deployment.DeepCopy()
newAPIObject.Spec.SetDefaults()
newAPIObject.Status = d.status
resetFields := d.apiObject.Spec.ResetImmutableFields(&newAPIObject.Spec)
if len(resetFields) > 0 {
log.Debug().Strs("fields", resetFields).Msg("Found modified immutable fields")
}
if err := newAPIObject.Spec.Validate(); err != nil {
d.createEvent(k8sutil.NewErrorEvent("Validation failed", err, d.apiObject))
// Try to reset object
if err := d.updateCRSpec(d.apiObject.Spec); err != nil {
log.Error().Err(err).Msg("Restore original spec failed")
d.createEvent(k8sutil.NewErrorEvent("Restore original failed", err, d.apiObject))
}
return nil
}
if len(resetFields) > 0 {
for _, fieldName := range resetFields {
log.Debug().Str("field", fieldName).Msg("Reset immutable field")
d.createEvent(k8sutil.NewImmutableFieldEvent(fieldName, d.apiObject))
}
}
// Save updated spec
if err := d.updateCRSpec(newAPIObject.Spec); err != nil {
return maskAny(fmt.Errorf("failed to update ArangoDeployment spec: %v", err))
}
return nil
}
@ -240,16 +269,67 @@ func (d *Deployment) updateCRStatus() error {
// Send update to API server
update := d.apiObject.DeepCopy()
update.Status = d.status
newAPIObject, err := d.deps.DatabaseCRCli.DatabaseV1alpha().ArangoDeployments(d.apiObject.Namespace).Update(update)
if err != nil {
return maskAny(fmt.Errorf("failed to update ArangoDeployment status: %v", err))
attempt := 0
for {
attempt++
update.Status = d.status
ns := d.apiObject.GetNamespace()
newAPIObject, err := d.deps.DatabaseCRCli.DatabaseV1alpha().ArangoDeployments(ns).Update(update)
if err == nil {
// Update internal object
d.apiObject = newAPIObject
return nil
}
if attempt < 10 && k8sutil.IsConflict(err) {
// API object may have been changed already,
// Reload api object and try again
var current *api.ArangoDeployment
current, err = d.deps.DatabaseCRCli.DatabaseV1alpha().ArangoDeployments(ns).Get(update.GetName(), metav1.GetOptions{})
if err == nil {
update = current.DeepCopy()
continue
}
}
if err != nil {
d.deps.Log.Debug().Err(err).Msg("failed to patch ArangoDeployment status")
return maskAny(fmt.Errorf("failed to patch ArangoDeployment status: %v", err))
}
}
}
// Update internal object
d.apiObject = newAPIObject
return nil
// Update the spec part of the API object (d.apiObject)
// to the given object, while preserving the status.
// On success, d.apiObject is updated.
func (d *Deployment) updateCRSpec(newSpec api.DeploymentSpec) error {
// Send update to API server
update := d.apiObject.DeepCopy()
attempt := 0
for {
attempt++
update.Spec = newSpec
update.Status = d.status
ns := d.apiObject.GetNamespace()
newAPIObject, err := d.deps.DatabaseCRCli.DatabaseV1alpha().ArangoDeployments(ns).Update(update)
if err == nil {
// Update internal object
d.apiObject = newAPIObject
return nil
}
if attempt < 10 && k8sutil.IsConflict(err) {
// API object may have been changed already,
// Reload api object and try again
var current *api.ArangoDeployment
current, err = d.deps.DatabaseCRCli.DatabaseV1alpha().ArangoDeployments(ns).Get(update.GetName(), metav1.GetOptions{})
if err == nil {
update = current.DeepCopy()
continue
}
}
if err != nil {
d.deps.Log.Debug().Err(err).Msg("failed to patch ArangoDeployment spec")
return maskAny(fmt.Errorf("failed to patch ArangoDeployment spec: %v", err))
}
}
}
// failOnError reports the given error and sets the deployment status to failed.

View file

@ -68,6 +68,16 @@ func NewPodGoneEvent(podName, role string, apiObject APIObject) *v1.Event {
return event
}
// NewImmutableFieldEvent creates an event indicating that an attempt was made to change a field
// that is immutable.
func NewImmutableFieldEvent(fieldName string, apiObject APIObject) *v1.Event {
event := newDeploymentEvent(apiObject)
event.Type = v1.EventTypeNormal
event.Reason = "Immutable Field Change"
event.Message = fmt.Sprintf("Changing field %s is not possible. It has been reset to its original value.", fieldName)
return event
}
// NewErrorEvent creates an even of type error.
func NewErrorEvent(reason string, err error, apiObject APIObject) *v1.Event {
event := newDeploymentEvent(apiObject)