From 69e2a9cb48fd49cf1449042984ea9c25edc0598c Mon Sep 17 00:00:00 2001 From: gmarek Date: Wed, 13 Sep 2017 16:46:26 +0200 Subject: [PATCH] Add new Events API group --- cmd/kube-apiserver/app/aggregator.go | 1 + cmd/kube-apiserver/app/server.go | 2 + .../app/import_known_versions.go | 1 + hack/.golint_failures | 6 + hack/lib/init.sh | 1 + hack/update-codegen.sh | 4 +- hack/update-generated-protobuf-dockerized.sh | 1 + pkg/api/testapi/testapi.go | 13 ++ pkg/apis/core/field_constants.go | 4 +- pkg/apis/core/fuzzer/fuzzer.go | 9 + pkg/apis/core/types.go | 45 ++++- pkg/apis/core/validation/events.go | 63 ++++++- pkg/apis/core/validation/events_test.go | 173 +++++++++++++++++- pkg/apis/events/doc.go | 18 ++ pkg/apis/events/install/install.go | 48 +++++ pkg/apis/events/register.go | 52 ++++++ pkg/apis/events/v1beta1/conversion.go | 58 ++++++ pkg/apis/events/v1beta1/doc.go | 23 +++ pkg/apis/events/v1beta1/register.go | 45 +++++ pkg/master/import_known_versions.go | 1 + pkg/master/master.go | 4 + pkg/registry/events/OWNERS | 8 + pkg/registry/events/event/doc.go | 17 ++ pkg/registry/events/event/strategy.go | 104 +++++++++++ pkg/registry/events/rest/storage_events.go | 62 +++++++ staging/src/k8s.io/api/core/v1/types.go | 44 ++++- staging/src/k8s.io/api/events/OWNERS | 8 + staging/src/k8s.io/api/events/v1beta1/doc.go | 21 +++ .../src/k8s.io/api/events/v1beta1/register.go | 53 ++++++ .../src/k8s.io/api/events/v1beta1/types.go | 122 ++++++++++++ .../pkg/apis/meta/v1/conversion.go | 7 + .../src/k8s.io/client-go/informers/factory.go | 6 + .../src/k8s.io/client-go/informers/generic.go | 5 + .../k8s.io/client-go/kubernetes/clientset.go | 22 +++ .../client-go/kubernetes/fake/register.go | 2 + .../client-go/kubernetes/scheme/register.go | 2 + .../etcd/etcd_storage_path_test.go | 8 + 37 files changed, 1046 insertions(+), 17 deletions(-) create mode 100644 pkg/apis/events/doc.go create mode 100644 pkg/apis/events/install/install.go create mode 100644 pkg/apis/events/register.go create mode 100644 pkg/apis/events/v1beta1/conversion.go create mode 100644 pkg/apis/events/v1beta1/doc.go create mode 100644 pkg/apis/events/v1beta1/register.go create mode 100644 pkg/registry/events/OWNERS create mode 100644 pkg/registry/events/event/doc.go create mode 100644 pkg/registry/events/event/strategy.go create mode 100644 pkg/registry/events/rest/storage_events.go create mode 100644 staging/src/k8s.io/api/events/OWNERS create mode 100644 staging/src/k8s.io/api/events/v1beta1/doc.go create mode 100644 staging/src/k8s.io/api/events/v1beta1/register.go create mode 100644 staging/src/k8s.io/api/events/v1beta1/types.go diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index f28b720c037..6d6e469434b 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -205,6 +205,7 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{ {Group: "apps", Version: "v1beta1"}: {group: 17800, version: 1}, {Group: "apps", Version: "v1beta2"}: {group: 17800, version: 9}, {Group: "apps", Version: "v1"}: {group: 17800, version: 15}, + {Group: "events.k8s.io", Version: "v1beta1"}: {group: 17750, version: 5}, {Group: "authentication.k8s.io", Version: "v1"}: {group: 17700, version: 15}, {Group: "authentication.k8s.io", Version: "v1beta1"}: {group: 17700, version: 9}, {Group: "authorization.k8s.io", Version: "v1"}: {group: 17600, version: 15}, diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index b39fe5cb767..f578ee677b4 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -66,6 +66,7 @@ import ( "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/events" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/storage" @@ -579,6 +580,7 @@ func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultSto storageFactory.AddCohabitatingResources(extensions.Resource("deployments"), apps.Resource("deployments")) storageFactory.AddCohabitatingResources(extensions.Resource("daemonsets"), apps.Resource("daemonsets")) storageFactory.AddCohabitatingResources(extensions.Resource("replicasets"), apps.Resource("replicasets")) + storageFactory.AddCohabitatingResources(api.Resource("events"), events.Resource("events")) for _, override := range s.Etcd.EtcdServersOverrides { tokens := strings.Split(override, "#") if len(tokens) != 2 { diff --git a/cmd/kube-controller-manager/app/import_known_versions.go b/cmd/kube-controller-manager/app/import_known_versions.go index 8d537650225..f63e298a240 100644 --- a/cmd/kube-controller-manager/app/import_known_versions.go +++ b/cmd/kube-controller-manager/app/import_known_versions.go @@ -30,6 +30,7 @@ import ( _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/policy/install" _ "k8s.io/kubernetes/pkg/apis/rbac/install" diff --git a/hack/.golint_failures b/hack/.golint_failures index 745f508e86c..6cd1db4fbb1 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -53,6 +53,8 @@ pkg/apis/core/v1/helper pkg/apis/core/v1/helper/qos pkg/apis/core/v1/validation pkg/apis/core/validation +pkg/apis/events +pkg/apis/events/v1beta1 pkg/apis/extensions pkg/apis/extensions/validation pkg/apis/imagepolicy @@ -292,6 +294,7 @@ pkg/registry/core/service/portallocator pkg/registry/core/service/portallocator/controller pkg/registry/core/service/storage pkg/registry/core/serviceaccount/storage +pkg/registry/events/rest pkg/registry/extensions/controller/storage pkg/registry/extensions/daemonset pkg/registry/extensions/daemonset/storage @@ -449,6 +452,7 @@ staging/src/k8s.io/api/batch/v1beta1 staging/src/k8s.io/api/batch/v2alpha1 staging/src/k8s.io/api/certificates/v1beta1 staging/src/k8s.io/api/core/v1 +staging/src/k8s.io/api/events/v1beta1 staging/src/k8s.io/api/extensions/v1beta1 staging/src/k8s.io/api/imagepolicy/v1alpha1 staging/src/k8s.io/api/networking/v1 @@ -659,6 +663,8 @@ staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1 staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/core/v1 staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake +staging/src/k8s.io/client-go/kubernetes/typed/events/v1beta1 +staging/src/k8s.io/client-go/kubernetes/typed/events/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/extensions/v1beta1 staging/src/k8s.io/client-go/kubernetes/typed/extensions/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/networking/v1 diff --git a/hack/lib/init.sh b/hack/lib/init.sh index 64a4099522a..3fd6b3c4b93 100755 --- a/hack/lib/init.sh +++ b/hack/lib/init.sh @@ -69,6 +69,7 @@ batch/v1beta1 \ batch/v2alpha1 \ certificates.k8s.io/v1beta1 \ extensions/v1beta1 \ +events.k8s.io/v1beta1 \ imagepolicy.k8s.io/v1alpha1 \ networking.k8s.io/v1 \ policy/v1beta1 \ diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 9d1fdeb1989..01135e2451a 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -59,10 +59,10 @@ for gv in "${GROUP_VERSIONS[@]}"; do # collect internal groups int_group="${pkg_dir%/*}/" if [[ "${pkg_dir}" = core/* ]]; then - int_group="api/" + int_group="api/" fi if ! [[ " ${INTERNAL_DIRS[@]:-} " =~ " ${int_group} " ]]; then - INTERNAL_DIRS+=("${int_group}") + INTERNAL_DIRS+=("${int_group}") fi done # delimit by commas for the command diff --git a/hack/update-generated-protobuf-dockerized.sh b/hack/update-generated-protobuf-dockerized.sh index 27e8eff705c..761d3700e00 100755 --- a/hack/update-generated-protobuf-dockerized.sh +++ b/hack/update-generated-protobuf-dockerized.sh @@ -60,6 +60,7 @@ PACKAGES=( k8s.io/api/apps/v1 k8s.io/api/authentication/v1 k8s.io/api/authentication/v1beta1 + k8s.io/api/events/v1beta1 k8s.io/api/rbac/v1alpha1 k8s.io/api/rbac/v1beta1 k8s.io/api/rbac/v1 diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index 95973def6e6..c1232e91ce2 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -43,6 +43,7 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/events" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/imagepolicy" "k8s.io/kubernetes/pkg/apis/networking" @@ -62,6 +63,7 @@ import ( _ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" _ "k8s.io/kubernetes/pkg/apis/networking/install" @@ -79,6 +81,7 @@ var ( Autoscaling TestGroup Batch TestGroup Extensions TestGroup + Events TestGroup Apps TestGroup Policy TestGroup Rbac TestGroup @@ -314,6 +317,15 @@ func init() { externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } + if _, ok := Groups[events.GroupName]; !ok { + externalGroupVersion := schema.GroupVersion{Group: events.GroupName, Version: legacyscheme.Registry.GroupOrDie(events.GroupName).GroupVersion.Version} + Groups[events.GroupName] = TestGroup{ + externalGroupVersion: externalGroupVersion, + internalGroupVersion: events.SchemeGroupVersion, + internalTypes: legacyscheme.Scheme.KnownTypes(events.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), + } + } Default = Groups[api.GroupName] Autoscaling = Groups[autoscaling.GroupName] @@ -322,6 +334,7 @@ func init() { Policy = Groups[policy.GroupName] Certificates = Groups[certificates.GroupName] Extensions = Groups[extensions.GroupName] + Events = Groups[events.GroupName] Rbac = Groups[rbac.GroupName] Scheduling = Groups[scheduling.GroupName] Settings = Groups[settings.GroupName] diff --git a/pkg/apis/core/field_constants.go b/pkg/apis/core/field_constants.go index 13149a52d55..a26f80568c0 100644 --- a/pkg/apis/core/field_constants.go +++ b/pkg/apis/core/field_constants.go @@ -25,8 +25,8 @@ const ( PodStatusField = "status.phase" SecretTypeField = "type" - EventReasonField = "reason" - EventSourceField = "source" + EventReasonField = "action" + EventSourceField = "reportingComponent" EventTypeField = "type" EventInvolvedKindField = "involvedObject.kind" EventInvolvedNamespaceField = "involvedObject.namespace" diff --git a/pkg/apis/core/fuzzer/fuzzer.go b/pkg/apis/core/fuzzer/fuzzer.go index e3483b9a22e..e979584723e 100644 --- a/pkg/apis/core/fuzzer/fuzzer.go +++ b/pkg/apis/core/fuzzer/fuzzer.go @@ -19,10 +19,12 @@ package fuzzer import ( "reflect" "strconv" + "time" fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -483,5 +485,12 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { c.FuzzNoCustom(s) s.Allocatable = s.Capacity }, + func(e *core.Event, c fuzz.Continue) { + c.FuzzNoCustom(e) + e.EventTime = metav1.MicroTime{Time: time.Unix(1, 1000)} + if e.Series != nil { + e.Series.LastObservedTime = metav1.MicroTime{Time: time.Unix(3, 3000)} + } + }, } } diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index bcdc0c28899..032e6d84c40 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -3958,7 +3958,7 @@ type Event struct { // +optional metav1.ObjectMeta - // Required. The object that this event is about. + // Required. The object that this event is about. Mapped to events.Event.regarding // +optional InvolvedObject ObjectReference @@ -3970,7 +3970,7 @@ type Event struct { Reason string // Optional. A human-readable description of the status of this operation. - // TODO: decide on maximum length. + // TODO: decide on maximum length. Mapped to events.Event.note // +optional Message string @@ -3993,8 +3993,49 @@ type Event struct { // Type of this event (Normal, Warning), new types could be added in the future. // +optional Type string + + // Time when this Event was first observed. + // +optional + EventTime metav1.MicroTime + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries + + // What action was taken/failed regarding to the Regarding object. + // +optional + Action string + + // Optional secondary object for more complex actions. + // +optional + Related *ObjectReference + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string } +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 + // Time of the last occurence observed + LastObservedTime metav1.MicroTime + // State of this Series: Ongoing or Finished + State EventSeriesState +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // EventList is a list of events. diff --git a/pkg/apis/core/validation/events.go b/pkg/apis/core/validation/events.go index dc0f5a9379d..ab265bd7644 100644 --- a/pkg/apis/core/validation/events.go +++ b/pkg/apis/core/validation/events.go @@ -18,6 +18,7 @@ package validation import ( "fmt" + "time" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,24 +29,68 @@ import ( "k8s.io/kubernetes/pkg/apis/core" ) +const ( + ReportingInstanceLengthLimit = 128 + ActionLengthLimit = 128 + ReasonLengthLimit = 128 + NoteLengthLimit = 1024 +) + // ValidateEvent makes sure that the event makes sense. func ValidateEvent(event *core.Event) field.ErrorList { allErrs := field.ErrorList{} + // Because go + zeroTime := time.Time{} - // Make sure event.Namespace and the involvedObject.Namespace agree - if len(event.InvolvedObject.Namespace) == 0 { - // event.Namespace must also be empty (or "default", for compatibility with old clients) - if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault { + // "New" Events need to have EventTime set, so it's validating old object. + if event.EventTime.Time == zeroTime { + // Make sure event.Namespace and the involvedInvolvedObject.Namespace agree + if len(event.InvolvedObject.Namespace) == 0 { + // event.Namespace must also be empty (or "default", for compatibility with old clients) + if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + } + } else { + // event namespace must match + if event.Namespace != event.InvolvedObject.Namespace { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + } + } + + } else { + if len(event.InvolvedObject.Namespace) == 0 && event.Namespace != metav1.NamespaceSystem { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) } - } else { - // event namespace must match - if event.Namespace != event.InvolvedObject.Namespace { - allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + if len(event.ReportingController) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reportingController"), "")) + } + for _, msg := range validation.IsQualifiedName(event.ReportingController) { + allErrs = append(allErrs, field.Invalid(field.NewPath("reportingController"), event.ReportingController, msg)) + } + if len(event.ReportingInstance) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reportingInstance"), "")) + } + if len(event.ReportingInstance) > ReportingInstanceLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("repotingIntance"), "", fmt.Sprintf("can have at most %v characters", ReportingInstanceLengthLimit))) + } + if len(event.Action) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("action"), "")) + } + if len(event.Action) > ActionLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("action"), "", fmt.Sprintf("can have at most %v characters", ActionLengthLimit))) + } + if len(event.Reason) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reason"), "")) + } + if len(event.Reason) > ReasonLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("reason"), "", fmt.Sprintf("can have at most %v characters", ReasonLengthLimit))) + } + if len(event.Message) > NoteLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("message"), "", fmt.Sprintf("can have at most %v characters", NoteLengthLimit))) } } - // For kinds we recognize, make sure involvedObject.Namespace is set for namespaced kinds + // For kinds we recognize, make sure InvolvedObject.Namespace is set for namespaced kinds if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil { if namespaced && len(event.InvolvedObject.Namespace) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind))) diff --git a/pkg/apis/core/validation/events_test.go b/pkg/apis/core/validation/events_test.go index c96ed24095e..1db7463f971 100644 --- a/pkg/apis/core/validation/events_test.go +++ b/pkg/apis/core/validation/events_test.go @@ -18,6 +18,7 @@ package validation import ( "testing" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/apis/core" @@ -215,7 +216,177 @@ func TestValidateEvent(t *testing.T) { for _, item := range table { if e, a := item.valid, len(ValidateEvent(item.Event)) == 0; e != a { - t.Errorf("%v: expected %v, got %v", item.Event.Name, e, a) + t.Errorf("%v: expected %v, got %v: %v", item.Event.Name, e, a, ValidateEvent(item.Event)) + } + } +} + +func TestValidateNewEvent(t *testing.T) { + someTime := metav1.MicroTime{Time: time.Unix(1505828956, 0)} + table := []struct { + *core.Event + valid bool + msg string + }{ + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + }, + valid: false, + msg: "Old Event with EventTime should trigger new validation and fail", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + }, + valid: true, + msg: "Valid new Event", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "my-contr@ller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + }, + valid: false, + msg: "not qualified reportingController", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + Action: "Do", + Reason: "Because", + }, + valid: false, + msg: "too long reporting instance", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + }, + valid: false, + msg: "missing reason", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Reason: "Because", + }, + valid: false, + msg: "missing action", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Reason: "Because", + }, + valid: false, + msg: "missing namespace", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + Message: `zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz`, + }, + valid: false, + msg: "too long message", + }, + } + + for _, item := range table { + if e, a := item.valid, len(ValidateEvent(item.Event)) == 0; e != a { + t.Errorf("%v: expected %v, got %v: %v", item.msg, e, a, ValidateEvent(item.Event)) } } } diff --git a/pkg/apis/events/doc.go b/pkg/apis/events/doc.go new file mode 100644 index 00000000000..15095ad3a29 --- /dev/null +++ b/pkg/apis/events/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2017 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. +*/ + +// +groupName=events.k8s.io +package events // import "k8s.io/kubernetes/pkg/apis/events" diff --git a/pkg/apis/events/install/install.go b/pkg/apis/events/install/install.go new file mode 100644 index 00000000000..9dbc1b4d47d --- /dev/null +++ b/pkg/apis/events/install/install.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 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 install installs the events API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/events" + "k8s.io/kubernetes/pkg/apis/events/v1beta1" +) + +func init() { + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) +} + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: events.GroupName, + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: events.AddToScheme, + }, + announced.VersionToSchemeFunc{ + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/pkg/apis/events/register.go b/pkg/apis/events/register.go new file mode 100644 index 00000000000..6156e800688 --- /dev/null +++ b/pkg/apis/events/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2017 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 events + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/apis/core" +) + +// GroupName is the group name use in this package +const GroupName = "events.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &core.Event{}, + &core.EventList{}, + ) + return nil +} diff --git a/pkg/apis/events/v1beta1/conversion.go b/pkg/apis/events/v1beta1/conversion.go new file mode 100644 index 00000000000..3a2be8e3168 --- /dev/null +++ b/pkg/apis/events/v1beta1/conversion.go @@ -0,0 +1,58 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + v1beta1 "k8s.io/api/events/v1beta1" + conversion "k8s.io/apimachinery/pkg/conversion" + k8s_api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" +) + +func Convert_v1beta1_Event_To_core_Event(in *v1beta1.Event, out *k8s_api.Event, s conversion.Scope) error { + if err := autoConvert_v1beta1_Event_To_core_Event(in, out, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_v1_ObjectReference_To_core_ObjectReference(&in.Regarding, &out.InvolvedObject, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_v1_EventSource_To_core_EventSource(&in.DeprecatedSource, &out.Source, s); err != nil { + return err + } + out.Message = in.Note + out.FirstTimestamp = in.DeprecatedFirstTimestamp + out.LastTimestamp = in.DeprecatedLastTimestamp + out.Count = in.DeprecatedCount + return nil +} + +func Convert_core_Event_To_v1beta1_Event(in *k8s_api.Event, out *v1beta1.Event, s conversion.Scope) error { + if err := autoConvert_core_Event_To_v1beta1_Event(in, out, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_core_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.Regarding, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_core_EventSource_To_v1_EventSource(&in.Source, &out.DeprecatedSource, s); err != nil { + return err + } + out.Note = in.Message + out.DeprecatedFirstTimestamp = in.FirstTimestamp + out.DeprecatedLastTimestamp = in.LastTimestamp + out.DeprecatedCount = in.Count + return nil +} diff --git a/pkg/apis/events/v1beta1/doc.go b/pkg/apis/events/v1beta1/doc.go new file mode 100644 index 00000000000..05932850418 --- /dev/null +++ b/pkg/apis/events/v1beta1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/events +// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/events/v1beta1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/events/v1beta1 + +// +groupName=events.k8s.io +package v1beta1 // import "k8s.io/kubernetes/pkg/apis/events/v1beta1" diff --git a/pkg/apis/events/v1beta1/register.go b/pkg/apis/events/v1beta1/register.go new file mode 100644 index 00000000000..7ff9379602f --- /dev/null +++ b/pkg/apis/events/v1beta1/register.go @@ -0,0 +1,45 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + eventsv1beta1 "k8s.io/api/events/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "events.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &eventsv1beta1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(RegisterDefaults) +} diff --git a/pkg/master/import_known_versions.go b/pkg/master/import_known_versions.go index 30a6b6b24cf..baf2502e92e 100644 --- a/pkg/master/import_known_versions.go +++ b/pkg/master/import_known_versions.go @@ -32,6 +32,7 @@ import ( _ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" _ "k8s.io/kubernetes/pkg/apis/networking/install" diff --git a/pkg/master/master.go b/pkg/master/master.go index dce550707b6..b20a6f5e929 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -37,6 +37,7 @@ import ( batchapiv1beta1 "k8s.io/api/batch/v1beta1" certificatesapiv1beta1 "k8s.io/api/certificates/v1beta1" apiv1 "k8s.io/api/core/v1" + eventsv1beta1 "k8s.io/api/events/v1beta1" extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" networkingapiv1 "k8s.io/api/networking/v1" policyapiv1beta1 "k8s.io/api/policy/v1beta1" @@ -77,6 +78,7 @@ import ( batchrest "k8s.io/kubernetes/pkg/registry/batch/rest" certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest" corerest "k8s.io/kubernetes/pkg/registry/core/rest" + eventsrest "k8s.io/kubernetes/pkg/registry/events/rest" extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest" networkingrest "k8s.io/kubernetes/pkg/registry/networking/rest" policyrest "k8s.io/kubernetes/pkg/registry/policy/rest" @@ -350,6 +352,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) // See https://github.com/kubernetes/kubernetes/issues/42392 appsrest.RESTStorageProvider{}, admissionregistrationrest.RESTStorageProvider{}, + eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL}, } m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...) @@ -479,6 +482,7 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { authorizationapiv1.SchemeGroupVersion, authorizationapiv1beta1.SchemeGroupVersion, networkingapiv1.SchemeGroupVersion, + eventsv1beta1.SchemeGroupVersion, ) // all extensions resources except these are disabled by default diff --git a/pkg/registry/events/OWNERS b/pkg/registry/events/OWNERS new file mode 100644 index 00000000000..d9ecd88564b --- /dev/null +++ b/pkg/registry/events/OWNERS @@ -0,0 +1,8 @@ +reviewers: +- gmarek +- deads2k +- sttts +approvers: +- gmarek +- deads2k +- sttts diff --git a/pkg/registry/events/event/doc.go b/pkg/registry/events/event/doc.go new file mode 100644 index 00000000000..90cedb7aa99 --- /dev/null +++ b/pkg/registry/events/event/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2017 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 event // import "k8s.io/kubernetes/pkg/registry/events/event" diff --git a/pkg/registry/events/event/strategy.go b/pkg/registry/events/event/strategy.go new file mode 100644 index 00000000000..f2958c4b17f --- /dev/null +++ b/pkg/registry/events/event/strategy.go @@ -0,0 +1,104 @@ +/* +Copyright 2017 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 event + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/generic" + apistorage "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" +) + +// eventStrategy implements verification logic for Pod Presets. +type eventStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod Preset objects. +var Strategy = eventStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns true because all Events need to be within a namespace. +func (eventStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a Pod Preset before creation. +func (eventStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (eventStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { +} + +// Validate validates a new Event. +func (eventStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +// Canonicalize normalizes the object after validation. +// AllowCreateOnUpdate is false for Event; this means POST is needed to create one. +func (eventStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (eventStrategy) Canonicalize(obj runtime.Object) {} + +// ValidateUpdate is the default update validation for an end user. +func (eventStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +// AllowUnconditionalUpdate is the default update policy for Event objects. +func (eventStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// SelectableFields returns a field set that represents the object. +func SelectableFields(pip *api.Event) fields.Set { + return generic.ObjectMetaFieldsSet(&pip.ObjectMeta, true) +} + +// GetAttrs returns labels and fields of a given object for filtering purposes. +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { + pip, ok := obj.(*api.Event) + if !ok { + return nil, nil, false, fmt.Errorf("given object is not a Event") + } + return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), pip.Initializers != nil, nil +} + +// Matcher is the filter used by the generic etcd backend to watch events +// from etcd to clients of the apiserver only interested in specific labels/fields. +func Matcher(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { + return apistorage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: GetAttrs, + } +} diff --git a/pkg/registry/events/rest/storage_events.go b/pkg/registry/events/rest/storage_events.go new file mode 100644 index 00000000000..03d6c661a52 --- /dev/null +++ b/pkg/registry/events/rest/storage_events.go @@ -0,0 +1,62 @@ +/* +Copyright 2017 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 rest + +import ( + "time" + + eventsapiv1beta1 "k8s.io/api/events/v1beta1" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/events" + eventstore "k8s.io/kubernetes/pkg/registry/core/event/storage" +) + +type RESTStorageProvider struct { + TTL time.Duration +} + +func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(events.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) + // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. + // TODO refactor the plumbing to provide the information in the APIGroupInfo + + if apiResourceConfigSource.AnyResourcesForVersionEnabled(eventsapiv1beta1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[eventsapiv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = eventsapiv1beta1.SchemeGroupVersion + } + + return apiGroupInfo, true +} + +func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + version := eventsapiv1beta1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("events")) { + eventsStorage := eventstore.NewREST(restOptionsGetter, uint64(p.TTL.Seconds())) + storage["events"] = eventsStorage + } + return storage +} + +func (p RESTStorageProvider) GroupName() string { + return events.GroupName +} diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index ce44f0ed353..bb5a5a104f4 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -4468,7 +4468,6 @@ const ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Event is a report of an event somewhere in the cluster. -// TODO: Decide whether to store these separately or with the object they apply to. type Event struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -4508,8 +4507,51 @@ type Event struct { // Type of this event (Normal, Warning), new types could be added in the future // +optional Type string `json:"type,omitempty" protobuf:"bytes,9,opt,name=type"` + + // Time when this Event was first observed. + // +optional + EventTime metav1.MicroTime `json:"eventTime,omitempty" protobuf:"bytes,10,opt,name=eventTime"` + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries `json:"series,omitempty" protobuf:"bytes,11,opt,name=series"` + + // What action was taken/failed regarding to the Regarding object. + // +optional + Action string `json:"action,omitempty" protobuf:"bytes,12,opt,name=action"` + + // Optional secondary object for more complex actions. + // +optional + Related *ObjectReference `json:"related,omitempty" protobuf:"bytes,13,opt,name=related"` + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string `json:"reportingComponent" protobuf:"bytes,14,opt,name=reportingComponent"` + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string `json:"reportingInstance" protobuf:"bytes,15,opt,name=reportingInstance"` } +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 `json:"count,omitempty" protobuf:"varint,1,name=count"` + // Time of the last occurence observed + LastObservedTime metav1.MicroTime `json:"lastObservedTime,omitempty" protobuf:"bytes,2,name=lastObservedTime"` + // State of this Series: Ongoing or Finished + State EventSeriesState `json:"state,omitempty" protobuf:"bytes,3,name=state"` +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // EventList is a list of events. diff --git a/staging/src/k8s.io/api/events/OWNERS b/staging/src/k8s.io/api/events/OWNERS new file mode 100644 index 00000000000..d9ecd88564b --- /dev/null +++ b/staging/src/k8s.io/api/events/OWNERS @@ -0,0 +1,8 @@ +reviewers: +- gmarek +- deads2k +- sttts +approvers: +- gmarek +- deads2k +- sttts diff --git a/staging/src/k8s.io/api/events/v1beta1/doc.go b/staging/src/k8s.io/api/events/v1beta1/doc.go new file mode 100644 index 00000000000..8b1a3e312de --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true + +// +groupName=events.k8s.io +package v1beta1 // import "k8s.io/api/events/v1beta1" diff --git a/staging/src/k8s.io/api/events/v1beta1/register.go b/staging/src/k8s.io/api/events/v1beta1/register.go new file mode 100644 index 00000000000..4506782914c --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/register.go @@ -0,0 +1,53 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "events.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Event{}, + &EventList{}, + ) + + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/events/v1beta1/types.go b/staging/src/k8s.io/api/events/v1beta1/types.go new file mode 100644 index 00000000000..1b68bd743e9 --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/types.go @@ -0,0 +1,122 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system. +type Event struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Required. Time when this Event was first observed. + EventTime metav1.MicroTime `json:"eventTime" protobuf:"bytes,2,opt,name=eventTime"` + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries `json:"series,omitempty" protobuf:"bytes,3,opt,name=series"` + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string `json:"reportingController,omitempty" protobuf:"bytes,4,opt,name=reportingController"` + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string `json:"reportingInstance,omitemtpy" protobuf:"bytes,5,opt,name=reportingInstance"` + + // What action was taken/failed regarding to the regarding object. + // +optional + Action string `json:"action,omitemtpy" protobuf:"bytes,6,name=action"` + + // Why the action was taken. + Reason string `json:"reason,omitempty" protobuf:"bytes,7,name=reason"` + + // The object this Event is about. In most cases it's an Object reporting controller implements. + // E.g. ReplicaSetController implements ReplicaSets and this event is emitted because + // it acts on some changes in a ReplicaSet object. + // +optional + Regarding corev1.ObjectReference `json:"regarding,omitempty" protobuf:"bytes,8,opt,name=regarding"` + + // Optional secondary object for more complex actions. E.g. when regarding object triggers + // a creation or deletion of related object. + // +optional + Related *corev1.ObjectReference `json:"related,omitempty" protobuf:"bytes,9,opt,name=related"` + + // Optional. A human-readable description of the status of this operation. + // Maximal length of the note is 1kB, but libraries should be prepared to + // handle values up to 64kB. + // +optional + Note string `json:"note,omitempty" protobuf:"bytes,10,opt,name=note"` + + // Type of this event (Normal, Warning), new types could be added in the + // future. + // +optional + Type string `json:"type,omitempty" protobuf:"bytes,11,opt,name=type"` + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedSource corev1.EventSource `json:"deprecatedSource,omitempty" protobuf:"bytes,12,opt,name=deprecatedSource"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedFirstTimestamp metav1.Time `json:"deprecatedFirstTimestamp,omitempty" protobuf:"bytes,13,opt,name=deprecatedFirstTimestamp"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedLastTimestamp metav1.Time `json:"deprecatedLastTimestamp,omitempty" protobuf:"bytes,14,opt,name=deprecatedLastTimestamp"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedCount int32 `json:"deprecatedCount,omitempty" protobuf:"varint,15,opt,name=deprecatedCount"` +} + +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 `json:"count" protobuf:"varint,1,opt,name=count"` + // Time when last Event from the series was seen before last heartbeat. + LastObservedTime metav1.MicroTime `json:"lastObservedTime" protobuf:"bytes,2,opt,name=lastObservedTime"` + // Information whether this series is ongoing or finished. + State EventSeriesState `json:"state" protobuf:"bytes,3,opt,name=state"` +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EventList is a list of Event objects. +type EventList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is a list of schema objects. + Items []Event `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go index a96f38ee21e..c62f853351e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go @@ -38,6 +38,7 @@ func AddConversionFuncs(scheme *runtime.Scheme) error { Convert_intstr_IntOrString_To_intstr_IntOrString, Convert_unversioned_Time_To_unversioned_Time, + Convert_unversioned_MicroTime_To_unversioned_MicroTime, Convert_Pointer_v1_Duration_To_v1_Duration, Convert_v1_Duration_To_Pointer_v1_Duration, @@ -199,6 +200,12 @@ func Convert_v1_Duration_To_Pointer_v1_Duration(in *Duration, out **Duration, s return nil } +func Convert_unversioned_MicroTime_To_unversioned_MicroTime(in *MicroTime, out *MicroTime, s conversion.Scope) error { + // Cannot deep copy these, because time.Time has unexported fields. + *out = *in + return nil +} + // Convert_Slice_string_To_unversioned_Time allows converting a URL query parameter value func Convert_Slice_string_To_unversioned_Time(input *[]string, out *Time, s conversion.Scope) error { str := "" diff --git a/staging/src/k8s.io/client-go/informers/factory.go b/staging/src/k8s.io/client-go/informers/factory.go index 89ef77bad0c..2214a57fde4 100644 --- a/staging/src/k8s.io/client-go/informers/factory.go +++ b/staging/src/k8s.io/client-go/informers/factory.go @@ -28,6 +28,7 @@ import ( batch "k8s.io/client-go/informers/batch" certificates "k8s.io/client-go/informers/certificates" core "k8s.io/client-go/informers/core" + events "k8s.io/client-go/informers/events" extensions "k8s.io/client-go/informers/extensions" internalinterfaces "k8s.io/client-go/informers/internalinterfaces" networking "k8s.io/client-go/informers/networking" @@ -140,6 +141,7 @@ type SharedInformerFactory interface { Batch() batch.Interface Certificates() certificates.Interface Core() core.Interface + Events() events.Interface Extensions() extensions.Interface Networking() networking.Interface Policy() policy.Interface @@ -173,6 +175,10 @@ func (f *sharedInformerFactory) Core() core.Interface { return core.New(f, f.namespace, f.tweakListOptions) } +func (f *sharedInformerFactory) Events() events.Interface { + return events.New(f) +} + func (f *sharedInformerFactory) Extensions() extensions.Interface { return extensions.New(f, f.namespace, f.tweakListOptions) } diff --git a/staging/src/k8s.io/client-go/informers/generic.go b/staging/src/k8s.io/client-go/informers/generic.go index f354781433e..c290c049090 100644 --- a/staging/src/k8s.io/client-go/informers/generic.go +++ b/staging/src/k8s.io/client-go/informers/generic.go @@ -32,6 +32,7 @@ import ( v2alpha1 "k8s.io/api/batch/v2alpha1" certificates_v1beta1 "k8s.io/api/certificates/v1beta1" core_v1 "k8s.io/api/core/v1" + events_v1beta1 "k8s.io/api/events/v1beta1" extensions_v1beta1 "k8s.io/api/extensions/v1beta1" networking_v1 "k8s.io/api/networking/v1" policy_v1beta1 "k8s.io/api/policy/v1beta1" @@ -173,6 +174,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case core_v1.SchemeGroupVersion.WithResource("serviceaccounts"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().V1().ServiceAccounts().Informer()}, nil + // Group=events, Version=v1beta1 + case events_v1beta1.SchemeGroupVersion.WithResource("events"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Events().V1beta1().Events().Informer()}, nil + // Group=extensions, Version=v1beta1 case extensions_v1beta1.SchemeGroupVersion.WithResource("daemonsets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().V1beta1().DaemonSets().Informer()}, nil diff --git a/staging/src/k8s.io/client-go/kubernetes/clientset.go b/staging/src/k8s.io/client-go/kubernetes/clientset.go index cb3f21ada67..a1c8a5a0a04 100644 --- a/staging/src/k8s.io/client-go/kubernetes/clientset.go +++ b/staging/src/k8s.io/client-go/kubernetes/clientset.go @@ -35,6 +35,7 @@ import ( batchv2alpha1 "k8s.io/client-go/kubernetes/typed/batch/v2alpha1" certificatesv1beta1 "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + eventsv1beta1 "k8s.io/client-go/kubernetes/typed/events/v1beta1" extensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" networkingv1 "k8s.io/client-go/kubernetes/typed/networking/v1" policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1" @@ -84,6 +85,9 @@ type Interface interface { CoreV1() corev1.CoreV1Interface // Deprecated: please explicitly pick a version if possible. Core() corev1.CoreV1Interface + EventsV1beta1() eventsv1beta1.EventsV1beta1Interface + // Deprecated: please explicitly pick a version if possible. + Events() eventsv1beta1.EventsV1beta1Interface ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface // Deprecated: please explicitly pick a version if possible. Extensions() extensionsv1beta1.ExtensionsV1beta1Interface @@ -131,6 +135,7 @@ type Clientset struct { batchV2alpha1 *batchv2alpha1.BatchV2alpha1Client certificatesV1beta1 *certificatesv1beta1.CertificatesV1beta1Client coreV1 *corev1.CoreV1Client + eventsV1beta1 *eventsv1beta1.EventsV1beta1Client extensionsV1beta1 *extensionsv1beta1.ExtensionsV1beta1Client networkingV1 *networkingv1.NetworkingV1Client policyV1beta1 *policyv1beta1.PolicyV1beta1Client @@ -272,6 +277,17 @@ func (c *Clientset) Core() corev1.CoreV1Interface { return c.coreV1 } +// EventsV1beta1 retrieves the EventsV1beta1Client +func (c *Clientset) EventsV1beta1() eventsv1beta1.EventsV1beta1Interface { + return c.eventsV1beta1 +} + +// Deprecated: Events retrieves the default version of EventsClient. +// Please explicitly pick a version. +func (c *Clientset) Events() eventsv1beta1.EventsV1beta1Interface { + return c.eventsV1beta1 +} + // ExtensionsV1beta1 retrieves the ExtensionsV1beta1Client func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface { return c.extensionsV1beta1 @@ -449,6 +465,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.eventsV1beta1, err = eventsv1beta1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.extensionsV1beta1, err = extensionsv1beta1.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -522,6 +542,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { cs.batchV2alpha1 = batchv2alpha1.NewForConfigOrDie(c) cs.certificatesV1beta1 = certificatesv1beta1.NewForConfigOrDie(c) cs.coreV1 = corev1.NewForConfigOrDie(c) + cs.eventsV1beta1 = eventsv1beta1.NewForConfigOrDie(c) cs.extensionsV1beta1 = extensionsv1beta1.NewForConfigOrDie(c) cs.networkingV1 = networkingv1.NewForConfigOrDie(c) cs.policyV1beta1 = policyv1beta1.NewForConfigOrDie(c) @@ -557,6 +578,7 @@ func New(c rest.Interface) *Clientset { cs.batchV2alpha1 = batchv2alpha1.New(c) cs.certificatesV1beta1 = certificatesv1beta1.New(c) cs.coreV1 = corev1.New(c) + cs.eventsV1beta1 = eventsv1beta1.New(c) cs.extensionsV1beta1 = extensionsv1beta1.New(c) cs.networkingV1 = networkingv1.New(c) cs.policyV1beta1 = policyv1beta1.New(c) diff --git a/staging/src/k8s.io/client-go/kubernetes/fake/register.go b/staging/src/k8s.io/client-go/kubernetes/fake/register.go index 2748fe0f95c..a385aa1ae05 100644 --- a/staging/src/k8s.io/client-go/kubernetes/fake/register.go +++ b/staging/src/k8s.io/client-go/kubernetes/fake/register.go @@ -33,6 +33,7 @@ import ( batchv2alpha1 "k8s.io/api/batch/v2alpha1" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" + eventsv1beta1 "k8s.io/api/events/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -90,6 +91,7 @@ func AddToScheme(scheme *runtime.Scheme) { batchv2alpha1.AddToScheme(scheme) certificatesv1beta1.AddToScheme(scheme) corev1.AddToScheme(scheme) + eventsv1beta1.AddToScheme(scheme) extensionsv1beta1.AddToScheme(scheme) networkingv1.AddToScheme(scheme) policyv1beta1.AddToScheme(scheme) diff --git a/staging/src/k8s.io/client-go/kubernetes/scheme/register.go b/staging/src/k8s.io/client-go/kubernetes/scheme/register.go index 56e21a568b7..99e970cdd6c 100644 --- a/staging/src/k8s.io/client-go/kubernetes/scheme/register.go +++ b/staging/src/k8s.io/client-go/kubernetes/scheme/register.go @@ -33,6 +33,7 @@ import ( batchv2alpha1 "k8s.io/api/batch/v2alpha1" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" + eventsv1beta1 "k8s.io/api/events/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -90,6 +91,7 @@ func AddToScheme(scheme *runtime.Scheme) { batchv2alpha1.AddToScheme(scheme) certificatesv1beta1.AddToScheme(scheme) corev1.AddToScheme(scheme) + eventsv1beta1.AddToScheme(scheme) extensionsv1beta1.AddToScheme(scheme) networkingv1.AddToScheme(scheme) policyv1beta1.AddToScheme(scheme) diff --git a/test/integration/etcd/etcd_storage_path_test.go b/test/integration/etcd/etcd_storage_path_test.go index 1b1b057e3cf..f63948fb414 100644 --- a/test/integration/etcd/etcd_storage_path_test.go +++ b/test/integration/etcd/etcd_storage_path_test.go @@ -247,6 +247,14 @@ var etcdStorageData = map[schema.GroupVersionResource]struct { }, // -- + // k8s.io/kubernetes/pkg/apis/events/v1beta1 + gvr("events.k8s.io", "v1beta1", "events"): { + stub: `{"metadata": {"name": "event2"}, "regarding": {"namespace": "etcdstoragepathtestnamespace"}, "note": "some data here", "eventTime": "2017-08-09T15:04:05.000000Z", "reportingInstance": "node-xyz", "reportingController": "k8s.io/my-controller", "action": "DidNothing", "reason": "Lazyness"}`, + expectedEtcdPath: "/registry/events/etcdstoragepathtestnamespace/event2", + expectedGVK: gvkP("", "v1", "Event"), + }, + // -- + // k8s.io/kubernetes/pkg/apis/extensions/v1beta1 gvr("extensions", "v1beta1", "daemonsets"): { stub: `{"metadata": {"name": "ds1"}, "spec": {"selector": {"matchLabels": {"u": "t"}}, "template": {"metadata": {"labels": {"u": "t"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container5"}]}}}}`,