From 820b051238e9f78c31ed676dc572c3e12419fc48 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Fri, 7 May 2021 10:46:22 -0700 Subject: [PATCH] Update Sequence to use correct conditions. --- cmd/schema/main.go | 5 + config/core/resources/sequence.yaml | 298 +++++++++++-------- go.sum | 1 - pkg/apis/flows/v1/sequence_lifecycle.go | 34 ++- pkg/apis/flows/v1/sequence_lifecycle_test.go | 107 +++++-- pkg/apis/flows/v1/sequence_types.go | 5 +- pkg/apis/flows/v1/zz_generated.deepcopy.go | 2 +- pkg/reconciler/sequence/sequence_test.go | 90 +++--- 8 files changed, 316 insertions(+), 226 deletions(-) diff --git a/cmd/schema/main.go b/cmd/schema/main.go index 62986fc52c5..c90530d165f 100644 --- a/cmd/schema/main.go +++ b/cmd/schema/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + flowsv1 "knative.dev/eventing/pkg/apis/flows/v1" "log" "knative.dev/hack/schema/commands" @@ -42,6 +43,10 @@ func main() { registry.Register(&sourcesv1.ApiServerSource{}) registry.Register(&sourcesv1.SinkBinding{}) + // Flows + registry.Register(&flowsv1.Sequence{}) + registry.Register(&flowsv1.Parallel{}) + if err := commands.New("knative.dev/eventing").Execute(); err != nil { log.Fatal("Error during command execution: ", err) } diff --git a/config/core/resources/sequence.yaml b/config/core/resources/sequence.yaml index 60bc9a5ca54..d6511deda02 100644 --- a/config/core/resources/sequence.yaml +++ b/config/core/resources/sequence.yaml @@ -37,225 +37,263 @@ spec: type: object properties: channelTemplate: - description: ChannelTemplate specifies which Channel CRD to use. If - left unspecified, it is set to the default Channel CRD for the - namespace (or cluster, in case there are no defaults for the namespace). + description: ChannelTemplate specifies which Channel CRD to use. If left unspecified, it is set to the default Channel CRD for the namespace (or cluster, in case there are no defaults for the namespace). type: object properties: apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string spec: - description: Spec defines the Spec to use for each channel - created. Passed in verbatim to the Channel CRD as Spec - section. - type: object - x-kubernetes-preserve-unknown-fields: true + description: Spec defines the Spec to use for each channel created. Passed in verbatim to the Channel CRD as Spec section. + type: string reply: - description: Reply is a Reference to where the result of the last - Subscriber gets sent to. + description: Reply is a Reference to where the result of the last Subscriber gets sent to. type: object - properties: &addressableProperties - ref: - description: Ref points to an Addressable. - type: object - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ - This is optional field, it gets defaulted to the - object holding it if left out.' - type: string - uri: - description: URI can be an absolute URL(non-empty scheme and - non-empty host) pointing to the target or a relative URI. - Relative URIs will be resolved using the base URI retrieved - from Ref. - type: string + properties: + ref: + description: Ref points to an Addressable. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref. + type: string steps: - description: Steps is the list of Destinations (processors / functions) - that will be called in the order provided. Each step has its own - delivery options + description: Steps is the list of Destinations (processors / functions) that will be called in the order provided. Each step has its own delivery options type: array items: type: object properties: - << : *addressableProperties delivery: - description: Delivery is the delivery specification for - events to the subscriber This includes things like - retries, DLQ, etc. + description: Delivery is the delivery specification for events to the subscriber This includes things like retries, DLQ, etc. type: object properties: backoffDelay: - description: 'BackoffDelay is the delay before - retrying. More information on Duration format: - - https://www.iso.org/iso-8601-date-and-time-format.html - - https://en.wikipedia.org/wiki/ISO_8601 For - linear policy, backoff delay is backoffDelay*. - For exponential policy, backoff delay is - backoffDelay*2^.' + description: 'BackoffDelay is the delay before retrying. More information on Duration format: - https://www.iso.org/iso-8601-date-and-time-format.html - https://en.wikipedia.org/wiki/ISO_8601 For linear policy, backoff delay is backoffDelay*. For exponential policy, backoff delay is backoffDelay*2^.' type: string backoffPolicy: - description: BackoffPolicy is the retry backoff - policy (linear, exponential). + description: BackoffPolicy is the retry backoff policy (linear, exponential). type: string deadLetterSink: - description: DeadLetterSink is the sink receiving - event that could not be sent to a destination. + description: DeadLetterSink is the sink receiving event that could not be sent to a destination. type: object properties: - << : *addressableProperties + ref: + description: Ref points to an Addressable. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref. + type: string retry: - description: Retry is the minimum number of retries - the sender should attempt when sending an - event before moving it to the dead letter - sink. + description: Retry is the minimum number of retries the sender should attempt when sending an event before moving it to the dead letter sink. type: integer format: int32 + ref: + description: Ref points to an Addressable. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref. + type: string status: - description: Status represents the current state of the Sequence. This data - may be out of date. + description: Status represents the current state of the Sequence. This data may be out of date. type: object + required: + - address properties: address: type: object + required: + - url properties: - url: - type: string + url: + type: string annotations: - description: Annotations is additional Status fields for the Resource - to save some additional State as well as convey more information - to the user. This is roughly akin to Annotations on any k8s resource, - just the reconciler conveying richer information outwards. + description: Annotations is additional Status fields for the Resource to save some additional State as well as convey more information to the user. This is roughly akin to Annotations on any k8s resource, just the reconciler conveying richer information outwards. type: object + x-kubernetes-preserve-unknown-fields: true channelStatuses: - description: ChannelStatuses is an array of corresponding Channel - statuses. Matches the Spec.Steps array in the order. + description: ChannelStatuses is an array of corresponding Channel statuses. Matches the Spec.Steps array in the order. type: array items: type: object properties: channel: - description: Channel is the reference to the underlying - channel. + description: Channel is the reference to the underlying channel. type: object - properties: &referentProperties + properties: apiVersion: description: API version of the referent. type: string fieldPath: - description: 'If referring to a piece of an object - instead of an entire object, this string should - contain a valid JSON/Go field access statement, - such as desiredState.manifest.containers[2]. - For example, if the object reference is to - a container within a pod, this would take - on a value like: "spec.containers{name}" (where - "name" refers to the name of the container - that triggered the event) or if no container - name is specified "spec.containers[2]" (container - with index 2 in this pod). This syntax is - chosen only to have some well-defined way - of referencing a part of an object.' + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.' type: string kind: - description: 'Kind of the referent. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string namespace: - description: 'Namespace of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' type: string resourceVersion: - description: 'Specific resourceVersion to which - this reference is made, if any. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string ready: - description: ReadyCondition indicates whether the Channel - is ready or not. + description: ReadyCondition indicates whether the Channel is ready or not. type: object - x-kubernetes-preserve-unknown-fields: true - properties: &readyConditionProperties + required: + - type + - status + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic differences (all other things held constant). + type: string message: - description: A human readable message indicating - details about the transition. + description: A human readable message indicating details about the transition. type: string reason: - description: The reason for the condition's last - transition. + description: The reason for the condition's last transition. type: string severity: - description: Severity with which to treat failures - of this type of condition. When this is not - specified, it defaults to Error. + description: Severity with which to treat failures of this type of condition. When this is not specified, it defaults to Error. type: string status: - description: Status of the condition, one of True, - False, Unknown. + description: Status of the condition, one of True, False, Unknown. type: string type: description: Type of condition. type: string conditions: - description: Conditions the latest available observations of a resource's - current state. + description: Conditions the latest available observations of a resource's current state. type: array items: type: object + required: + - type + - status properties: - <<: *readyConditionProperties + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: Severity with which to treat failures of this type of condition. When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string observedGeneration: - description: ObservedGeneration is the 'Generation' of the Service - that was last processed by the controller. + description: ObservedGeneration is the 'Generation' of the Service that was last processed by the controller. type: integer format: int64 subscriptionStatuses: - description: SubscriptionStatuses is an array of corresponding Subscription - statuses. Matches the Spec.Steps array in the order. + description: SubscriptionStatuses is an array of corresponding Subscription statuses. Matches the Spec.Steps array in the order. type: array items: type: object properties: ready: - description: ReadyCondition indicates whether the Subscription - is ready or not. + description: ReadyCondition indicates whether the Subscription is ready or not. type: object + required: + - type + - status properties: - <<: *readyConditionProperties + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: Severity with which to treat failures of this type of condition. When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string subscription: - description: Subscription is the reference to the underlying - Subscription. + description: Subscription is the reference to the underlying Subscription. type: object properties: - <<: *referentProperties + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string additionalPrinterColumns: - name: URL type: string diff --git a/go.sum b/go.sum index 540716ee558..67ef10bada3 100644 --- a/go.sum +++ b/go.sum @@ -940,7 +940,6 @@ gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca h1:PupagGYwj8+I4ubCxcmcBRk3VlUWtTg5huQpZR9flmE= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6 h1:4WsZyVtkthqrHTbDCJfiTs8IWNYE4uvsSDgaV6xpp+o= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/pkg/apis/flows/v1/sequence_lifecycle.go b/pkg/apis/flows/v1/sequence_lifecycle.go index ddcc8b240b0..47401c64b67 100644 --- a/pkg/apis/flows/v1/sequence_lifecycle.go +++ b/pkg/apis/flows/v1/sequence_lifecycle.go @@ -122,6 +122,11 @@ func (ss *SequenceStatus) PropagateChannelStatuses(channels []*eventingduckv1.Ch } for i, c := range channels { + // Mark the Sequence address as the Address of the first channel. + if i == 0 { + ss.setAddress(c.Status.Address) + } + ss.ChannelStatuses[i] = SequenceChannelStatus{ Channel: corev1.ObjectReference{ APIVersion: c.APIVersion, @@ -130,20 +135,16 @@ func (ss *SequenceStatus) PropagateChannelStatuses(channels []*eventingduckv1.Ch 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 { - ss.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} + + if ready := c.Status.GetCondition(apis.ConditionReady); ready != nil { + ss.ChannelStatuses[i].ReadyCondition = *ready + if !ready.IsTrue() { + allReady = false + } } else { - ss.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionFalse, Reason: "NotAddressable", Message: "Channel is not addressable"} + ss.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionUnknown, Reason: "NoReady", Message: "Channel does not have Ready condition"} allReady = false } - - // Mark the Sequence address as the Address of the first channel. - if i == 0 { - ss.setAddress(address) - } } if allReady { sCondSet.Manage(ss).MarkTrue(SequenceConditionChannelsReady) @@ -153,22 +154,23 @@ func (ss *SequenceStatus) PropagateChannelStatuses(channels []*eventingduckv1.Ch } func (ss *SequenceStatus) MarkChannelsNotReady(reason, messageFormat string, messageA ...interface{}) { - sCondSet.Manage(ss).MarkFalse(SequenceConditionChannelsReady, reason, messageFormat, messageA...) + sCondSet.Manage(ss).MarkUnknown(SequenceConditionChannelsReady, reason, messageFormat, messageA...) } func (ss *SequenceStatus) MarkSubscriptionsNotReady(reason, messageFormat string, messageA ...interface{}) { - sCondSet.Manage(ss).MarkFalse(SequenceConditionSubscriptionsReady, reason, messageFormat, messageA...) + sCondSet.Manage(ss).MarkUnknown(SequenceConditionSubscriptionsReady, reason, messageFormat, messageA...) } func (ss *SequenceStatus) MarkAddressableNotReady(reason, messageFormat string, messageA ...interface{}) { - sCondSet.Manage(ss).MarkFalse(SequenceConditionAddressable, reason, messageFormat, messageA...) + sCondSet.Manage(ss).MarkUnknown(SequenceConditionAddressable, reason, messageFormat, messageA...) } func (ss *SequenceStatus) setAddress(address *duckv1.Addressable) { if address == nil || address.URL == nil { - sCondSet.Manage(ss).MarkFalse(SequenceConditionAddressable, "emptyAddress", "addressable is nil") + ss.Address = duckv1.Addressable{} + sCondSet.Manage(ss).MarkUnknown(SequenceConditionAddressable, "emptyAddress", "addressable is nil") } else { - ss.AddressStatus.Address = &duckv1.Addressable{URL: address.URL} + ss.Address = duckv1.Addressable{URL: address.URL} sCondSet.Manage(ss).MarkTrue(SequenceConditionAddressable) } } diff --git a/pkg/apis/flows/v1/sequence_lifecycle_test.go b/pkg/apis/flows/v1/sequence_lifecycle_test.go index 6190e71c4e0..a7f383658a2 100644 --- a/pkg/apis/flows/v1/sequence_lifecycle_test.go +++ b/pkg/apis/flows/v1/sequence_lifecycle_test.go @@ -78,7 +78,16 @@ func getChannelable(ready bool) *eventingduckv1.Channelable { } if ready { + c.Status.SetConditions([]apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }}) c.Status.Address = &duckv1.Addressable{URL: URL} + } else { + c.Status.SetConditions([]apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionUnknown, + }}) } return &c @@ -212,7 +221,7 @@ func TestSequencePropagateSubscriptionStatuses(t *testing.T) { }{{ name: "empty", subs: []*messagingv1.Subscription{}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "empty status", subs: []*messagingv1.Subscription{{ @@ -227,11 +236,11 @@ func TestSequencePropagateSubscriptionStatuses(t *testing.T) { Status: messagingv1.SubscriptionStatus{}, }, }, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "one subscription not ready", subs: []*messagingv1.Subscription{getSubscription("sub0", false)}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "one subscription ready", subs: []*messagingv1.Subscription{getSubscription("sub0", true)}, @@ -239,7 +248,7 @@ func TestSequencePropagateSubscriptionStatuses(t *testing.T) { }, { name: "one subscription ready, one not", subs: []*messagingv1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "two subscriptions ready", subs: []*messagingv1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, @@ -267,11 +276,11 @@ func TestSequencePropagateChannelStatuses(t *testing.T) { }{{ name: "empty", channels: []*eventingduckv1.Channelable{}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "one channelable not ready", channels: []*eventingduckv1.Channelable{getChannelable(false)}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "one channelable ready", channels: []*eventingduckv1.Channelable{getChannelable(true)}, @@ -279,7 +288,7 @@ func TestSequencePropagateChannelStatuses(t *testing.T) { }, { name: "one channelable ready, one not", channels: []*eventingduckv1.Channelable{getChannelable(true), getChannelable(false)}, - want: corev1.ConditionFalse, + want: corev1.ConditionUnknown, }, { name: "two channelables ready", channels: []*eventingduckv1.Channelable{getChannelable(true), getChannelable(true)}, @@ -293,7 +302,7 @@ func TestSequencePropagateChannelStatuses(t *testing.T) { got := ps.GetCondition(SequenceConditionChannelsReady).Status want := test.want if want != got { - t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + t.Errorf("unexpected conditions: want=%q, got=%q", want, got) } }) } @@ -358,44 +367,80 @@ func TestSequenceReady(t *testing.T) { func TestSequencePropagateSetAddress(t *testing.T) { URL := apis.HTTP("example.com") + URL2 := apis.HTTP("another.example.com") tests := []struct { - name string - address *duckv1.Addressable - want *duckv1.Addressable - wantStatus corev1.ConditionStatus + name string + status SequenceStatus + address *duckv1.Addressable + want duckv1.Addressable + wantStatus corev1.ConditionStatus + wantAddress string }{{ name: "nil", + status: SequenceStatus{}, address: nil, - want: nil, - wantStatus: corev1.ConditionFalse, + want: duckv1.Addressable{}, + wantStatus: corev1.ConditionUnknown, }, { name: "empty", + status: SequenceStatus{}, address: &duckv1.Addressable{}, - want: nil, - wantStatus: corev1.ConditionFalse, + want: duckv1.Addressable{}, + wantStatus: corev1.ConditionUnknown, }, { - name: "URL", - address: &duckv1.Addressable{URL: URL}, - want: &duckv1.Addressable{URL: URL}, - wantStatus: corev1.ConditionTrue, + name: "URL", + status: SequenceStatus{}, + address: &duckv1.Addressable{URL: URL}, + want: duckv1.Addressable{URL: URL}, + wantStatus: corev1.ConditionTrue, + wantAddress: "http://example.com", + }, { + name: "New URL", + status: SequenceStatus{ + Address: duckv1.Addressable{ + URL: URL2, + }, + }, + address: &duckv1.Addressable{URL: URL}, + want: duckv1.Addressable{URL: URL}, + wantStatus: corev1.ConditionTrue, + wantAddress: "http://example.com", + }, { + name: "Clear URL", + status: SequenceStatus{ + Address: duckv1.Addressable{ + URL: URL, + }, + }, + address: nil, + want: duckv1.Addressable{}, + wantStatus: corev1.ConditionUnknown, }, { name: "nil", + status: SequenceStatus{}, address: &duckv1.Addressable{URL: nil}, - want: nil, - wantStatus: corev1.ConditionFalse, + want: duckv1.Addressable{}, + wantStatus: corev1.ConditionUnknown, }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ps := SequenceStatus{} - ps.setAddress(test.address) - got := ps.Address - if diff := cmp.Diff(test.want, got, ignoreAllButTypeAndStatus); diff != "" { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tt.status.setAddress(tt.address) + got := tt.status.Address + if diff := cmp.Diff(tt.want, got, ignoreAllButTypeAndStatus); diff != "" { t.Error("unexpected address (-want, +got) =", diff) } - gotStatus := ps.GetCondition(SequenceConditionAddressable).Status - if test.wantStatus != gotStatus { - t.Errorf("unexpected conditions (-want, +got) = %v %v", test.wantStatus, gotStatus) + gotStatus := tt.status.GetCondition(SequenceConditionAddressable).Status + if tt.wantStatus != gotStatus { + t.Errorf("unexpected conditions (-want, +got) = %v %v", tt.wantStatus, gotStatus) + } + gotAddress := "" + if got.URL != nil { + gotAddress = got.URL.String() + } + if diff := cmp.Diff(tt.wantAddress, gotAddress); diff != "" { + t.Error("unexpected address.url (-want, +got) =", diff) } }) } diff --git a/pkg/apis/flows/v1/sequence_types.go b/pkg/apis/flows/v1/sequence_types.go index 999f0b2dbd4..7b708604828 100644 --- a/pkg/apis/flows/v1/sequence_types.go +++ b/pkg/apis/flows/v1/sequence_types.go @@ -124,10 +124,11 @@ type SequenceStatus struct { // Matches the Spec.Steps array in the order. ChannelStatuses []SequenceChannelStatus `json:"channelStatuses"` - // AddressStatus is the starting point to this Sequence. Sending to this + // Address is the starting point to this Sequence. Sending to this // will target the first subscriber. // It generally has the form {channel}.{namespace}.svc.{cluster domain name} - duckv1.AddressStatus `json:",inline"` + // +optional + Address duckv1.Addressable `json:"address,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/flows/v1/zz_generated.deepcopy.go b/pkg/apis/flows/v1/zz_generated.deepcopy.go index dff47523b9a..2e550d0612f 100644 --- a/pkg/apis/flows/v1/zz_generated.deepcopy.go +++ b/pkg/apis/flows/v1/zz_generated.deepcopy.go @@ -364,7 +364,7 @@ func (in *SequenceStatus) DeepCopyInto(out *SequenceStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.AddressStatus.DeepCopyInto(&out.AddressStatus) + in.Address.DeepCopyInto(&out.Address) return } diff --git a/pkg/reconciler/sequence/sequence_test.go b/pkg/reconciler/sequence/sequence_test.go index b1d39a92dca..ba25027dc1d 100644 --- a/pkg/reconciler/sequence/sequence_test.go +++ b/pkg/reconciler/sequence/sequence_test.go @@ -202,9 +202,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }), @@ -284,9 +284,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, })), @@ -336,9 +336,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, })), @@ -393,9 +393,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, })), @@ -436,9 +436,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }), @@ -510,9 +510,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -524,9 +524,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -538,9 +538,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }), @@ -628,9 +628,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -642,9 +642,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -656,9 +656,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }), @@ -749,9 +749,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -763,9 +763,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, { @@ -777,9 +777,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }), @@ -855,9 +855,9 @@ func TestAllCases(t *testing.T) { }, ReadyCondition: apis.Condition{ Type: apis.ConditionReady, - Status: corev1.ConditionFalse, - Reason: "NotAddressable", - Message: "Channel is not addressable", + Status: corev1.ConditionUnknown, + Reason: "NoReady", + Message: "Channel does not have Ready condition", }, }, }),