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,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

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": {
"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"

View File

@ -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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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",

View File

@ -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)

View File

@ -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))

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 {
// 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
}

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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"
}
]
}
}

View File

@ -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ċ'