From 1bb44c584905d1a5713d28097f018f1f3d4e48dd Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Sun, 5 May 2019 20:57:42 +0000 Subject: [PATCH 01/31] pipelines, first cut of types def, generated code --- pkg/apis/eventing/v1alpha1/pipeline_types.go | 85 +++++++++ .../v1alpha1/zz_generated.deepcopy.go | 130 +++++++++++++ .../eventing/v1alpha1/eventing_client.go | 5 + .../v1alpha1/fake/fake_eventing_client.go | 4 + .../eventing/v1alpha1/fake/fake_pipeline.go | 140 ++++++++++++++ .../eventing/v1alpha1/generated_expansion.go | 2 + .../typed/eventing/v1alpha1/pipeline.go | 174 ++++++++++++++++++ .../eventing/v1alpha1/interface.go | 7 + .../eventing/v1alpha1/pipeline.go | 89 +++++++++ .../informers/externalversions/generic.go | 2 + .../eventing/v1alpha1/expansion_generated.go | 8 + .../listers/eventing/v1alpha1/pipeline.go | 94 ++++++++++ 12 files changed, 740 insertions(+) create mode 100644 pkg/apis/eventing/v1alpha1/pipeline_types.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go create mode 100644 pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go create mode 100644 pkg/client/listers/eventing/v1alpha1/pipeline.go diff --git a/pkg/apis/eventing/v1alpha1/pipeline_types.go b/pkg/apis/eventing/v1alpha1/pipeline_types.go new file mode 100644 index 00000000000..801d0d31963 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/pipeline_types.go @@ -0,0 +1,85 @@ +/* + * Copyright 2019 The Knative 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 v1alpha1 + +import ( + "github.com/knative/pkg/apis" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + "github.com/knative/pkg/webhook" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type Pipeline struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Pipeline. + Spec PipelineSpec `json:"spec,omitempty"` + + // Status represents the current state of the Pipeline. This data may be out of + // date. + // +optional + Status PipelineStatus `json:"status,omitempty"` +} + +// Check that Pipeline can be validated, can be defaulted, and has immutable fields. +var _ apis.Validatable = (*Pipeline)(nil) +var _ apis.Defaultable = (*Pipeline)(nil) +var _ apis.Immutable = (*Pipeline)(nil) +var _ runtime.Object = (*Pipeline)(nil) +var _ webhook.GenericCRD = (*Pipeline)(nil) + +type PipelineSpec struct { + // Steps is the list of Subscribers (processors / functions) that will be called in the order + // provided. + Steps []SubscriberSpec + + // Subscriber is the addressable that optionally receives events from the last step in the pipeline. + // +optional + Subscriber *SubscriberSpec `json:"subscriber,omitempty"` +} + +type StepStatus struct { + // Array of corresponding subscription statuses + SubscriptionStatus []SubscriptionStatus +} + +// PipelineStatus represents the current state of a Pipeline. +type PipelineStatus struct { + // inherits duck/v1alpha1 Status, which currently provides: + // * ObservedGeneration - the 'Generation' of the Service that was last processed by the controller. + // * Conditions - the latest available observations of a resource's current state. + duckv1alpha1.Status `json:",inline"` + + // Addressable is the starting point to this Pipeline. Sending to this will target the first Subscriber. + Address duckv1alpha1.Addressable `json:"address,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PipelineList is a collection of Pipelines. +type PipelineList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Pipeline `json:"items"` +} diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go index fe6dbb9bd2a..0b9594a6353 100644 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -430,6 +430,113 @@ func (in *EventTypeStatus) DeepCopy() *EventTypeStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Pipeline) DeepCopyInto(out *Pipeline) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pipeline. +func (in *Pipeline) DeepCopy() *Pipeline { + if in == nil { + return nil + } + out := new(Pipeline) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Pipeline) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineList) DeepCopyInto(out *PipelineList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Pipeline, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineList. +func (in *PipelineList) DeepCopy() *PipelineList { + if in == nil { + return nil + } + out := new(PipelineList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PipelineList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineSpec) DeepCopyInto(out *PipelineSpec) { + *out = *in + if in.Steps != nil { + in, out := &in.Steps, &out.Steps + *out = make([]SubscriberSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Subscriber != nil { + in, out := &in.Subscriber, &out.Subscriber + *out = new(SubscriberSpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineSpec. +func (in *PipelineSpec) DeepCopy() *PipelineSpec { + if in == nil { + return nil + } + out := new(PipelineSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + out.Address = in.Address + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. +func (in *PipelineStatus) DeepCopy() *PipelineStatus { + if in == nil { + return nil + } + out := new(PipelineStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplyStrategy) DeepCopyInto(out *ReplyStrategy) { *out = *in @@ -451,6 +558,29 @@ func (in *ReplyStrategy) DeepCopy() *ReplyStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepStatus) DeepCopyInto(out *StepStatus) { + *out = *in + if in.SubscriptionStatus != nil { + in, out := &in.SubscriptionStatus, &out.SubscriptionStatus + *out = make([]SubscriptionStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepStatus. +func (in *StepStatus) DeepCopy() *StepStatus { + if in == nil { + return nil + } + out := new(StepStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubscriberSpec) DeepCopyInto(out *SubscriberSpec) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go index 4af33411504..c6357654f3f 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go @@ -31,6 +31,7 @@ type EventingV1alpha1Interface interface { ChannelsGetter ClusterChannelProvisionersGetter EventTypesGetter + PipelinesGetter SubscriptionsGetter TriggersGetter } @@ -56,6 +57,10 @@ func (c *EventingV1alpha1Client) EventTypes(namespace string) EventTypeInterface return newEventTypes(c, namespace) } +func (c *EventingV1alpha1Client) Pipelines(namespace string) PipelineInterface { + return newPipelines(c, namespace) +} + func (c *EventingV1alpha1Client) Subscriptions(namespace string) SubscriptionInterface { return newSubscriptions(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go index 75da888a5e6..a575793648a 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go @@ -44,6 +44,10 @@ func (c *FakeEventingV1alpha1) EventTypes(namespace string) v1alpha1.EventTypeIn return &FakeEventTypes{c, namespace} } +func (c *FakeEventingV1alpha1) Pipelines(namespace string) v1alpha1.PipelineInterface { + return &FakePipelines{c, namespace} +} + func (c *FakeEventingV1alpha1) Subscriptions(namespace string) v1alpha1.SubscriptionInterface { return &FakeSubscriptions{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go new file mode 100644 index 00000000000..1bf76c3f1a5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go @@ -0,0 +1,140 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakePipelines implements PipelineInterface +type FakePipelines struct { + Fake *FakeEventingV1alpha1 + ns string +} + +var pipelinesResource = schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1alpha1", Resource: "pipelines"} + +var pipelinesKind = schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1alpha1", Kind: "Pipeline"} + +// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. +func (c *FakePipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// List takes label and field selectors, and returns the list of Pipelines that match those selectors. +func (c *FakePipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pipelinesResource, pipelinesKind, c.ns, opts), &v1alpha1.PipelineList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.PipelineList{ListMeta: obj.(*v1alpha1.PipelineList).ListMeta} + for _, item := range obj.(*v1alpha1.PipelineList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pipelines. +func (c *FakePipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(pipelinesResource, c.ns, opts)) + +} + +// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *FakePipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *FakePipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pipelinesResource, "status", c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. +func (c *FakePipelines) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(pipelinesResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.PipelineList{}) + return err +} + +// Patch applies the patch and returns the patched pipeline. +func (c *FakePipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pipelinesResource, c.ns, name, data, subresources...), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go index c33c3d484f4..0b3a045a222 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go @@ -26,6 +26,8 @@ type ClusterChannelProvisionerExpansion interface{} type EventTypeExpansion interface{} +type PipelineExpansion interface{} + type SubscriptionExpansion interface{} type TriggerExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go new file mode 100644 index 00000000000..3f202ba8147 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go @@ -0,0 +1,174 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + scheme "github.com/knative/eventing/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// PipelinesGetter has a method to return a PipelineInterface. +// A group's client should implement this interface. +type PipelinesGetter interface { + Pipelines(namespace string) PipelineInterface +} + +// PipelineInterface has methods to work with Pipeline resources. +type PipelineInterface interface { + Create(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + Update(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + UpdateStatus(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Pipeline, error) + List(opts v1.ListOptions) (*v1alpha1.PipelineList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) + PipelineExpansion +} + +// pipelines implements PipelineInterface +type pipelines struct { + client rest.Interface + ns string +} + +// newPipelines returns a Pipelines +func newPipelines(c *EventingV1alpha1Client, namespace string) *pipelines { + return &pipelines{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. +func (c *pipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Pipelines that match those selectors. +func (c *pipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { + result = &v1alpha1.PipelineList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pipelines. +func (c *pipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *pipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pipelines"). + Body(pipeline). + Do(). + Into(result) + return +} + +// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *pipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pipelines"). + Name(pipeline.Name). + Body(pipeline). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *pipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pipelines"). + Name(pipeline.Name). + SubResource("status"). + Body(pipeline). + Do(). + Into(result) + return +} + +// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. +func (c *pipelines) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pipelines"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched pipeline. +func (c *pipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("pipelines"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go index 614c6d2e90a..af7b267c4c1 100644 --- a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go @@ -32,6 +32,8 @@ type Interface interface { ClusterChannelProvisioners() ClusterChannelProvisionerInformer // EventTypes returns a EventTypeInformer. EventTypes() EventTypeInformer + // Pipelines returns a PipelineInformer. + Pipelines() PipelineInformer // Subscriptions returns a SubscriptionInformer. Subscriptions() SubscriptionInformer // Triggers returns a TriggerInformer. @@ -69,6 +71,11 @@ func (v *version) EventTypes() EventTypeInformer { return &eventTypeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Pipelines returns a PipelineInformer. +func (v *version) Pipelines() PipelineInformer { + return &pipelineInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Subscriptions returns a SubscriptionInformer. func (v *version) Subscriptions() SubscriptionInformer { return &subscriptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go b/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go new file mode 100644 index 00000000000..edaa64eabc1 --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go @@ -0,0 +1,89 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + versioned "github.com/knative/eventing/pkg/client/clientset/versioned" + internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// PipelineInformer provides access to a shared informer and lister for +// Pipelines. +type PipelineInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.PipelineLister +} + +type pipelineInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPipelineInformer constructs a new informer for Pipeline type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPipelineInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPipelineInformer constructs a new informer for Pipeline type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.EventingV1alpha1().Pipelines(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.EventingV1alpha1().Pipelines(namespace).Watch(options) + }, + }, + &eventingv1alpha1.Pipeline{}, + resyncPeriod, + indexers, + ) +} + +func (f *pipelineInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPipelineInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *pipelineInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&eventingv1alpha1.Pipeline{}, f.defaultInformer) +} + +func (f *pipelineInformer) Lister() v1alpha1.PipelineLister { + return v1alpha1.NewPipelineLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 5666ee78315..08cb4bc4ade 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -63,6 +63,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().ClusterChannelProvisioners().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("eventtypes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().EventTypes().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("pipelines"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().Pipelines().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subscriptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().Subscriptions().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("triggers"): diff --git a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go index 5f34cbf332d..f7b35672eb1 100644 --- a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go @@ -46,6 +46,14 @@ type EventTypeListerExpansion interface{} // EventTypeNamespaceLister. type EventTypeNamespaceListerExpansion interface{} +// PipelineListerExpansion allows custom methods to be added to +// PipelineLister. +type PipelineListerExpansion interface{} + +// PipelineNamespaceListerExpansion allows custom methods to be added to +// PipelineNamespaceLister. +type PipelineNamespaceListerExpansion interface{} + // SubscriptionListerExpansion allows custom methods to be added to // SubscriptionLister. type SubscriptionListerExpansion interface{} diff --git a/pkg/client/listers/eventing/v1alpha1/pipeline.go b/pkg/client/listers/eventing/v1alpha1/pipeline.go new file mode 100644 index 00000000000..b28e2e93890 --- /dev/null +++ b/pkg/client/listers/eventing/v1alpha1/pipeline.go @@ -0,0 +1,94 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// PipelineLister helps list Pipelines. +type PipelineLister interface { + // List lists all Pipelines in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) + // Pipelines returns an object that can list and get Pipelines. + Pipelines(namespace string) PipelineNamespaceLister + PipelineListerExpansion +} + +// pipelineLister implements the PipelineLister interface. +type pipelineLister struct { + indexer cache.Indexer +} + +// NewPipelineLister returns a new PipelineLister. +func NewPipelineLister(indexer cache.Indexer) PipelineLister { + return &pipelineLister{indexer: indexer} +} + +// List lists all Pipelines in the indexer. +func (s *pipelineLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Pipeline)) + }) + return ret, err +} + +// Pipelines returns an object that can list and get Pipelines. +func (s *pipelineLister) Pipelines(namespace string) PipelineNamespaceLister { + return pipelineNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// PipelineNamespaceLister helps list and get Pipelines. +type PipelineNamespaceLister interface { + // List lists all Pipelines in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) + // Get retrieves the Pipeline from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Pipeline, error) + PipelineNamespaceListerExpansion +} + +// pipelineNamespaceLister implements the PipelineNamespaceLister +// interface. +type pipelineNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Pipelines in the indexer for a given namespace. +func (s pipelineNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Pipeline)) + }) + return ret, err +} + +// Get retrieves the Pipeline from the indexer for a given namespace and name. +func (s pipelineNamespaceLister) Get(name string) (*v1alpha1.Pipeline, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("pipeline"), name) + } + return obj.(*v1alpha1.Pipeline), nil +} From 30453b4f831d3091e987b3fa0e3ddb5d31cb3abc Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 7 May 2019 19:58:40 +0000 Subject: [PATCH 02/31] checkpointing, beginning of pipeline reconciler, does not workie --- cmd/controller/main.go | 11 +- .../eventing/v1alpha1/pipeline_defaults.go | 27 +++ .../eventing/v1alpha1/pipeline_lifecycle.go | 57 ++++++ pkg/apis/eventing/v1alpha1/pipeline_types.go | 11 +- .../eventing/v1alpha1/pipeline_validation.go | 81 ++++++++ pkg/apis/eventing/v1alpha1/register.go | 6 +- pkg/reconciler/pipeline/pipeline.go | 186 ++++++++++++++++++ 7 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 pkg/apis/eventing/v1alpha1/pipeline_defaults.go create mode 100644 pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go create mode 100644 pkg/apis/eventing/v1alpha1/pipeline_validation.go create mode 100644 pkg/reconciler/pipeline/pipeline.go diff --git a/cmd/controller/main.go b/cmd/controller/main.go index edd16fe2b72..21603d23441 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -35,6 +35,7 @@ import ( "github.com/knative/eventing/pkg/reconciler/channel" "github.com/knative/eventing/pkg/reconciler/eventtype" "github.com/knative/eventing/pkg/reconciler/namespace" + "github.com/knative/eventing/pkg/reconciler/pipeline" "github.com/knative/eventing/pkg/reconciler/subscription" "github.com/knative/eventing/pkg/reconciler/trigger" "github.com/knative/pkg/configmap" @@ -81,7 +82,7 @@ func main() { logger.Info("Starting the controller") - const numControllers = 6 + const numControllers = 7 cfg.QPS = numControllers * rest.DefaultQPS cfg.Burst = numControllers * rest.DefaultBurst opt := reconciler.NewOptionsOrDie(cfg, logger, stopCh) @@ -96,6 +97,7 @@ func main() { subscriptionInformer := eventingInformerFactory.Eventing().V1alpha1().Subscriptions() brokerInformer := eventingInformerFactory.Eventing().V1alpha1().Brokers() eventTypeInformer := eventingInformerFactory.Eventing().V1alpha1().EventTypes() + pipelineInformer := eventingInformerFactory.Eventing().V1alpha1().Pipelines() // Kube serviceInformer := kubeInformerFactory.Core().V1().Services() @@ -165,6 +167,12 @@ func main() { eventTypeInformer, brokerInformer, ), + pipeline.NewController( + opt, + pipelineInformer, + channelInformer, + subscriptionInformer, + ), } // This line asserts at compile time that the length of controllers is equal to numControllers. // It is based on https://go101.org/article/tips.html#assert-at-compile-time, which notes that @@ -191,6 +199,7 @@ func main() { subscriptionInformer.Informer(), triggerInformer.Informer(), eventTypeInformer.Informer(), + pipelineInformer.Informer(), // Kube configMapInformer.Informer(), serviceInformer.Informer(), diff --git a/pkg/apis/eventing/v1alpha1/pipeline_defaults.go b/pkg/apis/eventing/v1alpha1/pipeline_defaults.go new file mode 100644 index 00000000000..6b5ae07d68f --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/pipeline_defaults.go @@ -0,0 +1,27 @@ +/* +Copyright 2019 The Knative 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 v1alpha1 + +import "context" + +func (s *Pipeline) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *PipelineSpec) SetDefaults(ctx context.Context) { + // TODO anything? +} diff --git a/pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go b/pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go new file mode 100644 index 00000000000..7c1bacffd24 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go @@ -0,0 +1,57 @@ +/* + * Copyright 2019 The Knative 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 v1alpha1 + +import duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + +const ( + // PipelineConditionReady has status True when all subconditions below have been set to True. + PipelineConditionReady = duckv1alpha1.ConditionReady + + // PipelineConditionReferencesResolved has status True when all the specified references have been successfully + // resolved. + PipelineConditionReferencesResolved duckv1alpha1.ConditionType = "Resolved" + + // PipelineConditionChannelReady has status True when controller has successfully added a + // pipeline to the spec.channel resource. + PipelineConditionChannelReady duckv1alpha1.ConditionType = "ChannelReady" +) + +// GetCondition returns the condition currently associated with the given type, or nil. +func (ss *PipelineStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition { + return subCondSet.Manage(ss).GetCondition(t) +} + +// IsReady returns true if the resource is ready overall. +func (ss *PipelineStatus) IsReady() bool { + return subCondSet.Manage(ss).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (ss *PipelineStatus) InitializeConditions() { + subCondSet.Manage(ss).InitializeConditions() +} + +// MarkReferencesResolved sets the ReferencesResolved condition to True state. +func (ss *PipelineStatus) MarkReferencesResolved() { + subCondSet.Manage(ss).MarkTrue(PipelineConditionReferencesResolved) +} + +// MarkChannelReady sets the ChannelReady condition to True state. +func (ss *PipelineStatus) MarkChannelReady() { + subCondSet.Manage(ss).MarkTrue(PipelineConditionChannelReady) +} diff --git a/pkg/apis/eventing/v1alpha1/pipeline_types.go b/pkg/apis/eventing/v1alpha1/pipeline_types.go index 801d0d31963..d3698f0c3d7 100644 --- a/pkg/apis/eventing/v1alpha1/pipeline_types.go +++ b/pkg/apis/eventing/v1alpha1/pipeline_types.go @@ -53,14 +53,19 @@ type PipelineSpec struct { // provided. Steps []SubscriberSpec - // Subscriber is the addressable that optionally receives events from the last step in the pipeline. + // Subscriber is the addressable that optionally receives replies from the last step in the pipeline. // +optional Subscriber *SubscriberSpec `json:"subscriber,omitempty"` } type StepStatus struct { - // Array of corresponding subscription statuses - SubscriptionStatus []SubscriptionStatus + // SubscriptionStatuses is an array of corresponding Subscription statuses. + // Matches the Spec.Steps array in the order. + SubscriptionStatuses []SubscriptionStatus + + // ChannelStatuses is an array of corresponding Channel statuses. + // Matches the Spec.Steps array in the order. + ChannelStatuses []ChannelStatus } // PipelineStatus represents the current state of a Pipeline. diff --git a/pkg/apis/eventing/v1alpha1/pipeline_validation.go b/pkg/apis/eventing/v1alpha1/pipeline_validation.go new file mode 100644 index 00000000000..ad30cc9cc05 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/pipeline_validation.go @@ -0,0 +1,81 @@ +/* +Copyright 2019 The Knative 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 v1alpha1 + +import ( + "context" + + "github.com/knative/pkg/apis" + "github.com/knative/pkg/kmp" +) + +func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError { + return t.Spec.Validate(ctx).ViaField("spec") +} + +func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + if len(ts.Steps) == 0 { + fe := apis.ErrMissingField("broker") + errs = errs.Also(fe) + } + + if ts.Filter == nil { + fe := apis.ErrMissingField("filter") + errs = errs.Also(fe) + } + + if ts.Filter != nil && ts.Filter.SourceAndType == nil { + fe := apis.ErrMissingField("filter.sourceAndType") + errs = errs.Also(fe) + } + + if isSubscriberSpecNilOrEmpty(ts.Subscriber) { + fe := apis.ErrMissingField("subscriber") + errs = errs.Also(fe) + } else if fe := isValidSubscriberSpec(*ts.Subscriber); fe != nil { + errs = errs.Also(fe.ViaField("subscriber")) + } + + return errs +} + +func (t *Pipeline) CheckImmutableFields(ctx context.Context, og apis.Immutable) *apis.FieldError { + if og == nil { + return nil + } + + original, ok := og.(*Pipeline) + if !ok { + return &apis.FieldError{Message: "The provided original was not a Pipeline"} + } + + if diff, err := kmp.ShortDiff(original.Spec.Broker, t.Spec.Broker); err != nil { + return &apis.FieldError{ + Message: "Failed to diff Pipeline", + Paths: []string{"spec"}, + Details: err.Error(), + } + } else if diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec", "broker"}, + Details: diff, + } + } + return nil +} diff --git a/pkg/apis/eventing/v1alpha1/register.go b/pkg/apis/eventing/v1alpha1/register.go index 977cad56aaf..51be16c3748 100644 --- a/pkg/apis/eventing/v1alpha1/register.go +++ b/pkg/apis/eventing/v1alpha1/register.go @@ -51,12 +51,14 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ChannelList{}, &ClusterChannelProvisioner{}, &ClusterChannelProvisionerList{}, + &EventType{}, + &EventTypeList{}, + &Pipeline{}, + &PipelineList{}, &Subscription{}, &SubscriptionList{}, &Trigger{}, &TriggerList{}, - &EventType{}, - &EventTypeList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go new file mode 100644 index 00000000000..50972cfa2d9 --- /dev/null +++ b/pkg/reconciler/pipeline/pipeline.go @@ -0,0 +1,186 @@ +/* +Copyright 2019 The Knative 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 pipeline + +import ( + "context" + "reflect" + + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/cache" + + "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + eventinginformers "github.com/knative/eventing/pkg/client/informers/externalversions/eventing/v1alpha1" + listers "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" + "github.com/knative/eventing/pkg/logging" + "github.com/knative/eventing/pkg/reconciler" + "github.com/knative/pkg/controller" + "go.uber.org/zap" +) + +const ( + // ReconcilerName is the name of the reconciler + ReconcilerName = "Pipelines" + // controllerAgentName is the string used by this controller to identify + // itself when creating events. + controllerAgentName = "pipeline-controller" + + pipelineReconciled = "PipelineReconciled" + pipelineReconcileFailed = "PipelineReconcileFailed" + pipelineUpdateStatusFailed = "PipelineUpdateStatusFailed" +) + +type Reconciler struct { + *reconciler.Base + + // listers index properties about resources + pipelineLister listers.PipelineLister + channelLister listers.ChannelLister + subscriptionLister listers.SubscriptionLister +} + +// Check that our Reconciler implements controller.Reconciler +var _ controller.Reconciler = (*Reconciler)(nil) + +// NewController initializes the controller and is called by the generated code +// Registers event handlers to enqueue events +func NewController( + opt reconciler.Options, + pipelineInformer eventinginformers.PipelineInformer, + channelInformer eventinginformers.ChannelInformer, + subscriptionInformer eventinginformers.SubscriptionInformer, +) *controller.Impl { + + r := &Reconciler{ + Base: reconciler.NewBase(opt, controllerAgentName), + pipelineLister: pipelineInformer.Lister(), + channelLister: channelInformer.Lister(), + subscriptionLister: subscriptionInformer.Lister(), + } + impl := controller.NewImpl(r, r.Logger, ReconcilerName, reconciler.MustNewStatsReporter(ReconcilerName, r.Logger)) + + r.Logger.Info("Setting up event handlers") + + pipelineInformer.Informer().AddEventHandler(reconciler.Handler(impl.Enqueue)) + + // Register handlers for Channel/Subscriptions that are owned by Pipeline, so that + // we get notified if they change. + subscriptionInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Channel")), + Handler: reconciler.Handler(impl.EnqueueControllerOf), + }) + subscriptionInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Subscription")), + Handler: reconciler.Handler(impl.EnqueueControllerOf), + }) + + return impl +} + +// Reconcile will check if the channel is being watched by provisioner's channel controller +// This will improve UX. See https://github.com/knative/eventing/issues/779 +func (r *Reconciler) Reconcile(ctx context.Context, key string) error { + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logging.FromContext(ctx).Error("invalid resource key") + return nil + } + + // Get the Pipeline resource with this namespace/name + original, err := r.pipelineLister.Pipelines(namespace).Get(name) + if apierrs.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing. + logging.FromContext(ctx).Error("Pipeline key in work queue no longer exists") + return nil + } else if err != nil { + return err + } + + // Delete is a no-op. + if original.DeletionTimestamp != nil { + return nil + } + + // Don't modify the informers copy + pipeline := original.DeepCopy() + + // Reconcile this copy of the Pipeline and then write back any status + // updates regardless of whether the reconcile error out. + reconcileErr := r.reconcile(ctx, pipeline) + if reconcileErr != nil { + logging.FromContext(ctx).Error("Error reconciling Pipeline", zap.Error(reconcileErr)) + } else { + logging.FromContext(ctx).Debug("Successfully reconciled Pipeline") + r.Recorder.Eventf(pipeline, corev1.EventTypeNormal, pipelineReconciled, "Pipeline reconciled: %s", key) + } + + if _, updateStatusErr := r.updateStatus(ctx, pipeline.DeepCopy()); updateStatusErr != nil { + logging.FromContext(ctx).Warn("Error updating Pipeline status", zap.Error(updateStatusErr)) + r.Recorder.Eventf(pipeline, corev1.EventTypeWarning, pipelineUpdateStatusFailed, "Failed to update pipeline status: %s", key) + return updateStatusErr + } + + // Requeue if the resource is not ready: + return reconcileErr +} + +func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error { + p.Status.InitializeConditions() + + // Do not Initialize() Status in pipeline-default-controller. It will set ChannelConditionProvisionerInstalled=True + // Directly call GetCondition(). If the Status was never initialized then GetCondition() will return nil and + // IsUnknown() will return true + c := ch.Status.GetCondition(v1alpha1.ChannelConditionProvisionerInstalled) + + if c == nil || c.IsUnknown() { + + var proName string + var proKind string + if ch.Spec.Provisioner != nil { + proName = ch.Spec.Provisioner.Name + proKind = ch.Spec.Provisioner.Kind + } + + ch.Status.MarkProvisionerNotInstalled( + "Provisioner not found.", + "Specified provisioner [Name:%s Kind:%s] is not installed or not controlling the channel.", + proName, + proKind, + ) + } + return nil +} + +func (r *Reconciler) updateStatus(ctx context.Context, desired *v1alpha1.Channel) (*v1alpha1.Channel, error) { + channel, err := r.channelLister.Channels(desired.Namespace).Get(desired.Name) + if err != nil { + return nil, err + } + + // If there's nothing to update, just return. + if reflect.DeepEqual(channel.Status, desired.Status) { + return channel, nil + } + + // Don't modify the informers copy. + existing := channel.DeepCopy() + existing.Status = desired.Status + + return r.EventingClientSet.EventingV1alpha1().Channels(desired.Namespace).UpdateStatus(existing) +} From 97ae5520cacb523f5b54df4cab5a625c7d76fc89 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 15 May 2019 18:35:05 +0000 Subject: [PATCH 03/31] types into messaging + clients --- .../eventing/v1alpha1/pipeline_validation.go | 81 -------- .../v1alpha1/zz_generated.deepcopy.go | 130 ------------- .../v1alpha1/pipeline_defaults.go | 0 .../v1alpha1/pipeline_lifecycle.go | 0 .../v1alpha1/pipeline_types.go | 9 +- .../messaging/v1alpha1/pipeline_validation.go | 44 +++++ .../v1alpha1/zz_generated.deepcopy.go | 138 ++++++++++++++ .../eventing/v1alpha1/eventing_client.go | 5 - .../v1alpha1/fake/fake_eventing_client.go | 4 - .../eventing/v1alpha1/generated_expansion.go | 2 - .../v1alpha1/fake/fake_messaging_client.go | 4 + .../messaging/v1alpha1/fake/fake_pipeline.go | 140 ++++++++++++++ .../messaging/v1alpha1/generated_expansion.go | 2 + .../messaging/v1alpha1/messaging_client.go | 5 + .../typed/messaging/v1alpha1/pipeline.go | 174 ++++++++++++++++++ .../eventing/v1alpha1/interface.go | 7 - .../informers/externalversions/generic.go | 4 +- .../messaging/v1alpha1/interface.go | 7 + .../messaging/v1alpha1/pipeline.go | 89 +++++++++ .../eventing/v1alpha1/expansion_generated.go | 8 - .../messaging/v1alpha1/expansion_generated.go | 8 + .../listers/messaging/v1alpha1/pipeline.go | 94 ++++++++++ 22 files changed, 712 insertions(+), 243 deletions(-) delete mode 100644 pkg/apis/eventing/v1alpha1/pipeline_validation.go rename pkg/apis/{eventing => messaging}/v1alpha1/pipeline_defaults.go (100%) rename pkg/apis/{eventing => messaging}/v1alpha1/pipeline_lifecycle.go (100%) rename pkg/apis/{eventing => messaging}/v1alpha1/pipeline_types.go (90%) create mode 100644 pkg/apis/messaging/v1alpha1/pipeline_validation.go create mode 100644 pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_pipeline.go create mode 100644 pkg/client/clientset/versioned/typed/messaging/v1alpha1/pipeline.go create mode 100644 pkg/client/informers/externalversions/messaging/v1alpha1/pipeline.go create mode 100644 pkg/client/listers/messaging/v1alpha1/pipeline.go diff --git a/pkg/apis/eventing/v1alpha1/pipeline_validation.go b/pkg/apis/eventing/v1alpha1/pipeline_validation.go deleted file mode 100644 index ad30cc9cc05..00000000000 --- a/pkg/apis/eventing/v1alpha1/pipeline_validation.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2019 The Knative 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 v1alpha1 - -import ( - "context" - - "github.com/knative/pkg/apis" - "github.com/knative/pkg/kmp" -) - -func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError { - return t.Spec.Validate(ctx).ViaField("spec") -} - -func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { - var errs *apis.FieldError - if len(ts.Steps) == 0 { - fe := apis.ErrMissingField("broker") - errs = errs.Also(fe) - } - - if ts.Filter == nil { - fe := apis.ErrMissingField("filter") - errs = errs.Also(fe) - } - - if ts.Filter != nil && ts.Filter.SourceAndType == nil { - fe := apis.ErrMissingField("filter.sourceAndType") - errs = errs.Also(fe) - } - - if isSubscriberSpecNilOrEmpty(ts.Subscriber) { - fe := apis.ErrMissingField("subscriber") - errs = errs.Also(fe) - } else if fe := isValidSubscriberSpec(*ts.Subscriber); fe != nil { - errs = errs.Also(fe.ViaField("subscriber")) - } - - return errs -} - -func (t *Pipeline) CheckImmutableFields(ctx context.Context, og apis.Immutable) *apis.FieldError { - if og == nil { - return nil - } - - original, ok := og.(*Pipeline) - if !ok { - return &apis.FieldError{Message: "The provided original was not a Pipeline"} - } - - if diff, err := kmp.ShortDiff(original.Spec.Broker, t.Spec.Broker); err != nil { - return &apis.FieldError{ - Message: "Failed to diff Pipeline", - Paths: []string{"spec"}, - Details: err.Error(), - } - } else if diff != "" { - return &apis.FieldError{ - Message: "Immutable fields changed (-old +new)", - Paths: []string{"spec", "broker"}, - Details: diff, - } - } - return nil -} diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go index 0b9594a6353..fe6dbb9bd2a 100644 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -430,113 +430,6 @@ func (in *EventTypeStatus) DeepCopy() *EventTypeStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Pipeline) DeepCopyInto(out *Pipeline) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pipeline. -func (in *Pipeline) DeepCopy() *Pipeline { - if in == nil { - return nil - } - out := new(Pipeline) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Pipeline) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PipelineList) DeepCopyInto(out *PipelineList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Pipeline, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineList. -func (in *PipelineList) DeepCopy() *PipelineList { - if in == nil { - return nil - } - out := new(PipelineList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PipelineList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PipelineSpec) DeepCopyInto(out *PipelineSpec) { - *out = *in - if in.Steps != nil { - in, out := &in.Steps, &out.Steps - *out = make([]SubscriberSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Subscriber != nil { - in, out := &in.Subscriber, &out.Subscriber - *out = new(SubscriberSpec) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineSpec. -func (in *PipelineSpec) DeepCopy() *PipelineSpec { - if in == nil { - return nil - } - out := new(PipelineSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { - *out = *in - in.Status.DeepCopyInto(&out.Status) - out.Address = in.Address - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. -func (in *PipelineStatus) DeepCopy() *PipelineStatus { - if in == nil { - return nil - } - out := new(PipelineStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplyStrategy) DeepCopyInto(out *ReplyStrategy) { *out = *in @@ -558,29 +451,6 @@ func (in *ReplyStrategy) DeepCopy() *ReplyStrategy { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StepStatus) DeepCopyInto(out *StepStatus) { - *out = *in - if in.SubscriptionStatus != nil { - in, out := &in.SubscriptionStatus, &out.SubscriptionStatus - *out = make([]SubscriptionStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepStatus. -func (in *StepStatus) DeepCopy() *StepStatus { - if in == nil { - return nil - } - out := new(StepStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubscriberSpec) DeepCopyInto(out *SubscriberSpec) { *out = *in diff --git a/pkg/apis/eventing/v1alpha1/pipeline_defaults.go b/pkg/apis/messaging/v1alpha1/pipeline_defaults.go similarity index 100% rename from pkg/apis/eventing/v1alpha1/pipeline_defaults.go rename to pkg/apis/messaging/v1alpha1/pipeline_defaults.go diff --git a/pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go similarity index 100% rename from pkg/apis/eventing/v1alpha1/pipeline_lifecycle.go rename to pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go diff --git a/pkg/apis/eventing/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go similarity index 90% rename from pkg/apis/eventing/v1alpha1/pipeline_types.go rename to pkg/apis/messaging/v1alpha1/pipeline_types.go index d3698f0c3d7..2db66024d79 100644 --- a/pkg/apis/eventing/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -17,6 +17,7 @@ package v1alpha1 import ( + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "github.com/knative/pkg/webhook" @@ -51,21 +52,21 @@ var _ webhook.GenericCRD = (*Pipeline)(nil) type PipelineSpec struct { // Steps is the list of Subscribers (processors / functions) that will be called in the order // provided. - Steps []SubscriberSpec + Steps []eventingv1alpha1.SubscriberSpec // Subscriber is the addressable that optionally receives replies from the last step in the pipeline. // +optional - Subscriber *SubscriberSpec `json:"subscriber,omitempty"` + Subscriber *eventingv1alpha1.SubscriberSpec `json:"subscriber,omitempty"` } type StepStatus struct { // SubscriptionStatuses is an array of corresponding Subscription statuses. // Matches the Spec.Steps array in the order. - SubscriptionStatuses []SubscriptionStatus + SubscriptionStatuses []eventingv1alpha1.SubscriptionStatus // ChannelStatuses is an array of corresponding Channel statuses. // Matches the Spec.Steps array in the order. - ChannelStatuses []ChannelStatus + ChannelStatuses []eventingv1alpha1.ChannelStatus } // PipelineStatus represents the current state of a Pipeline. diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go new file mode 100644 index 00000000000..db812e5c357 --- /dev/null +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -0,0 +1,44 @@ +/* +Copyright 2019 The Knative 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 v1alpha1 + +import ( + "context" + + "github.com/knative/pkg/apis" + "github.com/knative/pkg/kmp" +) + +func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError { + return t.Spec.Validate(ctx).ViaField("spec") +} + +func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if imcs.Subscribable != nil { + for i, subscriber := range imcs.Subscribable.Subscribers { + if subscriber.ReplyURI == "" && subscriber.SubscriberURI == "" { + fe := apis.ErrMissingField("replyURI", "subscriberURI") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe.ViaField(fmt.Sprintf("subscriber[%d]", i)).ViaField("subscribable")) + } + } + } + + return errs +} diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index 1ab4cf03025..db0ed0f4904 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1alpha1 import ( duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -124,3 +125,140 @@ func (in *InMemoryChannelStatus) DeepCopy() *InMemoryChannelStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Pipeline) DeepCopyInto(out *Pipeline) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pipeline. +func (in *Pipeline) DeepCopy() *Pipeline { + if in == nil { + return nil + } + out := new(Pipeline) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Pipeline) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineList) DeepCopyInto(out *PipelineList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Pipeline, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineList. +func (in *PipelineList) DeepCopy() *PipelineList { + if in == nil { + return nil + } + out := new(PipelineList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PipelineList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineSpec) DeepCopyInto(out *PipelineSpec) { + *out = *in + if in.Steps != nil { + in, out := &in.Steps, &out.Steps + *out = make([]eventingv1alpha1.SubscriberSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Subscriber != nil { + in, out := &in.Subscriber, &out.Subscriber + *out = new(eventingv1alpha1.SubscriberSpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineSpec. +func (in *PipelineSpec) DeepCopy() *PipelineSpec { + if in == nil { + return nil + } + out := new(PipelineSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + in.Address.DeepCopyInto(&out.Address) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. +func (in *PipelineStatus) DeepCopy() *PipelineStatus { + if in == nil { + return nil + } + out := new(PipelineStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepStatus) DeepCopyInto(out *StepStatus) { + *out = *in + if in.SubscriptionStatuses != nil { + in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses + *out = make([]eventingv1alpha1.SubscriptionStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ChannelStatuses != nil { + in, out := &in.ChannelStatuses, &out.ChannelStatuses + *out = make([]eventingv1alpha1.ChannelStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepStatus. +func (in *StepStatus) DeepCopy() *StepStatus { + if in == nil { + return nil + } + out := new(StepStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go index c6357654f3f..4af33411504 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go @@ -31,7 +31,6 @@ type EventingV1alpha1Interface interface { ChannelsGetter ClusterChannelProvisionersGetter EventTypesGetter - PipelinesGetter SubscriptionsGetter TriggersGetter } @@ -57,10 +56,6 @@ func (c *EventingV1alpha1Client) EventTypes(namespace string) EventTypeInterface return newEventTypes(c, namespace) } -func (c *EventingV1alpha1Client) Pipelines(namespace string) PipelineInterface { - return newPipelines(c, namespace) -} - func (c *EventingV1alpha1Client) Subscriptions(namespace string) SubscriptionInterface { return newSubscriptions(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go index a575793648a..75da888a5e6 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go @@ -44,10 +44,6 @@ func (c *FakeEventingV1alpha1) EventTypes(namespace string) v1alpha1.EventTypeIn return &FakeEventTypes{c, namespace} } -func (c *FakeEventingV1alpha1) Pipelines(namespace string) v1alpha1.PipelineInterface { - return &FakePipelines{c, namespace} -} - func (c *FakeEventingV1alpha1) Subscriptions(namespace string) v1alpha1.SubscriptionInterface { return &FakeSubscriptions{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go index 0b3a045a222..c33c3d484f4 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go @@ -26,8 +26,6 @@ type ClusterChannelProvisionerExpansion interface{} type EventTypeExpansion interface{} -type PipelineExpansion interface{} - type SubscriptionExpansion interface{} type TriggerExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_messaging_client.go b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_messaging_client.go index 749d241e42d..9a8159f46fb 100644 --- a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_messaging_client.go +++ b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_messaging_client.go @@ -32,6 +32,10 @@ func (c *FakeMessagingV1alpha1) InMemoryChannels(namespace string) v1alpha1.InMe return &FakeInMemoryChannels{c, namespace} } +func (c *FakeMessagingV1alpha1) Pipelines(namespace string) v1alpha1.PipelineInterface { + return &FakePipelines{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeMessagingV1alpha1) RESTClient() rest.Interface { diff --git a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_pipeline.go b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_pipeline.go new file mode 100644 index 00000000000..a09a90b64ae --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/fake/fake_pipeline.go @@ -0,0 +1,140 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakePipelines implements PipelineInterface +type FakePipelines struct { + Fake *FakeMessagingV1alpha1 + ns string +} + +var pipelinesResource = schema.GroupVersionResource{Group: "messaging.knative.dev", Version: "v1alpha1", Resource: "pipelines"} + +var pipelinesKind = schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1alpha1", Kind: "Pipeline"} + +// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. +func (c *FakePipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// List takes label and field selectors, and returns the list of Pipelines that match those selectors. +func (c *FakePipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pipelinesResource, pipelinesKind, c.ns, opts), &v1alpha1.PipelineList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.PipelineList{ListMeta: obj.(*v1alpha1.PipelineList).ListMeta} + for _, item := range obj.(*v1alpha1.PipelineList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pipelines. +func (c *FakePipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(pipelinesResource, c.ns, opts)) + +} + +// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *FakePipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *FakePipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pipelinesResource, "status", c.ns, pipeline), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} + +// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. +func (c *FakePipelines) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(pipelinesResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.PipelineList{}) + return err +} + +// Patch applies the patch and returns the patched pipeline. +func (c *FakePipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pipelinesResource, c.ns, name, data, subresources...), &v1alpha1.Pipeline{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Pipeline), err +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/generated_expansion.go index ed2a76fd1e7..e675ed43ee5 100644 --- a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/generated_expansion.go @@ -19,3 +19,5 @@ limitations under the License. package v1alpha1 type InMemoryChannelExpansion interface{} + +type PipelineExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/messaging_client.go b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/messaging_client.go index 2a8987e43a3..9154f211c57 100644 --- a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/messaging_client.go +++ b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/messaging_client.go @@ -28,6 +28,7 @@ import ( type MessagingV1alpha1Interface interface { RESTClient() rest.Interface InMemoryChannelsGetter + PipelinesGetter } // MessagingV1alpha1Client is used to interact with features provided by the messaging.knative.dev group. @@ -39,6 +40,10 @@ func (c *MessagingV1alpha1Client) InMemoryChannels(namespace string) InMemoryCha return newInMemoryChannels(c, namespace) } +func (c *MessagingV1alpha1Client) Pipelines(namespace string) PipelineInterface { + return newPipelines(c, namespace) +} + // NewForConfig creates a new MessagingV1alpha1Client for the given config. func NewForConfig(c *rest.Config) (*MessagingV1alpha1Client, error) { config := *c diff --git a/pkg/client/clientset/versioned/typed/messaging/v1alpha1/pipeline.go b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/pipeline.go new file mode 100644 index 00000000000..abafb1dffe9 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1alpha1/pipeline.go @@ -0,0 +1,174 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + scheme "github.com/knative/eventing/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// PipelinesGetter has a method to return a PipelineInterface. +// A group's client should implement this interface. +type PipelinesGetter interface { + Pipelines(namespace string) PipelineInterface +} + +// PipelineInterface has methods to work with Pipeline resources. +type PipelineInterface interface { + Create(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + Update(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + UpdateStatus(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Pipeline, error) + List(opts v1.ListOptions) (*v1alpha1.PipelineList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) + PipelineExpansion +} + +// pipelines implements PipelineInterface +type pipelines struct { + client rest.Interface + ns string +} + +// newPipelines returns a Pipelines +func newPipelines(c *MessagingV1alpha1Client, namespace string) *pipelines { + return &pipelines{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. +func (c *pipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Pipelines that match those selectors. +func (c *pipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { + result = &v1alpha1.PipelineList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pipelines. +func (c *pipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *pipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pipelines"). + Body(pipeline). + Do(). + Into(result) + return +} + +// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. +func (c *pipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pipelines"). + Name(pipeline.Name). + Body(pipeline). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *pipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pipelines"). + Name(pipeline.Name). + SubResource("status"). + Body(pipeline). + Do(). + Into(result) + return +} + +// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. +func (c *pipelines) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pipelines"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pipelines"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched pipeline. +func (c *pipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { + result = &v1alpha1.Pipeline{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("pipelines"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go index af7b267c4c1..614c6d2e90a 100644 --- a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go @@ -32,8 +32,6 @@ type Interface interface { ClusterChannelProvisioners() ClusterChannelProvisionerInformer // EventTypes returns a EventTypeInformer. EventTypes() EventTypeInformer - // Pipelines returns a PipelineInformer. - Pipelines() PipelineInformer // Subscriptions returns a SubscriptionInformer. Subscriptions() SubscriptionInformer // Triggers returns a TriggerInformer. @@ -71,11 +69,6 @@ func (v *version) EventTypes() EventTypeInformer { return &eventTypeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } -// Pipelines returns a PipelineInformer. -func (v *version) Pipelines() PipelineInformer { - return &pipelineInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} - // Subscriptions returns a SubscriptionInformer. func (v *version) Subscriptions() SubscriptionInformer { return &subscriptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 08cb4bc4ade..589efe24d91 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -63,8 +63,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().ClusterChannelProvisioners().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("eventtypes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().EventTypes().Informer()}, nil - case v1alpha1.SchemeGroupVersion.WithResource("pipelines"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().Pipelines().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subscriptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().Subscriptions().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("triggers"): @@ -73,6 +71,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=messaging.knative.dev, Version=v1alpha1 case messagingv1alpha1.SchemeGroupVersion.WithResource("inmemorychannels"): return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1alpha1().InMemoryChannels().Informer()}, nil + case messagingv1alpha1.SchemeGroupVersion.WithResource("pipelines"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1alpha1().Pipelines().Informer()}, nil // Group=sources.eventing.knative.dev, Version=v1alpha1 case sourcesv1alpha1.SchemeGroupVersion.WithResource("apiserversources"): diff --git a/pkg/client/informers/externalversions/messaging/v1alpha1/interface.go b/pkg/client/informers/externalversions/messaging/v1alpha1/interface.go index 60cbc3d2b54..55664d25a86 100644 --- a/pkg/client/informers/externalversions/messaging/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/messaging/v1alpha1/interface.go @@ -26,6 +26,8 @@ import ( type Interface interface { // InMemoryChannels returns a InMemoryChannelInformer. InMemoryChannels() InMemoryChannelInformer + // Pipelines returns a PipelineInformer. + Pipelines() PipelineInformer } type version struct { @@ -43,3 +45,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList func (v *version) InMemoryChannels() InMemoryChannelInformer { return &inMemoryChannelInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// Pipelines returns a PipelineInformer. +func (v *version) Pipelines() PipelineInformer { + return &pipelineInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/messaging/v1alpha1/pipeline.go b/pkg/client/informers/externalversions/messaging/v1alpha1/pipeline.go new file mode 100644 index 00000000000..7cd86b3bca4 --- /dev/null +++ b/pkg/client/informers/externalversions/messaging/v1alpha1/pipeline.go @@ -0,0 +1,89 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + messagingv1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + versioned "github.com/knative/eventing/pkg/client/clientset/versioned" + internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// PipelineInformer provides access to a shared informer and lister for +// Pipelines. +type PipelineInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.PipelineLister +} + +type pipelineInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPipelineInformer constructs a new informer for Pipeline type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPipelineInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPipelineInformer constructs a new informer for Pipeline type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1alpha1().Pipelines(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1alpha1().Pipelines(namespace).Watch(options) + }, + }, + &messagingv1alpha1.Pipeline{}, + resyncPeriod, + indexers, + ) +} + +func (f *pipelineInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPipelineInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *pipelineInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&messagingv1alpha1.Pipeline{}, f.defaultInformer) +} + +func (f *pipelineInformer) Lister() v1alpha1.PipelineLister { + return v1alpha1.NewPipelineLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go index f7b35672eb1..5f34cbf332d 100644 --- a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go @@ -46,14 +46,6 @@ type EventTypeListerExpansion interface{} // EventTypeNamespaceLister. type EventTypeNamespaceListerExpansion interface{} -// PipelineListerExpansion allows custom methods to be added to -// PipelineLister. -type PipelineListerExpansion interface{} - -// PipelineNamespaceListerExpansion allows custom methods to be added to -// PipelineNamespaceLister. -type PipelineNamespaceListerExpansion interface{} - // SubscriptionListerExpansion allows custom methods to be added to // SubscriptionLister. type SubscriptionListerExpansion interface{} diff --git a/pkg/client/listers/messaging/v1alpha1/expansion_generated.go b/pkg/client/listers/messaging/v1alpha1/expansion_generated.go index 8eaaa98e243..02221b279fd 100644 --- a/pkg/client/listers/messaging/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/messaging/v1alpha1/expansion_generated.go @@ -25,3 +25,11 @@ type InMemoryChannelListerExpansion interface{} // InMemoryChannelNamespaceListerExpansion allows custom methods to be added to // InMemoryChannelNamespaceLister. type InMemoryChannelNamespaceListerExpansion interface{} + +// PipelineListerExpansion allows custom methods to be added to +// PipelineLister. +type PipelineListerExpansion interface{} + +// PipelineNamespaceListerExpansion allows custom methods to be added to +// PipelineNamespaceLister. +type PipelineNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/messaging/v1alpha1/pipeline.go b/pkg/client/listers/messaging/v1alpha1/pipeline.go new file mode 100644 index 00000000000..4424b0df6ae --- /dev/null +++ b/pkg/client/listers/messaging/v1alpha1/pipeline.go @@ -0,0 +1,94 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// PipelineLister helps list Pipelines. +type PipelineLister interface { + // List lists all Pipelines in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) + // Pipelines returns an object that can list and get Pipelines. + Pipelines(namespace string) PipelineNamespaceLister + PipelineListerExpansion +} + +// pipelineLister implements the PipelineLister interface. +type pipelineLister struct { + indexer cache.Indexer +} + +// NewPipelineLister returns a new PipelineLister. +func NewPipelineLister(indexer cache.Indexer) PipelineLister { + return &pipelineLister{indexer: indexer} +} + +// List lists all Pipelines in the indexer. +func (s *pipelineLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Pipeline)) + }) + return ret, err +} + +// Pipelines returns an object that can list and get Pipelines. +func (s *pipelineLister) Pipelines(namespace string) PipelineNamespaceLister { + return pipelineNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// PipelineNamespaceLister helps list and get Pipelines. +type PipelineNamespaceLister interface { + // List lists all Pipelines in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) + // Get retrieves the Pipeline from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Pipeline, error) + PipelineNamespaceListerExpansion +} + +// pipelineNamespaceLister implements the PipelineNamespaceLister +// interface. +type pipelineNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Pipelines in the indexer for a given namespace. +func (s pipelineNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Pipeline)) + }) + return ret, err +} + +// Get retrieves the Pipeline from the indexer for a given namespace and name. +func (s pipelineNamespaceLister) Get(name string) (*v1alpha1.Pipeline, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("pipeline"), name) + } + return obj.(*v1alpha1.Pipeline), nil +} From f248e2d953705521d3ce619060afcac8c19bb9f7 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 15 May 2019 20:34:11 +0000 Subject: [PATCH 04/31] cleanup --- pkg/apis/messaging/v1alpha1/pipeline_types.go | 23 +++++++++++------- .../v1alpha1/zz_generated.deepcopy.go | 24 ++++--------------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index 2db66024d79..f086ef749e8 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -54,12 +54,20 @@ type PipelineSpec struct { // provided. Steps []eventingv1alpha1.SubscriberSpec + // TODO: Specify the CRD Channel which should be used to create the underlying Channels. + // Subscriber is the addressable that optionally receives replies from the last step in the pipeline. // +optional Subscriber *eventingv1alpha1.SubscriberSpec `json:"subscriber,omitempty"` } -type StepStatus struct { +// PipelineStatus represents the current state of a Pipeline. +type PipelineStatus struct { + // inherits duck/v1alpha1 Status, which currently provides: + // * ObservedGeneration - the 'Generation' of the Service that was last processed by the controller. + // * Conditions - the latest available observations of a resource's current state. + duckv1alpha1.Status `json:",inline"` + // SubscriptionStatuses is an array of corresponding Subscription statuses. // Matches the Spec.Steps array in the order. SubscriptionStatuses []eventingv1alpha1.SubscriptionStatus @@ -67,14 +75,6 @@ type StepStatus struct { // ChannelStatuses is an array of corresponding Channel statuses. // Matches the Spec.Steps array in the order. ChannelStatuses []eventingv1alpha1.ChannelStatus -} - -// PipelineStatus represents the current state of a Pipeline. -type PipelineStatus struct { - // inherits duck/v1alpha1 Status, which currently provides: - // * ObservedGeneration - the 'Generation' of the Service that was last processed by the controller. - // * Conditions - the latest available observations of a resource's current state. - duckv1alpha1.Status `json:",inline"` // Addressable is the starting point to this Pipeline. Sending to this will target the first Subscriber. Address duckv1alpha1.Addressable `json:"address,omitempty"` @@ -89,3 +89,8 @@ type PipelineList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Pipeline `json:"items"` } + +// GetGroupVersionKind returns GroupVersionKind for InMemoryChannels +func (imc *InMemoryChannel) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("InMemoryChannel") +} diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index db0ed0f4904..889ebe2cad4 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -219,23 +219,6 @@ func (in *PipelineSpec) DeepCopy() *PipelineSpec { func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { *out = *in in.Status.DeepCopyInto(&out.Status) - in.Address.DeepCopyInto(&out.Address) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. -func (in *PipelineStatus) DeepCopy() *PipelineStatus { - if in == nil { - return nil - } - out := new(PipelineStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StepStatus) DeepCopyInto(out *StepStatus) { - *out = *in if in.SubscriptionStatuses != nil { in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses *out = make([]eventingv1alpha1.SubscriptionStatus, len(*in)) @@ -250,15 +233,16 @@ func (in *StepStatus) DeepCopyInto(out *StepStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + in.Address.DeepCopyInto(&out.Address) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepStatus. -func (in *StepStatus) DeepCopy() *StepStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. +func (in *PipelineStatus) DeepCopy() *PipelineStatus { if in == nil { return nil } - out := new(StepStatus) + out := new(PipelineStatus) in.DeepCopyInto(out) return out } From d52c7a646777638d67a0f7e5de3478d963af7be9 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 16 May 2019 15:37:12 -0700 Subject: [PATCH 05/31] pr feedback, simple proposal for ChannelCRD Specs --- pkg/apis/messaging/v1alpha1/pipeline_types.go | 52 ++++++++++++-- .../v1alpha1/zz_generated.deepcopy.go | 72 +++++++++++++++++-- 2 files changed, 111 insertions(+), 13 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index f086ef749e8..fd5b695b8fb 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -21,6 +21,7 @@ import ( "github.com/knative/pkg/apis" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "github.com/knative/pkg/webhook" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -49,16 +50,53 @@ var _ apis.Immutable = (*Pipeline)(nil) var _ runtime.Object = (*Pipeline)(nil) var _ webhook.GenericCRD = (*Pipeline)(nil) +// This should be duck so that Broker can also use this +type ChannelTemplateSpec struct { + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + ChannelCRD corev1.ObjectReference `json:"channelCRD,omitempty"` + + // Spec defines the Spec to use for each channel created. Passed + // in verbatim to the Channel CRD as Spec section. + // +optional + Spec *runtime.RawExtension `json:"spec,omitempty"` +} + type PipelineSpec struct { // Steps is the list of Subscribers (processors / functions) that will be called in the order // provided. - Steps []eventingv1alpha1.SubscriberSpec + Steps []eventingv1alpha1.SubscriberSpec `json:"steps"` + + // ChannelTemplate specifies which Channel CRD to use + ChannelTemplate ChannelTemplateSpec `json:"channelTemplate"` + + // Reply is a Reference to where the result of the last Subscriber gets sent to. + // Currently due to limitations on the Subscription, this _must_ be a Channel. + // If that changes, we can change that later. + // + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // Kind must be "Channel" and APIVersion must be + // "eventing.knative.dev/v1alpha1" + // +optional + Reply *corev1.ObjectReference `json:"reply,omitempty"` +} - // TODO: Specify the CRD Channel which should be used to create the underlying Channels. +type PipelineChannelStatus struct { + // Channel is the reference to the underlying channel. + Channel corev1.ObjectReference `json:"channel"` + // ReadyCondition indicates whether the Channel is ready or not. + ReadyCondition duckv1alpha1.Condition `json:"ready"` +} - // Subscriber is the addressable that optionally receives replies from the last step in the pipeline. - // +optional - Subscriber *eventingv1alpha1.SubscriberSpec `json:"subscriber,omitempty"` +type PipelineSubscriptionStatus struct { + // Subscription is the reference to the underlying Subscription. + Subscription corev1.ObjectReference `json:"subscription"` + // ReadyCondition indicates whether the Subscription is ready or not. + ReadyCondition duckv1alpha1.Condition `json:"ready"` } // PipelineStatus represents the current state of a Pipeline. @@ -70,11 +108,11 @@ type PipelineStatus struct { // SubscriptionStatuses is an array of corresponding Subscription statuses. // Matches the Spec.Steps array in the order. - SubscriptionStatuses []eventingv1alpha1.SubscriptionStatus + SubscriptionStatuses []PipelineSubscriptionStatus // ChannelStatuses is an array of corresponding Channel statuses. // Matches the Spec.Steps array in the order. - ChannelStatuses []eventingv1alpha1.ChannelStatus + ChannelStatuses []PipelineChannelStatus // Addressable is the starting point to this Pipeline. Sending to this will target the first Subscriber. Address duckv1alpha1.Addressable `json:"address,omitempty"` diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index 889ebe2cad4..4593a7e9259 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -23,9 +23,32 @@ package v1alpha1 import ( duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelTemplateSpec) DeepCopyInto(out *ChannelTemplateSpec) { + *out = *in + out.ChannelCRD = in.ChannelCRD + if in.Spec != nil { + in, out := &in.Spec, &out.Spec + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelTemplateSpec. +func (in *ChannelTemplateSpec) DeepCopy() *ChannelTemplateSpec { + if in == nil { + return nil + } + out := new(ChannelTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InMemoryChannel) DeepCopyInto(out *InMemoryChannel) { *out = *in @@ -154,6 +177,24 @@ func (in *Pipeline) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineChannelStatus) DeepCopyInto(out *PipelineChannelStatus) { + *out = *in + out.Channel = in.Channel + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineChannelStatus. +func (in *PipelineChannelStatus) DeepCopy() *PipelineChannelStatus { + if in == nil { + return nil + } + out := new(PipelineChannelStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PipelineList) DeepCopyInto(out *PipelineList) { *out = *in @@ -197,10 +238,11 @@ func (in *PipelineSpec) DeepCopyInto(out *PipelineSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.Subscriber != nil { - in, out := &in.Subscriber, &out.Subscriber - *out = new(eventingv1alpha1.SubscriberSpec) - (*in).DeepCopyInto(*out) + in.ChannelTemplate.DeepCopyInto(&out.ChannelTemplate) + if in.Reply != nil { + in, out := &in.Reply, &out.Reply + *out = new(v1.ObjectReference) + **out = **in } return } @@ -221,14 +263,14 @@ func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { in.Status.DeepCopyInto(&out.Status) if in.SubscriptionStatuses != nil { in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses - *out = make([]eventingv1alpha1.SubscriptionStatus, len(*in)) + *out = make([]PipelineSubscriptionStatus, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ChannelStatuses != nil { in, out := &in.ChannelStatuses, &out.ChannelStatuses - *out = make([]eventingv1alpha1.ChannelStatus, len(*in)) + *out = make([]PipelineChannelStatus, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -246,3 +288,21 @@ func (in *PipelineStatus) DeepCopy() *PipelineStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineSubscriptionStatus) DeepCopyInto(out *PipelineSubscriptionStatus) { + *out = *in + out.Subscription = in.Subscription + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineSubscriptionStatus. +func (in *PipelineSubscriptionStatus) DeepCopy() *PipelineSubscriptionStatus { + if in == nil { + return nil + } + out := new(PipelineSubscriptionStatus) + in.DeepCopyInto(out) + return out +} From 45c2bb2a33f38a9fe6a36c82ac4f9adf6c01e435 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 28 May 2019 08:10:35 -0700 Subject: [PATCH 06/31] validation tests, more unit tests, still mosdef wip --- pkg/apis/eventing/v1alpha1/register.go | 2 - .../messaging/v1alpha1/pipeline_lifecycle.go | 12 +- pkg/apis/messaging/v1alpha1/pipeline_types.go | 7 +- .../messaging/v1alpha1/pipeline_validation.go | 158 +++++++++++++++- .../v1alpha1/pipeline_validation_test.go | 163 ++++++++++++++++ pkg/apis/messaging/v1alpha1/register.go | 2 + .../eventing/v1alpha1/fake/fake_pipeline.go | 140 -------------- .../typed/eventing/v1alpha1/pipeline.go | 174 ------------------ .../eventing/v1alpha1/pipeline.go | 89 --------- .../listers/eventing/v1alpha1/pipeline.go | 94 ---------- pkg/reconciler/pipeline/pipeline.go | 108 ++++++----- pkg/reconciler/pipeline/pipeline_test.go | 166 +++++++++++++++++ pkg/reconciler/pipeline/resources/channel.go | 53 ++++++ pkg/reconciler/testing/listers.go | 4 + pkg/reconciler/testing/pipeline.go | 123 +++++++++++++ 15 files changed, 739 insertions(+), 556 deletions(-) create mode 100644 pkg/apis/messaging/v1alpha1/pipeline_validation_test.go delete mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go delete mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go delete mode 100644 pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go delete mode 100644 pkg/client/listers/eventing/v1alpha1/pipeline.go create mode 100644 pkg/reconciler/pipeline/pipeline_test.go create mode 100644 pkg/reconciler/pipeline/resources/channel.go create mode 100644 pkg/reconciler/testing/pipeline.go diff --git a/pkg/apis/eventing/v1alpha1/register.go b/pkg/apis/eventing/v1alpha1/register.go index 51be16c3748..320b6be84b5 100644 --- a/pkg/apis/eventing/v1alpha1/register.go +++ b/pkg/apis/eventing/v1alpha1/register.go @@ -53,8 +53,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ClusterChannelProvisionerList{}, &EventType{}, &EventTypeList{}, - &Pipeline{}, - &PipelineList{}, &Subscription{}, &SubscriptionList{}, &Trigger{}, diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index 7c1bacffd24..6bba81445d1 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -18,6 +18,8 @@ package v1alpha1 import duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" +var pCondSet = duckv1alpha1.NewLivingConditionSet(PipelineConditionReferencesResolved) + const ( // PipelineConditionReady has status True when all subconditions below have been set to True. PipelineConditionReady = duckv1alpha1.ConditionReady @@ -33,25 +35,25 @@ const ( // GetCondition returns the condition currently associated with the given type, or nil. func (ss *PipelineStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition { - return subCondSet.Manage(ss).GetCondition(t) + return pCondSet.Manage(ss).GetCondition(t) } // IsReady returns true if the resource is ready overall. func (ss *PipelineStatus) IsReady() bool { - return subCondSet.Manage(ss).IsHappy() + return pCondSet.Manage(ss).IsHappy() } // InitializeConditions sets relevant unset conditions to Unknown state. func (ss *PipelineStatus) InitializeConditions() { - subCondSet.Manage(ss).InitializeConditions() + pCondSet.Manage(ss).InitializeConditions() } // MarkReferencesResolved sets the ReferencesResolved condition to True state. func (ss *PipelineStatus) MarkReferencesResolved() { - subCondSet.Manage(ss).MarkTrue(PipelineConditionReferencesResolved) + pCondSet.Manage(ss).MarkTrue(PipelineConditionReferencesResolved) } // MarkChannelReady sets the ChannelReady condition to True state. func (ss *PipelineStatus) MarkChannelReady() { - subCondSet.Manage(ss).MarkTrue(PipelineConditionChannelReady) + pCondSet.Manage(ss).MarkTrue(PipelineConditionChannelReady) } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index fd5b695b8fb..a0a9d555ab0 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) // +genclient @@ -46,7 +47,9 @@ type Pipeline struct { // Check that Pipeline can be validated, can be defaulted, and has immutable fields. var _ apis.Validatable = (*Pipeline)(nil) var _ apis.Defaultable = (*Pipeline)(nil) -var _ apis.Immutable = (*Pipeline)(nil) + +// TOOD: make appropriate fields immutable. +//var _ apis.Immutable = (*Pipeline)(nil) var _ runtime.Object = (*Pipeline)(nil) var _ webhook.GenericCRD = (*Pipeline)(nil) @@ -129,6 +132,6 @@ type PipelineList struct { } // GetGroupVersionKind returns GroupVersionKind for InMemoryChannels -func (imc *InMemoryChannel) GetGroupVersionKind() schema.GroupVersionKind { +func (p *Pipeline) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind("InMemoryChannel") } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go index db812e5c357..65c1e4ce04e 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -18,27 +18,167 @@ package v1alpha1 import ( "context" + "reflect" "github.com/knative/pkg/apis" - "github.com/knative/pkg/kmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + // "github.com/knative/pkg/kmp" + "github.com/google/go-cmp/cmp" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" ) func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError { - return t.Spec.Validate(ctx).ViaField("spec") + return p.Spec.Validate(ctx).ViaField("spec") } func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { var errs *apis.FieldError - if imcs.Subscribable != nil { - for i, subscriber := range imcs.Subscribable.Subscribers { - if subscriber.ReplyURI == "" && subscriber.SubscriberURI == "" { - fe := apis.ErrMissingField("replyURI", "subscriberURI") - fe.Details = "expected at least one of, got none" - errs = errs.Also(fe.ViaField(fmt.Sprintf("subscriber[%d]", i)).ViaField("subscribable")) - } + if len(ps.Steps) == 0 { + errs = errs.Also(apis.ErrMissingField("steps")) + } + + for i, s := range ps.Steps { + if e := isValidSubscriberSpec(s); e != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(s, "steps", i)) } } + if equality.Semantic.DeepEqual(ps.ChannelTemplate, ChannelTemplateSpec{}) { + errs = errs.Also(apis.ErrMissingField("channelTemplate.channelCRD")) + return errs + } + + if e := isValidObjectReferenceForChannelCRD(ps.ChannelTemplate.ChannelCRD); e != nil { + errs = errs.Also(e) + } + return errs } + +func isValidSubscriberSpec(s eventingv1alpha1.SubscriberSpec) *apis.FieldError { + var errs *apis.FieldError + + fieldsSet := make([]string, 0, 0) + if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) { + fieldsSet = append(fieldsSet, "ref") + } + if s.DeprecatedDNSName != nil && *s.DeprecatedDNSName != "" { + fieldsSet = append(fieldsSet, "dnsName") + } + if s.URI != nil && *s.URI != "" { + fieldsSet = append(fieldsSet, "uri") + } + if len(fieldsSet) == 0 { + errs = errs.Also(apis.ErrMissingOneOf("ref", "dnsName", "uri")) + } else if len(fieldsSet) > 1 { + errs = errs.Also(apis.ErrMultipleOneOf(fieldsSet...)) + } + + // If Ref given, check the fields. + if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) { + fe := isValidObjectReferenceForSubscriber(*s.Ref) + if fe != nil { + errs = errs.Also(fe.ViaField("ref")) + } + } + return errs +} + +func isValidObjectReferenceForSubscriber(f corev1.ObjectReference) *apis.FieldError { + return checkRequiredObjectReferenceFieldsForSubscriber(f). + Also(checkDisallowedObjectReferenceFieldsForSubscriber(f)) +} + +// Check the corev1.ObjectReference to make sure it has the required fields. They +// are not checked for anything more except that they are set. +func checkRequiredObjectReferenceFieldsForSubscriber(f corev1.ObjectReference) *apis.FieldError { + var errs *apis.FieldError + if f.Name == "" { + errs = errs.Also(apis.ErrMissingField("name")) + } + if f.APIVersion == "" { + errs = errs.Also(apis.ErrMissingField("apiVersion")) + } + if f.Kind == "" { + errs = errs.Also(apis.ErrMissingField("kind")) + } + return errs +} + +// Check the corev1.ObjectReference to make sure it only has the following fields set: +// Name, Kind, APIVersion +// If any other fields are set and is not the Zero value, returns an apis.FieldError +// with the fieldpaths for all those fields. +func checkDisallowedObjectReferenceFieldsForSubscriber(f corev1.ObjectReference) *apis.FieldError { + disallowedFields := []string{} + // See if there are any fields that have been set that should not be. + // TODO: Hoist this kind of stuff into pkg repository. + s := reflect.ValueOf(f) + typeOf := s.Type() + for i := 0; i < s.NumField(); i++ { + field := s.Field(i) + fieldName := typeOf.Field(i).Name + if fieldName == "Name" || fieldName == "Kind" || fieldName == "APIVersion" { + continue + } + if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { + disallowedFields = append(disallowedFields, fieldName) + } + } + if len(disallowedFields) > 0 { + fe := apis.ErrDisallowedFields(disallowedFields...) + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + } + return nil + +} + +func isValidObjectReferenceForChannelCRD(c corev1.ObjectReference) *apis.FieldError { + return checkRequiredObjectReferenceFieldsForChannelCRD(c). + Also(checkDisallowedObjectReferenceFieldsForChannelCRD(c)) +} + +// Check the corev1.ObjectReference to make sure it has the required fields. They +// are not checked for anything more except that they are set. +func checkRequiredObjectReferenceFieldsForChannelCRD(c corev1.ObjectReference) *apis.FieldError { + var errs *apis.FieldError + if c.APIVersion == "" { + errs = errs.Also(apis.ErrMissingField("apiVersion")) + } + if c.Kind == "" { + errs = errs.Also(apis.ErrMissingField("kind")) + } + return errs +} + +// Check the corev1.ObjectReference to make sure it only has the following fields set: +// Kind, APIVersion +// If any other fields are set and is not the Zero value, returns an apis.FieldError +// with the fieldpaths for all those fields. +func checkDisallowedObjectReferenceFieldsForChannelCRD(c corev1.ObjectReference) *apis.FieldError { + disallowedFields := []string{} + // See if there are any fields that have been set that should not be. + // TODO: Hoist this kind of stuff into pkg repository. + s := reflect.ValueOf(c) + typeOf := s.Type() + for i := 0; i < s.NumField(); i++ { + field := s.Field(i) + fieldName := typeOf.Field(i).Name + if fieldName == "Kind" || fieldName == "APIVersion" { + continue + } + if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { + disallowedFields = append(disallowedFields, fieldName) + } + } + if len(disallowedFields) > 0 { + fe := apis.ErrDisallowedFields(disallowedFields...) + fe.Details = "only apiVersion and kind are supported fields" + return fe + } + return nil + +} diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go new file mode 100644 index 00000000000..4f847276871 --- /dev/null +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go @@ -0,0 +1,163 @@ +/* +Copyright 2019 The Knative 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 v1alpha1 + +import ( + "context" + "testing" + + "github.com/knative/pkg/apis" + + "github.com/google/go-cmp/cmp" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +func TestPipelineValidation(t *testing.T) { + name := "invalid pipeline spec" + pipeline := &Pipeline{Spec: PipelineSpec{}} + + want := &apis.FieldError{ + Paths: []string{"spec.channelTemplate.channelCRD", "spec.steps"}, + Message: "missing field(s)", + } + + t.Run(name, func(t *testing.T) { + got := pipeline.Validate(context.TODO()) + if diff := cmp.Diff(want.Error(), got.Error()); diff != "" { + t.Errorf("Pipeline.Validate (-want, +got) = %v", diff) + } + }) +} + +func TestPipelineSpecValidation(t *testing.T) { + subscriberURI := "http://example.com" + tests := []struct { + name string + ts *PipelineSpec + want *apis.FieldError + }{{ + name: "invalid pipeline spec - empty", + ts: &PipelineSpec{}, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("channelTemplate.channelCRD", "steps") + return fe + }(), + }, { + name: "invalid pipeline spec - empty steps", + ts: &PipelineSpec{ + ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion"}}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("steps") + return fe + }(), + }, { + name: "missing channeltemplatespec", + ts: &PipelineSpec{ + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("channelTemplate.channelCRD") + return fe + }(), + }, { + name: "invalid channeltemplatespec has name", + ts: &PipelineSpec{ + ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion", Name: "namehere"}}, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("Name") + fe.Details = "only apiVersion and kind are supported fields" + return fe + }(), + }, { + name: "invalid channeltemplatespec missing apiversion", + ts: &PipelineSpec{ + ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind"}}, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("apiVersion") + return fe + }(), + }, { + name: "invalid channeltemplatespec missing kind", + ts: &PipelineSpec{ + ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{APIVersion: "myapiversion"}}, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("kind") + return fe + }(), + }, { + name: "valid pipeline", + ts: &PipelineSpec{ + ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion"}}, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + }, + want: func() *apis.FieldError { + return nil + }(), + /* + }, { + name: "missing filter.sourceAndType", + ts: &PipelineSpec{ + Broker: "test_broker", + Filter: &PipelineFilter{}, + Subscriber: validSubscriber, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("filter.sourceAndType") + return fe + }(), + }, { + name: "missing subscriber", + ts: &PipelineSpec{ + Broker: "test_broker", + Filter: validPipelineFilter, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("subscriber") + return fe + }(), + }, { + name: "missing subscriber.ref.name", + ts: &PipelineSpec{ + Broker: "test_broker", + Filter: validPipelineFilter, + Subscriber: invalidSubscriber, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("subscriber.ref.name") + return fe + }(), + */ + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ts.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: Validate PipelineSpec (-want, +got) = %v", test.name, diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1alpha1/register.go b/pkg/apis/messaging/v1alpha1/register.go index bd7d4163d65..ad261af27cc 100644 --- a/pkg/apis/messaging/v1alpha1/register.go +++ b/pkg/apis/messaging/v1alpha1/register.go @@ -47,6 +47,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &InMemoryChannel{}, &InMemoryChannelList{}, + &Pipeline{}, + &PipelineList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go deleted file mode 100644 index 1bf76c3f1a5..00000000000 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_pipeline.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2019 The Knative 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakePipelines implements PipelineInterface -type FakePipelines struct { - Fake *FakeEventingV1alpha1 - ns string -} - -var pipelinesResource = schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1alpha1", Resource: "pipelines"} - -var pipelinesKind = schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1alpha1", Kind: "Pipeline"} - -// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. -func (c *FakePipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Pipeline), err -} - -// List takes label and field selectors, and returns the list of Pipelines that match those selectors. -func (c *FakePipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(pipelinesResource, pipelinesKind, c.ns, opts), &v1alpha1.PipelineList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha1.PipelineList{ListMeta: obj.(*v1alpha1.PipelineList).ListMeta} - for _, item := range obj.(*v1alpha1.PipelineList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested pipelines. -func (c *FakePipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(pipelinesResource, c.ns, opts)) - -} - -// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. -func (c *FakePipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Pipeline), err -} - -// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. -func (c *FakePipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(pipelinesResource, c.ns, pipeline), &v1alpha1.Pipeline{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Pipeline), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakePipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(pipelinesResource, "status", c.ns, pipeline), &v1alpha1.Pipeline{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Pipeline), err -} - -// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. -func (c *FakePipelines) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(pipelinesResource, c.ns, name), &v1alpha1.Pipeline{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakePipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(pipelinesResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1alpha1.PipelineList{}) - return err -} - -// Patch applies the patch and returns the patched pipeline. -func (c *FakePipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(pipelinesResource, c.ns, name, data, subresources...), &v1alpha1.Pipeline{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.Pipeline), err -} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go deleted file mode 100644 index 3f202ba8147..00000000000 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/pipeline.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright 2019 The Knative 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. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - scheme "github.com/knative/eventing/pkg/client/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" -) - -// PipelinesGetter has a method to return a PipelineInterface. -// A group's client should implement this interface. -type PipelinesGetter interface { - Pipelines(namespace string) PipelineInterface -} - -// PipelineInterface has methods to work with Pipeline resources. -type PipelineInterface interface { - Create(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) - Update(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) - UpdateStatus(*v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1alpha1.Pipeline, error) - List(opts v1.ListOptions) (*v1alpha1.PipelineList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) - PipelineExpansion -} - -// pipelines implements PipelineInterface -type pipelines struct { - client rest.Interface - ns string -} - -// newPipelines returns a Pipelines -func newPipelines(c *EventingV1alpha1Client, namespace string) *pipelines { - return &pipelines{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the pipeline, and returns the corresponding pipeline object, and an error if there is any. -func (c *pipelines) Get(name string, options v1.GetOptions) (result *v1alpha1.Pipeline, err error) { - result = &v1alpha1.Pipeline{} - err = c.client.Get(). - Namespace(c.ns). - Resource("pipelines"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Pipelines that match those selectors. -func (c *pipelines) List(opts v1.ListOptions) (result *v1alpha1.PipelineList, err error) { - result = &v1alpha1.PipelineList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("pipelines"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested pipelines. -func (c *pipelines) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("pipelines"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a pipeline and creates it. Returns the server's representation of the pipeline, and an error, if there is any. -func (c *pipelines) Create(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { - result = &v1alpha1.Pipeline{} - err = c.client.Post(). - Namespace(c.ns). - Resource("pipelines"). - Body(pipeline). - Do(). - Into(result) - return -} - -// Update takes the representation of a pipeline and updates it. Returns the server's representation of the pipeline, and an error, if there is any. -func (c *pipelines) Update(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { - result = &v1alpha1.Pipeline{} - err = c.client.Put(). - Namespace(c.ns). - Resource("pipelines"). - Name(pipeline.Name). - Body(pipeline). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *pipelines) UpdateStatus(pipeline *v1alpha1.Pipeline) (result *v1alpha1.Pipeline, err error) { - result = &v1alpha1.Pipeline{} - err = c.client.Put(). - Namespace(c.ns). - Resource("pipelines"). - Name(pipeline.Name). - SubResource("status"). - Body(pipeline). - Do(). - Into(result) - return -} - -// Delete takes name of the pipeline and deletes it. Returns an error if one occurs. -func (c *pipelines) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("pipelines"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *pipelines) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("pipelines"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched pipeline. -func (c *pipelines) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Pipeline, err error) { - result = &v1alpha1.Pipeline{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("pipelines"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go b/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go deleted file mode 100644 index edaa64eabc1..00000000000 --- a/pkg/client/informers/externalversions/eventing/v1alpha1/pipeline.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2019 The Knative 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. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - time "time" - - eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - versioned "github.com/knative/eventing/pkg/client/clientset/versioned" - internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" - v1alpha1 "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" -) - -// PipelineInformer provides access to a shared informer and lister for -// Pipelines. -type PipelineInformer interface { - Informer() cache.SharedIndexInformer - Lister() v1alpha1.PipelineLister -} - -type pipelineInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewPipelineInformer constructs a new informer for Pipeline type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredPipelineInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredPipelineInformer constructs a new informer for Pipeline type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredPipelineInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.EventingV1alpha1().Pipelines(namespace).List(options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.EventingV1alpha1().Pipelines(namespace).Watch(options) - }, - }, - &eventingv1alpha1.Pipeline{}, - resyncPeriod, - indexers, - ) -} - -func (f *pipelineInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredPipelineInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *pipelineInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&eventingv1alpha1.Pipeline{}, f.defaultInformer) -} - -func (f *pipelineInformer) Lister() v1alpha1.PipelineLister { - return v1alpha1.NewPipelineLister(f.Informer().GetIndexer()) -} diff --git a/pkg/client/listers/eventing/v1alpha1/pipeline.go b/pkg/client/listers/eventing/v1alpha1/pipeline.go deleted file mode 100644 index b28e2e93890..00000000000 --- a/pkg/client/listers/eventing/v1alpha1/pipeline.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2019 The Knative 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. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" -) - -// PipelineLister helps list Pipelines. -type PipelineLister interface { - // List lists all Pipelines in the indexer. - List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) - // Pipelines returns an object that can list and get Pipelines. - Pipelines(namespace string) PipelineNamespaceLister - PipelineListerExpansion -} - -// pipelineLister implements the PipelineLister interface. -type pipelineLister struct { - indexer cache.Indexer -} - -// NewPipelineLister returns a new PipelineLister. -func NewPipelineLister(indexer cache.Indexer) PipelineLister { - return &pipelineLister{indexer: indexer} -} - -// List lists all Pipelines in the indexer. -func (s *pipelineLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.Pipeline)) - }) - return ret, err -} - -// Pipelines returns an object that can list and get Pipelines. -func (s *pipelineLister) Pipelines(namespace string) PipelineNamespaceLister { - return pipelineNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// PipelineNamespaceLister helps list and get Pipelines. -type PipelineNamespaceLister interface { - // List lists all Pipelines in the indexer for a given namespace. - List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) - // Get retrieves the Pipeline from the indexer for a given namespace and name. - Get(name string) (*v1alpha1.Pipeline, error) - PipelineNamespaceListerExpansion -} - -// pipelineNamespaceLister implements the PipelineNamespaceLister -// interface. -type pipelineNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Pipelines in the indexer for a given namespace. -func (s pipelineNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Pipeline, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.Pipeline)) - }) - return ret, err -} - -// Get retrieves the Pipeline from the indexer for a given namespace and name. -func (s pipelineNamespaceLister) Get(name string) (*v1alpha1.Pipeline, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1alpha1.Resource("pipeline"), name) - } - return obj.(*v1alpha1.Pipeline), nil -} diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 50972cfa2d9..5615f85167b 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -18,19 +18,25 @@ package pipeline import ( "context" + "fmt" "reflect" corev1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/tools/cache" - "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" eventinginformers "github.com/knative/eventing/pkg/client/informers/externalversions/eventing/v1alpha1" - listers "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" + informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" + eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" + listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" + "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" + "github.com/knative/eventing/pkg/reconciler/pipeline/resources" "github.com/knative/pkg/controller" "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -40,9 +46,9 @@ const ( // itself when creating events. controllerAgentName = "pipeline-controller" - pipelineReconciled = "PipelineReconciled" - pipelineReconcileFailed = "PipelineReconcileFailed" - pipelineUpdateStatusFailed = "PipelineUpdateStatusFailed" + reconciled = "Reconciled" + reconcileFailed = "ReconcileFailed" + updateStatusFailed = "UpdateStatusFailed" ) type Reconciler struct { @@ -50,8 +56,8 @@ type Reconciler struct { // listers index properties about resources pipelineLister listers.PipelineLister - channelLister listers.ChannelLister - subscriptionLister listers.SubscriptionLister + channelLister eventinglisters.ChannelLister + subscriptionLister eventinglisters.SubscriptionLister } // Check that our Reconciler implements controller.Reconciler @@ -61,7 +67,7 @@ var _ controller.Reconciler = (*Reconciler)(nil) // Registers event handlers to enqueue events func NewController( opt reconciler.Options, - pipelineInformer eventinginformers.PipelineInformer, + pipelineInformer informers.PipelineInformer, channelInformer eventinginformers.ChannelInformer, subscriptionInformer eventinginformers.SubscriptionInformer, ) *controller.Impl { @@ -80,7 +86,7 @@ func NewController( // Register handlers for Channel/Subscriptions that are owned by Pipeline, so that // we get notified if they change. - subscriptionInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + channelInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Channel")), Handler: reconciler.Handler(impl.EnqueueControllerOf), }) @@ -92,9 +98,11 @@ func NewController( return impl } -// Reconcile will check if the channel is being watched by provisioner's channel controller -// This will improve UX. See https://github.com/knative/eventing/issues/779 +// Reconcile compares the actual state with the desired, and attempts to +// reconcile the two. It then updates the Status block of the Pipeline resource +// with the current Status of the resource. func (r *Reconciler) Reconcile(ctx context.Context, key string) error { + logging.FromContext(ctx).Error("reconciling", zap.String("key", key)) // Convert the namespace/name string into a distinct namespace and name namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { @@ -112,11 +120,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { return err } - // Delete is a no-op. - if original.DeletionTimestamp != nil { - return nil - } - // Don't modify the informers copy pipeline := original.DeepCopy() @@ -125,14 +128,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { reconcileErr := r.reconcile(ctx, pipeline) if reconcileErr != nil { logging.FromContext(ctx).Error("Error reconciling Pipeline", zap.Error(reconcileErr)) + r.Recorder.Eventf(pipeline, corev1.EventTypeWarning, reconcileFailed, "Pipeline reconciliation failed: %v", reconcileErr) } else { logging.FromContext(ctx).Debug("Successfully reconciled Pipeline") - r.Recorder.Eventf(pipeline, corev1.EventTypeNormal, pipelineReconciled, "Pipeline reconciled: %s", key) + r.Recorder.Eventf(pipeline, corev1.EventTypeNormal, reconciled, "Pipeline reconciled") } if _, updateStatusErr := r.updateStatus(ctx, pipeline.DeepCopy()); updateStatusErr != nil { logging.FromContext(ctx).Warn("Error updating Pipeline status", zap.Error(updateStatusErr)) - r.Recorder.Eventf(pipeline, corev1.EventTypeWarning, pipelineUpdateStatusFailed, "Failed to update pipeline status: %s", key) + r.Recorder.Eventf(pipeline, corev1.EventTypeWarning, updateStatusFailed, "Failed to update pipeline status: %s", key) return updateStatusErr } @@ -140,47 +144,69 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { return reconcileErr } +func pipelineChannelName(pipelineName string, step int) string { + return fmt.Sprintf("%s-kn-pipeline-%d", pipelineName, step) +} + func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error { p.Status.InitializeConditions() - // Do not Initialize() Status in pipeline-default-controller. It will set ChannelConditionProvisionerInstalled=True - // Directly call GetCondition(). If the Status was never initialized then GetCondition() will return nil and - // IsUnknown() will return true - c := ch.Status.GetCondition(v1alpha1.ChannelConditionProvisionerInstalled) + // Reconciling pipeline is pretty straightforward, it does the following things: + // 1. Create a channel fronting the whole pipeline + // 2. For each of the Steps, create a Subscription to the previous Channel + // (hence the first step above for the first step in the "steps"), where the Subscriber points to the + // Step, and create intermediate channel for feeding the Reply to (if we allow Reply to be something else + // than channel, we could just (optionally) feed it directly to the following subscription. + // 3. Rinse and repeat step #2 above for each Step in the list + // 4. If there's a Reply, then the last Subscription will be configured to send the reply to that. + if p.DeletionTimestamp != nil { + // Everything is cleaned up by the garbage collector. + return nil + } - if c == nil || c.IsUnknown() { + channelResourceInterface, err := duck.ResourceInterface(r.DynamicClientSet, p.Namespace, &p.Spec.ChannelTemplate.ChannelCRD) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate.ChannelCRD), zap.Error(err)) + return err + } - var proName string - var proKind string - if ch.Spec.Provisioner != nil { - proName = ch.Spec.Provisioner.Name - proKind = ch.Spec.Provisioner.Kind - } + ingressChannelName := pipelineChannelName(p.Name, 0) - ch.Status.MarkProvisionerNotInstalled( - "Provisioner not found.", - "Specified provisioner [Name:%s Kind:%s] is not installed or not controlling the channel.", - proName, - proKind, - ) + // Use the duck typed one here instead of this Get. + c, err := channelResourceInterface.Get(ingressChannelName, metav1.GetOptions{}) + if err != nil { + if apierrs.IsNotFound(err) { + newChannel := resources.NewChannel(ingressChannelName, &p.Spec.ChannelTemplate) + channelResourceInterface.Create(newChannel, metav1.CreateOptions{}) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) + return err + } + logging.FromContext(ctx).Error(fmt.Sprintf("Created Channel: %s/%s", p.Namespace, ingressChannelName), zap.Any("NewChannel", zap.Any("NewChannel", newChannel))) + } else { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to get Channel: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) + return err + } } + logging.FromContext(ctx).Error(fmt.Sprintf("Found Channel: %s/%s", p.Namespace, ingressChannelName), zap.Any("NewChannel", c)) + return nil } -func (r *Reconciler) updateStatus(ctx context.Context, desired *v1alpha1.Channel) (*v1alpha1.Channel, error) { - channel, err := r.channelLister.Channels(desired.Namespace).Get(desired.Name) +func (r *Reconciler) updateStatus(ctx context.Context, desired *v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) { + p, err := r.pipelineLister.Pipelines(desired.Namespace).Get(desired.Name) if err != nil { return nil, err } // If there's nothing to update, just return. - if reflect.DeepEqual(channel.Status, desired.Status) { - return channel, nil + if reflect.DeepEqual(p.Status, desired.Status) { + return p, nil } // Don't modify the informers copy. - existing := channel.DeepCopy() + existing := p.DeepCopy() existing.Status = desired.Status - return r.EventingClientSet.EventingV1alpha1().Channels(desired.Namespace).UpdateStatus(existing) + return r.EventingClientSet.MessagingV1alpha1().Pipelines(desired.Namespace).UpdateStatus(existing) } diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go new file mode 100644 index 00000000000..0230f541a3f --- /dev/null +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -0,0 +1,166 @@ +/* +Copyright 2019 The Knative 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 pipeline + +import ( + // "fmt" + // "net/url" + "testing" + + // eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + fakeclientset "github.com/knative/eventing/pkg/client/clientset/versioned/fake" + informers "github.com/knative/eventing/pkg/client/informers/externalversions" + "github.com/knative/eventing/pkg/reconciler" + // "github.com/knative/eventing/pkg/reconciler/pipeline/resources" + reconciletesting "github.com/knative/eventing/pkg/reconciler/testing" + // "github.com/knative/eventing/pkg/utils" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + "github.com/knative/pkg/controller" + logtesting "github.com/knative/pkg/logging/testing" + . "github.com/knative/pkg/reconciler/testing" + // "github.com/knative/pkg/tracker" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + // "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + // kubeinformers "k8s.io/client-go/informers" + fakekubeclientset "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + // clientgotesting "k8s.io/client-go/testing" +) + +const ( + testNS = "test-namespace" + pipelineName = "test-pipeline" + pipelineUID = "test-pipeline-uid" + brokerName = "test-broker" + + channelServiceAddress = "test-pipeline-kn-channel.test-namespace.svc.cluster.local" + + subscriberAPIVersion = "v1" + subscriberKind = "Service" + subscriberName = "subscriberName" + subscriberURI = "http://example.com/subscriber" +) + +var ( + trueVal = true +) + +func init() { + // Add types to scheme + _ = v1alpha1.AddToScheme(scheme.Scheme) + _ = duckv1alpha1.AddToScheme(scheme.Scheme) +} + +func TestNewController(t *testing.T) { + kubeClient := fakekubeclientset.NewSimpleClientset() + eventingClient := fakeclientset.NewSimpleClientset() + + // Create informer factories with fake clients. The second parameter sets the + // resync period to zero, disabling it. + // kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0) + eventingInformerFactory := informers.NewSharedInformerFactory(eventingClient, 0) + + // Messaging + pipelineInformer := eventingInformerFactory.Messaging().V1alpha1().Pipelines() + + // Eventing + channelInformer := eventingInformerFactory.Eventing().V1alpha1().Channels() + subscriptionInformer := eventingInformerFactory.Eventing().V1alpha1().Subscriptions() + + // Kube + // serviceInformer := kubeInformerFactory.Core().V1().Services() + //endpointsInformer := kubeInformerFactory.Core().V1().Endpoints() + // deploymentInformer := kubeInformerFactory.Apps().V1().Deployments() + + c := NewController( + reconciler.Options{ + KubeClientSet: kubeClient, + EventingClientSet: eventingClient, + Logger: logtesting.TestLogger(t), + }, + pipelineInformer, + channelInformer, + subscriptionInformer) + + if c == nil { + t.Fatalf("Failed to create with NewController") + } +} + +func TestAllCases(t *testing.T) { + pKey := testNS + "/" + pipelineName + imc := metav1.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1alpha1", Kind: "inmemorychannel"} + table := TableTest{ + { + Name: "bad workqueue key", + // Make sure Reconcile handles bad keys. + Key: "too/many/parts", + }, { + Name: "key not found", + // Make sure Reconcile handles good keys that don't exist. + Key: "foo/not-found", + }, { // TODO: there is a bug in the controller, it will query for "" + // Name: "trigger key not found ", + // Objects: []runtime.Object{ + // reconciletesting.NewTrigger(triggerName, testNS), + // }, + // Key: "foo/incomplete", + // WantErr: true, + // WantEvents: []string{ + // Eventf(corev1.EventTypeWarning, "ChannelReferenceFetchFailed", "Failed to validate spec.channel exists: s \"\" not found"), + // }, + }, { + Name: "deleting", + Key: pKey, + Objects: []runtime.Object{ + reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineDeleted)}, + WantErr: false, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), + }, + }, { + Name: "channelworks", + Key: pKey, + Objects: []runtime.Object{ + reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpecCRD(imc))}, + WantErr: false, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), + }, + }, + } + + defer logtesting.ClearAll() + + table.Test(t, reconciletesting.MakeFactory(func(listers *reconciletesting.Listers, opt reconciler.Options) controller.Reconciler { + return &Reconciler{ + Base: reconciler.NewBase(opt, controllerAgentName), + pipelineLister: listers.GetPipelineLister(), + channelLister: listers.GetChannelLister(), + subscriptionLister: listers.GetSubscriptionLister(), + } + }, + false, + )) +} diff --git a/pkg/reconciler/pipeline/resources/channel.go b/pkg/reconciler/pipeline/resources/channel.go new file mode 100644 index 00000000000..8eab51db48a --- /dev/null +++ b/pkg/reconciler/pipeline/resources/channel.go @@ -0,0 +1,53 @@ +/* +Copyright 2019 The Knative 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 resources + +import ( + // "fmt" + // "net/url" + + // "github.com/knative/pkg/kmeta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + messagingv1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + // corev1 "k8s.io/api/core/v1" + // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + // "k8s.io/apimachinery/pkg/runtime/schema" +) + +func getObject(version, kind, name string, spec *runtime.RawExtension) *unstructured.Unstructured { + nonNilSpec := spec + if spec == nil { + nonNilSpec = "{}" + } + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": version, + "kind": kind, + "metadata": map[string]interface{}{ + "name": name, + }, + "spec": nonNilSpec, + }, + } +} + +// NewChannel returns a Channel CRD for the specififed GVK and Spec +func NewChannel(name string, cts *messagingv1alpha1.ChannelTemplateSpec) *unstructured.Unstructured { + return getObject(cts.ChannelCRD.APIVersion, cts.ChannelCRD.Kind, name, cts.Spec) +} diff --git a/pkg/reconciler/testing/listers.go b/pkg/reconciler/testing/listers.go index 70a2588997b..185e80b2429 100644 --- a/pkg/reconciler/testing/listers.go +++ b/pkg/reconciler/testing/listers.go @@ -136,6 +136,10 @@ func (l *Listers) GetChannelLister() eventinglisters.ChannelLister { return eventinglisters.NewChannelLister(l.indexerFor(&eventingv1alpha1.Channel{})) } +func (l *Listers) GetPipelineLister() messaginglisters.PipelineLister { + return messaginglisters.NewPipelineLister(l.indexerFor(&messagingv1alpha1.Pipeline{})) +} + func (l *Listers) GetCronJobSourceLister() sourcelisters.CronJobSourceLister { return sourcelisters.NewCronJobSourceLister(l.indexerFor(&sourcesv1alpha1.CronJobSource{})) } diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go new file mode 100644 index 00000000000..1380ffb4c06 --- /dev/null +++ b/pkg/reconciler/testing/pipeline.go @@ -0,0 +1,123 @@ +/* +Copyright 2019 The Knative 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 testing + +import ( + "context" + "time" + + "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + // appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + // "k8s.io/apimachinery/pkg/types" +) + +// PipelineOption enables further configuration of a Pipeline. +type PipelineOption func(*v1alpha1.Pipeline) + +// NewPipeline creates an Pipeline with PipelineOptions. +func NewPipeline(name, namespace string, popt ...PipelineOption) *v1alpha1.Pipeline { + p := &v1alpha1.Pipeline{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: v1alpha1.PipelineSpec{}, + } + for _, opt := range popt { + opt(p) + } + p.SetDefaults(context.Background()) + return p +} + +func WithInitPipelineConditions(p *v1alpha1.Pipeline) { + p.Status.InitializeConditions() +} + +func WithPipelineDeleted(p *v1alpha1.Pipeline) { + deleteTime := metav1.NewTime(time.Unix(1e9, 0)) + p.ObjectMeta.SetDeletionTimestamp(&deleteTime) +} + +func WithPipelineChannelTemplateSpecCRD(gvk metav1.GroupVersionKind) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Spec.ChannelTemplate = v1alpha1.ChannelTemplateSpec{ + ChannelCRD: corev1.ObjectReference{ + APIVersion: apiVersion(gvk), + Kind: gvk.Kind, + }, + } + } +} + +/* +func WithPipelineDeploymentNotReady(reason, message string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkDispatcherFailed(reason, message) + } +} + +func WithPipelineDeploymentReady() PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.PropagateDispatcherStatus(&appsv1.DeploymentStatus{Conditions: []appsv1.DeploymentCondition{{Type: appsv1.DeploymentAvailable, Status: corev1.ConditionTrue}}}) + } +} + +func WithPipelineServicetNotReady(reason, message string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkServiceFailed(reason, message) + } +} + +func WithPipelineServiceReady() PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkServiceTrue() + } +} + +func WithPipelineChannelServicetNotReady(reason, message string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkChannelServiceFailed(reason, message) + } +} + +func WithPipelineChannelServiceReady() PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkChannelServiceTrue() + } +} + +func WithPipelineEndpointsNotReady(reason, message string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkEndpointsFailed(reason, message) + } +} + +func WithPipelineEndpointsReady() PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkEndpointsTrue() + } +} + +func WithPipelineAddress(a string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.SetAddress(a) + } +} +*/ From e79d6c8e78fc35850851d81c748ecf6e619ae759 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 28 May 2019 18:53:03 -0700 Subject: [PATCH 07/31] use typemeta/objectmeta instead of object ref. matches podtemplatespec this way --- pkg/apis/messaging/v1alpha1/pipeline_types.go | 11 ++-- .../messaging/v1alpha1/pipeline_validation.go | 51 ------------------- .../v1alpha1/zz_generated.deepcopy.go | 17 ++++--- pkg/reconciler/pipeline/pipeline.go | 25 ++++++--- pkg/reconciler/pipeline/pipeline_test.go | 47 +++++++++++++++-- pkg/reconciler/pipeline/resources/channel.go | 39 +++++++------- pkg/reconciler/testing/pipeline.go | 11 ++-- 7 files changed, 102 insertions(+), 99 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index a0a9d555ab0..11fc7b3b574 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -54,16 +54,17 @@ var _ runtime.Object = (*Pipeline)(nil) var _ webhook.GenericCRD = (*Pipeline)(nil) // This should be duck so that Broker can also use this +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ChannelTemplateSpec struct { - // You can specify only the following fields of the ObjectReference: - // - Kind - // - APIVersion - ChannelCRD corev1.ObjectReference `json:"channelCRD,omitempty"` + metav1.TypeMeta `json:",inline"` + + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` // Spec defines the Spec to use for each channel created. Passed // in verbatim to the Channel CRD as Spec section. // +optional - Spec *runtime.RawExtension `json:"spec,omitempty"` + Spec runtime.RawExtension `json:"spec"` } type PipelineSpec struct { diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go index 65c1e4ce04e..e6c7693d3bb 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -50,10 +50,6 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { return errs } - if e := isValidObjectReferenceForChannelCRD(ps.ChannelTemplate.ChannelCRD); e != nil { - errs = errs.Also(e) - } - return errs } @@ -135,50 +131,3 @@ func checkDisallowedObjectReferenceFieldsForSubscriber(f corev1.ObjectReference) return nil } - -func isValidObjectReferenceForChannelCRD(c corev1.ObjectReference) *apis.FieldError { - return checkRequiredObjectReferenceFieldsForChannelCRD(c). - Also(checkDisallowedObjectReferenceFieldsForChannelCRD(c)) -} - -// Check the corev1.ObjectReference to make sure it has the required fields. They -// are not checked for anything more except that they are set. -func checkRequiredObjectReferenceFieldsForChannelCRD(c corev1.ObjectReference) *apis.FieldError { - var errs *apis.FieldError - if c.APIVersion == "" { - errs = errs.Also(apis.ErrMissingField("apiVersion")) - } - if c.Kind == "" { - errs = errs.Also(apis.ErrMissingField("kind")) - } - return errs -} - -// Check the corev1.ObjectReference to make sure it only has the following fields set: -// Kind, APIVersion -// If any other fields are set and is not the Zero value, returns an apis.FieldError -// with the fieldpaths for all those fields. -func checkDisallowedObjectReferenceFieldsForChannelCRD(c corev1.ObjectReference) *apis.FieldError { - disallowedFields := []string{} - // See if there are any fields that have been set that should not be. - // TODO: Hoist this kind of stuff into pkg repository. - s := reflect.ValueOf(c) - typeOf := s.Type() - for i := 0; i < s.NumField(); i++ { - field := s.Field(i) - fieldName := typeOf.Field(i).Name - if fieldName == "Kind" || fieldName == "APIVersion" { - continue - } - if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { - disallowedFields = append(disallowedFields, fieldName) - } - } - if len(disallowedFields) > 0 { - fe := apis.ErrDisallowedFields(disallowedFields...) - fe.Details = "only apiVersion and kind are supported fields" - return fe - } - return nil - -} diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index 4593a7e9259..860cd162707 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -30,12 +30,9 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ChannelTemplateSpec) DeepCopyInto(out *ChannelTemplateSpec) { *out = *in - out.ChannelCRD = in.ChannelCRD - if in.Spec != nil { - in, out := &in.Spec, &out.Spec - *out = new(runtime.RawExtension) - (*in).DeepCopyInto(*out) - } + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) return } @@ -49,6 +46,14 @@ func (in *ChannelTemplateSpec) DeepCopy() *ChannelTemplateSpec { return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChannelTemplateSpec) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InMemoryChannel) DeepCopyInto(out *InMemoryChannel) { *out = *in diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 5615f85167b..2db5d17e8eb 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -18,6 +18,7 @@ package pipeline import ( "context" + "errors" "fmt" "reflect" @@ -30,10 +31,11 @@ import ( informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" - "github.com/knative/eventing/pkg/duck" + // "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" + duckapis "github.com/knative/pkg/apis" "github.com/knative/pkg/controller" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -164,10 +166,17 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error return nil } - channelResourceInterface, err := duck.ResourceInterface(r.DynamicClientSet, p.Namespace, &p.Spec.ChannelTemplate.ChannelCRD) - if err != nil { - logging.FromContext(ctx).Error(fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate.ChannelCRD), zap.Error(err)) - return err + // Convert the object into runtime.Object so we can grab the schema.ObjectKind and create the correct interface client + + obj := p.Spec.ChannelTemplate.DeepCopyObject() + + // channelResourceInterface, err := duck.ResourceInterface(r.DynamicClientSet, p.Namespace, &p.Spec.ChannelTemplate.ChannelCRD) + channelResourceInterface := r.DynamicClientSet.Resource(duckapis.KindToResource(obj.GetObjectKind().GroupVersionKind())) + + if channelResourceInterface == nil { + msg := fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate) + logging.FromContext(ctx).Error(msg) + errors.New(msg) } ingressChannelName := pipelineChannelName(p.Name, 0) @@ -176,7 +185,11 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error c, err := channelResourceInterface.Get(ingressChannelName, metav1.GetOptions{}) if err != nil { if apierrs.IsNotFound(err) { - newChannel := resources.NewChannel(ingressChannelName, &p.Spec.ChannelTemplate) + newChannel, err := resources.NewChannel(ingressChannelName, p) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel resource object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) + return err + } channelResourceInterface.Create(newChannel, metav1.CreateOptions{}) if err != nil { logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index 0230f541a3f..121a97fdc00 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -36,7 +36,7 @@ import ( // "github.com/knative/pkg/tracker" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" // kubeinformers "k8s.io/client-go/informers" fakekubeclientset "k8s.io/client-go/kubernetes/fake" @@ -104,9 +104,47 @@ func TestNewController(t *testing.T) { } } +func createChannel(pipelineName string, stepNumber int) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "messaging.knative.dev/v1alpha1", + "kind": "inmemorychannel", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "namespace": testNS, + "name": pipelineChannelName(pipelineName, stepNumber), + }, + "spec": map[string]interface{}{}, + // "spec": map[string]interface{}{ + // "something": "foo", + // }, + }, + } + +} + func TestAllCases(t *testing.T) { pKey := testNS + "/" + pipelineName - imc := metav1.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1alpha1", Kind: "inmemorychannel"} + imc := v1alpha1.ChannelTemplateSpec{ + metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + }, + metav1.ObjectMeta{}, + runtime.RawExtension{Raw: []byte("{}")}, + } + + /* + imcWithSpec := v1alpha1.ChannelTemplateSpec{ + metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + }, + metav1.ObjectMeta{}, + runtime.RawExtension{Raw: []byte("{}")}, + } + */ + table := TableTest{ { Name: "bad workqueue key", @@ -143,11 +181,14 @@ func TestAllCases(t *testing.T) { Objects: []runtime.Object{ reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithInitPipelineConditions, - reconciletesting.WithPipelineChannelTemplateSpecCRD(imc))}, + reconciletesting.WithPipelineChannelTemplateSpec(imc))}, WantErr: false, WantEvents: []string{ Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), }, + WantCreates: []runtime.Object{ + createChannel(pipelineName, 0), + }, }, } diff --git a/pkg/reconciler/pipeline/resources/channel.go b/pkg/reconciler/pipeline/resources/channel.go index 8eab51db48a..cb619a1ef30 100644 --- a/pkg/reconciler/pipeline/resources/channel.go +++ b/pkg/reconciler/pipeline/resources/channel.go @@ -17,37 +17,36 @@ limitations under the License. package resources import ( + "encoding/json" // "fmt" // "net/url" // "github.com/knative/pkg/kmeta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - messagingv1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" // corev1 "k8s.io/api/core/v1" // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + // "k8s.io/apimachinery/pkg/runtime" // "k8s.io/apimachinery/pkg/runtime/schema" ) -func getObject(version, kind, name string, spec *runtime.RawExtension) *unstructured.Unstructured { - nonNilSpec := spec - if spec == nil { - nonNilSpec = "{}" +// NewChannel returns a Channel CRD for the specififed GVK and Spec +func NewChannel(name string, p *v1alpha1.Pipeline) (*unstructured.Unstructured, error) { + // Set the name of the resource we're creating as well as the namespace, etc. + new := p.DeepCopy() + template := new.Spec.ChannelTemplate + template.Name = name + template.Namespace = p.Namespace + // TODO: Set the owner ref, etc. + raw, err := json.Marshal(template) + if err != nil { + return nil, err } - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": version, - "kind": kind, - "metadata": map[string]interface{}{ - "name": name, - }, - "spec": nonNilSpec, - }, + u := &unstructured.Unstructured{} + err = json.Unmarshal(raw, u) + if err != nil { + return nil, err } -} - -// NewChannel returns a Channel CRD for the specififed GVK and Spec -func NewChannel(name string, cts *messagingv1alpha1.ChannelTemplateSpec) *unstructured.Unstructured { - return getObject(cts.ChannelCRD.APIVersion, cts.ChannelCRD.Kind, name, cts.Spec) + return u, nil } diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index 1380ffb4c06..fabb797198a 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -22,7 +22,7 @@ import ( "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" // appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" + // corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // "k8s.io/apimachinery/pkg/types" ) @@ -55,14 +55,9 @@ func WithPipelineDeleted(p *v1alpha1.Pipeline) { p.ObjectMeta.SetDeletionTimestamp(&deleteTime) } -func WithPipelineChannelTemplateSpecCRD(gvk metav1.GroupVersionKind) PipelineOption { +func WithPipelineChannelTemplateSpec(cts v1alpha1.ChannelTemplateSpec) PipelineOption { return func(p *v1alpha1.Pipeline) { - p.Spec.ChannelTemplate = v1alpha1.ChannelTemplateSpec{ - ChannelCRD: corev1.ObjectReference{ - APIVersion: apiVersion(gvk), - Kind: gvk.Kind, - }, - } + p.Spec.ChannelTemplate = cts } } From 8ce3fde43147836caac57759c5c8b1bb10eb69f1 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 28 May 2019 18:58:52 -0700 Subject: [PATCH 08/31] latest pkg and move to new controller sig --- pkg/reconciler/pipeline/pipeline.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 2db5d17e8eb..6e6f7b1c0dc 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -80,7 +80,7 @@ func NewController( channelLister: channelInformer.Lister(), subscriptionLister: subscriptionInformer.Lister(), } - impl := controller.NewImpl(r, r.Logger, ReconcilerName, reconciler.MustNewStatsReporter(ReconcilerName, r.Logger)) + impl := controller.NewImpl(r, r.Logger, ReconcilerName) r.Logger.Info("Setting up event handlers") From 6115fe0a8fa7a514de68f10d11163d07efb7de49 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 28 May 2019 19:07:48 -0700 Subject: [PATCH 09/31] use the new controller style --- pkg/reconciler/pipeline/pipeline.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 6e6f7b1c0dc..47c4a1ac25c 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -84,17 +84,17 @@ func NewController( r.Logger.Info("Setting up event handlers") - pipelineInformer.Informer().AddEventHandler(reconciler.Handler(impl.Enqueue)) + pipelineInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) // Register handlers for Channel/Subscriptions that are owned by Pipeline, so that // we get notified if they change. channelInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Channel")), - Handler: reconciler.Handler(impl.EnqueueControllerOf), + Handler: controller.HandleAll(impl.EnqueueControllerOf), }) subscriptionInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Subscription")), - Handler: reconciler.Handler(impl.EnqueueControllerOf), + Handler: controller.HandleAll(impl.EnqueueControllerOf), }) return impl From c8de5f4ae5291eddfa8d06de02308f63803280d7 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 29 May 2019 09:29:29 -0700 Subject: [PATCH 10/31] nothing --- pkg/reconciler/pipeline/pipeline.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 47c4a1ac25c..8e653be1569 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -186,6 +186,7 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error if err != nil { if apierrs.IsNotFound(err) { newChannel, err := resources.NewChannel(ingressChannelName, p) + logging.FromContext(ctx).Error(fmt.Sprintf("Creating Channel Object: %+v", newChannel)) if err != nil { logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel resource object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) return err From b4b30ac0db9957b11319f3d3878fd85629f82c68 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 29 May 2019 09:49:50 -0700 Subject: [PATCH 11/31] use namespace for dynamic client, duh --- pkg/reconciler/pipeline/pipeline.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 8e653be1569..e998f3061af 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -171,7 +171,7 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error obj := p.Spec.ChannelTemplate.DeepCopyObject() // channelResourceInterface, err := duck.ResourceInterface(r.DynamicClientSet, p.Namespace, &p.Spec.ChannelTemplate.ChannelCRD) - channelResourceInterface := r.DynamicClientSet.Resource(duckapis.KindToResource(obj.GetObjectKind().GroupVersionKind())) + channelResourceInterface := r.DynamicClientSet.Resource(duckapis.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) if channelResourceInterface == nil { msg := fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate) From 2b25f2269a4ee52edb41840ed26d652f585e640b Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 29 May 2019 10:52:07 -0700 Subject: [PATCH 12/31] just a test to wire things together, creates IMC --- cmd/controller/main.go | 4 +- config/200-controller-clusterrole.yaml | 10 +++ config/300-pipeline.yaml | 91 ++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 config/300-pipeline.yaml diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 21603d23441..47a56e5aa3e 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -97,7 +97,9 @@ func main() { subscriptionInformer := eventingInformerFactory.Eventing().V1alpha1().Subscriptions() brokerInformer := eventingInformerFactory.Eventing().V1alpha1().Brokers() eventTypeInformer := eventingInformerFactory.Eventing().V1alpha1().EventTypes() - pipelineInformer := eventingInformerFactory.Eventing().V1alpha1().Pipelines() + + // Messaging + pipelineInformer := eventingInformerFactory.Messaging().V1alpha1().Pipelines() // Kube serviceInformer := kubeInformerFactory.Core().V1().Services() diff --git a/config/200-controller-clusterrole.yaml b/config/200-controller-clusterrole.yaml index cf734475777..dfecaa942ef 100644 --- a/config/200-controller-clusterrole.yaml +++ b/config/200-controller-clusterrole.yaml @@ -76,6 +76,16 @@ rules: verbs: - "update" + # Our own resources and statuses we care about. + - apiGroups: + - "messaging.knative.dev" + resources: + - "pipelines" + - "pipelines/status" + - "inmemorychannels" + - "inmemorychannels/status" + verbs: *everything + # Source resources and statuses we care about. - apiGroups: - "sources.eventing.knative.dev" diff --git a/config/300-pipeline.yaml b/config/300-pipeline.yaml new file mode 100644 index 00000000000..0b0b10423f8 --- /dev/null +++ b/config/300-pipeline.yaml @@ -0,0 +1,91 @@ +# Copyright 2018 The Knative 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. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: pipelines.messaging.knative.dev + labels: + knative.dev/crd-install: "true" +spec: + group: messaging.knative.dev + version: v1alpha1 + names: + kind: Pipeline + plural: pipelines + singular: pipeline + categories: + - all + - knative + - eventing + scope: Namespaced + subresources: + status: {} + additionalPrinterColumns: + - name: Ready + type: string + JSONPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + JSONPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - name: Age + type: date + JSONPath: .metadata.creationTimestamp + validation: + openAPIV3Schema: + properties: + spec: + required: + - steps + - channelTemplate + properties: + steps: + type: array + items: + properties: + dnsName: + type: string + minLength: 1 + uri: + type: string + minLength: 1 + ref: + type: object + required: + - apiVersion + - kind + - name + properties: + apiVersion: + type: string + minLength: 1 + kind: + type: string + minLength: 1 + name: + type: string + minLength: 1 + channelTemplate: + type: object + required: + - apiVersion + - kind + properties: + apiVersion: + type: string + minLength: 1 + kind: + type: string + minLength: 1 + spec: + type: object From a6cefac7433aad3eab7e270fc1c600e83172afa0 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 29 May 2019 15:31:51 -0700 Subject: [PATCH 13/31] reconcile subscription objects --- pkg/reconciler/pipeline/pipeline.go | 100 ++++++++++++++---- pkg/reconciler/pipeline/pipeline_test.go | 31 ++++-- pkg/reconciler/pipeline/resources/channel.go | 17 ++- .../pipeline/resources/subscription.go | 64 +++++++++++ pkg/reconciler/testing/pipeline.go | 9 +- 5 files changed, 187 insertions(+), 34 deletions(-) create mode 100644 pkg/reconciler/pipeline/resources/subscription.go diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index e998f3061af..6b81737e9bb 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -26,11 +26,14 @@ import ( apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/tools/cache" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" eventinginformers "github.com/knative/eventing/pkg/client/informers/externalversions/eventing/v1alpha1" informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" // "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" @@ -146,7 +149,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { return reconcileErr } -func pipelineChannelName(pipelineName string, step int) string { +func pipelineSubscriptionName(pipelineName string, step int) string { return fmt.Sprintf("%s-kn-pipeline-%d", pipelineName, step) } @@ -170,7 +173,6 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error obj := p.Spec.ChannelTemplate.DeepCopyObject() - // channelResourceInterface, err := duck.ResourceInterface(r.DynamicClientSet, p.Namespace, &p.Spec.ChannelTemplate.ChannelCRD) channelResourceInterface := r.DynamicClientSet.Resource(duckapis.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) if channelResourceInterface == nil { @@ -179,30 +181,31 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error errors.New(msg) } - ingressChannelName := pipelineChannelName(p.Name, 0) + for i := 0; i < len(p.Spec.Steps); i++ { + ingressChannelName := resources.PipelineChannelName(p.Name, i) + // Use the duck typed one here instead of this Get. + c, err := r.reconcileChannel(ctx, ingressChannelName, channelResourceInterface, p) + if err != nil { + // TODO: Propagate Status through... + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Channel Object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) + // TODO: Bail on first error? + continue + } + // TODO: Propagate Status through... + logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Channel Object: %s/%s %+v", p.Namespace, ingressChannelName, c)) + } - // Use the duck typed one here instead of this Get. - c, err := channelResourceInterface.Get(ingressChannelName, metav1.GetOptions{}) - if err != nil { - if apierrs.IsNotFound(err) { - newChannel, err := resources.NewChannel(ingressChannelName, p) - logging.FromContext(ctx).Error(fmt.Sprintf("Creating Channel Object: %+v", newChannel)) - if err != nil { - logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel resource object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) - return err - } - channelResourceInterface.Create(newChannel, metav1.CreateOptions{}) - if err != nil { - logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) - return err - } - logging.FromContext(ctx).Error(fmt.Sprintf("Created Channel: %s/%s", p.Namespace, ingressChannelName), zap.Any("NewChannel", zap.Any("NewChannel", newChannel))) - } else { - logging.FromContext(ctx).Error(fmt.Sprintf("Failed to get Channel: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) - return err + for i := 0; i < len(p.Spec.Steps); i++ { + s, err := r.reconcileSubscription(ctx, i, p) + if err != nil { + // TODO: Propagate Status through... + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Subscription Object for step: %d", i), zap.Error(err)) + // TODO: Bail on first error? + continue } + // TODO: Propagate Status through... + logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, s)) } - logging.FromContext(ctx).Error(fmt.Sprintf("Found Channel: %s/%s", p.Namespace, ingressChannelName), zap.Any("NewChannel", c)) return nil } @@ -224,3 +227,54 @@ func (r *Reconciler) updateStatus(ctx context.Context, desired *v1alpha1.Pipelin return r.EventingClientSet.MessagingV1alpha1().Pipelines(desired.Namespace).UpdateStatus(existing) } + +func (r *Reconciler) reconcileChannel(ctx context.Context, channelName string, channelResourceInterface dynamic.ResourceInterface, p *v1alpha1.Pipeline) (*unstructured.Unstructured, error) { + // Use the duck typed one here instead of this Get. + c, err := channelResourceInterface.Get(channelName, metav1.GetOptions{}) + if err != nil { + if apierrs.IsNotFound(err) { + newChannel, err := resources.NewChannel(channelName, p) + logging.FromContext(ctx).Error(fmt.Sprintf("Creating Channel Object: %+v", newChannel)) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel resource object: %s/%s", p.Namespace, channelName), zap.Error(err)) + return nil, err + } + created, err := channelResourceInterface.Create(newChannel, metav1.CreateOptions{}) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel: %s/%s", p.Namespace, channelName), zap.Error(err)) + return nil, err + } + logging.FromContext(ctx).Error(fmt.Sprintf("Created Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", zap.Any("NewChannel", newChannel))) + return created, nil + } else { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to get Channel: %s/%s", p.Namespace, channelName), zap.Error(err)) + return nil, err + } + } + logging.FromContext(ctx).Error(fmt.Sprintf("Found Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", c)) + return c, nil +} + +func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1alpha1.Pipeline) (*eventingv1alpha1.Subscription, error) { + expected := resources.NewSubscription(step, p) + + subName := resources.PipelineSubscriptionName(p.Name, step) + sub, err := r.subscriptionLister.Subscriptions(p.Namespace).Get(subName) + + // If the resource doesn't exist, we'll create it. + if apierrs.IsNotFound(err) { + sub = expected + logging.FromContext(ctx).Info("Creating subscription") + newSub, err := r.EventingClientSet.EventingV1alpha1().Subscriptions(sub.Namespace).Create(sub) + if err != nil { + // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) + return nil, err + } + return newSub, nil + } else if err != nil { + logging.FromContext(ctx).Error("Failed to get subscription", zap.Error(err)) + // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipelines's subscription failed: %v", err) + return nil, err + } + return sub, nil +} diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index 121a97fdc00..eac6678d022 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -17,16 +17,17 @@ limitations under the License. package pipeline import ( - // "fmt" + "fmt" // "net/url" "testing" - // eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" fakeclientset "github.com/knative/eventing/pkg/client/clientset/versioned/fake" informers "github.com/knative/eventing/pkg/client/informers/externalversions" "github.com/knative/eventing/pkg/reconciler" - // "github.com/knative/eventing/pkg/reconciler/pipeline/resources" + // "github.com/knative/pkg/kmeta" + "github.com/knative/eventing/pkg/reconciler/pipeline/resources" reconciletesting "github.com/knative/eventing/pkg/reconciler/testing" // "github.com/knative/eventing/pkg/utils" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" @@ -112,7 +113,17 @@ func createChannel(pipelineName string, stepNumber int) *unstructured.Unstructur "metadata": map[string]interface{}{ "creationTimestamp": nil, "namespace": testNS, - "name": pipelineChannelName(pipelineName, stepNumber), + "name": resources.PipelineChannelName(pipelineName, stepNumber), + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "messaging.knative.dev/v1alpha1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "InMemoryChannel", + "name": pipelineName, + "uid": "", + }, + }, }, "spec": map[string]interface{}{}, // "spec": map[string]interface{}{ @@ -123,6 +134,13 @@ func createChannel(pipelineName string, stepNumber int) *unstructured.Unstructur } +func createSubscriber(stepNumber int) eventingv1alpha1.SubscriberSpec { + uriString := fmt.Sprintf("http://example.com/%d", stepNumber) + return eventingv1alpha1.SubscriberSpec{ + URI: &uriString, + } +} + func TestAllCases(t *testing.T) { pKey := testNS + "/" + pipelineName imc := v1alpha1.ChannelTemplateSpec{ @@ -176,12 +194,13 @@ func TestAllCases(t *testing.T) { Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), }, }, { - Name: "channelworks", + Name: "singlestep", Key: pKey, Objects: []runtime.Object{ reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithInitPipelineConditions, - reconciletesting.WithPipelineChannelTemplateSpec(imc))}, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))}, WantErr: false, WantEvents: []string{ Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), diff --git a/pkg/reconciler/pipeline/resources/channel.go b/pkg/reconciler/pipeline/resources/channel.go index cb619a1ef30..d3b098cff96 100644 --- a/pkg/reconciler/pipeline/resources/channel.go +++ b/pkg/reconciler/pipeline/resources/channel.go @@ -18,26 +18,35 @@ package resources import ( "encoding/json" - // "fmt" + "fmt" // "net/url" - // "github.com/knative/pkg/kmeta" + "github.com/knative/pkg/kmeta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" // corev1 "k8s.io/api/core/v1" - // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // "k8s.io/apimachinery/pkg/runtime" // "k8s.io/apimachinery/pkg/runtime/schema" ) -// NewChannel returns a Channel CRD for the specififed GVK and Spec +// PipelineChannelName creates a name for the Channel fronting a specific step. +func PipelineChannelName(pipelineName string, step int) string { + return fmt.Sprintf("%s-kn-pipeline-%d", pipelineName, step) +} + +// NewChannel returns an unstructured.Unstructured based on the ChannelTemplateSpec +// for a given pipeline. func NewChannel(name string, p *v1alpha1.Pipeline) (*unstructured.Unstructured, error) { // Set the name of the resource we're creating as well as the namespace, etc. new := p.DeepCopy() template := new.Spec.ChannelTemplate template.Name = name template.Namespace = p.Namespace + template.OwnerReferences = []metav1.OwnerReference{ + *kmeta.NewControllerRef(p), + } // TODO: Set the owner ref, etc. raw, err := json.Marshal(template) if err != nil { diff --git a/pkg/reconciler/pipeline/resources/subscription.go b/pkg/reconciler/pipeline/resources/subscription.go new file mode 100644 index 00000000000..0e86fe1b626 --- /dev/null +++ b/pkg/reconciler/pipeline/resources/subscription.go @@ -0,0 +1,64 @@ +/* +Copyright 2019 The Knative 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 resources + +import ( + "fmt" + // "net/url" + + "github.com/knative/pkg/kmeta" + + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func PipelineSubscriptionName(pipelineName string, step int) string { + return fmt.Sprintf("%s-kn-pipeline-%d", pipelineName, step) +} + +func NewSubscription(stepNumber int, p *v1alpha1.Pipeline) *eventingv1alpha1.Subscription { + return &eventingv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: p.Namespace, + Name: PipelineSubscriptionName(p.Name, stepNumber), + + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(p), + }, + }, + Spec: eventingv1alpha1.SubscriptionSpec{ + Channel: corev1.ObjectReference{ + APIVersion: p.Spec.ChannelTemplate.APIVersion, + Kind: p.Spec.ChannelTemplate.Kind, + Name: PipelineChannelName(p.Name, stepNumber), + }, + Subscriber: &p.Spec.Steps[stepNumber], + /* + Reply: &eventingv1alpha1.ReplyStrategy{ + Channel: &corev1.ObjectReference{ + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + Kind: "Channel", + Name: brokerIngress.Name, + }, + }, + */ + }, + } + +} diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index fabb797198a..b7233070c18 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -20,6 +20,7 @@ import ( "context" "time" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" // appsv1 "k8s.io/api/apps/v1" // corev1 "k8s.io/api/core/v1" @@ -61,11 +62,17 @@ func WithPipelineChannelTemplateSpec(cts v1alpha1.ChannelTemplateSpec) PipelineO } } +func WithPipelineSteps(steps []eventingv1alpha1.SubscriberSpec) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Spec.Steps = steps + } +} + /* func WithPipelineDeploymentNotReady(reason, message string) PipelineOption { return func(p *v1alpha1.Pipeline) { p.Status.MarkDispatcherFailed(reason, message) - } + }2 } func WithPipelineDeploymentReady() PipelineOption { From 45366148035013e53d4b7cd99a57e9ce8d224f97 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 30 May 2019 11:22:37 -0700 Subject: [PATCH 14/31] check that the subscription gets created --- pkg/reconciler/pipeline/pipeline_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index eac6678d022..e83a609dd26 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -207,6 +207,7 @@ func TestAllCases(t *testing.T) { }, WantCreates: []runtime.Object{ createChannel(pipelineName, 0), + resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))), }, }, } From 691678223f35d5f42a78395a37b7f5018a152864 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 30 May 2019 12:29:11 -0700 Subject: [PATCH 15/31] add multistep pipeline test --- pkg/reconciler/pipeline/pipeline_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index e83a609dd26..8ecc2141d6b 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -209,6 +209,28 @@ func TestAllCases(t *testing.T) { createChannel(pipelineName, 0), resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))), }, + }, { + Name: "threestep", + Key: pKey, + Objects: []runtime.Object{ + reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{ + createSubscriber(0), + createSubscriber(1), + createSubscriber(2)}))}, + WantErr: false, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), + }, + WantCreates: []runtime.Object{ + createChannel(pipelineName, 0), + createChannel(pipelineName, 1), + createChannel(pipelineName, 2), + resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), + resources.NewSubscription(1, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), + resources.NewSubscription(2, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)})))}, }, } From beb2d0f1e235d7e5508ab6f60ab281eb9ffc58a2 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 30 May 2019 12:33:25 -0700 Subject: [PATCH 16/31] fix the sock --- config/300-pipeline.yaml | 2 +- pkg/apis/messaging/v1alpha1/pipeline_types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/300-pipeline.yaml b/config/300-pipeline.yaml index 0b0b10423f8..c40abde43ef 100644 --- a/config/300-pipeline.yaml +++ b/config/300-pipeline.yaml @@ -1,4 +1,4 @@ -# Copyright 2018 The Knative Authors +# Copyright 2019 The Knative Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index 11fc7b3b574..2654102e756 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -48,7 +48,7 @@ type Pipeline struct { var _ apis.Validatable = (*Pipeline)(nil) var _ apis.Defaultable = (*Pipeline)(nil) -// TOOD: make appropriate fields immutable. +// TODO: make appropriate fields immutable. //var _ apis.Immutable = (*Pipeline)(nil) var _ runtime.Object = (*Pipeline)(nil) var _ webhook.GenericCRD = (*Pipeline)(nil) From 7c3b6963ce1c98c3602b7e1b3bdb62168e1f0ed1 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 30 May 2019 14:43:15 -0700 Subject: [PATCH 17/31] checkpoint before pulling in channelable duck --- .../messaging/v1alpha1/pipeline_lifecycle.go | 61 ++++++++++++------- pkg/apis/messaging/v1alpha1/pipeline_types.go | 9 ++- .../v1alpha1/zz_generated.deepcopy.go | 10 +-- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index 6bba81445d1..bb4f2c640ab 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -16,44 +16,63 @@ package v1alpha1 -import duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" +import ( + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/pkg/apis" + corev1 "k8s.io/api/core/v1" +) -var pCondSet = duckv1alpha1.NewLivingConditionSet(PipelineConditionReferencesResolved) +var pCondSet = apis.NewLivingConditionSet(PipelineConditionReady, PipelineChannelsReady, PipelineSubscriptionsReady) const ( // PipelineConditionReady has status True when all subconditions below have been set to True. - PipelineConditionReady = duckv1alpha1.ConditionReady + PipelineConditionReady = apis.ConditionReady - // PipelineConditionReferencesResolved has status True when all the specified references have been successfully - // resolved. - PipelineConditionReferencesResolved duckv1alpha1.ConditionType = "Resolved" + // PipelineChannelsReady has status True when all the channels created as part of + // this pipeline are ready. + PipelineChannelsReady apis.ConditionType = "ChannelsReady" - // PipelineConditionChannelReady has status True when controller has successfully added a - // pipeline to the spec.channel resource. - PipelineConditionChannelReady duckv1alpha1.ConditionType = "ChannelReady" + // PipelineSubscriptionsReady has status True when all the subscriptions created as part of + // this pipeline are ready. + PipelineSubscriptionsReady apis.ConditionType = "SubscriptionsReady" ) // GetCondition returns the condition currently associated with the given type, or nil. -func (ss *PipelineStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition { - return pCondSet.Manage(ss).GetCondition(t) +func (ps *PipelineStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return pCondSet.Manage(ps).GetCondition(t) } // IsReady returns true if the resource is ready overall. -func (ss *PipelineStatus) IsReady() bool { - return pCondSet.Manage(ss).IsHappy() +func (ps *PipelineStatus) IsReady() bool { + return pCondSet.Manage(ps).IsHappy() } // InitializeConditions sets relevant unset conditions to Unknown state. -func (ss *PipelineStatus) InitializeConditions() { - pCondSet.Manage(ss).InitializeConditions() +func (ps *PipelineStatus) InitializeConditions() { + pCondSet.Manage(ps).InitializeConditions() } -// MarkReferencesResolved sets the ReferencesResolved condition to True state. -func (ss *PipelineStatus) MarkReferencesResolved() { - pCondSet.Manage(ss).MarkTrue(PipelineConditionReferencesResolved) +// PropagateSubscriptionStatuses sets the SubscriptionStatuses and PipelineSubscriptionsReady based on +// the status of the incoming subscriptions. +func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []eventingv1alpha1.Subscription) { + ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) + for i, s := range subscriptions { + ss := s.Status + ps.SubscriptionStatuses[i] = PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: s.APIVersion, + Kind: s.Kind, + Name: s.Name, + }, + ReadyCondition: ss.GetCondition(eventingv1alpha1.SubscriptionConditionReady), + } + } + pCondSet.Manage(ps).MarkTrue(PipelineSubscriptionsReady) } -// MarkChannelReady sets the ChannelReady condition to True state. -func (ss *PipelineStatus) MarkChannelReady() { - pCondSet.Manage(ss).MarkTrue(PipelineConditionChannelReady) +// PropagateChannelStatuses sets the ChannelStatuses and PipelineChannelsReady based on the +// status of the incoming channels. +func (ps *PipelineStatus) PropagateChannelStatuses() { + // ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) + pCondSet.Manage(ps).MarkTrue(PipelineSubscriptionsReady) } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index 2654102e756..dce9e6eb909 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -20,6 +20,7 @@ import ( eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1" "github.com/knative/pkg/webhook" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -92,15 +93,17 @@ type PipelineSpec struct { type PipelineChannelStatus struct { // Channel is the reference to the underlying channel. Channel corev1.ObjectReference `json:"channel"` + // ReadyCondition indicates whether the Channel is ready or not. - ReadyCondition duckv1alpha1.Condition `json:"ready"` + ReadyCondition apis.Condition `json:"ready"` } type PipelineSubscriptionStatus struct { // Subscription is the reference to the underlying Subscription. Subscription corev1.ObjectReference `json:"subscription"` + // ReadyCondition indicates whether the Subscription is ready or not. - ReadyCondition duckv1alpha1.Condition `json:"ready"` + ReadyCondition apis.Condition `json:"ready"` } // PipelineStatus represents the current state of a Pipeline. @@ -108,7 +111,7 @@ type PipelineStatus struct { // inherits duck/v1alpha1 Status, which currently provides: // * ObservedGeneration - the 'Generation' of the Service that was last processed by the controller. // * Conditions - the latest available observations of a resource's current state. - duckv1alpha1.Status `json:",inline"` + duckv1beta1.Status `json:",inline"` // SubscriptionStatuses is an array of corresponding Subscription statuses. // Matches the Spec.Steps array in the order. diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index 860cd162707..8bf7cbc7061 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -186,7 +186,6 @@ func (in *Pipeline) DeepCopyObject() runtime.Object { func (in *PipelineChannelStatus) DeepCopyInto(out *PipelineChannelStatus) { *out = *in out.Channel = in.Channel - in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) return } @@ -269,16 +268,12 @@ func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { if in.SubscriptionStatuses != nil { in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses *out = make([]PipelineSubscriptionStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + copy(*out, *in) } if in.ChannelStatuses != nil { in, out := &in.ChannelStatuses, &out.ChannelStatuses *out = make([]PipelineChannelStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + copy(*out, *in) } in.Address.DeepCopyInto(&out.Address) return @@ -298,7 +293,6 @@ func (in *PipelineStatus) DeepCopy() *PipelineStatus { func (in *PipelineSubscriptionStatus) DeepCopyInto(out *PipelineSubscriptionStatus) { *out = *in out.Subscription = in.Subscription - in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) return } From d1f9aeba7fa6d7ca7d92060b556768edbc515101 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 30 May 2019 14:50:40 -0700 Subject: [PATCH 18/31] pulling in channelable --- pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go | 4 ++-- pkg/reconciler/pipeline/pipeline.go | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index bb4f2c640ab..0f9f55e20de 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -54,7 +54,7 @@ func (ps *PipelineStatus) InitializeConditions() { // PropagateSubscriptionStatuses sets the SubscriptionStatuses and PipelineSubscriptionsReady based on // the status of the incoming subscriptions. -func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []eventingv1alpha1.Subscription) { +func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventingv1alpha1.Subscription) { ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) for i, s := range subscriptions { ss := s.Status @@ -64,7 +64,7 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []eventing Kind: s.Kind, Name: s.Name, }, - ReadyCondition: ss.GetCondition(eventingv1alpha1.SubscriptionConditionReady), + ReadyCondition: *ss.GetCondition(eventingv1alpha1.SubscriptionConditionReady), } } pCondSet.Manage(ps).MarkTrue(PipelineSubscriptionsReady) diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 6b81737e9bb..e719579bf7d 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -195,17 +195,18 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Channel Object: %s/%s %+v", p.Namespace, ingressChannelName, c)) } + subs := []*eventingv1alpha1.Subscription{} for i := 0; i < len(p.Spec.Steps); i++ { s, err := r.reconcileSubscription(ctx, i, p) if err != nil { // TODO: Propagate Status through... logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Subscription Object for step: %d", i), zap.Error(err)) - // TODO: Bail on first error? - continue + return err } - // TODO: Propagate Status through... + subs = append(subs, s) logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, s)) } + p.Status.PropagateSubscriptionStatuses(subs) return nil } From 6f7f20d682e8c56193f3e750f9f446236f190ce8 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Fri, 31 May 2019 15:29:58 -0700 Subject: [PATCH 19/31] update statuses + more tests --- .../messaging/v1alpha1/pipeline_lifecycle.go | 51 +++++++++--- .../v1alpha1/pipeline_lifecycle_test.go | 74 +++++++++++++++++ pkg/apis/messaging/v1alpha1/pipeline_types.go | 15 +++- .../messaging/v1alpha1/pipeline_validation.go | 14 +++- .../v1alpha1/pipeline_validation_test.go | 47 +++++------ .../v1alpha1/zz_generated.deepcopy.go | 38 ++++++++- pkg/reconciler/pipeline/pipeline.go | 35 +++++--- pkg/reconciler/pipeline/pipeline_test.go | 83 ++++++++++++++++++- pkg/reconciler/pipeline/resources/channel.go | 25 ++++-- .../pipeline/resources/subscription.go | 5 +- pkg/reconciler/testing/pipeline.go | 17 ++-- 11 files changed, 332 insertions(+), 72 deletions(-) create mode 100644 pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index 0f9f55e20de..3601180d0c1 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -17,12 +17,13 @@ package v1alpha1 import ( + duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" corev1 "k8s.io/api/core/v1" ) -var pCondSet = apis.NewLivingConditionSet(PipelineConditionReady, PipelineChannelsReady, PipelineSubscriptionsReady) +var pCondSet = apis.NewLivingConditionSet(PipelineConditionReady, PipelineConditionChannelsReady, PipelineConditionSubscriptionsReady) const ( // PipelineConditionReady has status True when all subconditions below have been set to True. @@ -30,11 +31,11 @@ const ( // PipelineChannelsReady has status True when all the channels created as part of // this pipeline are ready. - PipelineChannelsReady apis.ConditionType = "ChannelsReady" + PipelineConditionChannelsReady apis.ConditionType = "ChannelsReady" // PipelineSubscriptionsReady has status True when all the subscriptions created as part of // this pipeline are ready. - PipelineSubscriptionsReady apis.ConditionType = "SubscriptionsReady" + PipelineConditionSubscriptionsReady apis.ConditionType = "SubscriptionsReady" ) // GetCondition returns the condition currently associated with the given type, or nil. @@ -52,27 +53,55 @@ func (ps *PipelineStatus) InitializeConditions() { pCondSet.Manage(ps).InitializeConditions() } -// PropagateSubscriptionStatuses sets the SubscriptionStatuses and PipelineSubscriptionsReady based on +// PropagateSubscriptionStatuses sets the SubscriptionStatuses and PipelineConditionSubscriptionsReady based on // the status of the incoming subscriptions. func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventingv1alpha1.Subscription) { ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) + allReady := true for i, s := range subscriptions { - ss := s.Status ps.SubscriptionStatuses[i] = PipelineSubscriptionStatus{ Subscription: corev1.ObjectReference{ APIVersion: s.APIVersion, Kind: s.Kind, Name: s.Name, }, - ReadyCondition: *ss.GetCondition(eventingv1alpha1.SubscriptionConditionReady), } + readyCondition := s.Status.GetCondition(eventingv1alpha1.SubscriptionConditionReady) + if readyCondition != nil { + ps.SubscriptionStatuses[i].ReadyCondition = *readyCondition + if readyCondition.Status != corev1.ConditionTrue { + allReady = false + } + } else { + allReady = false + } + + } + if allReady { + pCondSet.Manage(ps).MarkTrue(PipelineConditionSubscriptionsReady) } - pCondSet.Manage(ps).MarkTrue(PipelineSubscriptionsReady) } -// PropagateChannelStatuses sets the ChannelStatuses and PipelineChannelsReady based on the +// PropagateChannelStatuses sets the ChannelStatuses and PipelineConditionChannelsReady based on the // status of the incoming channels. -func (ps *PipelineStatus) PropagateChannelStatuses() { - // ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) - pCondSet.Manage(ps).MarkTrue(PipelineSubscriptionsReady) +func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Channelable) { + ps.ChannelStatuses = make([]PipelineChannelStatus, len(channels)) + allReady := true + for i, s := range channels { + ps.ChannelStatuses[i] = PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: s.APIVersion, + Kind: s.Kind, + Name: s.Name, + }, + } + address := s.Status.AddressStatus.Address + // TODO: Once channealables actually display status, check it here. + if address == nil { + allReady = false + } + } + if allReady { + pCondSet.Manage(ps).MarkTrue(PipelineConditionChannelsReady) + } } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go new file mode 100644 index 00000000000..264e4d0e890 --- /dev/null +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2019 The Knative 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 v1alpha1 + +import ( + // "context" + "testing" + + // "github.com/knative/eventing/pkg/apis/messaging" + "github.com/knative/pkg/apis" + + "github.com/google/go-cmp/cmp" + duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1" + // authv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" +) + +var pipelineConditionReady = apis.Condition{ + Type: PipelineConditionReady, + Status: corev1.ConditionTrue, +} + +var pipelineConditionChannelsReady = apis.Condition{ + Type: PipelineConditionChannelsReady, + Status: corev1.ConditionFalse, +} + +var pipelineSubscriptionsReady = apis.Condition{ + Type: PipelineConditionSubscriptionsReady, + Status: corev1.ConditionTrue, +} + +func TestPipelineGetCondition(t *testing.T) { + tests := []struct { + name string + ss *PipelineStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + ss: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{ + pipelineConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &pipelineConditionReady, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("unexpected condition (-want, +got) = %v", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index dce9e6eb909..81e9291c385 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -30,7 +30,8 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - +// Pipeline defines a sequence of Subscribers that will be wired in +// series through Channels and Subscriptions. type Pipeline struct { metav1.TypeMeta `json:",inline"` // +optional @@ -59,6 +60,18 @@ var _ webhook.GenericCRD = (*Pipeline)(nil) type ChannelTemplateSpec struct { metav1.TypeMeta `json:",inline"` + // Spec defines the Spec to use for each channel created. Passed + // in verbatim to the Channel CRD as Spec section. + // +optional + Spec runtime.RawExtension `json:"spec"` +} + +// Internal version of ChannelTemplateSpec that includes ObjectMeta so that +// we can easily create new Channels off of it. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ChannelTemplateSpecInternal struct { + metav1.TypeMeta `json:",inline"` + // +optional metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go index e6c7693d3bb..86fb20c148e 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -20,12 +20,11 @@ import ( "context" "reflect" + "github.com/google/go-cmp/cmp" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" - // "github.com/knative/pkg/kmp" - "github.com/google/go-cmp/cmp" - eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" ) func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError { @@ -46,10 +45,17 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { } if equality.Semantic.DeepEqual(ps.ChannelTemplate, ChannelTemplateSpec{}) { - errs = errs.Also(apis.ErrMissingField("channelTemplate.channelCRD")) + errs = errs.Also(apis.ErrMissingField("channelTemplate")) return errs } + if len(ps.ChannelTemplate.APIVersion) == 0 { + errs = errs.Also(apis.ErrMissingField("channelTemplate.apiVersion")) + } + + if len(ps.ChannelTemplate.Kind) == 0 { + errs = errs.Also(apis.ErrMissingField("channelTemplate.kind")) + } return errs } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go index 4f847276871..12bfba79aa5 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go @@ -20,11 +20,12 @@ import ( "context" "testing" - "github.com/knative/pkg/apis" - "github.com/google/go-cmp/cmp" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - corev1 "k8s.io/api/core/v1" + "github.com/knative/pkg/apis" + // corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) func TestPipelineValidation(t *testing.T) { @@ -32,7 +33,7 @@ func TestPipelineValidation(t *testing.T) { pipeline := &Pipeline{Spec: PipelineSpec{}} want := &apis.FieldError{ - Paths: []string{"spec.channelTemplate.channelCRD", "spec.steps"}, + Paths: []string{"spec.channelTemplate", "spec.steps"}, Message: "missing field(s)", } @@ -46,6 +47,13 @@ func TestPipelineValidation(t *testing.T) { func TestPipelineSpecValidation(t *testing.T) { subscriberURI := "http://example.com" + validChannelTemplate := ChannelTemplateSpec{ + metav1.TypeMeta{ + Kind: "mykind", + APIVersion: "myapiversion", + }, + runtime.RawExtension{}, + } tests := []struct { name string ts *PipelineSpec @@ -54,13 +62,13 @@ func TestPipelineSpecValidation(t *testing.T) { name: "invalid pipeline spec - empty", ts: &PipelineSpec{}, want: func() *apis.FieldError { - fe := apis.ErrMissingField("channelTemplate.channelCRD", "steps") + fe := apis.ErrMissingField("channelTemplate", "steps") return fe }(), }, { name: "invalid pipeline spec - empty steps", ts: &PipelineSpec{ - ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion"}}, + ChannelTemplate: validChannelTemplate, }, want: func() *apis.FieldError { fe := apis.ErrMissingField("steps") @@ -72,44 +80,33 @@ func TestPipelineSpecValidation(t *testing.T) { Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("channelTemplate.channelCRD") - return fe - }(), - }, { - name: "invalid channeltemplatespec has name", - ts: &PipelineSpec{ - ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion", Name: "namehere"}}, - Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, - }, - want: func() *apis.FieldError { - fe := apis.ErrDisallowedFields("Name") - fe.Details = "only apiVersion and kind are supported fields" + fe := apis.ErrMissingField("channelTemplate") return fe }(), }, { - name: "invalid channeltemplatespec missing apiversion", + name: "invalid channeltemplatespec missing APIVersion", ts: &PipelineSpec{ - ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind"}}, + ChannelTemplate: ChannelTemplateSpec{metav1.TypeMeta{Kind: "mykind"}, runtime.RawExtension{}}, Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("apiVersion") + fe := apis.ErrMissingField("channelTemplate.apiVersion") return fe }(), }, { - name: "invalid channeltemplatespec missing kind", + name: "invalid channeltemplatespec missing Kind", ts: &PipelineSpec{ - ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{APIVersion: "myapiversion"}}, + ChannelTemplate: ChannelTemplateSpec{metav1.TypeMeta{APIVersion: "myapiversion"}, runtime.RawExtension{}}, Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("kind") + fe := apis.ErrMissingField("channelTemplate.kind") return fe }(), }, { name: "valid pipeline", ts: &PipelineSpec{ - ChannelTemplate: ChannelTemplateSpec{ChannelCRD: corev1.ObjectReference{Kind: "mykind", APIVersion: "myapiversion"}}, + ChannelTemplate: validChannelTemplate, Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, }, want: func() *apis.FieldError { diff --git a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go index 8bf7cbc7061..f75d64954e8 100644 --- a/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/messaging/v1alpha1/zz_generated.deepcopy.go @@ -31,7 +31,6 @@ import ( func (in *ChannelTemplateSpec) DeepCopyInto(out *ChannelTemplateSpec) { *out = *in out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) return } @@ -54,6 +53,33 @@ func (in *ChannelTemplateSpec) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelTemplateSpecInternal) DeepCopyInto(out *ChannelTemplateSpecInternal) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelTemplateSpecInternal. +func (in *ChannelTemplateSpecInternal) DeepCopy() *ChannelTemplateSpecInternal { + if in == nil { + return nil + } + out := new(ChannelTemplateSpecInternal) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChannelTemplateSpecInternal) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InMemoryChannel) DeepCopyInto(out *InMemoryChannel) { *out = *in @@ -186,6 +212,7 @@ func (in *Pipeline) DeepCopyObject() runtime.Object { func (in *PipelineChannelStatus) DeepCopyInto(out *PipelineChannelStatus) { *out = *in out.Channel = in.Channel + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) return } @@ -268,12 +295,16 @@ func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { if in.SubscriptionStatuses != nil { in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses *out = make([]PipelineSubscriptionStatus, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.ChannelStatuses != nil { in, out := &in.ChannelStatuses, &out.ChannelStatuses *out = make([]PipelineChannelStatus, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } in.Address.DeepCopyInto(&out.Address) return @@ -293,6 +324,7 @@ func (in *PipelineStatus) DeepCopy() *PipelineStatus { func (in *PipelineSubscriptionStatus) DeepCopyInto(out *PipelineSubscriptionStatus) { *out = *in out.Subscription = in.Subscription + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) return } diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index e719579bf7d..11603716359 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -26,22 +26,24 @@ import ( apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/tools/cache" + duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" eventinginformers "github.com/knative/eventing/pkg/client/informers/externalversions/eventing/v1alpha1" informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" // "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" - duckapis "github.com/knative/pkg/apis" + duckroot "github.com/knative/pkg/apis" + duckapis "github.com/knative/pkg/apis/duck" "github.com/knative/pkg/controller" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" ) const ( @@ -173,7 +175,7 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error obj := p.Spec.ChannelTemplate.DeepCopyObject() - channelResourceInterface := r.DynamicClientSet.Resource(duckapis.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) + channelResourceInterface := r.DynamicClientSet.Resource(duckroot.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) if channelResourceInterface == nil { msg := fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate) @@ -181,30 +183,39 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error errors.New(msg) } + channels := []*duckv1alpha1.Channelable{} for i := 0; i < len(p.Spec.Steps); i++ { ingressChannelName := resources.PipelineChannelName(p.Name, i) // Use the duck typed one here instead of this Get. c, err := r.reconcileChannel(ctx, ingressChannelName, channelResourceInterface, p) if err != nil { - // TODO: Propagate Status through... logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Channel Object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) - // TODO: Bail on first error? - continue + return err + + } + // Let's convert to Channel duck + channelable := &duckv1alpha1.Channelable{} + err = duckapis.FromUnstructured(c, channelable) + if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to convert to Channelable Object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) + return err + } - // TODO: Propagate Status through... + channels = append(channels, channelable) logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Channel Object: %s/%s %+v", p.Namespace, ingressChannelName, c)) } + p.Status.PropagateChannelStatuses(channels) subs := []*eventingv1alpha1.Subscription{} for i := 0; i < len(p.Spec.Steps); i++ { - s, err := r.reconcileSubscription(ctx, i, p) + sub, err := r.reconcileSubscription(ctx, i, p) if err != nil { // TODO: Propagate Status through... logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Subscription Object for step: %d", i), zap.Error(err)) return err } - subs = append(subs, s) - logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, s)) + subs = append(subs, sub) + logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, sub)) } p.Status.PropagateSubscriptionStatuses(subs) @@ -265,7 +276,7 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1a // If the resource doesn't exist, we'll create it. if apierrs.IsNotFound(err) { sub = expected - logging.FromContext(ctx).Info("Creating subscription") + logging.FromContext(ctx).Info(fmt.Sprintf("Creating subscription: %+v : SUBSCRIBERSPEC: %+v", sub, sub.Spec.Subscriber)) newSub, err := r.EventingClientSet.EventingV1alpha1().Subscriptions(sub.Namespace).Create(sub) if err != nil { // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index 8ecc2141d6b..b7a93f1a349 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -42,7 +42,7 @@ import ( // kubeinformers "k8s.io/client-go/informers" fakekubeclientset "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" - // clientgotesting "k8s.io/client-go/testing" + clientgotesting "k8s.io/client-go/testing" ) const ( @@ -148,7 +148,6 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", }, - metav1.ObjectMeta{}, runtime.RawExtension{Raw: []byte("{}")}, } @@ -209,6 +208,30 @@ func TestAllCases(t *testing.T) { createChannel(pipelineName, 0), resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))), }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}), + reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 0), + }, + }, + }), + reconciletesting.WithPipelineSubscriptionStatuses([]v1alpha1.PipelineSubscriptionStatus{ + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 0), + }, + }, + })), + }}, }, { Name: "threestep", Key: pKey, @@ -231,6 +254,62 @@ func TestAllCases(t *testing.T) { resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), resources.NewSubscription(1, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), resources.NewSubscription(2, reconciletesting.NewPipeline(pipelineName, testNS, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)})))}, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{ + createSubscriber(0), + createSubscriber(1), + createSubscriber(2), + }), + reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 0), + }, + }, + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 1), + }, + }, + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 2), + }, + }, + }), + reconciletesting.WithPipelineSubscriptionStatuses([]v1alpha1.PipelineSubscriptionStatus{ + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 0), + }, + }, + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 1), + }, + }, + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 2), + }, + }, + })), + }}, }, } diff --git a/pkg/reconciler/pipeline/resources/channel.go b/pkg/reconciler/pipeline/resources/channel.go index d3b098cff96..44842d34e32 100644 --- a/pkg/reconciler/pipeline/resources/channel.go +++ b/pkg/reconciler/pipeline/resources/channel.go @@ -40,13 +40,26 @@ func PipelineChannelName(pipelineName string, step int) string { // for a given pipeline. func NewChannel(name string, p *v1alpha1.Pipeline) (*unstructured.Unstructured, error) { // Set the name of the resource we're creating as well as the namespace, etc. - new := p.DeepCopy() - template := new.Spec.ChannelTemplate - template.Name = name - template.Namespace = p.Namespace - template.OwnerReferences = []metav1.OwnerReference{ - *kmeta.NewControllerRef(p), + template := v1alpha1.ChannelTemplateSpecInternal{ + metav1.TypeMeta{ + Kind: p.Spec.ChannelTemplate.Kind, + APIVersion: p.Spec.ChannelTemplate.APIVersion, + }, + metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(p), + }, + Name: name, + Namespace: p.Namespace, + }, + p.Spec.ChannelTemplate.Spec, } + // template := new.Spec.ChannelTemplate + // template.Name = name + // template.Namespace = p.Namespace + // template.OwnerReferences = []metav1.OwnerReference{ + // *kmeta.NewControllerRef(p), + // } // TODO: Set the owner ref, etc. raw, err := json.Marshal(template) if err != nil { diff --git a/pkg/reconciler/pipeline/resources/subscription.go b/pkg/reconciler/pipeline/resources/subscription.go index 0e86fe1b626..0fb910906c1 100644 --- a/pkg/reconciler/pipeline/resources/subscription.go +++ b/pkg/reconciler/pipeline/resources/subscription.go @@ -18,7 +18,6 @@ package resources import ( "fmt" - // "net/url" "github.com/knative/pkg/kmeta" @@ -34,6 +33,10 @@ func PipelineSubscriptionName(pipelineName string, step int) string { func NewSubscription(stepNumber int, p *v1alpha1.Pipeline) *eventingv1alpha1.Subscription { return &eventingv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: "Subscription", + APIVersion: "eventing.knative.dev/v1alpha1", + }, ObjectMeta: metav1.ObjectMeta{ Namespace: p.Namespace, Name: PipelineSubscriptionName(p.Name, stepNumber), diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index b7233070c18..15d16b5fb11 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -22,10 +22,7 @@ import ( eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" - // appsv1 "k8s.io/api/apps/v1" - // corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // "k8s.io/apimachinery/pkg/types" ) // PipelineOption enables further configuration of a Pipeline. @@ -68,13 +65,19 @@ func WithPipelineSteps(steps []eventingv1alpha1.SubscriberSpec) PipelineOption { } } -/* -func WithPipelineDeploymentNotReady(reason, message string) PipelineOption { +func WithPipelineSubscriptionStatuses(subscriptionStatuses []v1alpha1.PipelineSubscriptionStatus) PipelineOption { return func(p *v1alpha1.Pipeline) { - p.Status.MarkDispatcherFailed(reason, message) - }2 + p.Status.SubscriptionStatuses = subscriptionStatuses + } } +func WithPipelineChannelStatuses(channelStatuses []v1alpha1.PipelineChannelStatus) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.ChannelStatuses = channelStatuses + } +} + +/* func WithPipelineDeploymentReady() PipelineOption { return func(p *v1alpha1.Pipeline) { p.Status.PropagateDispatcherStatus(&appsv1.DeploymentStatus{Conditions: []appsv1.DeploymentCondition{{Type: appsv1.DeploymentAvailable, Status: corev1.ConditionTrue}}}) From f4326a8d65c9ef33136f1d70174ff3301bee5793 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 10:34:55 -0700 Subject: [PATCH 20/31] add lifecycle tests --- .../messaging/v1alpha1/pipeline_lifecycle.go | 14 + .../v1alpha1/pipeline_lifecycle_test.go | 263 +++++++++++++++++- 2 files changed, 273 insertions(+), 4 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index 3601180d0c1..bcd80b5a32c 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -58,6 +58,11 @@ func (ps *PipelineStatus) InitializeConditions() { func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventingv1alpha1.Subscription) { ps.SubscriptionStatuses = make([]PipelineSubscriptionStatus, len(subscriptions)) allReady := true + // If there are no subscriptions, treat that as a False case. Could go either way, but this seems right. + if len(subscriptions) == 0 { + allReady = false + + } for i, s := range subscriptions { ps.SubscriptionStatuses[i] = PipelineSubscriptionStatus{ Subscription: corev1.ObjectReference{ @@ -79,6 +84,8 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventin } if allReady { pCondSet.Manage(ps).MarkTrue(PipelineConditionSubscriptionsReady) + } else { + pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, "SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") } } @@ -87,6 +94,11 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventin func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Channelable) { ps.ChannelStatuses = make([]PipelineChannelStatus, len(channels)) allReady := true + // If there are no channels, treat that as a False case. Could go either way, but this seems right. + if len(channels) == 0 { + allReady = false + + } for i, s := range channels { ps.ChannelStatuses[i] = PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -103,5 +115,7 @@ func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Chan } if allReady { pCondSet.Manage(ps).MarkTrue(PipelineConditionChannelsReady) + } else { + pCondSet.Manage(ps).MarkFalse(PipelineConditionChannelsReady, "ChannelsNotReady", "Channels are not ready yet, or there are none") } } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index 264e4d0e890..1bcb464dec3 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -17,14 +17,16 @@ limitations under the License. package v1alpha1 import ( - // "context" "testing" - // "github.com/knative/eventing/pkg/apis/messaging" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" "github.com/google/go-cmp/cmp" + duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" + pkgduckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // authv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" ) @@ -36,14 +38,47 @@ var pipelineConditionReady = apis.Condition{ var pipelineConditionChannelsReady = apis.Condition{ Type: PipelineConditionChannelsReady, - Status: corev1.ConditionFalse, + Status: corev1.ConditionTrue, } -var pipelineSubscriptionsReady = apis.Condition{ +var pipelineConditionSubscriptionsReady = apis.Condition{ Type: PipelineConditionSubscriptionsReady, Status: corev1.ConditionTrue, } +func getSubscription(ready bool) *eventingv1alpha1.Subscription { + s := eventingv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{}, + Status: eventingv1alpha1.SubscriptionStatus{}, + } + if ready { + s.Status.MarkChannelReady() + s.Status.MarkReferencesResolved() + } + return &s +} + +func getChannelable(ready bool) *duckv1alpha1.Channelable { + s := duckv1alpha1.Channelable{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "InMemoryChannel", + }, + ObjectMeta: metav1.ObjectMeta{}, + Status: duckv1alpha1.ChannelableStatus{}, + } + + if ready { + s.Status.Address = &pkgduckv1alpha1.Addressable{} + } + + return &s +} + func TestPipelineGetCondition(t *testing.T) { tests := []struct { name string @@ -72,3 +107,223 @@ func TestPipelineGetCondition(t *testing.T) { }) } } + +func TestPipelineInitializeConditions(t *testing.T) { + tests := []struct { + name string + ts *PipelineStatus + want *PipelineStatus + }{{ + name: "empty", + ts: &PipelineStatus{}, + want: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: PipelineConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: PipelineConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: PipelineConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one false", + ts: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: PipelineConditionChannelsReady, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: PipelineConditionChannelsReady, + Status: corev1.ConditionFalse, + }, { + Type: PipelineConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: PipelineConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one true", + ts: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: PipelineConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &PipelineStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: PipelineConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: PipelineConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: PipelineConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.ts.InitializeConditions() + if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { + t.Errorf("unexpected conditions (-want, +got) = %v", diff) + } + }) + } +} + +func TestPipelinePropagateSubscriptionStatuses(t *testing.T) { + tests := []struct { + name string + subs []*eventingv1alpha1.Subscription + want corev1.ConditionStatus + }{{ + name: "empty", + subs: []*eventingv1alpha1.Subscription{}, + want: corev1.ConditionFalse, + }, { + name: "one subscription not ready", + subs: []*eventingv1alpha1.Subscription{getSubscription(false)}, + want: corev1.ConditionFalse, + }, { + name: "one subscription ready", + subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + want: corev1.ConditionTrue, + }, { + name: "one subscription ready, one not", + subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(false)}, + want: corev1.ConditionFalse, + }, { + name: "two subscriptions ready", + subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := PipelineStatus{} + ps.PropagateSubscriptionStatuses(test.subs) + got := ps.GetCondition(PipelineConditionSubscriptionsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestPipelinePropagateChannelStatuses(t *testing.T) { + tests := []struct { + name string + channels []*duckv1alpha1.Channelable + want corev1.ConditionStatus + }{{ + name: "empty", + channels: []*duckv1alpha1.Channelable{}, + want: corev1.ConditionFalse, + }, { + name: "one channelable not ready", + channels: []*duckv1alpha1.Channelable{getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "one channelable ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true)}, + want: corev1.ConditionTrue, + }, { + name: "one channelable ready, one not", + channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "two channelables ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := PipelineStatus{} + ps.PropagateChannelStatuses(test.channels) + got := ps.GetCondition(PipelineConditionChannelsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestPipelineReady(t *testing.T) { + tests := []struct { + name string + subs []*eventingv1alpha1.Subscription + channels []*duckv1alpha1.Channelable + want bool + }{{ + name: "empty", + subs: []*eventingv1alpha1.Subscription{}, + channels: []*duckv1alpha1.Channelable{}, + want: false, + }, { + name: "one channelable not ready, one subscription ready", + channels: []*duckv1alpha1.Channelable{getChannelable(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + want: false, + }, { + name: "one channelable ready, one subscription not ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(false)}, + want: false, + }, { + name: "one channelable ready, one subscription ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + want: true, + }, { + name: "one channelable ready, one not, two subsriptions ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + want: false, + }, { + name: "two channelables ready, one subscription ready, one not", + channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(false)}, + want: false, + }, { + name: "two channelables ready, two subscriptions ready", + channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := PipelineStatus{} + ps.PropagateChannelStatuses(test.channels) + ps.PropagateSubscriptionStatuses(test.subs) + got := ps.IsReady() + want := test.want + if want != got { + // if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} From 3eeffe8e59ba7088d1996b5339df448f2434d2f2 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 12:54:39 -0700 Subject: [PATCH 21/31] fix unit for reconciler (status) --- .../messaging/v1alpha1/pipeline_lifecycle.go | 14 +++++- pkg/reconciler/pipeline/pipeline_test.go | 11 ++--- pkg/reconciler/testing/pipeline.go | 46 ++----------------- 3 files changed, 20 insertions(+), 51 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index bcd80b5a32c..9c03ca3f6a8 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -85,7 +85,8 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventin if allReady { pCondSet.Manage(ps).MarkTrue(PipelineConditionSubscriptionsReady) } else { - pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, "SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") + ps.MarkSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") + // pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, "SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") } } @@ -116,6 +117,15 @@ func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Chan if allReady { pCondSet.Manage(ps).MarkTrue(PipelineConditionChannelsReady) } else { - pCondSet.Manage(ps).MarkFalse(PipelineConditionChannelsReady, "ChannelsNotReady", "Channels are not ready yet, or there are none") + ps.MarkChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none") + // pCondSet.Manage(ps).MarkFalse(PipelineConditionChannelsReady, "ChannelsNotReady", "Channels are not ready yet, or there are none") } } + +func (ps *PipelineStatus) MarkChannelsNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(PipelineConditionChannelsReady, reason, messageFormat, messageA...) +} + +func (ps *PipelineStatus) MarkSubscriptionsNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, reason, messageFormat, messageA...) +} diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index b7a93f1a349..3570651657a 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -18,7 +18,6 @@ package pipeline import ( "fmt" - // "net/url" "testing" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" @@ -75,7 +74,6 @@ func TestNewController(t *testing.T) { // Create informer factories with fake clients. The second parameter sets the // resync period to zero, disabling it. - // kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0) eventingInformerFactory := informers.NewSharedInformerFactory(eventingClient, 0) // Messaging @@ -85,11 +83,6 @@ func TestNewController(t *testing.T) { channelInformer := eventingInformerFactory.Eventing().V1alpha1().Channels() subscriptionInformer := eventingInformerFactory.Eventing().V1alpha1().Subscriptions() - // Kube - // serviceInformer := kubeInformerFactory.Core().V1().Services() - //endpointsInformer := kubeInformerFactory.Core().V1().Endpoints() - // deploymentInformer := kubeInformerFactory.Apps().V1().Deployments() - c := NewController( reconciler.Options{ KubeClientSet: kubeClient, @@ -213,6 +206,8 @@ func TestAllCases(t *testing.T) { reconciletesting.WithInitPipelineConditions, reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}), + reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), + reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -263,6 +258,8 @@ func TestAllCases(t *testing.T) { createSubscriber(1), createSubscriber(2), }), + reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), + reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index 15d16b5fb11..8fe143ce55e 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -77,52 +77,14 @@ func WithPipelineChannelStatuses(channelStatuses []v1alpha1.PipelineChannelStatu } } -/* -func WithPipelineDeploymentReady() PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.PropagateDispatcherStatus(&appsv1.DeploymentStatus{Conditions: []appsv1.DeploymentCondition{{Type: appsv1.DeploymentAvailable, Status: corev1.ConditionTrue}}}) - } -} - -func WithPipelineServicetNotReady(reason, message string) PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.MarkServiceFailed(reason, message) - } -} - -func WithPipelineServiceReady() PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.MarkServiceTrue() - } -} - -func WithPipelineChannelServicetNotReady(reason, message string) PipelineOption { +func WithPipelineChannelsNotReady(reason, message string) PipelineOption { return func(p *v1alpha1.Pipeline) { - p.Status.MarkChannelServiceFailed(reason, message) + p.Status.MarkChannelsNotReady(reason, message) } } -func WithPipelineChannelServiceReady() PipelineOption { +func WithPipelineSubscriptionssNotReady(reason, message string) PipelineOption { return func(p *v1alpha1.Pipeline) { - p.Status.MarkChannelServiceTrue() + p.Status.MarkSubscriptionsNotReady(reason, message) } } - -func WithPipelineEndpointsNotReady(reason, message string) PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.MarkEndpointsFailed(reason, message) - } -} - -func WithPipelineEndpointsReady() PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.MarkEndpointsTrue() - } -} - -func WithPipelineAddress(a string) PipelineOption { - return func(p *v1alpha1.Pipeline) { - p.Status.SetAddress(a) - } -} -*/ From f46ac9f2e68d5160aaa2d95c5ec1645927487966 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 13:01:53 -0700 Subject: [PATCH 22/31] moar tests --- .../messaging/v1alpha1/pipeline_lifecycle_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index 1bcb464dec3..b9ca9744290 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -58,6 +58,9 @@ func getSubscription(ready bool) *eventingv1alpha1.Subscription { if ready { s.Status.MarkChannelReady() s.Status.MarkReferencesResolved() + } else { + s.Status.MarkChannelNotReady("testInducedFailure", "Test Induced failure") + s.Status.MarkReferencesNotResolved("testInducedFailure", "Test Induced failure") } return &s } @@ -199,6 +202,18 @@ func TestPipelinePropagateSubscriptionStatuses(t *testing.T) { name: "empty", subs: []*eventingv1alpha1.Subscription{}, want: corev1.ConditionFalse, + }, { + name: "empty status", + subs: []*eventingv1alpha1.Subscription{&eventingv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{}, + Status: eventingv1alpha1.SubscriptionStatus{}, + }, + }, + want: corev1.ConditionFalse, }, { name: "one subscription not ready", subs: []*eventingv1alpha1.Subscription{getSubscription(false)}, From 336feb0ebfbdbc2bc13cebd0c6820cfb8dfaa5bd Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 17:01:55 -0700 Subject: [PATCH 23/31] add reply-to as the last stage --- .../v1alpha1/subscription_validation.go | 4 +- .../v1alpha1/subscription_validation_test.go | 2 +- .../eventing/v1alpha1/trigger_validation.go | 2 +- pkg/apis/messaging/v1alpha1/pipeline_types.go | 2 +- .../messaging/v1alpha1/pipeline_validation.go | 84 +-------- pkg/reconciler/pipeline/pipeline_test.go | 161 +++++++++++++++++- .../pipeline/resources/subscription.go | 26 +-- pkg/reconciler/testing/pipeline.go | 7 + 8 files changed, 181 insertions(+), 107 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go index adb45a01bcd..4a6d25678a7 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -52,7 +52,7 @@ func (ss *SubscriptionSpec) Validate(ctx context.Context) *apis.FieldError { } if !missingSubscriber { - if fe := isValidSubscriberSpec(*ss.Subscriber); fe != nil { + if fe := IsValidSubscriberSpec(*ss.Subscriber); fe != nil { errs = errs.Also(fe.ViaField("subscriber")) } } @@ -78,7 +78,7 @@ func isSubscriberSpecNilOrEmpty(s *SubscriberSpec) bool { return false } -func isValidSubscriberSpec(s SubscriberSpec) *apis.FieldError { +func IsValidSubscriberSpec(s SubscriberSpec) *apis.FieldError { var errs *apis.FieldError fieldsSet := make([]string, 0, 0) diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go index cf7c27a4ef7..1b5e869eed3 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go @@ -625,7 +625,7 @@ func TestValidgetValidSubscriber(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := isValidSubscriberSpec(test.s) + got := IsValidSubscriberSpec(test.s) if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { t.Errorf("%s: isValidSubscriber (-want, +got) = %v", test.name, diff) } diff --git a/pkg/apis/eventing/v1alpha1/trigger_validation.go b/pkg/apis/eventing/v1alpha1/trigger_validation.go index 79aed96f714..dd0b30ca60b 100644 --- a/pkg/apis/eventing/v1alpha1/trigger_validation.go +++ b/pkg/apis/eventing/v1alpha1/trigger_validation.go @@ -47,7 +47,7 @@ func (ts *TriggerSpec) Validate(ctx context.Context) *apis.FieldError { if isSubscriberSpecNilOrEmpty(ts.Subscriber) { fe := apis.ErrMissingField("subscriber") errs = errs.Also(fe) - } else if fe := isValidSubscriberSpec(*ts.Subscriber); fe != nil { + } else if fe := IsValidSubscriberSpec(*ts.Subscriber); fe != nil { errs = errs.Also(fe.ViaField("subscriber")) } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index 81e9291c385..30ea1013ac9 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -150,5 +150,5 @@ type PipelineList struct { // GetGroupVersionKind returns GroupVersionKind for InMemoryChannels func (p *Pipeline) GetGroupVersionKind() schema.GroupVersionKind { - return SchemeGroupVersion.WithKind("InMemoryChannel") + return SchemeGroupVersion.WithKind("Pipeline") } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go index 86fb20c148e..3e6b302ec10 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -18,12 +18,9 @@ package v1alpha1 import ( "context" - "reflect" - "github.com/google/go-cmp/cmp" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" ) @@ -39,7 +36,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { } for i, s := range ps.Steps { - if e := isValidSubscriberSpec(s); e != nil { + if e := eventingv1alpha1.IsValidSubscriberSpec(s); e != nil { errs = errs.Also(apis.ErrInvalidArrayValue(s, "steps", i)) } } @@ -58,82 +55,3 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { } return errs } - -func isValidSubscriberSpec(s eventingv1alpha1.SubscriberSpec) *apis.FieldError { - var errs *apis.FieldError - - fieldsSet := make([]string, 0, 0) - if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) { - fieldsSet = append(fieldsSet, "ref") - } - if s.DeprecatedDNSName != nil && *s.DeprecatedDNSName != "" { - fieldsSet = append(fieldsSet, "dnsName") - } - if s.URI != nil && *s.URI != "" { - fieldsSet = append(fieldsSet, "uri") - } - if len(fieldsSet) == 0 { - errs = errs.Also(apis.ErrMissingOneOf("ref", "dnsName", "uri")) - } else if len(fieldsSet) > 1 { - errs = errs.Also(apis.ErrMultipleOneOf(fieldsSet...)) - } - - // If Ref given, check the fields. - if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) { - fe := isValidObjectReferenceForSubscriber(*s.Ref) - if fe != nil { - errs = errs.Also(fe.ViaField("ref")) - } - } - return errs -} - -func isValidObjectReferenceForSubscriber(f corev1.ObjectReference) *apis.FieldError { - return checkRequiredObjectReferenceFieldsForSubscriber(f). - Also(checkDisallowedObjectReferenceFieldsForSubscriber(f)) -} - -// Check the corev1.ObjectReference to make sure it has the required fields. They -// are not checked for anything more except that they are set. -func checkRequiredObjectReferenceFieldsForSubscriber(f corev1.ObjectReference) *apis.FieldError { - var errs *apis.FieldError - if f.Name == "" { - errs = errs.Also(apis.ErrMissingField("name")) - } - if f.APIVersion == "" { - errs = errs.Also(apis.ErrMissingField("apiVersion")) - } - if f.Kind == "" { - errs = errs.Also(apis.ErrMissingField("kind")) - } - return errs -} - -// Check the corev1.ObjectReference to make sure it only has the following fields set: -// Name, Kind, APIVersion -// If any other fields are set and is not the Zero value, returns an apis.FieldError -// with the fieldpaths for all those fields. -func checkDisallowedObjectReferenceFieldsForSubscriber(f corev1.ObjectReference) *apis.FieldError { - disallowedFields := []string{} - // See if there are any fields that have been set that should not be. - // TODO: Hoist this kind of stuff into pkg repository. - s := reflect.ValueOf(f) - typeOf := s.Type() - for i := 0; i < s.NumField(); i++ { - field := s.Field(i) - fieldName := typeOf.Field(i).Name - if fieldName == "Name" || fieldName == "Kind" || fieldName == "APIVersion" { - continue - } - if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { - disallowedFields = append(disallowedFields, fieldName) - } - } - if len(disallowedFields) > 0 { - fe := apis.ErrDisallowedFields(disallowedFields...) - fe.Details = "only name, apiVersion and kind are supported fields" - return fe - } - return nil - -} diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index 3570651657a..b56abe71b2b 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -45,10 +45,11 @@ import ( ) const ( - testNS = "test-namespace" - pipelineName = "test-pipeline" - pipelineUID = "test-pipeline-uid" - brokerName = "test-broker" + testNS = "test-namespace" + pipelineName = "test-pipeline" + pipelineUID = "test-pipeline-uid" + replyChannelName = "reply-channel" + brokerName = "test-broker" channelServiceAddress = "test-pipeline-kn-channel.test-namespace.svc.cluster.local" @@ -98,6 +99,15 @@ func TestNewController(t *testing.T) { } } +func createReplyChannel(channelName string) *corev1.ObjectReference { + return &corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: channelName, + } + +} + func createChannel(pipelineName string, stepNumber int) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ @@ -112,16 +122,13 @@ func createChannel(pipelineName string, stepNumber int) *unstructured.Unstructur "apiVersion": "messaging.knative.dev/v1alpha1", "blockOwnerDeletion": true, "controller": true, - "kind": "InMemoryChannel", + "kind": "Pipeline", "name": pipelineName, "uid": "", }, }, }, "spec": map[string]interface{}{}, - // "spec": map[string]interface{}{ - // "something": "foo", - // }, }, } @@ -227,6 +234,53 @@ func TestAllCases(t *testing.T) { }, })), }}, + }, { + Name: "singlestepwithreply", + Key: pKey, + Objects: []runtime.Object{ + reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))}, + WantErr: false, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), + }, + WantCreates: []runtime.Object{ + createChannel(pipelineName, 0), + resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}))), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), + reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 0), + }, + }, + }), + reconciletesting.WithPipelineSubscriptionStatuses([]v1alpha1.PipelineSubscriptionStatus{ + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 0), + }, + }, + })), + }}, }, { Name: "threestep", Key: pKey, @@ -307,6 +361,97 @@ func TestAllCases(t *testing.T) { }, })), }}, + }, { + Name: "threestepwithreply", + Key: pKey, + Objects: []runtime.Object{ + reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{ + createSubscriber(0), + createSubscriber(1), + createSubscriber(2)}))}, + WantErr: false, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Reconciled", "Pipeline reconciled"), + }, + WantCreates: []runtime.Object{ + createChannel(pipelineName, 0), + createChannel(pipelineName, 1), + createChannel(pipelineName, 2), + resources.NewSubscription(0, reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), + resources.NewSubscription(1, reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)}))), + resources.NewSubscription(2, reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0), createSubscriber(1), createSubscriber(2)})))}, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: reconciletesting.NewPipeline(pipelineName, testNS, + reconciletesting.WithInitPipelineConditions, + reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineChannelTemplateSpec(imc), + reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{ + createSubscriber(0), + createSubscriber(1), + createSubscriber(2), + }), + reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), + reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 0), + }, + }, + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 1), + }, + }, + v1alpha1.PipelineChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: resources.PipelineChannelName(pipelineName, 2), + }, + }, + }), + reconciletesting.WithPipelineSubscriptionStatuses([]v1alpha1.PipelineSubscriptionStatus{ + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 0), + }, + }, + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 1), + }, + }, + v1alpha1.PipelineSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + Name: resources.PipelineSubscriptionName(pipelineName, 2), + }, + }, + })), + }}, }, } diff --git a/pkg/reconciler/pipeline/resources/subscription.go b/pkg/reconciler/pipeline/resources/subscription.go index 0fb910906c1..f810bb58f5f 100644 --- a/pkg/reconciler/pipeline/resources/subscription.go +++ b/pkg/reconciler/pipeline/resources/subscription.go @@ -32,7 +32,7 @@ func PipelineSubscriptionName(pipelineName string, step int) string { } func NewSubscription(stepNumber int, p *v1alpha1.Pipeline) *eventingv1alpha1.Subscription { - return &eventingv1alpha1.Subscription{ + r := &eventingv1alpha1.Subscription{ TypeMeta: metav1.TypeMeta{ Kind: "Subscription", APIVersion: "eventing.knative.dev/v1alpha1", @@ -52,16 +52,20 @@ func NewSubscription(stepNumber int, p *v1alpha1.Pipeline) *eventingv1alpha1.Sub Name: PipelineChannelName(p.Name, stepNumber), }, Subscriber: &p.Spec.Steps[stepNumber], - /* - Reply: &eventingv1alpha1.ReplyStrategy{ - Channel: &corev1.ObjectReference{ - APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), - Kind: "Channel", - Name: brokerIngress.Name, - }, - }, - */ }, } - + // If it's not the last step, use the next channel as the reply to, if it's the very + // last one, we'll use the (optional) reply from the Pipeline Spec. + if stepNumber < len(p.Spec.Steps)-1 { + r.Spec.Reply = &eventingv1alpha1.ReplyStrategy{ + Channel: &corev1.ObjectReference{ + APIVersion: p.Spec.ChannelTemplate.APIVersion, + Kind: p.Spec.ChannelTemplate.Kind, + Name: PipelineChannelName(p.Name, stepNumber+1), + }, + } + } else if p.Spec.Reply != nil { + r.Spec.Reply = &eventingv1alpha1.ReplyStrategy{Channel: p.Spec.Reply} + } + return r } diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index 8fe143ce55e..e4dac8a6ebe 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -22,6 +22,7 @@ import ( eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -65,6 +66,12 @@ func WithPipelineSteps(steps []eventingv1alpha1.SubscriberSpec) PipelineOption { } } +func WithPipelineReply(reply *corev1.ObjectReference) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Spec.Reply = reply + } +} + func WithPipelineSubscriptionStatuses(subscriptionStatuses []v1alpha1.PipelineSubscriptionStatus) PipelineOption { return func(p *v1alpha1.Pipeline) { p.Status.SubscriptionStatuses = subscriptionStatuses From 16512f3131f11d3ff851f05dcecb2ccc554ee918 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 20:29:04 -0500 Subject: [PATCH 24/31] tests, wire up the reply --- .../subscribable_channelable_validation.go | 4 +- ...ubscribable_channelable_validation_test.go | 2 +- .../v1alpha1/subscription_validation.go | 4 +- .../messaging/v1alpha1/pipeline_validation.go | 5 ++ .../v1alpha1/pipeline_validation_test.go | 55 ++++++++++++++++++- pkg/reconciler/pipeline/pipeline.go | 9 +-- pkg/reconciler/pipeline/pipeline_test.go | 27 --------- 7 files changed, 67 insertions(+), 39 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go b/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go index b4da810c854..03f527b36ba 100644 --- a/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go @@ -31,10 +31,10 @@ func isChannelEmpty(f corev1.ObjectReference) bool { // Valid if it is a valid object reference. func isValidChannel(f corev1.ObjectReference) *apis.FieldError { - return isValidObjectReference(f) + return IsValidObjectReference(f) } -func isValidObjectReference(f corev1.ObjectReference) *apis.FieldError { +func IsValidObjectReference(f corev1.ObjectReference) *apis.FieldError { return checkRequiredObjectReferenceFields(f). Also(checkDisallowedObjectReferenceFields(f)) } diff --git a/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation_test.go b/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation_test.go index 453f6bccec2..79890cf36f9 100644 --- a/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation_test.go +++ b/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation_test.go @@ -141,7 +141,7 @@ func TestIsValidObjectReference(t *testing.T) { for _, fe := range test.want { allWanted = allWanted.Also(fe) } - got := isValidObjectReference(test.ref) + got := IsValidObjectReference(test.ref) if diff := cmp.Diff(allWanted.Error(), got.Error()); diff != "" { t.Errorf("%s: validation (-want, +got) = %v", test.name, diff) } diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go index 4a6d25678a7..3e6e9923f3e 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -99,7 +99,7 @@ func IsValidSubscriberSpec(s SubscriberSpec) *apis.FieldError { // If Ref given, check the fields. if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) { - fe := isValidObjectReference(*s.Ref) + fe := IsValidObjectReference(*s.Ref) if fe != nil { errs = errs.Also(fe.ViaField("ref")) } @@ -112,7 +112,7 @@ func isReplyStrategyNilOrEmpty(r *ReplyStrategy) bool { } func isValidReply(r ReplyStrategy) *apis.FieldError { - if fe := isValidObjectReference(*r.Channel); fe != nil { + if fe := IsValidObjectReference(*r.Channel); fe != nil { return fe.ViaField("channel") } return nil diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation.go b/pkg/apis/messaging/v1alpha1/pipeline_validation.go index 3e6b302ec10..8d9cc4acc3b 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation.go @@ -53,5 +53,10 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError { if len(ps.ChannelTemplate.Kind) == 0 { errs = errs.Also(apis.ErrMissingField("channelTemplate.kind")) } + if ps.Reply != nil { + if err := eventingv1alpha1.IsValidObjectReference(*ps.Reply); err != nil { + errs = errs.Also(err.ViaField("reply")) + } + } return errs } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go index 12bfba79aa5..308d7f1ad95 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go @@ -23,7 +23,7 @@ import ( "github.com/google/go-cmp/cmp" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" - // corev1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -45,6 +45,23 @@ func TestPipelineValidation(t *testing.T) { }) } +func makeValidReply(channelName string) *corev1.ObjectReference { + return &corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Name: channelName, + } +} + +func makeInvalidReply(channelName string) *corev1.ObjectReference { + return &corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + Namespace: "notallowed", + Name: channelName, + } +} + func TestPipelineSpecValidation(t *testing.T) { subscriberURI := "http://example.com" validChannelTemplate := ChannelTemplateSpec{ @@ -112,6 +129,42 @@ func TestPipelineSpecValidation(t *testing.T) { want: func() *apis.FieldError { return nil }(), + }, { + name: "valid pipeline with valid reply", + ts: &PipelineSpec{ + ChannelTemplate: validChannelTemplate, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + Reply: makeValidReply("reply-channel"), + }, + want: func() *apis.FieldError { + return nil + }(), + }, { + name: "valid pipeline with invalid missing name", + ts: &PipelineSpec{ + ChannelTemplate: validChannelTemplate, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + Reply: &corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "inmemorychannel", + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("reply.name") + return fe + }(), + }, { + name: "valid pipeline with invalid reply", + ts: &PipelineSpec{ + ChannelTemplate: validChannelTemplate, + Steps: []eventingv1alpha1.SubscriberSpec{{URI: &subscriberURI}}, + Reply: makeInvalidReply("reply-channel"), + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("reply.Namespace") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), /* }, { name: "missing filter.sourceAndType", diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 11603716359..0a8fa30d68c 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -33,7 +33,6 @@ import ( informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" - // "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" @@ -172,7 +171,6 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error } // Convert the object into runtime.Object so we can grab the schema.ObjectKind and create the correct interface client - obj := p.Spec.ChannelTemplate.DeepCopyObject() channelResourceInterface := r.DynamicClientSet.Resource(duckroot.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) @@ -186,14 +184,13 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error channels := []*duckv1alpha1.Channelable{} for i := 0; i < len(p.Spec.Steps); i++ { ingressChannelName := resources.PipelineChannelName(p.Name, i) - // Use the duck typed one here instead of this Get. c, err := r.reconcileChannel(ctx, ingressChannelName, channelResourceInterface, p) if err != nil { logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Channel Object: %s/%s", p.Namespace, ingressChannelName), zap.Error(err)) return err } - // Let's convert to Channel duck + // Convert to Channel duck so that we can treat all Channels the same. channelable := &duckv1alpha1.Channelable{} err = duckapis.FromUnstructured(c, channelable) if err != nil { @@ -210,7 +207,6 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error for i := 0; i < len(p.Spec.Steps); i++ { sub, err := r.reconcileSubscription(ctx, i, p) if err != nil { - // TODO: Propagate Status through... logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Subscription Object for step: %d", i), zap.Error(err)) return err } @@ -241,7 +237,6 @@ func (r *Reconciler) updateStatus(ctx context.Context, desired *v1alpha1.Pipelin } func (r *Reconciler) reconcileChannel(ctx context.Context, channelName string, channelResourceInterface dynamic.ResourceInterface, p *v1alpha1.Pipeline) (*unstructured.Unstructured, error) { - // Use the duck typed one here instead of this Get. c, err := channelResourceInterface.Get(channelName, metav1.GetOptions{}) if err != nil { if apierrs.IsNotFound(err) { @@ -279,12 +274,14 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1a logging.FromContext(ctx).Info(fmt.Sprintf("Creating subscription: %+v : SUBSCRIBERSPEC: %+v", sub, sub.Spec.Subscriber)) newSub, err := r.EventingClientSet.EventingV1alpha1().Subscriptions(sub.Namespace).Create(sub) if err != nil { + // TODO: Send events here, or elsewhere? // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) return nil, err } return newSub, nil } else if err != nil { logging.FromContext(ctx).Error("Failed to get subscription", zap.Error(err)) + // TODO: Send events here, or elsewhere? // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipelines's subscription failed: %v", err) return nil, err } diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index b56abe71b2b..3a6efe4b55e 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -25,20 +25,16 @@ import ( fakeclientset "github.com/knative/eventing/pkg/client/clientset/versioned/fake" informers "github.com/knative/eventing/pkg/client/informers/externalversions" "github.com/knative/eventing/pkg/reconciler" - // "github.com/knative/pkg/kmeta" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" reconciletesting "github.com/knative/eventing/pkg/reconciler/testing" - // "github.com/knative/eventing/pkg/utils" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "github.com/knative/pkg/controller" logtesting "github.com/knative/pkg/logging/testing" . "github.com/knative/pkg/reconciler/testing" - // "github.com/knative/pkg/tracker" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - // kubeinformers "k8s.io/client-go/informers" fakekubeclientset "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" clientgotesting "k8s.io/client-go/testing" @@ -49,18 +45,6 @@ const ( pipelineName = "test-pipeline" pipelineUID = "test-pipeline-uid" replyChannelName = "reply-channel" - brokerName = "test-broker" - - channelServiceAddress = "test-pipeline-kn-channel.test-namespace.svc.cluster.local" - - subscriberAPIVersion = "v1" - subscriberKind = "Service" - subscriberName = "subscriberName" - subscriberURI = "http://example.com/subscriber" -) - -var ( - trueVal = true ) func init() { @@ -151,17 +135,6 @@ func TestAllCases(t *testing.T) { runtime.RawExtension{Raw: []byte("{}")}, } - /* - imcWithSpec := v1alpha1.ChannelTemplateSpec{ - metav1.TypeMeta{ - APIVersion: "messaging.knative.dev/v1alpha1", - Kind: "inmemorychannel", - }, - metav1.ObjectMeta{}, - runtime.RawExtension{Raw: []byte("{}")}, - } - */ - table := TableTest{ { Name: "bad workqueue key", From 7278ea2400a31e5b94f8770d6c3a04290900fe12 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Mon, 3 Jun 2019 20:48:16 -0500 Subject: [PATCH 25/31] remove cruft, fixed TODOs --- .../messaging/v1alpha1/pipeline_lifecycle_test.go | 3 +-- pkg/reconciler/pipeline/resources/channel.go | 11 ----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index b9ca9744290..865500f0b11 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -26,9 +26,8 @@ import ( duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" pkgduckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // authv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var pipelineConditionReady = apis.Condition{ diff --git a/pkg/reconciler/pipeline/resources/channel.go b/pkg/reconciler/pipeline/resources/channel.go index 44842d34e32..8a62c7eac1e 100644 --- a/pkg/reconciler/pipeline/resources/channel.go +++ b/pkg/reconciler/pipeline/resources/channel.go @@ -19,16 +19,12 @@ package resources import ( "encoding/json" "fmt" - // "net/url" "github.com/knative/pkg/kmeta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" v1alpha1 "github.com/knative/eventing/pkg/apis/messaging/v1alpha1" - // corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // "k8s.io/apimachinery/pkg/runtime" - // "k8s.io/apimachinery/pkg/runtime/schema" ) // PipelineChannelName creates a name for the Channel fronting a specific step. @@ -54,13 +50,6 @@ func NewChannel(name string, p *v1alpha1.Pipeline) (*unstructured.Unstructured, }, p.Spec.ChannelTemplate.Spec, } - // template := new.Spec.ChannelTemplate - // template.Name = name - // template.Namespace = p.Namespace - // template.OwnerReferences = []metav1.OwnerReference{ - // *kmeta.NewControllerRef(p), - // } - // TODO: Set the owner ref, etc. raw, err := json.Marshal(template) if err != nil { return nil, err From 17fca8e010a6cdf00059ae061712aae043301277 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 5 Jun 2019 13:46:58 +0300 Subject: [PATCH 26/31] fill in addressable, watch subscriptions and channel CRD via tracker --- cmd/controller/main.go | 2 +- .../messaging/v1alpha1/pipeline_lifecycle.go | 40 +++++++++++++++-- .../v1alpha1/pipeline_lifecycle_test.go | 12 ++++- pkg/reconciler/pipeline/pipeline.go | 44 +++++++++++-------- pkg/reconciler/pipeline/pipeline_test.go | 30 ++++++++----- pkg/reconciler/testing/pipeline.go | 8 +++- 6 files changed, 102 insertions(+), 34 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 47a56e5aa3e..944d96d0f51 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -172,7 +172,7 @@ func main() { pipeline.NewController( opt, pipelineInformer, - channelInformer, + addressableInformer, subscriptionInformer, ), } diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index 9c03ca3f6a8..eaf6d77c0a5 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -20,10 +20,11 @@ import ( duckv1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" "github.com/knative/pkg/apis" + pkgduckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" corev1 "k8s.io/api/core/v1" ) -var pCondSet = apis.NewLivingConditionSet(PipelineConditionReady, PipelineConditionChannelsReady, PipelineConditionSubscriptionsReady) +var pCondSet = apis.NewLivingConditionSet(PipelineConditionReady, PipelineConditionChannelsReady, PipelineConditionSubscriptionsReady, PipelineConditionAddressable) const ( // PipelineConditionReady has status True when all subconditions below have been set to True. @@ -36,6 +37,10 @@ const ( // PipelineSubscriptionsReady has status True when all the subscriptions created as part of // this pipeline are ready. PipelineConditionSubscriptionsReady apis.ConditionType = "SubscriptionsReady" + + // PipelineConditionAddressable has status true when this Pipeline meets + // the Addressable contract and has a non-empty hostname. + PipelineConditionAddressable apis.ConditionType = "Addressable" ) // GetCondition returns the condition currently associated with the given type, or nil. @@ -86,7 +91,6 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventin pCondSet.Manage(ps).MarkTrue(PipelineConditionSubscriptionsReady) } else { ps.MarkSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") - // pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, "SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") } } @@ -113,12 +117,15 @@ func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Chan if address == nil { allReady = false } + // If the first channel is addressable, mark it as such + if i == 0 { + ps.setAddress(address) + } } if allReady { pCondSet.Manage(ps).MarkTrue(PipelineConditionChannelsReady) } else { ps.MarkChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none") - // pCondSet.Manage(ps).MarkFalse(PipelineConditionChannelsReady, "ChannelsNotReady", "Channels are not ready yet, or there are none") } } @@ -129,3 +136,30 @@ func (ps *PipelineStatus) MarkChannelsNotReady(reason, messageFormat string, mes func (ps *PipelineStatus) MarkSubscriptionsNotReady(reason, messageFormat string, messageA ...interface{}) { pCondSet.Manage(ps).MarkFalse(PipelineConditionSubscriptionsReady, reason, messageFormat, messageA...) } + +func (ps *PipelineStatus) MarkAddressableNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(PipelineConditionAddressable, reason, messageFormat, messageA...) +} + +// TODO: Use the new beta duck types. +func (ps *PipelineStatus) setAddress(address *pkgduckv1alpha1.Addressable) { + if address == nil { + ps.Address.Hostname = "" + ps.Address.URL = nil + pCondSet.Manage(ps).MarkFalse(PipelineConditionAddressable, "emptyHostname", "hostname is the empty string") + return + } + + if address.URL != nil { + ps.Address.Hostname = address.URL.Host + ps.Address.URL = address.URL + pCondSet.Manage(ps).MarkTrue(PipelineConditionAddressable) + } else if address.Hostname != "" { + ps.Address.Hostname = address.Hostname + pCondSet.Manage(ps).MarkTrue(PipelineConditionAddressable) + } else { + ps.Address.Hostname = "" + ps.Address.URL = nil + pCondSet.Manage(ps).MarkFalse(PipelineConditionAddressable, "emptyHostname", "hostname is the empty string") + } +} diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index 865500f0b11..d95d54a0218 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -65,6 +65,7 @@ func getSubscription(ready bool) *eventingv1alpha1.Subscription { } func getChannelable(ready bool) *duckv1alpha1.Channelable { + URL, _ := apis.ParseURL("http://example.com") s := duckv1alpha1.Channelable{ TypeMeta: metav1.TypeMeta{ APIVersion: "messaging.knative.dev/v1alpha1", @@ -75,7 +76,7 @@ func getChannelable(ready bool) *duckv1alpha1.Channelable { } if ready { - s.Status.Address = &pkgduckv1alpha1.Addressable{} + s.Status.Address = &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{URL}, ""} } return &s @@ -121,6 +122,9 @@ func TestPipelineInitializeConditions(t *testing.T) { want: &PipelineStatus{ Status: duckv1beta1.Status{ Conditions: []apis.Condition{{ + Type: PipelineConditionAddressable, + Status: corev1.ConditionUnknown, + }, { Type: PipelineConditionChannelsReady, Status: corev1.ConditionUnknown, }, { @@ -145,6 +149,9 @@ func TestPipelineInitializeConditions(t *testing.T) { want: &PipelineStatus{ Status: duckv1beta1.Status{ Conditions: []apis.Condition{{ + Type: PipelineConditionAddressable, + Status: corev1.ConditionUnknown, + }, { Type: PipelineConditionChannelsReady, Status: corev1.ConditionFalse, }, { @@ -169,6 +176,9 @@ func TestPipelineInitializeConditions(t *testing.T) { want: &PipelineStatus{ Status: duckv1beta1.Status{ Conditions: []apis.Condition{{ + Type: PipelineConditionAddressable, + Status: corev1.ConditionUnknown, + }, { Type: PipelineConditionChannelsReady, Status: corev1.ConditionUnknown, }, { diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 0a8fa30d68c..6c0c9bb8fa8 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -33,12 +33,15 @@ import ( informers "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" eventinglisters "github.com/knative/eventing/pkg/client/listers/eventing/v1alpha1" listers "github.com/knative/eventing/pkg/client/listers/messaging/v1alpha1" + "github.com/knative/eventing/pkg/duck" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/reconciler" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" + "github.com/knative/eventing/pkg/utils" duckroot "github.com/knative/pkg/apis" duckapis "github.com/knative/pkg/apis/duck" "github.com/knative/pkg/controller" + "github.com/knative/pkg/tracker" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -61,9 +64,10 @@ type Reconciler struct { *reconciler.Base // listers index properties about resources - pipelineLister listers.PipelineLister - channelLister eventinglisters.ChannelLister - subscriptionLister eventinglisters.SubscriptionLister + pipelineLister listers.PipelineLister + tracker tracker.Interface + addressableInformer duck.AddressableInformer + subscriptionLister eventinglisters.SubscriptionLister } // Check that our Reconciler implements controller.Reconciler @@ -74,30 +78,27 @@ var _ controller.Reconciler = (*Reconciler)(nil) func NewController( opt reconciler.Options, pipelineInformer informers.PipelineInformer, - channelInformer eventinginformers.ChannelInformer, + addressableInformer duck.AddressableInformer, subscriptionInformer eventinginformers.SubscriptionInformer, ) *controller.Impl { r := &Reconciler{ - Base: reconciler.NewBase(opt, controllerAgentName), - pipelineLister: pipelineInformer.Lister(), - channelLister: channelInformer.Lister(), - subscriptionLister: subscriptionInformer.Lister(), + Base: reconciler.NewBase(opt, controllerAgentName), + pipelineLister: pipelineInformer.Lister(), + addressableInformer: addressableInformer, + subscriptionLister: subscriptionInformer.Lister(), } impl := controller.NewImpl(r, r.Logger, ReconcilerName) r.Logger.Info("Setting up event handlers") + r.tracker = tracker.New(impl.EnqueueKey, opt.GetTrackerLease()) pipelineInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) - // Register handlers for Channel/Subscriptions that are owned by Pipeline, so that + // Register handler for Subscriptions that are owned by Pipeline, so that // we get notified if they change. - channelInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ - FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Channel")), - Handler: controller.HandleAll(impl.EnqueueControllerOf), - }) subscriptionInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ - FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Subscription")), + FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("Pipeline")), Handler: controller.HandleAll(impl.EnqueueControllerOf), }) @@ -170,10 +171,8 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error return nil } - // Convert the object into runtime.Object so we can grab the schema.ObjectKind and create the correct interface client - obj := p.Spec.ChannelTemplate.DeepCopyObject() - - channelResourceInterface := r.DynamicClientSet.Resource(duckroot.KindToResource(obj.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) + // obj := p.Spec.ChannelTemplate + channelResourceInterface := r.DynamicClientSet.Resource(duckroot.KindToResource(p.Spec.ChannelTemplate.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) if channelResourceInterface == nil { msg := fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate) @@ -181,6 +180,9 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error errors.New(msg) } + // Tell tracker to reconcile this Pipeline whenever my channels change. + track := r.addressableInformer.TrackInNamespace(r.tracker, p) + channels := []*duckv1alpha1.Channelable{} for i := 0; i < len(p.Spec.Steps); i++ { ingressChannelName := resources.PipelineChannelName(p.Name, i) @@ -198,7 +200,12 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error return err } + // Track channels and enqueue pipeline when they change. channels = append(channels, channelable) + if err = track(utils.ObjectRef(channelable, channelable.GroupVersionKind())); err != nil { + logging.FromContext(ctx).Error("Unable to track changes to Channel", zap.Error(err)) + return err + } logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Channel Object: %s/%s %+v", p.Namespace, ingressChannelName, c)) } p.Status.PropagateChannelStatuses(channels) @@ -274,6 +281,7 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1a logging.FromContext(ctx).Info(fmt.Sprintf("Creating subscription: %+v : SUBSCRIBERSPEC: %+v", sub, sub.Spec.Subscriber)) newSub, err := r.EventingClientSet.EventingV1alpha1().Subscriptions(sub.Namespace).Create(sub) if err != nil { + logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Subscription Object for step: %d", step), zap.Error(err)) // TODO: Send events here, or elsewhere? // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) return nil, err diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index 3a6efe4b55e..ea3f9d5f915 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -31,6 +31,7 @@ import ( "github.com/knative/pkg/controller" logtesting "github.com/knative/pkg/logging/testing" . "github.com/knative/pkg/reconciler/testing" + "github.com/knative/pkg/tracker" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -53,6 +54,12 @@ func init() { _ = duckv1alpha1.AddToScheme(scheme.Scheme) } +type fakeAddressableInformer struct{} + +func (*fakeAddressableInformer) TrackInNamespace(tracker.Interface, metav1.Object) func(corev1.ObjectReference) error { + return func(corev1.ObjectReference) error { return nil } +} + func TestNewController(t *testing.T) { kubeClient := fakekubeclientset.NewSimpleClientset() eventingClient := fakeclientset.NewSimpleClientset() @@ -65,7 +72,6 @@ func TestNewController(t *testing.T) { pipelineInformer := eventingInformerFactory.Messaging().V1alpha1().Pipelines() // Eventing - channelInformer := eventingInformerFactory.Eventing().V1alpha1().Channels() subscriptionInformer := eventingInformerFactory.Eventing().V1alpha1().Subscriptions() c := NewController( @@ -75,7 +81,7 @@ func TestNewController(t *testing.T) { Logger: logtesting.TestLogger(t), }, pipelineInformer, - channelInformer, + &fakeAddressableInformer{}, subscriptionInformer) if c == nil { @@ -187,7 +193,8 @@ func TestAllCases(t *testing.T) { reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}), reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), - reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineAddressableNotReady("emptyHostname", "hostname is the empty string"), + reconciletesting.WithPipelineSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -233,8 +240,9 @@ func TestAllCases(t *testing.T) { reconciletesting.WithPipelineChannelTemplateSpec(imc), reconciletesting.WithPipelineSteps([]eventingv1alpha1.SubscriberSpec{createSubscriber(0)}), reconciletesting.WithPipelineReply(createReplyChannel(replyChannelName)), + reconciletesting.WithPipelineAddressableNotReady("emptyHostname", "hostname is the empty string"), reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), - reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -286,7 +294,8 @@ func TestAllCases(t *testing.T) { createSubscriber(2), }), reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), - reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineAddressableNotReady("emptyHostname", "hostname is the empty string"), + reconciletesting.WithPipelineSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -377,7 +386,8 @@ func TestAllCases(t *testing.T) { createSubscriber(2), }), reconciletesting.WithPipelineChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none"), - reconciletesting.WithPipelineSubscriptionssNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), + reconciletesting.WithPipelineAddressableNotReady("emptyHostname", "hostname is the empty string"), + reconciletesting.WithPipelineSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none"), reconciletesting.WithPipelineChannelStatuses([]v1alpha1.PipelineChannelStatus{ v1alpha1.PipelineChannelStatus{ Channel: corev1.ObjectReference{ @@ -432,10 +442,10 @@ func TestAllCases(t *testing.T) { table.Test(t, reconciletesting.MakeFactory(func(listers *reconciletesting.Listers, opt reconciler.Options) controller.Reconciler { return &Reconciler{ - Base: reconciler.NewBase(opt, controllerAgentName), - pipelineLister: listers.GetPipelineLister(), - channelLister: listers.GetChannelLister(), - subscriptionLister: listers.GetSubscriptionLister(), + Base: reconciler.NewBase(opt, controllerAgentName), + pipelineLister: listers.GetPipelineLister(), + addressableInformer: &fakeAddressableInformer{}, + subscriptionLister: listers.GetSubscriptionLister(), } }, false, diff --git a/pkg/reconciler/testing/pipeline.go b/pkg/reconciler/testing/pipeline.go index e4dac8a6ebe..1029397c383 100644 --- a/pkg/reconciler/testing/pipeline.go +++ b/pkg/reconciler/testing/pipeline.go @@ -90,8 +90,14 @@ func WithPipelineChannelsNotReady(reason, message string) PipelineOption { } } -func WithPipelineSubscriptionssNotReady(reason, message string) PipelineOption { +func WithPipelineSubscriptionsNotReady(reason, message string) PipelineOption { return func(p *v1alpha1.Pipeline) { p.Status.MarkSubscriptionsNotReady(reason, message) } } + +func WithPipelineAddressableNotReady(reason, message string) PipelineOption { + return func(p *v1alpha1.Pipeline) { + p.Status.MarkAddressableNotReady(reason, message) + } +} From 6a51003cdd5740b1c832f131b6386ae214169a32 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 5 Jun 2019 14:33:53 +0300 Subject: [PATCH 27/31] more tests explicitly for setAddress --- .../v1alpha1/pipeline_lifecycle_test.go | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index d95d54a0218..bf7949876a1 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -345,9 +345,58 @@ func TestPipelineReady(t *testing.T) { got := ps.IsReady() want := test.want if want != got { - // if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) } }) } } + +func TestPipelinePropagateSetAddress(t *testing.T) { + URL, _ := apis.ParseURL("http://example.com") + tests := []struct { + name string + address *pkgduckv1alpha1.Addressable + want *pkgduckv1alpha1.Addressable + wantStatus corev1.ConditionStatus + }{{ + name: "nil", + address: nil, + want: &pkgduckv1alpha1.Addressable{}, + wantStatus: corev1.ConditionFalse, + }, { + name: "empty", + address: &pkgduckv1alpha1.Addressable{}, + want: &pkgduckv1alpha1.Addressable{}, + wantStatus: corev1.ConditionFalse, + }, { + name: "URL", + address: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{URL}, ""}, + want: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{URL}, "example.com"}, + wantStatus: corev1.ConditionTrue, + }, { + name: "hostname", + address: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{}, "myhostname"}, + want: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{}, "myhostname"}, + wantStatus: corev1.ConditionTrue, + }, { + name: "nil", + address: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{nil}, ""}, + want: &pkgduckv1alpha1.Addressable{duckv1beta1.Addressable{}, ""}, + wantStatus: corev1.ConditionFalse, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := PipelineStatus{} + ps.setAddress(test.address) + got := ps.Address + if diff := cmp.Diff(test.want, &got, ignoreAllButTypeAndStatus); diff != "" { + t.Errorf("unexpected address (-want, +got) = %v", diff) + } + gotStatus := ps.GetCondition(PipelineConditionAddressable).Status + if test.wantStatus != gotStatus { + t.Errorf("unexpected conditions (-want, +got) = %v %v", test.wantStatus, gotStatus) + } + }) + } +} From 9f39d0aa830fe7d8af23a35645d77282c5cad743 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 5 Jun 2019 15:27:30 +0300 Subject: [PATCH 28/31] remove commented out cruft --- .../v1alpha1/pipeline_validation_test.go | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go index 308d7f1ad95..ce04fbc1ae2 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_validation_test.go @@ -165,42 +165,7 @@ func TestPipelineSpecValidation(t *testing.T) { fe.Details = "only name, apiVersion and kind are supported fields" return fe }(), - /* - }, { - name: "missing filter.sourceAndType", - ts: &PipelineSpec{ - Broker: "test_broker", - Filter: &PipelineFilter{}, - Subscriber: validSubscriber, - }, - want: func() *apis.FieldError { - fe := apis.ErrMissingField("filter.sourceAndType") - return fe - }(), - }, { - name: "missing subscriber", - ts: &PipelineSpec{ - Broker: "test_broker", - Filter: validPipelineFilter, - }, - want: func() *apis.FieldError { - fe := apis.ErrMissingField("subscriber") - return fe - }(), - }, { - name: "missing subscriber.ref.name", - ts: &PipelineSpec{ - Broker: "test_broker", - Filter: validPipelineFilter, - Subscriber: invalidSubscriber, - }, - want: func() *apis.FieldError { - fe := apis.ErrMissingField("subscriber.ref.name") - return fe - }(), - */ - }, - } + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { From a5623804648d0002717ddf1aacb4d02aa94cfe9c Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 6 Jun 2019 12:45:45 +0300 Subject: [PATCH 29/31] address pr feedback. populate channelstatuses --- config/300-pipeline.yaml | 51 ++++++++------- .../messaging/v1alpha1/pipeline_lifecycle.go | 19 ++++-- .../v1alpha1/pipeline_lifecycle_test.go | 38 ++++++----- pkg/apis/messaging/v1alpha1/pipeline_types.go | 8 +-- pkg/reconciler/pipeline/pipeline.go | 37 +++++------ pkg/reconciler/pipeline/pipeline_test.go | 65 +++++++++++++++++++ 6 files changed, 147 insertions(+), 71 deletions(-) diff --git a/config/300-pipeline.yaml b/config/300-pipeline.yaml index c40abde43ef..be2da2656c6 100644 --- a/config/300-pipeline.yaml +++ b/config/300-pipeline.yaml @@ -28,6 +28,7 @@ spec: - all - knative - eventing + - messaging scope: Namespaced subresources: status: {} @@ -52,29 +53,33 @@ spec: steps: type: array items: - properties: - dnsName: - type: string - minLength: 1 - uri: - type: string - minLength: 1 - ref: - type: object - required: - - apiVersion - - kind - - name - properties: - apiVersion: - type: string - minLength: 1 - kind: - type: string - minLength: 1 - name: - type: string - minLength: 1 + type: object + properties: + dnsName: + type: string + minLength: 1 + uri: + type: string + minLength: 1 + ref: + type: object + required: + - apiVersion + - kind + - name + properties: + apiVersion: + type: string + minLength: 1 + kind: + type: string + minLength: 1 + name: + type: string + minLength: 1 + namespace: + type: string + maxLength: 0 channelTemplate: type: object required: diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index eaf6d77c0a5..d8e6919fe40 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -74,6 +74,7 @@ func (ps *PipelineStatus) PropagateSubscriptionStatuses(subscriptions []*eventin APIVersion: s.APIVersion, Kind: s.Kind, Name: s.Name, + Namespace: s.Namespace, }, } readyCondition := s.Status.GetCondition(eventingv1alpha1.SubscriptionConditionReady) @@ -104,19 +105,23 @@ func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Chan allReady = false } - for i, s := range channels { + for i, c := range channels { ps.ChannelStatuses[i] = PipelineChannelStatus{ Channel: corev1.ObjectReference{ - APIVersion: s.APIVersion, - Kind: s.Kind, - Name: s.Name, + APIVersion: c.APIVersion, + Kind: c.Kind, + Name: c.Name, + Namespace: c.Namespace, }, } - address := s.Status.AddressStatus.Address - // TODO: Once channealables actually display status, check it here. - if address == nil { + address := c.Status.AddressStatus.Address + if address != nil { + ps.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} + } else { + ps.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionFalse, Reason: "NotAddressable", Message: "Channel is not addressable"} allReady = false } + // If the first channel is addressable, mark it as such if i == 0 { ps.setAddress(address) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go index bf7949876a1..a91cee89d3d 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle_test.go @@ -45,21 +45,26 @@ var pipelineConditionSubscriptionsReady = apis.Condition{ Status: corev1.ConditionTrue, } -func getSubscription(ready bool) *eventingv1alpha1.Subscription { +func getSubscription(name string, ready bool) *eventingv1alpha1.Subscription { s := eventingv1alpha1.Subscription{ TypeMeta: metav1.TypeMeta{ APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", }, - ObjectMeta: metav1.ObjectMeta{}, - Status: eventingv1alpha1.SubscriptionStatus{}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "testns", + }, + Status: eventingv1alpha1.SubscriptionStatus{}, } if ready { s.Status.MarkChannelReady() s.Status.MarkReferencesResolved() + s.Status.MarkAddedToChannel() } else { s.Status.MarkChannelNotReady("testInducedFailure", "Test Induced failure") s.Status.MarkReferencesNotResolved("testInducedFailure", "Test Induced failure") + s.Status.MarkNotAddedToChannel("testInducedfailure", "Test Induced failure") } return &s } @@ -218,26 +223,29 @@ func TestPipelinePropagateSubscriptionStatuses(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", }, - ObjectMeta: metav1.ObjectMeta{}, - Status: eventingv1alpha1.SubscriptionStatus{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "sub", + Namespace: "testns", + }, + Status: eventingv1alpha1.SubscriptionStatus{}, }, }, want: corev1.ConditionFalse, }, { name: "one subscription not ready", - subs: []*eventingv1alpha1.Subscription{getSubscription(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", false)}, want: corev1.ConditionFalse, }, { name: "one subscription ready", - subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true)}, want: corev1.ConditionTrue, }, { name: "one subscription ready, one not", - subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, want: corev1.ConditionFalse, }, { name: "two subscriptions ready", - subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, want: corev1.ConditionTrue, }} @@ -308,32 +316,32 @@ func TestPipelineReady(t *testing.T) { }, { name: "one channelable not ready, one subscription ready", channels: []*duckv1alpha1.Channelable{getChannelable(false)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true)}, want: false, }, { name: "one channelable ready, one subscription not ready", channels: []*duckv1alpha1.Channelable{getChannelable(true)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", false)}, want: false, }, { name: "one channelable ready, one subscription ready", channels: []*duckv1alpha1.Channelable{getChannelable(true)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true)}, want: true, }, { name: "one channelable ready, one not, two subsriptions ready", channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(false)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, want: false, }, { name: "two channelables ready, one subscription ready, one not", channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(true)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(false)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, want: false, }, { name: "two channelables ready, two subscriptions ready", channels: []*duckv1alpha1.Channelable{getChannelable(true), getChannelable(true)}, - subs: []*eventingv1alpha1.Subscription{getSubscription(true), getSubscription(true)}, + subs: []*eventingv1alpha1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, want: true, }} diff --git a/pkg/apis/messaging/v1alpha1/pipeline_types.go b/pkg/apis/messaging/v1alpha1/pipeline_types.go index 30ea1013ac9..9d9f51c4ebe 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_types.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_types.go @@ -90,15 +90,15 @@ type PipelineSpec struct { ChannelTemplate ChannelTemplateSpec `json:"channelTemplate"` // Reply is a Reference to where the result of the last Subscriber gets sent to. - // Currently due to limitations on the Subscription, this _must_ be a Channel. - // If that changes, we can change that later. // // You can specify only the following fields of the ObjectReference: // - Kind // - APIVersion // - Name - // Kind must be "Channel" and APIVersion must be - // "eventing.knative.dev/v1alpha1" + // + // The resource pointed by this ObjectReference must meet the Addressable contract + // with a reference to the Addressable duck type. If the resource does not meet this contract, + // it will be reflected in the Subscription's status. // +optional Reply *corev1.ObjectReference `json:"reply,omitempty"` } diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 6c0c9bb8fa8..90784d00bf9 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -109,11 +109,11 @@ func NewController( // reconcile the two. It then updates the Status block of the Pipeline resource // with the current Status of the resource. func (r *Reconciler) Reconcile(ctx context.Context, key string) error { - logging.FromContext(ctx).Error("reconciling", zap.String("key", key)) + logging.FromContext(ctx).Debug("reconciling", zap.String("key", key)) // Convert the namespace/name string into a distinct namespace and name namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { - logging.FromContext(ctx).Error("invalid resource key") + logging.FromContext(ctx).Error("invalid resource key", zap.Error(err)) return nil } @@ -141,7 +141,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { r.Recorder.Eventf(pipeline, corev1.EventTypeNormal, reconciled, "Pipeline reconciled") } - if _, updateStatusErr := r.updateStatus(ctx, pipeline.DeepCopy()); updateStatusErr != nil { + if _, updateStatusErr := r.updateStatus(ctx, pipeline); updateStatusErr != nil { logging.FromContext(ctx).Warn("Error updating Pipeline status", zap.Error(updateStatusErr)) r.Recorder.Eventf(pipeline, corev1.EventTypeWarning, updateStatusFailed, "Failed to update pipeline status: %s", key) return updateStatusErr @@ -151,10 +151,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { return reconcileErr } -func pipelineSubscriptionName(pipelineName string, step int) string { - return fmt.Sprintf("%s-kn-pipeline-%d", pipelineName, step) -} - func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error { p.Status.InitializeConditions() @@ -171,19 +167,18 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error return nil } - // obj := p.Spec.ChannelTemplate channelResourceInterface := r.DynamicClientSet.Resource(duckroot.KindToResource(p.Spec.ChannelTemplate.GetObjectKind().GroupVersionKind())).Namespace(p.Namespace) if channelResourceInterface == nil { msg := fmt.Sprintf("Unable to create dynamic client for: %+v", p.Spec.ChannelTemplate) logging.FromContext(ctx).Error(msg) - errors.New(msg) + return errors.New(msg) } // Tell tracker to reconcile this Pipeline whenever my channels change. track := r.addressableInformer.TrackInNamespace(r.tracker, p) - channels := []*duckv1alpha1.Channelable{} + channels := make([]*duckv1alpha1.Channelable, 0, len(p.Spec.Steps)) for i := 0; i < len(p.Spec.Steps); i++ { ingressChannelName := resources.PipelineChannelName(p.Name, i) c, err := r.reconcileChannel(ctx, ingressChannelName, channelResourceInterface, p) @@ -210,15 +205,14 @@ func (r *Reconciler) reconcile(ctx context.Context, p *v1alpha1.Pipeline) error } p.Status.PropagateChannelStatuses(channels) - subs := []*eventingv1alpha1.Subscription{} + subs := make([]*eventingv1alpha1.Subscription, 0, len(p.Spec.Steps)) for i := 0; i < len(p.Spec.Steps); i++ { sub, err := r.reconcileSubscription(ctx, i, p) if err != nil { - logging.FromContext(ctx).Error(fmt.Sprintf("Failed to reconcile Subscription Object for step: %d", i), zap.Error(err)) - return err + return fmt.Errorf("Failed to reconcile Subscription Object for step: %d : %s", i, err) } subs = append(subs, sub) - logging.FromContext(ctx).Info(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, sub)) + logging.FromContext(ctx).Debug(fmt.Sprintf("Reconciled Subscription Object for step: %d: %+v", i, sub)) } p.Status.PropagateSubscriptionStatuses(subs) @@ -258,14 +252,14 @@ func (r *Reconciler) reconcileChannel(ctx context.Context, channelName string, c logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Channel: %s/%s", p.Namespace, channelName), zap.Error(err)) return nil, err } - logging.FromContext(ctx).Error(fmt.Sprintf("Created Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", zap.Any("NewChannel", newChannel))) + logging.FromContext(ctx).Info(fmt.Sprintf("Created Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", newChannel)) return created, nil } else { logging.FromContext(ctx).Error(fmt.Sprintf("Failed to get Channel: %s/%s", p.Namespace, channelName), zap.Error(err)) return nil, err } } - logging.FromContext(ctx).Error(fmt.Sprintf("Found Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", c)) + logging.FromContext(ctx).Debug(fmt.Sprintf("Found Channel: %s/%s", p.Namespace, channelName), zap.Any("NewChannel", c)) return c, nil } @@ -278,20 +272,19 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1a // If the resource doesn't exist, we'll create it. if apierrs.IsNotFound(err) { sub = expected - logging.FromContext(ctx).Info(fmt.Sprintf("Creating subscription: %+v : SUBSCRIBERSPEC: %+v", sub, sub.Spec.Subscriber)) + logging.FromContext(ctx).Info(fmt.Sprintf("Creating subscription: %+v", sub)) newSub, err := r.EventingClientSet.EventingV1alpha1().Subscriptions(sub.Namespace).Create(sub) if err != nil { - logging.FromContext(ctx).Error(fmt.Sprintf("Failed to create Subscription Object for step: %d", step), zap.Error(err)) // TODO: Send events here, or elsewhere? - // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) - return nil, err + //r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) + return nil, fmt.Errorf("Failed to create Subscription Object for step: %d", step) } return newSub, nil } else if err != nil { logging.FromContext(ctx).Error("Failed to get subscription", zap.Error(err)) // TODO: Send events here, or elsewhere? - // r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipelines's subscription failed: %v", err) - return nil, err + //r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipelines's subscription failed: %v", err) + return nil, fmt.Errorf("Failed to get subscription: %s", err) } return sub, nil } diff --git a/pkg/reconciler/pipeline/pipeline_test.go b/pkg/reconciler/pipeline/pipeline_test.go index ea3f9d5f915..fc714db2e49 100644 --- a/pkg/reconciler/pipeline/pipeline_test.go +++ b/pkg/reconciler/pipeline/pipeline_test.go @@ -27,6 +27,7 @@ import ( "github.com/knative/eventing/pkg/reconciler" "github.com/knative/eventing/pkg/reconciler/pipeline/resources" reconciletesting "github.com/knative/eventing/pkg/reconciler/testing" + "github.com/knative/pkg/apis" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "github.com/knative/pkg/controller" logtesting "github.com/knative/pkg/logging/testing" @@ -201,6 +202,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 0), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, }), @@ -210,6 +218,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 0), + Namespace: testNS, }, }, })), @@ -249,6 +258,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 0), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, }), @@ -258,6 +274,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 0), + Namespace: testNS, }, }, })), @@ -302,6 +319,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 0), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, v1alpha1.PipelineChannelStatus{ @@ -309,6 +333,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 1), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, v1alpha1.PipelineChannelStatus{ @@ -316,6 +347,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 2), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, }), @@ -325,6 +363,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 0), + Namespace: testNS, }, }, v1alpha1.PipelineSubscriptionStatus{ @@ -332,6 +371,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 1), + Namespace: testNS, }, }, v1alpha1.PipelineSubscriptionStatus{ @@ -339,6 +379,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 2), + Namespace: testNS, }, }, })), @@ -394,6 +435,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 0), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, v1alpha1.PipelineChannelStatus{ @@ -401,6 +449,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 1), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, v1alpha1.PipelineChannelStatus{ @@ -408,6 +463,13 @@ func TestAllCases(t *testing.T) { APIVersion: "messaging.knative.dev/v1alpha1", Kind: "inmemorychannel", Name: resources.PipelineChannelName(pipelineName, 2), + Namespace: testNS, + }, + ReadyCondition: apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "NotAddressable", + Message: "Channel is not addressable", }, }, }), @@ -417,6 +479,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 0), + Namespace: testNS, }, }, v1alpha1.PipelineSubscriptionStatus{ @@ -424,6 +487,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 1), + Namespace: testNS, }, }, v1alpha1.PipelineSubscriptionStatus{ @@ -431,6 +495,7 @@ func TestAllCases(t *testing.T) { APIVersion: "eventing.knative.dev/v1alpha1", Kind: "Subscription", Name: resources.PipelineSubscriptionName(pipelineName, 2), + Namespace: testNS, }, }, })), From d3738267b029274649bfdfe00e0dcac6b9a61c4d Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 6 Jun 2019 15:10:04 +0300 Subject: [PATCH 30/31] run update-codegen --- .../messaging/v1alpha1/pipeline/fake/fake.go | 40 ++++++++++++++ .../messaging/v1alpha1/pipeline/pipeline.go | 52 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 pkg/client/injection/informers/messaging/v1alpha1/pipeline/fake/fake.go create mode 100644 pkg/client/injection/informers/messaging/v1alpha1/pipeline/pipeline.go diff --git a/pkg/client/injection/informers/messaging/v1alpha1/pipeline/fake/fake.go b/pkg/client/injection/informers/messaging/v1alpha1/pipeline/fake/fake.go new file mode 100644 index 00000000000..e3e0a94465e --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1alpha1/pipeline/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + fake "github.com/knative/eventing/pkg/client/injection/informers/messaging/factory/fake" + pipeline "github.com/knative/eventing/pkg/client/injection/informers/messaging/v1alpha1/pipeline" + controller "github.com/knative/pkg/controller" + injection "github.com/knative/pkg/injection" +) + +var Get = pipeline.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Messaging().V1alpha1().Pipelines() + return context.WithValue(ctx, pipeline.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/messaging/v1alpha1/pipeline/pipeline.go b/pkg/client/injection/informers/messaging/v1alpha1/pipeline/pipeline.go new file mode 100644 index 00000000000..bd7dc176353 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1alpha1/pipeline/pipeline.go @@ -0,0 +1,52 @@ +/* +Copyright 2019 The Knative 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pipeline + +import ( + "context" + + v1alpha1 "github.com/knative/eventing/pkg/client/informers/externalversions/messaging/v1alpha1" + factory "github.com/knative/eventing/pkg/client/injection/informers/messaging/factory" + controller "github.com/knative/pkg/controller" + injection "github.com/knative/pkg/injection" + logging "github.com/knative/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Messaging().V1alpha1().Pipelines() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha1.PipelineInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Fatalf( + "Unable to fetch %T from context.", (v1alpha1.PipelineInformer)(nil)) + } + return untyped.(v1alpha1.PipelineInformer) +} From 59f9b16f3338382aa697fc428fae676c49351c70 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 6 Jun 2019 19:11:52 +0300 Subject: [PATCH 31/31] add a todo, return an error when create sub fails --- pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go | 2 ++ pkg/reconciler/pipeline/pipeline.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go index d8e6919fe40..9bccb747256 100644 --- a/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go +++ b/pkg/apis/messaging/v1alpha1/pipeline_lifecycle.go @@ -114,6 +114,8 @@ func (ps *PipelineStatus) PropagateChannelStatuses(channels []*duckv1alpha1.Chan Namespace: c.Namespace, }, } + // TODO: Once the addressable has a real status to dig through, use that here instead of + // addressable, because it might be addressable but not ready. address := c.Status.AddressStatus.Address if address != nil { ps.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} diff --git a/pkg/reconciler/pipeline/pipeline.go b/pkg/reconciler/pipeline/pipeline.go index 90784d00bf9..f18562c180c 100644 --- a/pkg/reconciler/pipeline/pipeline.go +++ b/pkg/reconciler/pipeline/pipeline.go @@ -277,7 +277,7 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, step int, p *v1a if err != nil { // TODO: Send events here, or elsewhere? //r.Recorder.Eventf(p, corev1.EventTypeWarning, subscriptionCreateFailed, "Create Pipeline's subscription failed: %v", err) - return nil, fmt.Errorf("Failed to create Subscription Object for step: %d", step) + return nil, fmt.Errorf("Failed to create Subscription Object for step: %d : %s", step, err) } return newSub, nil } else if err != nil {