mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #73405 from wozniakjan/namespace_status_conditions
Add namespace status conditions
This commit is contained in:
commit
efaacf786a
@ -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,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,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,NodeList,Items
|
||||
API rule violation: list_type_missing,k8s.io/api/core/v1,NodeSelector,NodeSelectorTerms
|
||||
|
36
api/openapi-spec/swagger.json
generated
36
api/openapi-spec/swagger.json
generated
@ -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": {
|
||||
"description": "NamespaceList is a list of Namespaces.",
|
||||
"properties": {
|
||||
@ -8712,6 +8739,15 @@
|
||||
"io.k8s.api.core.v1.NamespaceStatus": {
|
||||
"description": "NamespaceStatus is information about the current status of a Namespace.",
|
||||
"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": {
|
||||
"description": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/",
|
||||
"type": "string"
|
||||
|
@ -4006,6 +4006,8 @@ type NamespaceStatus struct {
|
||||
// Phase is the current lifecycle phase of the namespace.
|
||||
// +optional
|
||||
Phase NamespacePhase
|
||||
// +optional
|
||||
Conditions []NamespaceCondition
|
||||
}
|
||||
|
||||
type NamespacePhase string
|
||||
@ -4018,6 +4020,30 @@ const (
|
||||
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
|
||||
|
||||
// A namespace provides a scope for Names.
|
||||
|
40
pkg/apis/core/v1/zz_generated.conversion.go
generated
40
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -870,6 +870,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
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 {
|
||||
return Convert_v1_NamespaceList_To_core_NamespaceList(a.(*v1.NamespaceList), b.(*core.NamespaceList), scope)
|
||||
}); 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
out.ListMeta = in.ListMeta
|
||||
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 {
|
||||
out.Phase = core.NamespacePhase(in.Phase)
|
||||
out.Conditions = *(*[]core.NamespaceCondition)(unsafe.Pointer(&in.Conditions))
|
||||
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 {
|
||||
out.Phase = v1.NamespacePhase(in.Phase)
|
||||
out.Conditions = *(*[]v1.NamespaceCondition)(unsafe.Pointer(&in.Conditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
26
pkg/apis/core/zz_generated.deepcopy.go
generated
26
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -2191,7 +2191,7 @@ func (in *Namespace) DeepCopyInto(out *Namespace) {
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
@ -2213,6 +2213,23 @@ func (in *Namespace) DeepCopyObject() runtime.Object {
|
||||
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.
|
||||
func (in *NamespaceList) DeepCopyInto(out *NamespaceList) {
|
||||
*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.
|
||||
func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) {
|
||||
*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
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,10 @@ load(
|
||||
|
||||
go_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",
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
|
@ -134,7 +134,7 @@ func (d *namespacedResourcesDeleter) Delete(nsName string) error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
@ -292,7 +292,7 @@ func (d *namespacedResourcesDeleter) updateNamespaceStatusFunc(namespace *v1.Nam
|
||||
}
|
||||
newNamespace := v1.Namespace{}
|
||||
newNamespace.ObjectMeta = namespace.ObjectMeta
|
||||
newNamespace.Status = namespace.Status
|
||||
newNamespace.Status = *namespace.Status.DeepCopy()
|
||||
newNamespace.Status.Phase = v1.NamespaceTerminating
|
||||
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.
|
||||
// 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.
|
||||
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
|
||||
conditionUpdater := namespaceConditionUpdater{}
|
||||
estimate := int64(0)
|
||||
klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s", namespace)
|
||||
|
||||
@ -489,6 +492,7 @@ func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespac
|
||||
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
|
||||
errs = append(errs, err)
|
||||
conditionUpdater.ProcessDiscoverResourcesErr(err)
|
||||
}
|
||||
// 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)
|
||||
@ -496,6 +500,7 @@ func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespac
|
||||
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
|
||||
errs = append(errs, err)
|
||||
conditionUpdater.ProcessGroupVersionErr(err)
|
||||
}
|
||||
for gvr := range groupVersionResources {
|
||||
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
|
||||
// groupVersionResources.
|
||||
errs = append(errs, err)
|
||||
conditionUpdater.ProcessDeleteContentErr(err)
|
||||
}
|
||||
if gvrEstimate > estimate {
|
||||
estimate = gvrEstimate
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, estimate: %v", namespace, estimate)
|
||||
|
@ -137,6 +137,8 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
|
||||
kubeClientActionSet sets.String
|
||||
metadataClientActionSet sets.String
|
||||
gvrError error
|
||||
expectErrorOnDelete error
|
||||
expectStatus *v1.NamespaceStatus
|
||||
}{
|
||||
"pending-finalize": {
|
||||
testNamespace: testNamespacePendingFinalize,
|
||||
@ -165,6 +167,23 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
|
||||
metadataClientActionSet: sets.NewString(),
|
||||
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 {
|
||||
@ -179,11 +198,11 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
|
||||
}
|
||||
|
||||
fn := func() ([]*metav1.APIResourceList, error) {
|
||||
return resources, nil
|
||||
return resources, testInput.gvrError
|
||||
}
|
||||
d := NewNamespacedResourcesDeleter(mockClient.CoreV1().Namespaces(), metadataClient, mockClient.CoreV1(), fn, v1.FinalizerKubernetes, true)
|
||||
if err := d.Delete(testInput.testNamespace.Name); err != nil {
|
||||
t.Errorf("scenario %s - Unexpected error when synching namespace %v", scenario, err)
|
||||
if err := d.Delete(testInput.testNamespace.Name); !matchErrors(err, testInput.expectErrorOnDelete) {
|
||||
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
|
||||
@ -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,
|
||||
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
|
||||
func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(handler))
|
||||
|
171
pkg/controller/namespace/deletion/status_condition_utils.go
Normal file
171
pkg/controller/namespace/deletion/status_condition_utils.go
Normal 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
|
||||
}
|
@ -145,7 +145,7 @@ func (nm *NamespaceController) worker() {
|
||||
} else {
|
||||
// rather than wait for a full resync, re-add the namespace to the queue to be processed
|
||||
nm.queue.AddRateLimited(key)
|
||||
utilruntime.HandleError(err)
|
||||
utilruntime.HandleError(fmt.Errorf("deletion of namespace %v failed: %v", key, err))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
2321
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
2321
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -2018,6 +2018,24 @@ message Namespace {
|
||||
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.
|
||||
message NamespaceList {
|
||||
// Standard list metadata.
|
||||
@ -2044,6 +2062,12 @@ message NamespaceStatus {
|
||||
// More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
|
||||
// +optional
|
||||
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.
|
||||
|
@ -4617,6 +4617,12 @@ type NamespaceStatus struct {
|
||||
// More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
|
||||
// +optional
|
||||
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
|
||||
@ -4629,6 +4635,32 @@ const (
|
||||
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:nonNamespaced
|
||||
// +genclient:skipVerbs=deleteCollection
|
||||
|
@ -994,6 +994,16 @@ func (Namespace) SwaggerDoc() map[string]string {
|
||||
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{
|
||||
"": "NamespaceList is a list of Namespaces.",
|
||||
"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{
|
||||
"": "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/",
|
||||
"": "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/",
|
||||
"conditions": "Represents the latest available observations of a namespace's current state.",
|
||||
}
|
||||
|
||||
func (NamespaceStatus) SwaggerDoc() map[string]string {
|
||||
|
@ -2189,7 +2189,7 @@ func (in *Namespace) DeepCopyInto(out *Namespace) {
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
@ -2211,6 +2211,23 @@ func (in *Namespace) DeepCopyObject() runtime.Object {
|
||||
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.
|
||||
func (in *NamespaceList) DeepCopyInto(out *NamespaceList) {
|
||||
*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.
|
||||
func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) {
|
||||
*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
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,15 @@
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
@ -33,4 +33,10 @@ spec:
|
||||
finalizers:
|
||||
- '@Hr鯹)晿'
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2956-02-24T15:15:18Z"
|
||||
message: "20"
|
||||
reason: "19"
|
||||
status: Ȋ甞谐颋DžSǡƏS$+½H牗洝尿
|
||||
type: 夸eɑeʤ脽ěĂ凗蓏Ŋ蛊ĉy
|
||||
phase: '`Ĩɘ.蘯6ċ'
|
||||
|
Loading…
Reference in New Issue
Block a user