...

Source file src/k8s.io/kubernetes/pkg/apis/core/validation/events.go

Documentation: k8s.io/kubernetes/pkg/apis/core/validation

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	eventsv1beta1 "k8s.io/api/events/v1beta1"
    26  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/util/validation"
    30  	"k8s.io/apimachinery/pkg/util/validation/field"
    31  	"k8s.io/kubernetes/pkg/apis/core"
    32  )
    33  
    34  const (
    35  	ReportingInstanceLengthLimit = 128
    36  	ActionLengthLimit            = 128
    37  	ReasonLengthLimit            = 128
    38  	NoteLengthLimit              = 1024
    39  )
    40  
    41  func ValidateEventCreate(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
    42  	// Make sure events always pass legacy validation.
    43  	allErrs := legacyValidateEvent(event, requestVersion)
    44  	if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion {
    45  		// No further validation for backwards compatibility.
    46  		return allErrs
    47  	}
    48  
    49  	// Strict validation applies to creation via events.k8s.io/v1 API and newer.
    50  	allErrs = append(allErrs, ValidateObjectMeta(&event.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
    51  	allErrs = append(allErrs, validateV1EventSeries(event)...)
    52  	zeroTime := time.Time{}
    53  	if event.EventTime.Time == zeroTime {
    54  		allErrs = append(allErrs, field.Required(field.NewPath("eventTime"), ""))
    55  	}
    56  	if event.Type != v1.EventTypeNormal && event.Type != v1.EventTypeWarning {
    57  		allErrs = append(allErrs, field.Invalid(field.NewPath("type"), "", fmt.Sprintf("has invalid value: %v", event.Type)))
    58  	}
    59  	if event.FirstTimestamp.Time != zeroTime {
    60  		allErrs = append(allErrs, field.Invalid(field.NewPath("firstTimestamp"), "", "needs to be unset"))
    61  	}
    62  	if event.LastTimestamp.Time != zeroTime {
    63  		allErrs = append(allErrs, field.Invalid(field.NewPath("lastTimestamp"), "", "needs to be unset"))
    64  	}
    65  	if event.Count != 0 {
    66  		allErrs = append(allErrs, field.Invalid(field.NewPath("count"), "", "needs to be unset"))
    67  	}
    68  	if event.Source.Component != "" || event.Source.Host != "" {
    69  		allErrs = append(allErrs, field.Invalid(field.NewPath("source"), "", "needs to be unset"))
    70  	}
    71  	return allErrs
    72  }
    73  
    74  func ValidateEventUpdate(newEvent, oldEvent *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
    75  	// Make sure the new event always passes legacy validation.
    76  	allErrs := legacyValidateEvent(newEvent, requestVersion)
    77  	if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion {
    78  		// No further validation for backwards compatibility.
    79  		return allErrs
    80  	}
    81  
    82  	// Strict validation applies to update via events.k8s.io/v1 API and newer.
    83  	allErrs = append(allErrs, ValidateObjectMetaUpdate(&newEvent.ObjectMeta, &oldEvent.ObjectMeta, field.NewPath("metadata"))...)
    84  	// if the series was modified, validate the new data
    85  	if !reflect.DeepEqual(newEvent.Series, oldEvent.Series) {
    86  		allErrs = append(allErrs, validateV1EventSeries(newEvent)...)
    87  	}
    88  
    89  	allErrs = append(allErrs, ValidateImmutableField(newEvent.InvolvedObject, oldEvent.InvolvedObject, field.NewPath("involvedObject"))...)
    90  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Reason, oldEvent.Reason, field.NewPath("reason"))...)
    91  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Message, oldEvent.Message, field.NewPath("message"))...)
    92  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Source, oldEvent.Source, field.NewPath("source"))...)
    93  	allErrs = append(allErrs, ValidateImmutableField(newEvent.FirstTimestamp, oldEvent.FirstTimestamp, field.NewPath("firstTimestamp"))...)
    94  	allErrs = append(allErrs, ValidateImmutableField(newEvent.LastTimestamp, oldEvent.LastTimestamp, field.NewPath("lastTimestamp"))...)
    95  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Count, oldEvent.Count, field.NewPath("count"))...)
    96  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Reason, oldEvent.Reason, field.NewPath("reason"))...)
    97  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Type, oldEvent.Type, field.NewPath("type"))...)
    98  
    99  	// Disallow changes to eventTime greater than microsecond-level precision.
   100  	// Tolerating sub-microsecond changes is required to tolerate updates
   101  	// from clients that correctly truncate to microsecond-precision when serializing,
   102  	// or from clients built with incorrect nanosecond-precision protobuf serialization.
   103  	// See https://github.com/kubernetes/kubernetes/issues/111928
   104  	newTruncated := newEvent.EventTime.Truncate(time.Microsecond).UTC()
   105  	oldTruncated := oldEvent.EventTime.Truncate(time.Microsecond).UTC()
   106  	if newTruncated != oldTruncated {
   107  		allErrs = append(allErrs, ValidateImmutableField(newEvent.EventTime, oldEvent.EventTime, field.NewPath("eventTime"))...)
   108  	}
   109  
   110  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Action, oldEvent.Action, field.NewPath("action"))...)
   111  	allErrs = append(allErrs, ValidateImmutableField(newEvent.Related, oldEvent.Related, field.NewPath("related"))...)
   112  	allErrs = append(allErrs, ValidateImmutableField(newEvent.ReportingController, oldEvent.ReportingController, field.NewPath("reportingController"))...)
   113  	allErrs = append(allErrs, ValidateImmutableField(newEvent.ReportingInstance, oldEvent.ReportingInstance, field.NewPath("reportingInstance"))...)
   114  
   115  	return allErrs
   116  }
   117  
   118  func validateV1EventSeries(event *core.Event) field.ErrorList {
   119  	allErrs := field.ErrorList{}
   120  	zeroTime := time.Time{}
   121  	if event.Series != nil {
   122  		if event.Series.Count < 2 {
   123  			allErrs = append(allErrs, field.Invalid(field.NewPath("series.count"), "", "should be at least 2"))
   124  		}
   125  		if event.Series.LastObservedTime.Time == zeroTime {
   126  			allErrs = append(allErrs, field.Required(field.NewPath("series.lastObservedTime"), ""))
   127  		}
   128  	}
   129  	return allErrs
   130  }
   131  
   132  // legacyValidateEvent makes sure that the event makes sense.
   133  func legacyValidateEvent(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
   134  	allErrs := field.ErrorList{}
   135  	// Because go
   136  	zeroTime := time.Time{}
   137  
   138  	reportingControllerFieldName := "reportingController"
   139  	if requestVersion == v1.SchemeGroupVersion {
   140  		reportingControllerFieldName = "reportingComponent"
   141  	}
   142  
   143  	// "New" Events need to have EventTime set, so it's validating old object.
   144  	if event.EventTime.Time == zeroTime {
   145  		// Make sure event.Namespace and the involvedInvolvedObject.Namespace agree
   146  		if len(event.InvolvedObject.Namespace) == 0 {
   147  			// event.Namespace must also be empty (or "default", for compatibility with old clients)
   148  			if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault {
   149  				allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
   150  			}
   151  		} else {
   152  			// event namespace must match
   153  			if event.Namespace != event.InvolvedObject.Namespace {
   154  				allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
   155  			}
   156  		}
   157  
   158  	} else {
   159  		if len(event.InvolvedObject.Namespace) == 0 && event.Namespace != metav1.NamespaceDefault && event.Namespace != metav1.NamespaceSystem {
   160  			allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
   161  		}
   162  		if len(event.ReportingController) == 0 {
   163  			allErrs = append(allErrs, field.Required(field.NewPath(reportingControllerFieldName), ""))
   164  		}
   165  		allErrs = append(allErrs, ValidateQualifiedName(event.ReportingController, field.NewPath(reportingControllerFieldName))...)
   166  		if len(event.ReportingInstance) == 0 {
   167  			allErrs = append(allErrs, field.Required(field.NewPath("reportingInstance"), ""))
   168  		}
   169  		if len(event.ReportingInstance) > ReportingInstanceLengthLimit {
   170  			allErrs = append(allErrs, field.Invalid(field.NewPath("reportingInstance"), "", fmt.Sprintf("can have at most %v characters", ReportingInstanceLengthLimit)))
   171  		}
   172  		if len(event.Action) == 0 {
   173  			allErrs = append(allErrs, field.Required(field.NewPath("action"), ""))
   174  		}
   175  		if len(event.Action) > ActionLengthLimit {
   176  			allErrs = append(allErrs, field.Invalid(field.NewPath("action"), "", fmt.Sprintf("can have at most %v characters", ActionLengthLimit)))
   177  		}
   178  		if len(event.Reason) == 0 {
   179  			allErrs = append(allErrs, field.Required(field.NewPath("reason"), ""))
   180  		}
   181  		if len(event.Reason) > ReasonLengthLimit {
   182  			allErrs = append(allErrs, field.Invalid(field.NewPath("reason"), "", fmt.Sprintf("can have at most %v characters", ReasonLengthLimit)))
   183  		}
   184  		if len(event.Message) > NoteLengthLimit {
   185  			allErrs = append(allErrs, field.Invalid(field.NewPath("message"), "", fmt.Sprintf("can have at most %v characters", NoteLengthLimit)))
   186  		}
   187  	}
   188  
   189  	for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) {
   190  		allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg))
   191  	}
   192  	return allErrs
   193  }
   194  

View as plain text