diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 1ca11e4304384..52a4a18f86f9c 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -2476,6 +2476,16 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList { // handle updateable fields by munging those fields prior to deep equal comparison. mungedPod := *newPod + + // allow hostname and subdomain to be updated if they are empty. This allows for migration between the beta + // annotations and the GA field when upgrading between Kubernetes 1.6.x and 1.7.x. + if oldPod.Spec.Hostname == "" { + mungedPod.Spec.Hostname = oldPod.Spec.Hostname + } + if oldPod.Spec.Subdomain == "" { + mungedPod.Spec.Subdomain = oldPod.Spec.Subdomain + } + // munge containers[*].image var newContainers []api.Container for ix, container := range mungedPod.Spec.Containers { diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index dfa5a6a658baf..69a1cb26547be 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -4836,6 +4836,54 @@ func TestValidatePodUpdate(t *testing.T) { false, "added invalid new toleration to existing tolerations in pod spec updates", }, + { + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Hostname: "bar"}, + }, + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Hostname: ""}, + }, + true, + "update empty hostname", + }, + { + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Subdomain: "bar"}, + }, + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Subdomain: ""}, + }, + true, + "update empty subdomain", + }, + { + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Hostname: "bar"}, + }, + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Hostname: "baz"}, + }, + false, + "update hostname", + }, + { + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Subdomain: "bar"}, + }, + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{Subdomain: "baz"}, + }, + false, + "update subdomain", + }, } for _, test := range tests { diff --git a/pkg/controller/statefulset/stateful_set_control.go b/pkg/controller/statefulset/stateful_set_control.go index 87cf3a9ba9ab7..29bf0dda71ab1 100644 --- a/pkg/controller/statefulset/stateful_set_control.go +++ b/pkg/controller/statefulset/stateful_set_control.go @@ -139,6 +139,21 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p set.Name, replicas[i].Name) return nil } + // Enforce the StatefulSet invariants - we do this without respect to the Pod's readiness so that the endpoints + // controller can be notified of identity changes if a Pod becomes unready due to a DNS inconsistency with respect + // to the Pods identity. + if !identityMatches(set, replicas[i]) || !storageMatches(set, replicas[i]) { + // Make a deep copy so we don't mutate the shared cache + copy, err := api.Scheme.DeepCopy(replicas[i]) + if err != nil { + return err + } + replica := copy.(*v1.Pod) + if err := ssc.podControl.UpdateStatefulPod(set, replica); err != nil { + return err + } + } + // If we have a Pod that has been created but is not running and ready we can not make progress. // We must ensure that all for each Pod, when we create it, all of its predecessors, with respect to its // ordinal, are Running and Ready. @@ -147,19 +162,6 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p set.Name, replicas[i].Name) return nil } - // Enforce the StatefulSet invariants - if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) { - continue - } - // Make a deep copy so we don't mutate the shared cache - copy, err := api.Scheme.DeepCopy(replicas[i]) - if err != nil { - return err - } - replica := copy.(*v1.Pod) - if err := ssc.podControl.UpdateStatefulPod(set, replica); err != nil { - return err - } } // At this point, all of the current Replicas are Running and Ready, we can consider termination. diff --git a/pkg/controller/statefulset/stateful_set_utils.go b/pkg/controller/statefulset/stateful_set_utils.go index 2e6afae2b82ca..6bc42339c800c 100644 --- a/pkg/controller/statefulset/stateful_set_utils.go +++ b/pkg/controller/statefulset/stateful_set_utils.go @@ -207,6 +207,12 @@ func updateStorage(set *apps.StatefulSet, pod *v1.Pod) { func updateIdentity(set *apps.StatefulSet, pod *v1.Pod) { pod.Name = getPodName(set, getOrdinal(pod)) pod.Namespace = set.Namespace + if pod.Spec.Hostname == "" { + pod.Spec.Hostname = pod.Name + } + if pod.Spec.Subdomain == "" { + pod.Spec.Subdomain = set.Spec.ServiceName + } if pod.Annotations == nil { pod.Annotations = make(map[string]string) } diff --git a/pkg/controller/statefulset/stateful_set_utils_test.go b/pkg/controller/statefulset/stateful_set_utils_test.go index e7efc91101dec..92067a5eacec7 100644 --- a/pkg/controller/statefulset/stateful_set_utils_test.go +++ b/pkg/controller/statefulset/stateful_set_utils_test.go @@ -139,6 +139,22 @@ func TestUpdateIdentity(t *testing.T) { if !identityMatches(set, pod) { t.Error("updateIdentity failed to update the Pods namespace") } + pod.Spec.Hostname = "" + pod.Spec.Subdomain = "" + updateIdentity(set, pod) + if pod.Spec.Hostname != pod.Name || pod.Spec.Subdomain != set.Spec.ServiceName { + t.Errorf("want hostame=%s subdomain=%s got hostname=%s subdomain=%s", + pod.Name, + set.Spec.ServiceName, + pod.Spec.Hostname, + set.Spec.ServiceName) + } + pod.Spec.Hostname = "foo" + pod.Spec.Subdomain = "bar" + updateIdentity(set, pod) + if pod.Spec.Hostname != "foo" || pod.Spec.Subdomain != "bar" { + t.Errorf("want hostame=foo subdomain=bar got hostname=%s subdomain=%s", pod.Spec.Hostname, set.Spec.ServiceName) + } pod = newStatefulSetPod(set, 1) delete(pod.Annotations, podapi.PodHostnameAnnotation) pod.Spec.Hostname = ""