Merge pull request #73405 from wozniakjan/namespace_status_conditions

Add namespace status conditions
This commit is contained in:
Kubernetes Prow Robot 2019-08-29 15:34:58 -07:00 committed by GitHub
commit efaacf786a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1843 additions and 978 deletions

View File

@ -147,6 +147,7 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,List,Items
API rule violation: list_type_missing,k8s.io/api/core/v1,LoadBalancerStatus,Ingress API rule violation: list_type_missing,k8s.io/api/core/v1,LoadBalancerStatus,Ingress
API rule violation: list_type_missing,k8s.io/api/core/v1,NamespaceList,Items API rule violation: list_type_missing,k8s.io/api/core/v1,NamespaceList,Items
API rule violation: list_type_missing,k8s.io/api/core/v1,NamespaceSpec,Finalizers API rule violation: list_type_missing,k8s.io/api/core/v1,NamespaceSpec,Finalizers
API rule violation: list_type_missing,k8s.io/api/core/v1,NamespaceStatus,Conditions
API rule violation: list_type_missing,k8s.io/api/core/v1,NodeAffinity,PreferredDuringSchedulingIgnoredDuringExecution API rule violation: list_type_missing,k8s.io/api/core/v1,NodeAffinity,PreferredDuringSchedulingIgnoredDuringExecution
API rule violation: list_type_missing,k8s.io/api/core/v1,NodeList,Items API rule violation: list_type_missing,k8s.io/api/core/v1,NodeList,Items
API rule violation: list_type_missing,k8s.io/api/core/v1,NodeSelector,NodeSelectorTerms API rule violation: list_type_missing,k8s.io/api/core/v1,NodeSelector,NodeSelectorTerms

View File

@ -8661,6 +8661,33 @@
} }
] ]
}, },
"io.k8s.api.core.v1.NamespaceCondition": {
"description": "NamespaceCondition contains details about state of namespace.",
"properties": {
"lastTransitionTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
},
"message": {
"type": "string"
},
"reason": {
"type": "string"
},
"status": {
"description": "Status of the condition, one of True, False, Unknown.",
"type": "string"
},
"type": {
"description": "Type of namespace controller condition.",
"type": "string"
}
},
"required": [
"type",
"status"
],
"type": "object"
},
"io.k8s.api.core.v1.NamespaceList": { "io.k8s.api.core.v1.NamespaceList": {
"description": "NamespaceList is a list of Namespaces.", "description": "NamespaceList is a list of Namespaces.",
"properties": { "properties": {
@ -8712,6 +8739,15 @@
"io.k8s.api.core.v1.NamespaceStatus": { "io.k8s.api.core.v1.NamespaceStatus": {
"description": "NamespaceStatus is information about the current status of a Namespace.", "description": "NamespaceStatus is information about the current status of a Namespace.",
"properties": { "properties": {
"conditions": {
"description": "Represents the latest available observations of a namespace's current state.",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.NamespaceCondition"
},
"type": "array",
"x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge"
},
"phase": { "phase": {
"description": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", "description": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/",
"type": "string" "type": "string"

View File

@ -4006,6 +4006,8 @@ type NamespaceStatus struct {
// Phase is the current lifecycle phase of the namespace. // Phase is the current lifecycle phase of the namespace.
// +optional // +optional
Phase NamespacePhase Phase NamespacePhase
// +optional
Conditions []NamespaceCondition
} }
type NamespacePhase string type NamespacePhase string
@ -4018,6 +4020,30 @@ const (
NamespaceTerminating NamespacePhase = "Terminating" NamespaceTerminating NamespacePhase = "Terminating"
) )
// NamespaceConditionType defines constants reporting on status during namespace lifetime and deletion progress
type NamespaceConditionType string
// These are valid conditions of a namespace.
const (
NamespaceDeletionDiscoveryFailure NamespaceConditionType = "NamespaceDeletionDiscoveryFailure"
NamespaceDeletionContentFailure NamespaceConditionType = "NamespaceDeletionContentFailure"
NamespaceDeletionGVParsingFailure NamespaceConditionType = "NamespaceDeletionGroupVersionParsingFailure"
)
// NamespaceCondition contains details about state of namespace.
type NamespaceCondition struct {
// Type of namespace controller condition.
Type NamespaceConditionType
// Status of the condition, one of True, False, Unknown.
Status ConditionStatus
// +optional
LastTransitionTime metav1.Time
// +optional
Reason string
// +optional
Message string
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// A namespace provides a scope for Names. // A namespace provides a scope for Names.

View File

@ -870,6 +870,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddGeneratedConversionFunc((*v1.NamespaceCondition)(nil), (*core.NamespaceCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_NamespaceCondition_To_core_NamespaceCondition(a.(*v1.NamespaceCondition), b.(*core.NamespaceCondition), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.NamespaceCondition)(nil), (*v1.NamespaceCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_NamespaceCondition_To_v1_NamespaceCondition(a.(*core.NamespaceCondition), b.(*v1.NamespaceCondition), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.NamespaceList)(nil), (*core.NamespaceList)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddGeneratedConversionFunc((*v1.NamespaceList)(nil), (*core.NamespaceList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_NamespaceList_To_core_NamespaceList(a.(*v1.NamespaceList), b.(*core.NamespaceList), scope) return Convert_v1_NamespaceList_To_core_NamespaceList(a.(*v1.NamespaceList), b.(*core.NamespaceList), scope)
}); err != nil { }); err != nil {
@ -4397,6 +4407,34 @@ func Convert_core_Namespace_To_v1_Namespace(in *core.Namespace, out *v1.Namespac
return autoConvert_core_Namespace_To_v1_Namespace(in, out, s) return autoConvert_core_Namespace_To_v1_Namespace(in, out, s)
} }
func autoConvert_v1_NamespaceCondition_To_core_NamespaceCondition(in *v1.NamespaceCondition, out *core.NamespaceCondition, s conversion.Scope) error {
out.Type = core.NamespaceConditionType(in.Type)
out.Status = core.ConditionStatus(in.Status)
out.LastTransitionTime = in.LastTransitionTime
out.Reason = in.Reason
out.Message = in.Message
return nil
}
// Convert_v1_NamespaceCondition_To_core_NamespaceCondition is an autogenerated conversion function.
func Convert_v1_NamespaceCondition_To_core_NamespaceCondition(in *v1.NamespaceCondition, out *core.NamespaceCondition, s conversion.Scope) error {
return autoConvert_v1_NamespaceCondition_To_core_NamespaceCondition(in, out, s)
}
func autoConvert_core_NamespaceCondition_To_v1_NamespaceCondition(in *core.NamespaceCondition, out *v1.NamespaceCondition, s conversion.Scope) error {
out.Type = v1.NamespaceConditionType(in.Type)
out.Status = v1.ConditionStatus(in.Status)
out.LastTransitionTime = in.LastTransitionTime
out.Reason = in.Reason
out.Message = in.Message
return nil
}
// Convert_core_NamespaceCondition_To_v1_NamespaceCondition is an autogenerated conversion function.
func Convert_core_NamespaceCondition_To_v1_NamespaceCondition(in *core.NamespaceCondition, out *v1.NamespaceCondition, s conversion.Scope) error {
return autoConvert_core_NamespaceCondition_To_v1_NamespaceCondition(in, out, s)
}
func autoConvert_v1_NamespaceList_To_core_NamespaceList(in *v1.NamespaceList, out *core.NamespaceList, s conversion.Scope) error { func autoConvert_v1_NamespaceList_To_core_NamespaceList(in *v1.NamespaceList, out *core.NamespaceList, s conversion.Scope) error {
out.ListMeta = in.ListMeta out.ListMeta = in.ListMeta
out.Items = *(*[]core.Namespace)(unsafe.Pointer(&in.Items)) out.Items = *(*[]core.Namespace)(unsafe.Pointer(&in.Items))
@ -4441,6 +4479,7 @@ func Convert_core_NamespaceSpec_To_v1_NamespaceSpec(in *core.NamespaceSpec, out
func autoConvert_v1_NamespaceStatus_To_core_NamespaceStatus(in *v1.NamespaceStatus, out *core.NamespaceStatus, s conversion.Scope) error { func autoConvert_v1_NamespaceStatus_To_core_NamespaceStatus(in *v1.NamespaceStatus, out *core.NamespaceStatus, s conversion.Scope) error {
out.Phase = core.NamespacePhase(in.Phase) out.Phase = core.NamespacePhase(in.Phase)
out.Conditions = *(*[]core.NamespaceCondition)(unsafe.Pointer(&in.Conditions))
return nil return nil
} }
@ -4451,6 +4490,7 @@ func Convert_v1_NamespaceStatus_To_core_NamespaceStatus(in *v1.NamespaceStatus,
func autoConvert_core_NamespaceStatus_To_v1_NamespaceStatus(in *core.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error { func autoConvert_core_NamespaceStatus_To_v1_NamespaceStatus(in *core.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error {
out.Phase = v1.NamespacePhase(in.Phase) out.Phase = v1.NamespacePhase(in.Phase)
out.Conditions = *(*[]v1.NamespaceCondition)(unsafe.Pointer(&in.Conditions))
return nil return nil
} }

View File

@ -2191,7 +2191,7 @@ func (in *Namespace) DeepCopyInto(out *Namespace) {
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec) in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status in.Status.DeepCopyInto(&out.Status)
return return
} }
@ -2213,6 +2213,23 @@ func (in *Namespace) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceCondition) DeepCopyInto(out *NamespaceCondition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceCondition.
func (in *NamespaceCondition) DeepCopy() *NamespaceCondition {
if in == nil {
return nil
}
out := new(NamespaceCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceList) DeepCopyInto(out *NamespaceList) { func (in *NamespaceList) DeepCopyInto(out *NamespaceList) {
*out = *in *out = *in
@ -2270,6 +2287,13 @@ func (in *NamespaceSpec) DeepCopy() *NamespaceSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) { func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) {
*out = *in *out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]NamespaceCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return return
} }

View File

@ -8,7 +8,10 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["namespaced_resources_deleter.go"], srcs = [
"namespaced_resources_deleter.go",
"status_condition_utils.go",
],
importpath = "k8s.io/kubernetes/pkg/controller/namespace/deletion", importpath = "k8s.io/kubernetes/pkg/controller/namespace/deletion",
deps = [ deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",

View File

@ -134,7 +134,7 @@ func (d *namespacedResourcesDeleter) Delete(nsName string) error {
} }
// there may still be content for us to remove // there may still be content for us to remove
estimate, err := d.deleteAllContent(namespace.Name, *namespace.DeletionTimestamp) estimate, err := d.deleteAllContent(namespace)
if err != nil { if err != nil {
return err return err
} }
@ -292,7 +292,7 @@ func (d *namespacedResourcesDeleter) updateNamespaceStatusFunc(namespace *v1.Nam
} }
newNamespace := v1.Namespace{} newNamespace := v1.Namespace{}
newNamespace.ObjectMeta = namespace.ObjectMeta newNamespace.ObjectMeta = namespace.ObjectMeta
newNamespace.Status = namespace.Status newNamespace.Status = *namespace.Status.DeepCopy()
newNamespace.Status.Phase = v1.NamespaceTerminating newNamespace.Status.Phase = v1.NamespaceTerminating
return d.nsClient.UpdateStatus(&newNamespace) return d.nsClient.UpdateStatus(&newNamespace)
} }
@ -480,8 +480,11 @@ func (d *namespacedResourcesDeleter) deleteAllContentForGroupVersionResource(
// deleteAllContent will use the dynamic client to delete each resource identified in groupVersionResources. // deleteAllContent will use the dynamic client to delete each resource identified in groupVersionResources.
// It returns an estimate of the time remaining before the remaining resources are deleted. // It returns an estimate of the time remaining before the remaining resources are deleted.
// If estimate > 0, not all resources are guaranteed to be gone. // If estimate > 0, not all resources are guaranteed to be gone.
func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespaceDeletedAt metav1.Time) (int64, error) { func (d *namespacedResourcesDeleter) deleteAllContent(ns *v1.Namespace) (int64, error) {
namespace := ns.Name
namespaceDeletedAt := *ns.DeletionTimestamp
var errs []error var errs []error
conditionUpdater := namespaceConditionUpdater{}
estimate := int64(0) estimate := int64(0)
klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s", namespace) klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s", namespace)
@ -489,6 +492,7 @@ func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespac
if err != nil { if err != nil {
// discovery errors are not fatal. We often have some set of resources we can operate against even if we don't have a complete list // discovery errors are not fatal. We often have some set of resources we can operate against even if we don't have a complete list
errs = append(errs, err) errs = append(errs, err)
conditionUpdater.ProcessDiscoverResourcesErr(err)
} }
// TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter // TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources) deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
@ -496,6 +500,7 @@ func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespac
if err != nil { if err != nil {
// discovery errors are not fatal. We often have some set of resources we can operate against even if we don't have a complete list // discovery errors are not fatal. We often have some set of resources we can operate against even if we don't have a complete list
errs = append(errs, err) errs = append(errs, err)
conditionUpdater.ProcessGroupVersionErr(err)
} }
for gvr := range groupVersionResources { for gvr := range groupVersionResources {
gvrEstimate, err := d.deleteAllContentForGroupVersionResource(gvr, namespace, namespaceDeletedAt) gvrEstimate, err := d.deleteAllContentForGroupVersionResource(gvr, namespace, namespaceDeletedAt)
@ -503,12 +508,19 @@ func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespac
// If there is an error, hold on to it but proceed with all the remaining // If there is an error, hold on to it but proceed with all the remaining
// groupVersionResources. // groupVersionResources.
errs = append(errs, err) errs = append(errs, err)
conditionUpdater.ProcessDeleteContentErr(err)
} }
if gvrEstimate > estimate { if gvrEstimate > estimate {
estimate = gvrEstimate estimate = gvrEstimate
} }
} }
if len(errs) > 0 { if len(errs) > 0 {
if hasChanged := conditionUpdater.Update(ns); hasChanged {
if _, err = d.nsClient.UpdateStatus(ns); err != nil {
utilruntime.HandleError(fmt.Errorf("couldn't update status condition for namespace %q: %v", namespace, err))
}
}
return estimate, utilerrors.NewAggregate(errs) return estimate, utilerrors.NewAggregate(errs)
} }
klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, estimate: %v", namespace, estimate) klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, estimate: %v", namespace, estimate)

View File

@ -137,6 +137,8 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
kubeClientActionSet sets.String kubeClientActionSet sets.String
metadataClientActionSet sets.String metadataClientActionSet sets.String
gvrError error gvrError error
expectErrorOnDelete error
expectStatus *v1.NamespaceStatus
}{ }{
"pending-finalize": { "pending-finalize": {
testNamespace: testNamespacePendingFinalize, testNamespace: testNamespacePendingFinalize,
@ -165,6 +167,23 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
metadataClientActionSet: sets.NewString(), metadataClientActionSet: sets.NewString(),
gvrError: fmt.Errorf("test error"), gvrError: fmt.Errorf("test error"),
}, },
"groupVersionResourceErr-finalize": {
testNamespace: testNamespacePendingFinalize,
kubeClientActionSet: sets.NewString(
strings.Join([]string{"get", "namespaces", ""}, "-"),
strings.Join([]string{"list", "pods", ""}, "-"),
strings.Join([]string{"update", "namespaces", "status"}, "-"),
),
metadataClientActionSet: metadataClientActionSet,
gvrError: fmt.Errorf("test error"),
expectErrorOnDelete: fmt.Errorf("test error"),
expectStatus: &v1.NamespaceStatus{
Phase: v1.NamespaceTerminating,
Conditions: []v1.NamespaceCondition{
{Type: v1.NamespaceDeletionDiscoveryFailure},
},
},
},
} }
for scenario, testInput := range scenarios { for scenario, testInput := range scenarios {
@ -179,11 +198,11 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
} }
fn := func() ([]*metav1.APIResourceList, error) { fn := func() ([]*metav1.APIResourceList, error) {
return resources, nil return resources, testInput.gvrError
} }
d := NewNamespacedResourcesDeleter(mockClient.CoreV1().Namespaces(), metadataClient, mockClient.CoreV1(), fn, v1.FinalizerKubernetes, true) d := NewNamespacedResourcesDeleter(mockClient.CoreV1().Namespaces(), metadataClient, mockClient.CoreV1(), fn, v1.FinalizerKubernetes, true)
if err := d.Delete(testInput.testNamespace.Name); err != nil { if err := d.Delete(testInput.testNamespace.Name); !matchErrors(err, testInput.expectErrorOnDelete) {
t.Errorf("scenario %s - Unexpected error when synching namespace %v", scenario, err) t.Errorf("scenario %s - expected error %q when syncing namespace, got %q, %v", scenario, testInput.expectErrorOnDelete, err, testInput.expectErrorOnDelete == err)
} }
// validate traffic from kube client // validate traffic from kube client
@ -205,6 +224,31 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
t.Errorf("scenario %s - metadata client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario, t.Errorf("scenario %s - metadata client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario,
testInput.metadataClientActionSet, actionSet, testInput.metadataClientActionSet.Difference(actionSet)) testInput.metadataClientActionSet, actionSet, testInput.metadataClientActionSet.Difference(actionSet))
} }
// validate status conditions
if testInput.expectStatus != nil {
obj, err := mockClient.Tracker().Get(schema.GroupVersionResource{Version: "v1", Resource: "namespaces"}, testInput.testNamespace.Namespace, testInput.testNamespace.Name)
if err != nil {
t.Errorf("Unexpected error in getting the namespace: %v", err)
continue
}
ns, ok := obj.(*v1.Namespace)
if !ok {
t.Errorf("Expected a namespace but received %v", obj)
continue
}
if ns.Status.Phase != testInput.expectStatus.Phase {
t.Errorf("Expected namespace status phase %v but received %v", testInput.expectStatus.Phase, ns.Status.Phase)
continue
}
for _, expCondition := range testInput.expectStatus.Conditions {
nsCondition := getCondition(ns.Status.Conditions, expCondition.Type)
if nsCondition == nil {
t.Errorf("Missing namespace status condition %v", expCondition.Type)
continue
}
}
}
} }
} }
@ -271,6 +315,17 @@ func TestSyncNamespaceThatIsActive(t *testing.T) {
} }
} }
// matchError returns true if errors match, false if they don't, compares by error message only for convenience which should be sufficient for these tests
func matchErrors(e1, e2 error) bool {
if e1 == nil && e2 == nil {
return true
}
if e1 != nil && e2 != nil {
return e1.Error() == e2.Error()
}
return false
}
// testServerAndClientConfig returns a server that listens and a config that can reference it // testServerAndClientConfig returns a server that listens and a config that can reference it
func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) { func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) {
srv := httptest.NewServer(http.HandlerFunc(handler)) srv := httptest.NewServer(http.HandlerFunc(handler))

View File

@ -0,0 +1,171 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package deletion
import (
"fmt"
"sort"
"strings"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
)
// NamespaceConditionUpdater interface that translates namespace deleter errors
// into namespace status conditions.
type NamespaceConditionUpdater interface {
ProcessDiscoverResourcesErr(e error)
ProcessGroupVersionErr(e error)
ProcessDeleteContentErr(e error)
Update(*v1.Namespace) bool
}
type namespaceConditionUpdater struct {
newConditions []v1.NamespaceCondition
deleteContentErrors []error
}
var _ NamespaceConditionUpdater = &namespaceConditionUpdater{}
var (
// conditionTypes Namespace condition types that are maintained by namespace_deleter controller.
conditionTypes = []v1.NamespaceConditionType{
v1.NamespaceDeletionDiscoveryFailure,
v1.NamespaceDeletionGVParsingFailure,
v1.NamespaceDeletionContentFailure,
}
okMessages = map[v1.NamespaceConditionType]string{
v1.NamespaceDeletionDiscoveryFailure: "All resources successfully discovered",
v1.NamespaceDeletionGVParsingFailure: "All legacy kube types successfully parsed",
v1.NamespaceDeletionContentFailure: "All content successfully deleted",
}
okReasons = map[v1.NamespaceConditionType]string{
v1.NamespaceDeletionDiscoveryFailure: "ResourcesDiscovered",
v1.NamespaceDeletionGVParsingFailure: "ParsedGroupVersions",
v1.NamespaceDeletionContentFailure: "ContentDeleted",
}
)
// ProcessGroupVersionErr creates error condition if parsing GroupVersion of resources fails.
func (u *namespaceConditionUpdater) ProcessGroupVersionErr(err error) {
d := v1.NamespaceCondition{
Type: v1.NamespaceDeletionGVParsingFailure,
Status: v1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: "GroupVersionParsingFailed",
Message: err.Error(),
}
u.newConditions = append(u.newConditions, d)
}
// ProcessDiscoverResourcesErr creates error condition from ErrGroupDiscoveryFailed.
func (u *namespaceConditionUpdater) ProcessDiscoverResourcesErr(err error) {
var msg string
if derr, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok {
msg = fmt.Sprintf("Discovery failed for some groups, %d failing: %v", len(derr.Groups), err)
} else {
msg = err.Error()
}
d := v1.NamespaceCondition{
Type: v1.NamespaceDeletionDiscoveryFailure,
Status: v1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: "DiscoveryFailed",
Message: msg,
}
u.newConditions = append(u.newConditions, d)
}
// ProcessDeleteContentErr creates error condition from multiple delete content errors.
func (u *namespaceConditionUpdater) ProcessDeleteContentErr(err error) {
u.deleteContentErrors = append(u.deleteContentErrors, err)
}
// Update compiles processed errors from namespace deletion into status conditions.
func (u *namespaceConditionUpdater) Update(ns *v1.Namespace) bool {
if c := getCondition(u.newConditions, v1.NamespaceDeletionContentFailure); c == nil {
if c := makeDeleteContentCondition(u.deleteContentErrors); c != nil {
u.newConditions = append(u.newConditions, *c)
}
}
return updateConditions(&ns.Status, u.newConditions)
}
func makeDeleteContentCondition(err []error) *v1.NamespaceCondition {
if len(err) == 0 {
return nil
}
msgs := make([]string, 0, len(err))
for _, e := range err {
msgs = append(msgs, e.Error())
}
sort.Strings(msgs)
return &v1.NamespaceCondition{
Type: v1.NamespaceDeletionContentFailure,
Status: v1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: "ContentDeletionFailed",
Message: fmt.Sprintf("Failed to delete all resource types, %d remaining: %v", len(err), strings.Join(msgs, ", ")),
}
}
func updateConditions(status *v1.NamespaceStatus, newConditions []v1.NamespaceCondition) (hasChanged bool) {
for _, conditionType := range conditionTypes {
newCondition := getCondition(newConditions, conditionType)
oldCondition := getCondition(status.Conditions, conditionType)
if newCondition == nil && oldCondition == nil {
// both are nil, no update necessary
continue
}
if oldCondition == nil {
// only new condition of this type exists, add to the list
status.Conditions = append(status.Conditions, *newCondition)
hasChanged = true
} else if newCondition == nil {
// only old condition of this type exists, set status to false
if oldCondition.Status != v1.ConditionFalse {
oldCondition.Status = v1.ConditionFalse
oldCondition.Message = okMessages[conditionType]
oldCondition.Reason = okReasons[conditionType]
oldCondition.LastTransitionTime = metav1.Now()
hasChanged = true
}
} else if oldCondition.Message != newCondition.Message {
// old condition needs to be updated
if oldCondition.Status != newCondition.Status {
oldCondition.LastTransitionTime = metav1.Now()
}
oldCondition.Type = newCondition.Type
oldCondition.Status = newCondition.Status
oldCondition.Reason = newCondition.Reason
oldCondition.Message = newCondition.Message
hasChanged = true
}
}
return
}
func getCondition(conditions []v1.NamespaceCondition, conditionType v1.NamespaceConditionType) *v1.NamespaceCondition {
for i := range conditions {
if conditions[i].Type == conditionType {
return &(conditions[i])
}
}
return nil
}

View File

@ -145,7 +145,7 @@ func (nm *NamespaceController) worker() {
} else { } else {
// rather than wait for a full resync, re-add the namespace to the queue to be processed // rather than wait for a full resync, re-add the namespace to the queue to be processed
nm.queue.AddRateLimited(key) nm.queue.AddRateLimited(key)
utilruntime.HandleError(err) utilruntime.HandleError(fmt.Errorf("deletion of namespace %v failed: %v", key, err))
} }
return false return false
} }

File diff suppressed because it is too large Load Diff

View File

@ -2018,6 +2018,24 @@ message Namespace {
optional NamespaceStatus status = 3; optional NamespaceStatus status = 3;
} }
// NamespaceCondition contains details about state of namespace.
message NamespaceCondition {
// Type of namespace controller condition.
optional string type = 1;
// Status of the condition, one of True, False, Unknown.
optional string status = 2;
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4;
// +optional
optional string reason = 5;
// +optional
optional string message = 6;
}
// NamespaceList is a list of Namespaces. // NamespaceList is a list of Namespaces.
message NamespaceList { message NamespaceList {
// Standard list metadata. // Standard list metadata.
@ -2044,6 +2062,12 @@ message NamespaceStatus {
// More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
// +optional // +optional
optional string phase = 1; optional string phase = 1;
// Represents the latest available observations of a namespace's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
repeated NamespaceCondition conditions = 2;
} }
// Node is a worker node in Kubernetes. // Node is a worker node in Kubernetes.

View File

@ -4617,6 +4617,12 @@ type NamespaceStatus struct {
// More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
// +optional // +optional
Phase NamespacePhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=NamespacePhase"` Phase NamespacePhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=NamespacePhase"`
// Represents the latest available observations of a namespace's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []NamespaceCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"`
} }
type NamespacePhase string type NamespacePhase string
@ -4629,6 +4635,32 @@ const (
NamespaceTerminating NamespacePhase = "Terminating" NamespaceTerminating NamespacePhase = "Terminating"
) )
type NamespaceConditionType string
// These are valid conditions of a namespace.
const (
// NamespaceDeletionDiscoveryFailure contains information about namespace deleter errors during resource discovery.
NamespaceDeletionDiscoveryFailure NamespaceConditionType = "NamespaceDeletionDiscoveryFailure"
// NamespaceDeletionContentFailure contains information about namespace deleter errors during deletion of resources.
NamespaceDeletionContentFailure NamespaceConditionType = "NamespaceDeletionContentFailure"
// NamespaceDeletionGVParsingFailure contains information about namespace deleter errors parsing GV for legacy types.
NamespaceDeletionGVParsingFailure NamespaceConditionType = "NamespaceDeletionGroupVersionParsingFailure"
)
// NamespaceCondition contains details about state of namespace.
type NamespaceCondition struct {
// Type of namespace controller condition.
Type NamespaceConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=NamespaceConditionType"`
// Status of the condition, one of True, False, Unknown.
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"`
// +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
// +optional
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}
// +genclient // +genclient
// +genclient:nonNamespaced // +genclient:nonNamespaced
// +genclient:skipVerbs=deleteCollection // +genclient:skipVerbs=deleteCollection

View File

@ -994,6 +994,16 @@ func (Namespace) SwaggerDoc() map[string]string {
return map_Namespace return map_Namespace
} }
var map_NamespaceCondition = map[string]string{
"": "NamespaceCondition contains details about state of namespace.",
"type": "Type of namespace controller condition.",
"status": "Status of the condition, one of True, False, Unknown.",
}
func (NamespaceCondition) SwaggerDoc() map[string]string {
return map_NamespaceCondition
}
var map_NamespaceList = map[string]string{ var map_NamespaceList = map[string]string{
"": "NamespaceList is a list of Namespaces.", "": "NamespaceList is a list of Namespaces.",
"metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
@ -1014,8 +1024,9 @@ func (NamespaceSpec) SwaggerDoc() map[string]string {
} }
var map_NamespaceStatus = map[string]string{ var map_NamespaceStatus = map[string]string{
"": "NamespaceStatus is information about the current status of a Namespace.", "": "NamespaceStatus is information about the current status of a Namespace.",
"phase": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", "phase": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/",
"conditions": "Represents the latest available observations of a namespace's current state.",
} }
func (NamespaceStatus) SwaggerDoc() map[string]string { func (NamespaceStatus) SwaggerDoc() map[string]string {

View File

@ -2189,7 +2189,7 @@ func (in *Namespace) DeepCopyInto(out *Namespace) {
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec) in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status in.Status.DeepCopyInto(&out.Status)
return return
} }
@ -2211,6 +2211,23 @@ func (in *Namespace) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceCondition) DeepCopyInto(out *NamespaceCondition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceCondition.
func (in *NamespaceCondition) DeepCopy() *NamespaceCondition {
if in == nil {
return nil
}
out := new(NamespaceCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceList) DeepCopyInto(out *NamespaceList) { func (in *NamespaceList) DeepCopyInto(out *NamespaceList) {
*out = *in *out = *in
@ -2268,6 +2285,13 @@ func (in *NamespaceSpec) DeepCopy() *NamespaceSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) { func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) {
*out = *in *out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]NamespaceCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return return
} }

View File

@ -46,6 +46,15 @@
] ]
}, },
"status": { "status": {
"phase": "`Ĩɘ.蘯6ċ" "phase": "`Ĩɘ.蘯6ċ",
"conditions": [
{
"type": "夸eɑeʤ脽ěĂ凗蓏Ŋ蛊ĉy",
"status": "Ȋ甞谐颋DžSǡƏS$+½H牗洝尿",
"lastTransitionTime": "2956-02-24T15:15:18Z",
"reason": "19",
"message": "20"
}
]
} }
} }

View File

@ -33,4 +33,10 @@ spec:
finalizers: finalizers:
- '@Hr鯹)晿' - '@Hr鯹)晿'
status: status:
conditions:
- lastTransitionTime: "2956-02-24T15:15:18Z"
message: "20"
reason: "19"
status: Ȋ甞谐颋DžSǡƏS$+½H牗洝尿
type: 夸eɑeʤ脽ěĂ凗蓏Ŋ蛊ĉy
phase: '`Ĩɘ.蘯6ċ' phase: '`Ĩɘ.蘯6ċ'