diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f1882f6471..60fbb5d7cb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -139,7 +139,7 @@ jobs: max-parallel: 7 matrix: driver: [jetstream] - case: [e2e-suite-1, e2e-suite-2, kafka-e2e, http-e2e, nats-e2e, sdks-e2e, reduce-e2e] + case: [e2e-suite-1, e2e-suite-2, kafka-e2e, http-e2e, nats-e2e, redis-source-e2e, sdks-e2e, reduce-e2e] steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/Makefile b/Makefile index 78e5f1f944..de342203da 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,7 @@ test-e2e-suite-2: test-kafka-e2e: test-http-e2e: test-nats-e2e: +test-redis-source-e2e: test-sdks-e2e: test-reduce-e2e: test-%: diff --git a/api/json-schema/schema.json b/api/json-schema/schema.json index 6f8d235632..0d035ec475 100644 --- a/api/json-schema/schema.json +++ b/api/json-schema/schema.json @@ -18899,6 +18899,53 @@ }, "type": "object" }, + "io.numaproj.numaflow.v1alpha1.RedisStreamsSource": { + "properties": { + "consumerGroup": { + "type": "string" + }, + "masterName": { + "description": "Only required when Sentinel is used", + "type": "string" + }, + "password": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", + "description": "Redis password secret selector" + }, + "readFromBeginning": { + "description": "if true, stream starts being read from the beginning; otherwise, the latest", + "type": "boolean" + }, + "sentinelPassword": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", + "description": "Sentinel password secret selector" + }, + "sentinelUrl": { + "description": "Sentinel URL, will be ignored if Redis URL is provided", + "type": "string" + }, + "stream": { + "type": "string" + }, + "tls": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.TLS" + }, + "url": { + "description": "Redis URL", + "type": "string" + }, + "user": { + "description": "Redis user", + "type": "string" + } + }, + "required": [ + "stream", + "consumerGroup", + "readFromBeginning" + ], + "type": "object" + }, "io.numaproj.numaflow.v1alpha1.SASL": { "properties": { "gssapi": { @@ -19036,6 +19083,9 @@ "nats": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.NatsSource" }, + "redisStreams": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.RedisStreamsSource" + }, "transformer": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDTransformer" } diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 9e8d1624e0..d8724731cd 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -18885,6 +18885,53 @@ } } }, + "io.numaproj.numaflow.v1alpha1.RedisStreamsSource": { + "type": "object", + "required": [ + "stream", + "consumerGroup", + "readFromBeginning" + ], + "properties": { + "consumerGroup": { + "type": "string" + }, + "masterName": { + "description": "Only required when Sentinel is used", + "type": "string" + }, + "password": { + "description": "Redis password secret selector", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector" + }, + "readFromBeginning": { + "description": "if true, stream starts being read from the beginning; otherwise, the latest", + "type": "boolean" + }, + "sentinelPassword": { + "description": "Sentinel password secret selector", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector" + }, + "sentinelUrl": { + "description": "Sentinel URL, will be ignored if Redis URL is provided", + "type": "string" + }, + "stream": { + "type": "string" + }, + "tls": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.TLS" + }, + "url": { + "description": "Redis URL", + "type": "string" + }, + "user": { + "description": "Redis user", + "type": "string" + } + } + }, "io.numaproj.numaflow.v1alpha1.SASL": { "type": "object", "required": [ @@ -19023,6 +19070,9 @@ "nats": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.NatsSource" }, + "redisStreams": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.RedisStreamsSource" + }, "transformer": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDTransformer" } diff --git a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml index 71922dd570..6b7674bb30 100644 --- a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml @@ -4186,6 +4186,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: diff --git a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml index ab73ce5ff2..40aae048f2 100644 --- a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml @@ -2643,6 +2643,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: diff --git a/config/install.yaml b/config/install.yaml index ce12d492d6..b9a257bb94 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -6631,6 +6631,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: @@ -10551,6 +10632,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: diff --git a/config/namespace-install.yaml b/config/namespace-install.yaml index 206686024a..2452321cd7 100644 --- a/config/namespace-install.yaml +++ b/config/namespace-install.yaml @@ -6631,6 +6631,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: @@ -10551,6 +10632,87 @@ spec: - subject - url type: object + redisStreams: + properties: + consumerGroup: + type: string + masterName: + type: string + password: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + readFromBeginning: + type: boolean + sentinelPassword: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + sentinelUrl: + type: string + stream: + type: string + tls: + properties: + caCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + insecureSkipVerify: + type: boolean + type: object + url: + type: string + user: + type: string + required: + - consumerGroup + - readFromBeginning + - stream + type: object transformer: properties: builtin: diff --git a/docs/APIs.md b/docs/APIs.md index a587cdb26c..000d783463 100644 --- a/docs/APIs.md +++ b/docs/APIs.md @@ -3439,7 +3439,8 @@ RedisConfig
(Appears on: BufferServiceConfig, -RedisBufferService) +RedisBufferService, +RedisStreamsSource)
@@ -3598,6 +3599,78 @@ config +
+(Appears on: +Source) +
++
++Field + | ++Description + | +
---|---|
+RedisConfig
+ RedisConfig
+
+ |
+
+
+(Members of +RedisConfig contains connectivity info + + |
+
+stream string
+ |
++ | +
+consumerGroup string
+ |
++ | +
+readFromBeginning bool
+ |
+
+ +if true, stream starts being read from the beginning; otherwise, the +latest + + |
+
+tls
+TLS
+ |
++(Optional) + | +
redisStreams
+
+RedisStreamsSource
+transformer
UDTransformer
@@ -4092,7 +4175,8 @@ TLS
(Appears on:
KafkaSink,
KafkaSource,
-NatsSource)
+NatsSource,
+RedisStreamsSource)
diff --git a/pkg/apis/numaflow/v1alpha1/generated.pb.go b/pkg/apis/numaflow/v1alpha1/generated.pb.go index 32240e884c..8e3841f31b 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.pb.go +++ b/pkg/apis/numaflow/v1alpha1/generated.pb.go @@ -1448,10 +1448,38 @@ func (m *RedisSettings) XXX_DiscardUnknown() { var xxx_messageInfo_RedisSettings proto.InternalMessageInfo +func (m *RedisStreamsSource) Reset() { *m = RedisStreamsSource{} } +func (*RedisStreamsSource) ProtoMessage() {} +func (*RedisStreamsSource) Descriptor() ([]byte, []int) { + return fileDescriptor_9d0d1b17d3865563, []int{50} +} +func (m *RedisStreamsSource) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RedisStreamsSource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RedisStreamsSource) XXX_Merge(src proto.Message) { + xxx_messageInfo_RedisStreamsSource.Merge(m, src) +} +func (m *RedisStreamsSource) XXX_Size() int { + return m.Size() +} +func (m *RedisStreamsSource) XXX_DiscardUnknown() { + xxx_messageInfo_RedisStreamsSource.DiscardUnknown(m) +} + +var xxx_messageInfo_RedisStreamsSource proto.InternalMessageInfo + func (m *SASL) Reset() { *m = SASL{} } func (*SASL) ProtoMessage() {} func (*SASL) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{50} + return fileDescriptor_9d0d1b17d3865563, []int{51} } func (m *SASL) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1479,7 +1507,7 @@ var xxx_messageInfo_SASL proto.InternalMessageInfo func (m *SASLPlain) Reset() { *m = SASLPlain{} } func (*SASLPlain) ProtoMessage() {} func (*SASLPlain) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{51} + return fileDescriptor_9d0d1b17d3865563, []int{52} } func (m *SASLPlain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1507,7 +1535,7 @@ var xxx_messageInfo_SASLPlain proto.InternalMessageInfo func (m *Scale) Reset() { *m = Scale{} } func (*Scale) ProtoMessage() {} func (*Scale) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{52} + return fileDescriptor_9d0d1b17d3865563, []int{53} } func (m *Scale) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1535,7 +1563,7 @@ var xxx_messageInfo_Scale proto.InternalMessageInfo func (m *Sink) Reset() { *m = Sink{} } func (*Sink) ProtoMessage() {} func (*Sink) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{53} + return fileDescriptor_9d0d1b17d3865563, []int{54} } func (m *Sink) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1563,7 +1591,7 @@ var xxx_messageInfo_Sink proto.InternalMessageInfo func (m *SlidingWindow) Reset() { *m = SlidingWindow{} } func (*SlidingWindow) ProtoMessage() {} func (*SlidingWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{54} + return fileDescriptor_9d0d1b17d3865563, []int{55} } func (m *SlidingWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1591,7 +1619,7 @@ var xxx_messageInfo_SlidingWindow proto.InternalMessageInfo func (m *Source) Reset() { *m = Source{} } func (*Source) ProtoMessage() {} func (*Source) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{55} + return fileDescriptor_9d0d1b17d3865563, []int{56} } func (m *Source) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1619,7 +1647,7 @@ var xxx_messageInfo_Source proto.InternalMessageInfo func (m *Status) Reset() { *m = Status{} } func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{56} + return fileDescriptor_9d0d1b17d3865563, []int{57} } func (m *Status) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1647,7 +1675,7 @@ var xxx_messageInfo_Status proto.InternalMessageInfo func (m *TLS) Reset() { *m = TLS{} } func (*TLS) ProtoMessage() {} func (*TLS) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{57} + return fileDescriptor_9d0d1b17d3865563, []int{58} } func (m *TLS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1675,7 +1703,7 @@ var xxx_messageInfo_TLS proto.InternalMessageInfo func (m *Templates) Reset() { *m = Templates{} } func (*Templates) ProtoMessage() {} func (*Templates) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{58} + return fileDescriptor_9d0d1b17d3865563, []int{59} } func (m *Templates) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1703,7 +1731,7 @@ var xxx_messageInfo_Templates proto.InternalMessageInfo func (m *Transformer) Reset() { *m = Transformer{} } func (*Transformer) ProtoMessage() {} func (*Transformer) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{59} + return fileDescriptor_9d0d1b17d3865563, []int{60} } func (m *Transformer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1731,7 +1759,7 @@ var xxx_messageInfo_Transformer proto.InternalMessageInfo func (m *UDF) Reset() { *m = UDF{} } func (*UDF) ProtoMessage() {} func (*UDF) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{60} + return fileDescriptor_9d0d1b17d3865563, []int{61} } func (m *UDF) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1759,7 +1787,7 @@ var xxx_messageInfo_UDF proto.InternalMessageInfo func (m *UDSink) Reset() { *m = UDSink{} } func (*UDSink) ProtoMessage() {} func (*UDSink) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{61} + return fileDescriptor_9d0d1b17d3865563, []int{62} } func (m *UDSink) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1787,7 +1815,7 @@ var xxx_messageInfo_UDSink proto.InternalMessageInfo func (m *UDTransformer) Reset() { *m = UDTransformer{} } func (*UDTransformer) ProtoMessage() {} func (*UDTransformer) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{62} + return fileDescriptor_9d0d1b17d3865563, []int{63} } func (m *UDTransformer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1815,7 +1843,7 @@ var xxx_messageInfo_UDTransformer proto.InternalMessageInfo func (m *Vertex) Reset() { *m = Vertex{} } func (*Vertex) ProtoMessage() {} func (*Vertex) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{63} + return fileDescriptor_9d0d1b17d3865563, []int{64} } func (m *Vertex) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1843,7 +1871,7 @@ var xxx_messageInfo_Vertex proto.InternalMessageInfo func (m *VertexInstance) Reset() { *m = VertexInstance{} } func (*VertexInstance) ProtoMessage() {} func (*VertexInstance) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{64} + return fileDescriptor_9d0d1b17d3865563, []int{65} } func (m *VertexInstance) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1871,7 +1899,7 @@ var xxx_messageInfo_VertexInstance proto.InternalMessageInfo func (m *VertexLimits) Reset() { *m = VertexLimits{} } func (*VertexLimits) ProtoMessage() {} func (*VertexLimits) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{65} + return fileDescriptor_9d0d1b17d3865563, []int{66} } func (m *VertexLimits) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1899,7 +1927,7 @@ var xxx_messageInfo_VertexLimits proto.InternalMessageInfo func (m *VertexList) Reset() { *m = VertexList{} } func (*VertexList) ProtoMessage() {} func (*VertexList) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{66} + return fileDescriptor_9d0d1b17d3865563, []int{67} } func (m *VertexList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1927,7 +1955,7 @@ var xxx_messageInfo_VertexList proto.InternalMessageInfo func (m *VertexSpec) Reset() { *m = VertexSpec{} } func (*VertexSpec) ProtoMessage() {} func (*VertexSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{67} + return fileDescriptor_9d0d1b17d3865563, []int{68} } func (m *VertexSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1955,7 +1983,7 @@ var xxx_messageInfo_VertexSpec proto.InternalMessageInfo func (m *VertexStatus) Reset() { *m = VertexStatus{} } func (*VertexStatus) ProtoMessage() {} func (*VertexStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{68} + return fileDescriptor_9d0d1b17d3865563, []int{69} } func (m *VertexStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1983,7 +2011,7 @@ var xxx_messageInfo_VertexStatus proto.InternalMessageInfo func (m *Watermark) Reset() { *m = Watermark{} } func (*Watermark) ProtoMessage() {} func (*Watermark) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{69} + return fileDescriptor_9d0d1b17d3865563, []int{70} } func (m *Watermark) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2011,7 +2039,7 @@ var xxx_messageInfo_Watermark proto.InternalMessageInfo func (m *Window) Reset() { *m = Window{} } func (*Window) ProtoMessage() {} func (*Window) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{70} + return fileDescriptor_9d0d1b17d3865563, []int{71} } func (m *Window) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2095,6 +2123,7 @@ func init() { proto.RegisterType((*RedisBufferService)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RedisBufferService") proto.RegisterType((*RedisConfig)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RedisConfig") proto.RegisterType((*RedisSettings)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RedisSettings") + proto.RegisterType((*RedisStreamsSource)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RedisStreamsSource") proto.RegisterType((*SASL)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SASL") proto.RegisterType((*SASLPlain)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SASLPlain") proto.RegisterType((*Scale)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.Scale") @@ -2124,383 +2153,389 @@ func init() { } var fileDescriptor_9d0d1b17d3865563 = []byte{ - // 6002 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x7d, 0x5f, 0x8c, 0x5c, 0xc9, - 0x55, 0xf7, 0xf6, 0xdf, 0xe9, 0x3e, 0x3d, 0xfe, 0x57, 0xb6, 0x77, 0xdb, 0x13, 0xaf, 0xdb, 0xb9, - 0xd1, 0xee, 0xe7, 0x7c, 0x5f, 0x32, 0xfe, 0x76, 0xbe, 0xcd, 0x17, 0x07, 0x48, 0x76, 0xa7, 0x67, - 0x3c, 0x5e, 0xef, 0xcc, 0xd8, 0x9d, 0xd3, 0x33, 0xf6, 0x26, 0x0b, 0x59, 0x6a, 0x6e, 0xd7, 0xf4, - 0xdc, 0xed, 0xdb, 0xf7, 0x76, 0xee, 0xad, 0x1e, 0x7b, 0x16, 0x22, 0x42, 0xf2, 0x90, 0x44, 0x04, - 0x05, 0x09, 0x21, 0x45, 0xa0, 0x20, 0x21, 0x21, 0x21, 0x90, 0x22, 0xf1, 0x40, 0x5e, 0xe0, 0x01, - 0x78, 0x41, 0x81, 0x07, 0xc8, 0x03, 0x12, 0x41, 0xa0, 0x16, 0x19, 0x9e, 0x78, 0x00, 0x45, 0xca, - 0x4b, 0x64, 0x21, 0x81, 0xea, 0xcf, 0xfd, 0xdb, 0xb7, 0x6d, 0x4f, 0xf7, 0xcc, 0xc6, 0x11, 0x6f, - 0x7d, 0x4f, 0x9d, 0xf3, 0x3b, 0x75, 0xeb, 0x56, 0x9d, 0x3a, 0x75, 0x4e, 0x55, 0x35, 0xdc, 0xea, - 0x5a, 0x7c, 0x6f, 0xb8, 0xb3, 0x68, 0xba, 0xfd, 0xeb, 0xce, 0xb0, 0x4f, 0x07, 0x9e, 0xfb, 0xae, - 0xfc, 0xb1, 0x6b, 0xbb, 0x0f, 0xae, 0x0f, 0x7a, 0xdd, 0xeb, 0x74, 0x60, 0xf9, 0x11, 0x65, 0xff, - 0x15, 0x6a, 0x0f, 0xf6, 0xe8, 0x2b, 0xd7, 0xbb, 0xcc, 0x61, 0x1e, 0xe5, 0xac, 0xb3, 0x38, 0xf0, - 0x5c, 0xee, 0x92, 0x8f, 0x47, 0x40, 0x8b, 0x01, 0xd0, 0x62, 0x20, 0xb6, 0x38, 0xe8, 0x75, 0x17, - 0x05, 0x50, 0x44, 0x09, 0x80, 0x16, 0x3e, 0x1a, 0xab, 0x41, 0xd7, 0xed, 0xba, 0xd7, 0x25, 0xde, - 0xce, 0x70, 0x57, 0x3e, 0xc9, 0x07, 0xf9, 0x4b, 0xe9, 0x59, 0x30, 0x7a, 0x37, 0xfc, 0x45, 0xcb, - 0x15, 0xd5, 0xba, 0x6e, 0xba, 0x1e, 0xbb, 0xbe, 0x3f, 0x56, 0x97, 0x85, 0x57, 0x23, 0x9e, 0x3e, - 0x35, 0xf7, 0x2c, 0x87, 0x79, 0x07, 0xc1, 0xbb, 0x5c, 0xf7, 0x98, 0xef, 0x0e, 0x3d, 0x93, 0x1d, - 0x49, 0xca, 0xbf, 0xde, 0x67, 0x9c, 0x66, 0xe9, 0xba, 0x3e, 0x49, 0xca, 0x1b, 0x3a, 0xdc, 0xea, - 0x8f, 0xab, 0xf9, 0xff, 0x4f, 0x12, 0xf0, 0xcd, 0x3d, 0xd6, 0xa7, 0x69, 0x39, 0xe3, 0x9f, 0xaa, - 0x70, 0x7e, 0x79, 0xc7, 0xe7, 0x1e, 0x35, 0x79, 0xcb, 0xed, 0x6c, 0xb1, 0xfe, 0xc0, 0xa6, 0x9c, - 0x91, 0x1e, 0x54, 0x44, 0xdd, 0x3a, 0x94, 0xd3, 0x7a, 0xee, 0x6a, 0xee, 0x5a, 0x6d, 0x69, 0x79, - 0x71, 0xca, 0x6f, 0xb1, 0xb8, 0xa9, 0x81, 0x9a, 0xf3, 0x87, 0xa3, 0x46, 0x25, 0x78, 0xc2, 0x50, - 0x01, 0xf9, 0x66, 0x0e, 0xe6, 0x1d, 0xb7, 0xc3, 0xda, 0xcc, 0x66, 0x26, 0x77, 0xbd, 0x7a, 0xfe, - 0x6a, 0xe1, 0x5a, 0x6d, 0xe9, 0x73, 0x53, 0x6b, 0xcc, 0x78, 0xa3, 0xc5, 0x3b, 0x31, 0x05, 0x37, - 0x1d, 0xee, 0x1d, 0x34, 0x2f, 0x7c, 0x77, 0xd4, 0x78, 0xee, 0x70, 0xd4, 0x98, 0x8f, 0x17, 0x61, - 0xa2, 0x26, 0x64, 0x1b, 0x6a, 0xdc, 0xb5, 0x45, 0x93, 0x59, 0xae, 0xe3, 0xd7, 0x0b, 0xb2, 0x62, - 0x57, 0x16, 0x55, 0x6b, 0x0b, 0xf5, 0x8b, 0xa2, 0xbb, 0x2c, 0xee, 0xbf, 0xb2, 0xb8, 0x15, 0xb2, - 0x35, 0xcf, 0x6b, 0xe0, 0x5a, 0x44, 0xf3, 0x31, 0x8e, 0x43, 0x18, 0x9c, 0xf1, 0x99, 0x39, 0xf4, - 0x2c, 0x7e, 0xb0, 0xe2, 0x3a, 0x9c, 0x3d, 0xe4, 0xf5, 0xa2, 0x6c, 0xe5, 0x97, 0xb3, 0xa0, 0x5b, - 0x6e, 0xa7, 0x9d, 0xe4, 0x6e, 0x9e, 0x3f, 0x1c, 0x35, 0xce, 0xa4, 0x88, 0x98, 0xc6, 0x24, 0x0e, - 0x9c, 0xb5, 0xfa, 0xb4, 0xcb, 0x5a, 0x43, 0xdb, 0x6e, 0x33, 0xd3, 0x63, 0xdc, 0xaf, 0x97, 0xe4, - 0x2b, 0x5c, 0xcb, 0xd2, 0xb3, 0xe1, 0x9a, 0xd4, 0xbe, 0xbb, 0xf3, 0x2e, 0x33, 0x39, 0xb2, 0x5d, - 0xe6, 0x31, 0xc7, 0x64, 0xcd, 0xba, 0x7e, 0x99, 0xb3, 0xb7, 0x53, 0x48, 0x38, 0x86, 0x4d, 0x6e, - 0xc1, 0xb9, 0x81, 0x67, 0xb9, 0xb2, 0x0a, 0x36, 0xf5, 0xfd, 0x3b, 0xb4, 0xcf, 0xea, 0xe5, 0xab, - 0xb9, 0x6b, 0xd5, 0xe6, 0x25, 0x0d, 0x73, 0xae, 0x95, 0x66, 0xc0, 0x71, 0x19, 0x72, 0x0d, 0x2a, - 0x01, 0xb1, 0x3e, 0x77, 0x35, 0x77, 0xad, 0xa4, 0xfa, 0x4e, 0x20, 0x8b, 0x61, 0x29, 0x59, 0x83, - 0x0a, 0xdd, 0xdd, 0xb5, 0x1c, 0xc1, 0x59, 0x91, 0x4d, 0x78, 0x39, 0xeb, 0xd5, 0x96, 0x35, 0x8f, - 0xc2, 0x09, 0x9e, 0x30, 0x94, 0x25, 0x6f, 0x02, 0xf1, 0x99, 0xb7, 0x6f, 0x99, 0x6c, 0xd9, 0x34, - 0xdd, 0xa1, 0xc3, 0x65, 0xdd, 0xab, 0xb2, 0xee, 0x0b, 0xba, 0xee, 0xa4, 0x3d, 0xc6, 0x81, 0x19, - 0x52, 0xe4, 0x75, 0x38, 0xab, 0x87, 0x5d, 0xd4, 0x0a, 0x20, 0x91, 0x2e, 0x88, 0x86, 0xc4, 0x54, - 0x19, 0x8e, 0x71, 0x93, 0x0e, 0x5c, 0xa6, 0x43, 0xee, 0xf6, 0x05, 0x64, 0x52, 0xe9, 0x96, 0xdb, - 0x63, 0x4e, 0xbd, 0x76, 0x35, 0x77, 0xad, 0xd2, 0xbc, 0x7a, 0x38, 0x6a, 0x5c, 0x5e, 0x7e, 0x0c, - 0x1f, 0x3e, 0x16, 0x85, 0xdc, 0x85, 0x6a, 0xc7, 0xf1, 0x5b, 0xae, 0x6d, 0x99, 0x07, 0xf5, 0x79, - 0x59, 0xc1, 0x57, 0xf4, 0xab, 0x56, 0x57, 0xef, 0xb4, 0x55, 0xc1, 0xa3, 0x51, 0xe3, 0xf2, 0xb8, - 0x75, 0x5c, 0x0c, 0xcb, 0x31, 0xc2, 0x20, 0x9b, 0x12, 0x70, 0xc5, 0x75, 0x76, 0xad, 0x6e, 0xfd, - 0x94, 0xfc, 0x1a, 0x57, 0x27, 0x74, 0xe8, 0xd5, 0x3b, 0x6d, 0xc5, 0xd7, 0x3c, 0xa5, 0xd5, 0xa9, - 0x47, 0x8c, 0x10, 0x16, 0x5e, 0x83, 0x73, 0x63, 0xa3, 0x96, 0x9c, 0x85, 0x42, 0x8f, 0x1d, 0x48, - 0xa3, 0x54, 0x45, 0xf1, 0x93, 0x5c, 0x80, 0xd2, 0x3e, 0xb5, 0x87, 0xac, 0x9e, 0x97, 0x34, 0xf5, - 0xf0, 0x33, 0xf9, 0x1b, 0x39, 0x63, 0x54, 0x85, 0xd3, 0x81, 0x2d, 0xb8, 0xc7, 0x3c, 0xce, 0x1e, - 0x92, 0xab, 0x50, 0x74, 0xc4, 0xf7, 0x90, 0xf2, 0xcd, 0x79, 0xfd, 0xba, 0x45, 0xf9, 0x1d, 0x64, - 0x09, 0x31, 0xa1, 0xac, 0x6c, 0xb9, 0xc4, 0xab, 0x2d, 0xbd, 0x36, 0xb5, 0x19, 0x6a, 0x4b, 0x98, - 0x26, 0x1c, 0x8e, 0x1a, 0x65, 0xf5, 0x1b, 0x35, 0x34, 0x79, 0x1b, 0x8a, 0xbe, 0xe5, 0xf4, 0xea, - 0x05, 0xa9, 0xe2, 0x93, 0xd3, 0xab, 0xb0, 0x9c, 0x5e, 0xb3, 0x22, 0xde, 0x40, 0xfc, 0x42, 0x09, - 0x4a, 0xee, 0x43, 0x61, 0xd8, 0xd9, 0xd5, 0x16, 0xe5, 0xe7, 0xa6, 0xc6, 0xde, 0x5e, 0x5d, 0x6b, - 0xce, 0x1d, 0x8e, 0x1a, 0x85, 0xed, 0xd5, 0x35, 0x14, 0x88, 0xe4, 0x1b, 0x39, 0x38, 0x67, 0xba, - 0x0e, 0xa7, 0x62, 0x7e, 0x09, 0x2c, 0x6b, 0xbd, 0x24, 0xf5, 0xbc, 0x39, 0xb5, 0x9e, 0x95, 0x34, - 0x62, 0xf3, 0xa2, 0x30, 0x14, 0x63, 0x64, 0x1c, 0xd7, 0x4d, 0x7e, 0x27, 0x07, 0x17, 0xc5, 0x00, - 0x1e, 0x63, 0x96, 0x66, 0xe7, 0x78, 0x6b, 0x75, 0xe9, 0x70, 0xd4, 0xb8, 0x78, 0x3b, 0x4b, 0x19, - 0x66, 0xd7, 0x41, 0xd4, 0xee, 0x3c, 0x1d, 0x9f, 0x8b, 0xa4, 0x49, 0xab, 0x2d, 0x6d, 0x1c, 0xe7, - 0xfc, 0xd6, 0xfc, 0x80, 0xee, 0xca, 0x59, 0xd3, 0x39, 0x66, 0xd5, 0x82, 0xdc, 0x84, 0xb9, 0x7d, - 0xd7, 0x1e, 0xf6, 0x99, 0x5f, 0xaf, 0xc8, 0x49, 0x61, 0x21, 0x6b, 0xac, 0xde, 0x93, 0x2c, 0xcd, - 0x33, 0x1a, 0x7e, 0x4e, 0x3d, 0xfb, 0x18, 0xc8, 0x12, 0x0b, 0xca, 0xb6, 0xd5, 0xb7, 0xb8, 0x2f, - 0xad, 0x65, 0x6d, 0xe9, 0xe6, 0xd4, 0xaf, 0xa5, 0x86, 0xe8, 0x86, 0x04, 0x53, 0xa3, 0x46, 0xfd, - 0x46, 0xad, 0x80, 0x98, 0x50, 0xf2, 0x4d, 0x6a, 0x2b, 0x6b, 0x5a, 0x5b, 0xfa, 0xd4, 0xf4, 0xc3, - 0x46, 0xa0, 0x34, 0x4f, 0xe9, 0x77, 0x2a, 0xc9, 0x47, 0x54, 0xd8, 0xe4, 0x17, 0xe0, 0x74, 0xe2, - 0x6b, 0xfa, 0xf5, 0x9a, 0x6c, 0x9d, 0x17, 0xb3, 0x5a, 0x27, 0xe4, 0x6a, 0x3e, 0xaf, 0xc1, 0x4e, - 0x27, 0x7a, 0x88, 0x8f, 0x29, 0x30, 0xb2, 0x0e, 0x15, 0xdf, 0xea, 0x30, 0x93, 0x7a, 0x7e, 0x7d, - 0xfe, 0x69, 0x80, 0xcf, 0x6a, 0xe0, 0x4a, 0x5b, 0x8b, 0x61, 0x08, 0x60, 0xdc, 0x87, 0x53, 0xcb, - 0x43, 0xbe, 0xe7, 0x7a, 0xd6, 0x7b, 0xd2, 0xb3, 0x20, 0x6b, 0x50, 0xe2, 0x72, 0x86, 0x50, 0x4e, - 0xdb, 0x4b, 0x59, 0xd0, 0x6a, 0xb6, 0x5e, 0x67, 0x07, 0x81, 0x61, 0x6d, 0x56, 0x45, 0x23, 0xa8, - 0x19, 0x43, 0x89, 0x1b, 0xbf, 0x97, 0x83, 0x6a, 0x93, 0xfa, 0x96, 0x29, 0xe0, 0xc9, 0x0a, 0x14, - 0x87, 0x3e, 0xf3, 0x8e, 0x06, 0x2a, 0xad, 0xd2, 0xb6, 0xcf, 0x3c, 0x94, 0xc2, 0xe4, 0x2e, 0x54, - 0x06, 0xd4, 0xf7, 0x1f, 0xb8, 0x5e, 0x47, 0x5b, 0xd6, 0xa7, 0x04, 0x52, 0x53, 0xbf, 0x16, 0xc5, - 0x10, 0xc4, 0xa8, 0x41, 0xb5, 0x69, 0x53, 0xb3, 0xb7, 0xe7, 0xda, 0xcc, 0xf8, 0x2c, 0x94, 0x9b, - 0xc3, 0xdd, 0x5d, 0xe6, 0x3d, 0x85, 0x85, 0x5f, 0x84, 0x22, 0x3f, 0x18, 0xe8, 0xf9, 0x22, 0x9c, - 0xdd, 0x8b, 0x5b, 0x07, 0x03, 0xf6, 0x68, 0xd4, 0x00, 0x85, 0x23, 0x9e, 0x50, 0xf2, 0x19, 0x3f, - 0xca, 0xc1, 0x79, 0x45, 0xd4, 0xb3, 0xa8, 0x9a, 0x9f, 0x08, 0x83, 0x92, 0xc7, 0x3a, 0x96, 0xaf, - 0xdb, 0x65, 0x75, 0xea, 0xee, 0x88, 0x02, 0x45, 0x4f, 0x87, 0xf2, 0x5b, 0x48, 0x02, 0x2a, 0x74, - 0x32, 0x84, 0xea, 0xbb, 0x8c, 0xfb, 0xdc, 0x63, 0xb4, 0xaf, 0x5b, 0xee, 0x8d, 0xa9, 0x55, 0xbd, - 0xc9, 0x78, 0x5b, 0x22, 0xc5, 0x67, 0xdf, 0x90, 0x88, 0x91, 0x26, 0xe3, 0xc7, 0x45, 0xa8, 0x86, - 0xbd, 0x90, 0x7c, 0x08, 0x4a, 0xd2, 0xdd, 0xd3, 0xcd, 0x1a, 0x0e, 0x1d, 0xe9, 0x15, 0xa2, 0x2a, - 0x23, 0x2f, 0xc1, 0x9c, 0xe9, 0xf6, 0xfb, 0xd4, 0xe9, 0x48, 0x17, 0xbe, 0xda, 0xac, 0x09, 0x8b, - 0xb1, 0xa2, 0x48, 0x18, 0x94, 0x91, 0xcb, 0x50, 0xa4, 0x5e, 0x57, 0x79, 0xd3, 0x55, 0xd5, 0x4f, - 0x96, 0xbd, 0xae, 0x8f, 0x92, 0x4a, 0x3e, 0x01, 0x05, 0xe6, 0xec, 0xd7, 0x8b, 0x93, 0x4d, 0xd2, - 0x4d, 0x67, 0xff, 0x1e, 0xf5, 0x9a, 0x35, 0x5d, 0x87, 0xc2, 0x4d, 0x67, 0x1f, 0x85, 0x0c, 0xd9, - 0x80, 0x39, 0xe6, 0xec, 0xaf, 0x79, 0x6e, 0x5f, 0xbb, 0xb9, 0x1f, 0x9c, 0x20, 0x2e, 0x58, 0xf4, - 0xec, 0x1c, 0x1a, 0x36, 0x4d, 0xc6, 0x00, 0x82, 0x7c, 0x06, 0xe6, 0x95, 0x8d, 0xdb, 0x14, 0x2e, - 0x93, 0x5f, 0x2f, 0x4b, 0xc8, 0xc6, 0x64, 0x23, 0x29, 0xf9, 0xa2, 0x65, 0x45, 0x8c, 0xe8, 0x63, - 0x02, 0x8a, 0x7c, 0x06, 0xaa, 0xc1, 0x8a, 0xd1, 0xd7, 0xb3, 0x41, 0xa6, 0x47, 0x8e, 0x9a, 0x09, - 0xd9, 0xe7, 0x87, 0x96, 0xc7, 0xfa, 0xcc, 0xe1, 0x7e, 0xf3, 0x5c, 0xe0, 0xa3, 0x05, 0xa5, 0x3e, - 0x46, 0x68, 0x64, 0x67, 0x7c, 0x69, 0xa1, 0xfc, 0xe2, 0x0f, 0x4d, 0x18, 0x6d, 0x53, 0xac, 0x2b, - 0x3e, 0x07, 0x67, 0x42, 0xdf, 0x5f, 0xbb, 0x8f, 0xca, 0x53, 0x7e, 0x55, 0x88, 0xdf, 0x4e, 0x16, - 0x3d, 0x1a, 0x35, 0x5e, 0xcc, 0x70, 0x20, 0x23, 0x06, 0x4c, 0x83, 0x19, 0x7f, 0x5e, 0x80, 0xf1, - 0xe9, 0x3f, 0xd9, 0x68, 0xb9, 0xe3, 0x6e, 0xb4, 0xf4, 0x0b, 0x29, 0xe3, 0x70, 0x43, 0x8b, 0xcd, - 0xfe, 0x52, 0x59, 0x1f, 0xa6, 0x70, 0xdc, 0x1f, 0xe6, 0x59, 0x19, 0x3b, 0xc6, 0x57, 0x8b, 0x70, - 0x7a, 0x95, 0xb2, 0xbe, 0xeb, 0x3c, 0xd1, 0x19, 0xca, 0x3d, 0x13, 0xce, 0xd0, 0x35, 0xa8, 0x78, - 0x6c, 0x60, 0x5b, 0x26, 0xf5, 0xe5, 0xa7, 0xd7, 0x2b, 0x4e, 0xd4, 0x34, 0x0c, 0x4b, 0x27, 0x38, - 0xc1, 0x85, 0x67, 0xd2, 0x09, 0x2e, 0xfe, 0xe4, 0x9d, 0x60, 0xe3, 0x5b, 0x05, 0x28, 0xde, 0xec, - 0x74, 0x99, 0x98, 0x98, 0x77, 0x45, 0xf7, 0x4a, 0x4d, 0xcc, 0xb2, 0xe3, 0xc8, 0x12, 0xb2, 0x00, - 0x79, 0xee, 0xea, 0x91, 0x07, 0xba, 0x3c, 0xbf, 0xe5, 0x62, 0x9e, 0xbb, 0xe4, 0x3d, 0x00, 0xd3, - 0x75, 0x3a, 0x56, 0x10, 0x88, 0x99, 0xed, 0xc5, 0xd6, 0x5c, 0xef, 0x01, 0xf5, 0x3a, 0x2b, 0x21, - 0x62, 0xf3, 0xf4, 0xe1, 0xa8, 0x01, 0xd1, 0x33, 0xc6, 0xb4, 0x91, 0x6e, 0xe8, 0xe2, 0xaa, 0x06, - 0x5d, 0x99, 0x5a, 0xaf, 0x68, 0x88, 0xc7, 0x38, 0xb8, 0xaf, 0x40, 0x6d, 0x40, 0x3d, 0x6a, 0xdb, - 0xcc, 0xb6, 0xfc, 0xbe, 0x5c, 0x59, 0x95, 0x9a, 0x67, 0x0e, 0x47, 0x8d, 0x5a, 0x2b, 0x22, 0x63, - 0x9c, 0x87, 0xbc, 0x06, 0x65, 0xd7, 0x59, 0x1b, 0xda, 0xb6, 0x0e, 0xb4, 0xfc, 0x2f, 0x01, 0x7b, - 0x57, 0x52, 0x1e, 0x8d, 0x1a, 0x97, 0x94, 0xdf, 0x22, 0x9e, 0xee, 0x7b, 0x16, 0xb7, 0x9c, 0x6e, - 0x9b, 0x7b, 0x94, 0xb3, 0xee, 0x01, 0x6a, 0x31, 0xe3, 0xd7, 0x73, 0x00, 0x51, 0xb5, 0xc8, 0x27, - 0xe1, 0xcc, 0x8e, 0x94, 0xd9, 0xa4, 0x0f, 0x37, 0x98, 0xd3, 0xe5, 0x7b, 0xf2, 0x83, 0x15, 0x95, - 0x05, 0x6a, 0x26, 0x8b, 0x30, 0xcd, 0x4b, 0x5e, 0x87, 0xb3, 0x8a, 0xb4, 0xed, 0x53, 0x8d, 0x29, - 0x3f, 0xe8, 0x29, 0x15, 0xfb, 0x68, 0xa6, 0xca, 0x70, 0x8c, 0xdb, 0xa0, 0x50, 0x5b, 0xb3, 0x1e, - 0xb2, 0xce, 0x7d, 0xcb, 0xe9, 0xb8, 0x0f, 0x08, 0x42, 0xd9, 0x8e, 0xaa, 0x51, 0x5b, 0x5a, 0x8c, - 0x99, 0xa5, 0x30, 0xd4, 0x19, 0x35, 0x79, 0x9f, 0x71, 0x2a, 0x0c, 0xd5, 0xea, 0x50, 0x07, 0xe3, - 0x54, 0x33, 0xab, 0xda, 0x6a, 0x24, 0xe3, 0x55, 0x38, 0x37, 0xd6, 0x01, 0x48, 0x03, 0x4a, 0x3d, - 0x76, 0x70, 0x5b, 0xb8, 0xce, 0xc2, 0x2d, 0x91, 0x7e, 0xd8, 0xba, 0x20, 0xa0, 0xa2, 0x1b, 0xff, - 0x99, 0x83, 0xca, 0xda, 0xd0, 0x31, 0xa5, 0xa3, 0xfd, 0x64, 0x2f, 0x33, 0xf0, 0x72, 0xf2, 0x99, - 0x5e, 0xce, 0x10, 0xca, 0xbd, 0x07, 0xa1, 0x17, 0x54, 0x5b, 0xda, 0x9c, 0xbe, 0x2b, 0xeb, 0x2a, - 0x2d, 0xae, 0x4b, 0x3c, 0x15, 0xdb, 0x3c, 0xad, 0x2b, 0x54, 0x5e, 0xbf, 0x2f, 0x95, 0x6a, 0x65, - 0x0b, 0x9f, 0x80, 0x5a, 0x8c, 0xed, 0x48, 0xc1, 0x94, 0x5f, 0x2d, 0x42, 0xf9, 0x56, 0xbb, 0xbd, - 0xdc, 0xba, 0x4d, 0x3e, 0x06, 0x35, 0x1d, 0xf6, 0xba, 0x13, 0xb5, 0x41, 0x18, 0xf5, 0x6c, 0x47, - 0x45, 0x18, 0xe7, 0x13, 0x3e, 0xa4, 0xc7, 0xa8, 0xdd, 0xd7, 0x23, 0x3c, 0xf4, 0x21, 0x51, 0x10, - 0x51, 0x95, 0x91, 0x8f, 0x40, 0x45, 0x2c, 0x17, 0x64, 0xe3, 0x16, 0x24, 0x5f, 0xb8, 0x00, 0xda, - 0xd6, 0x74, 0x0c, 0x39, 0xc8, 0x0d, 0xa8, 0xd0, 0x21, 0xdf, 0x13, 0xce, 0xba, 0x1c, 0x9b, 0xd5, - 0xe6, 0x65, 0x19, 0xe0, 0xd3, 0xb4, 0x47, 0xa3, 0xc6, 0xfc, 0x3a, 0x36, 0x3f, 0x16, 0x3c, 0x63, - 0xc8, 0x4d, 0x28, 0x9c, 0x0e, 0x56, 0x12, 0x6a, 0xc9, 0xa1, 0xe3, 0x18, 0x4f, 0xb9, 0x28, 0x21, - 0x62, 0xa9, 0xd7, 0x4a, 0x00, 0x60, 0x0a, 0x90, 0xbc, 0x0d, 0xf3, 0x3d, 0x76, 0xc0, 0xe9, 0x8e, - 0x56, 0x50, 0x3e, 0x8a, 0x82, 0xb3, 0xc2, 0x85, 0x5c, 0x8f, 0x89, 0x63, 0x02, 0x8c, 0xf8, 0x70, - 0xa1, 0xc7, 0xbc, 0x1d, 0xe6, 0xb9, 0x7a, 0xe5, 0xa0, 0x95, 0xcc, 0x1d, 0x45, 0x49, 0xfd, 0x70, - 0xd4, 0xb8, 0xb0, 0x9e, 0x01, 0x83, 0x99, 0xe0, 0xc6, 0x8f, 0x73, 0x70, 0xe6, 0x96, 0x4a, 0x21, - 0xb8, 0x9e, 0x72, 0x02, 0xc8, 0x25, 0x28, 0x78, 0x83, 0xa1, 0xec, 0x04, 0x05, 0x15, 0x2f, 0xc2, - 0xd6, 0x36, 0x0a, 0x1a, 0x79, 0x0b, 0x2a, 0x1d, 0x3d, 0x0e, 0xf5, 0xc2, 0xe5, 0xa8, 0xa3, 0x57, - 0x4e, 0xc2, 0xc1, 0x13, 0x86, 0x68, 0x62, 0xa5, 0xd1, 0xf7, 0xbb, 0x6d, 0xeb, 0x3d, 0xd5, 0x49, - 0x4a, 0x6a, 0xa5, 0xb1, 0xa9, 0x48, 0x18, 0x94, 0x89, 0x59, 0xbd, 0xc7, 0x0e, 0x56, 0x84, 0xd3, - 0x2d, 0xbb, 0x87, 0x9e, 0xd5, 0xd7, 0x35, 0x0d, 0xc3, 0x52, 0x31, 0xfa, 0x55, 0xbf, 0x2f, 0x49, - 0x63, 0x27, 0x47, 0xff, 0x3d, 0x41, 0xd0, 0x43, 0xc0, 0xf8, 0x46, 0x1e, 0x9e, 0xbf, 0xc5, 0xb8, - 0x72, 0x6a, 0x56, 0xd9, 0xc0, 0x76, 0x0f, 0x84, 0x67, 0x89, 0xec, 0xf3, 0xe4, 0x75, 0x00, 0xcb, - 0xdf, 0x69, 0xef, 0x9b, 0xb2, 0x1b, 0xaa, 0xd1, 0x70, 0x55, 0x77, 0x5a, 0xb8, 0xdd, 0x6e, 0xea, - 0x92, 0x47, 0x89, 0x27, 0x8c, 0xc9, 0x44, 0xab, 0xab, 0xfc, 0x63, 0x56, 0x57, 0x6d, 0x80, 0x41, - 0xe4, 0x9f, 0xaa, 0xb1, 0xf1, 0xff, 0x02, 0x35, 0x47, 0x71, 0x4d, 0x63, 0x30, 0x33, 0x78, 0x8c, - 0xc6, 0x9f, 0x16, 0x60, 0xe1, 0x16, 0xe3, 0xe1, 0xe2, 0x51, 0x8f, 0xfb, 0xf6, 0x80, 0x99, 0xa2, - 0x55, 0xbe, 0x92, 0x83, 0xb2, 0x4d, 0x77, 0x98, 0xed, 0x4b, 0x8b, 0x5a, 0x5b, 0x7a, 0x67, 0x6a, - 0x13, 0x37, 0x59, 0xcb, 0xe2, 0x86, 0xd4, 0x90, 0x32, 0x7a, 0x8a, 0x88, 0x5a, 0xbd, 0x30, 0x57, - 0xa6, 0x3d, 0xf4, 0x39, 0xf3, 0x5a, 0xae, 0xc7, 0xb5, 0x7b, 0x17, 0x9a, 0xab, 0x95, 0xa8, 0x08, - 0xe3, 0x7c, 0x64, 0x09, 0xc0, 0xb4, 0x2d, 0xe6, 0x70, 0x29, 0xa5, 0xba, 0x19, 0x09, 0xda, 0x7b, - 0x25, 0x2c, 0xc1, 0x18, 0x97, 0x50, 0xd5, 0x77, 0x1d, 0x8b, 0xbb, 0x4a, 0x55, 0x31, 0xa9, 0x6a, - 0x33, 0x2a, 0xc2, 0x38, 0x9f, 0x14, 0x63, 0xdc, 0xb3, 0x4c, 0x5f, 0x8a, 0x95, 0x52, 0x62, 0x51, - 0x11, 0xc6, 0xf9, 0x84, 0x35, 0x8f, 0xbd, 0xff, 0x91, 0xac, 0xf9, 0x9f, 0x55, 0xe0, 0x4a, 0xa2, - 0x59, 0x39, 0xe5, 0x6c, 0x77, 0x68, 0xb7, 0x19, 0x0f, 0x3e, 0xe0, 0x94, 0x56, 0xfe, 0xd7, 0xa2, - 0xef, 0xae, 0xf2, 0x78, 0xe6, 0xf1, 0x7c, 0xf7, 0xb1, 0x0a, 0x3e, 0xd5, 0xb7, 0xbf, 0x0e, 0x55, - 0x87, 0x72, 0x5f, 0x0e, 0x24, 0x3d, 0x66, 0xc2, 0xa5, 0xe0, 0x9d, 0xa0, 0x00, 0x23, 0x1e, 0xd2, - 0x82, 0x0b, 0xba, 0x89, 0x6f, 0x3e, 0x1c, 0xb8, 0x1e, 0x67, 0x9e, 0x92, 0xd5, 0xb3, 0x8b, 0x96, - 0xbd, 0xb0, 0x99, 0xc1, 0x83, 0x99, 0x92, 0x64, 0x13, 0xce, 0x9b, 0x2a, 0xb7, 0xc1, 0x6c, 0x97, - 0x76, 0x02, 0xc0, 0x92, 0x04, 0x0c, 0x57, 0x2a, 0x2b, 0xe3, 0x2c, 0x98, 0x25, 0x97, 0xee, 0xcd, - 0xe5, 0xa9, 0x7a, 0xf3, 0xdc, 0x34, 0xbd, 0xb9, 0x32, 0x5d, 0x6f, 0xae, 0x3e, 0x5d, 0x6f, 0x16, - 0x2d, 0x2f, 0xfa, 0x11, 0xf3, 0xc4, 0x6c, 0xad, 0x26, 0x9c, 0x58, 0xea, 0x2c, 0x6c, 0xf9, 0x76, - 0x06, 0x0f, 0x66, 0x4a, 0x92, 0x1d, 0x58, 0x50, 0xf4, 0x9b, 0x8e, 0xe9, 0x1d, 0x0c, 0xc4, 0xcc, - 0x11, 0xc3, 0xad, 0x49, 0x5c, 0x43, 0xe3, 0x2e, 0xb4, 0x27, 0x72, 0xe2, 0x63, 0x50, 0xc8, 0xcf, - 0xc2, 0x29, 0xf5, 0x95, 0x36, 0xe9, 0x40, 0xc2, 0xaa, 0x44, 0xda, 0x45, 0x0d, 0x7b, 0x6a, 0x25, - 0x5e, 0x88, 0x49, 0x5e, 0xb2, 0x0c, 0x67, 0x06, 0xfb, 0xa6, 0xf8, 0x79, 0x7b, 0xf7, 0x0e, 0x63, - 0x1d, 0xd6, 0x91, 0x69, 0xb3, 0x6a, 0xf3, 0x85, 0x20, 0xee, 0xd0, 0x4a, 0x16, 0x63, 0x9a, 0x9f, - 0xdc, 0x80, 0x79, 0x9f, 0x53, 0x8f, 0xeb, 0x28, 0x5b, 0xfd, 0xb4, 0x4a, 0x34, 0x06, 0x41, 0xa8, - 0x76, 0xac, 0x0c, 0x13, 0x9c, 0xb3, 0x58, 0x8f, 0x47, 0x6a, 0x32, 0x94, 0x61, 0xca, 0x94, 0xd9, - 0xff, 0x72, 0xda, 0xec, 0xbf, 0x3d, 0xcb, 0xf0, 0xcf, 0xd0, 0xf0, 0x54, 0xc3, 0xfe, 0x4d, 0x20, - 0x9e, 0x0e, 0xaa, 0xaa, 0xe5, 0x68, 0xcc, 0xf2, 0x87, 0xe9, 0x5c, 0x1c, 0xe3, 0xc0, 0x0c, 0x29, - 0xd2, 0x86, 0x8b, 0x3e, 0x73, 0xb8, 0xe5, 0x30, 0x3b, 0x09, 0xa7, 0xa6, 0x84, 0x17, 0x35, 0xdc, - 0xc5, 0x76, 0x16, 0x13, 0x66, 0xcb, 0xce, 0xd2, 0xf8, 0xff, 0x5c, 0x95, 0xf3, 0xae, 0x6a, 0x9a, - 0x63, 0x33, 0xdb, 0x5f, 0x49, 0x9b, 0xed, 0x77, 0x66, 0xff, 0x6e, 0xd3, 0x99, 0xec, 0x25, 0x00, - 0xf9, 0x15, 0xe2, 0x36, 0x3b, 0xb4, 0x54, 0x18, 0x96, 0x60, 0x8c, 0x4b, 0x8c, 0xc2, 0xa0, 0x9d, - 0xe3, 0xe6, 0x3a, 0x1c, 0x85, 0xed, 0x78, 0x21, 0x26, 0x79, 0x27, 0x9a, 0xfc, 0xd2, 0xd4, 0x26, - 0xff, 0x4d, 0x20, 0x89, 0x60, 0x88, 0xc2, 0x2b, 0x27, 0x77, 0x13, 0xdc, 0x1e, 0xe3, 0xc0, 0x0c, - 0xa9, 0x09, 0x5d, 0x79, 0xee, 0x78, 0xbb, 0x72, 0x65, 0xfa, 0xae, 0x4c, 0xde, 0x81, 0x4b, 0x52, - 0x95, 0x6e, 0x9f, 0x24, 0xb0, 0x32, 0xfe, 0x1f, 0xd4, 0xc0, 0x97, 0x70, 0x12, 0x23, 0x4e, 0xc6, - 0x10, 0xdf, 0xc7, 0xf4, 0x58, 0x47, 0x28, 0xa7, 0xf6, 0xe4, 0x89, 0x61, 0x25, 0x83, 0x07, 0x33, - 0x25, 0x45, 0x17, 0xe3, 0xa2, 0x1b, 0xd2, 0x1d, 0x9b, 0x75, 0xf4, 0x6e, 0x8a, 0xb0, 0x8b, 0x6d, - 0x6d, 0xb4, 0x75, 0x09, 0xc6, 0xb8, 0xb2, 0x6c, 0xf5, 0xfc, 0x11, 0x6d, 0xf5, 0x2d, 0x19, 0x39, - 0xdc, 0x4d, 0x4c, 0x09, 0xda, 0xe0, 0x87, 0xfb, 0x63, 0x56, 0xd2, 0x0c, 0x38, 0x2e, 0x23, 0xa7, - 0x4a, 0xd3, 0xb3, 0x06, 0xdc, 0x4f, 0x62, 0x9d, 0x4e, 0x4d, 0x95, 0x19, 0x3c, 0x98, 0x29, 0x29, - 0x9c, 0x94, 0x3d, 0x46, 0x6d, 0xbe, 0x97, 0x04, 0x3c, 0x93, 0x74, 0x52, 0xde, 0x18, 0x67, 0xc1, - 0x2c, 0xb9, 0x59, 0xcc, 0xdb, 0xd7, 0xf3, 0x70, 0xfe, 0x16, 0xd3, 0xfb, 0x35, 0x5a, 0x6e, 0x27, - 0xb0, 0x6b, 0xff, 0x43, 0x57, 0x59, 0x5f, 0xce, 0xc3, 0xdc, 0x2d, 0xcf, 0x1d, 0x0e, 0x9a, 0x07, - 0xa4, 0x0b, 0xe5, 0x07, 0x32, 0x2a, 0xa6, 0x63, 0x61, 0xd3, 0x6f, 0x4d, 0x51, 0xc1, 0xb5, 0xc8, - 0x04, 0xab, 0x67, 0xd4, 0xf0, 0xa2, 0xa5, 0x7a, 0xec, 0x80, 0xa9, 0x44, 0x6d, 0x25, 0x6a, 0xa9, - 0x75, 0x41, 0x44, 0x55, 0x46, 0xde, 0x85, 0x39, 0x9f, 0xbb, 0x5e, 0x60, 0xa4, 0x67, 0x09, 0x8b, - 0xb6, 0x9a, 0x9f, 0x6e, 0x2b, 0x28, 0xb5, 0x90, 0xd7, 0x0f, 0x18, 0x28, 0x30, 0xbe, 0x95, 0x03, - 0x78, 0x63, 0x6b, 0xab, 0xa5, 0x63, 0x0e, 0x1d, 0x28, 0xd2, 0x61, 0x18, 0x12, 0x5c, 0x9b, 0x3e, - 0x77, 0x10, 0x4f, 0x9e, 0xeb, 0x18, 0xdd, 0x90, 0xef, 0xa1, 0x44, 0x27, 0x1f, 0x86, 0x39, 0x3d, - 0x43, 0xea, 0x76, 0x08, 0xf3, 0x1d, 0x7a, 0x16, 0xc5, 0xa0, 0xdc, 0xf8, 0x61, 0x1e, 0x9e, 0xbf, - 0xed, 0x70, 0xe6, 0xb5, 0x39, 0x1b, 0x24, 0x72, 0xc5, 0xe4, 0x17, 0xc7, 0xb6, 0x52, 0xfe, 0xdf, - 0xa7, 0x0b, 0x82, 0xa8, 0x9d, 0x78, 0x9b, 0x8c, 0xd3, 0xc8, 0x36, 0x45, 0xb4, 0xd8, 0xfe, 0xc9, - 0x21, 0x14, 0xfd, 0x01, 0x33, 0x75, 0x88, 0xa5, 0x3d, 0x75, 0x6b, 0x64, 0xbf, 0x80, 0x18, 0x7f, - 0x51, 0x80, 0x53, 0x8e, 0x46, 0xa9, 0x8e, 0x7c, 0x01, 0xca, 0x3e, 0xa7, 0x7c, 0x18, 0x44, 0xe3, - 0xb7, 0x8f, 0x5b, 0xb1, 0x04, 0x8f, 0xfa, 0xa8, 0x7a, 0x46, 0xad, 0xd4, 0xf8, 0x61, 0x0e, 0x16, - 0xb2, 0x05, 0x37, 0x2c, 0x9f, 0x93, 0x9f, 0x1f, 0x6b, 0xf6, 0xa7, 0x8c, 0x3d, 0x09, 0x69, 0xd9, - 0xe8, 0x61, 0xdc, 0x31, 0xa0, 0xc4, 0x9a, 0x9c, 0x43, 0xc9, 0xe2, 0xac, 0x1f, 0xf8, 0x4a, 0x77, - 0x8f, 0xf9, 0xd5, 0x63, 0xb6, 0x49, 0x68, 0x41, 0xa5, 0xcc, 0xf8, 0x6a, 0x7e, 0xd2, 0x2b, 0x8b, - 0xcf, 0x42, 0xec, 0xe4, 0x7e, 0x84, 0xf5, 0xd9, 0xf6, 0x23, 0x24, 0x2b, 0x34, 0xbe, 0x2d, 0xe1, - 0x97, 0xc7, 0xb7, 0x25, 0xdc, 0x9d, 0x7d, 0x5b, 0x42, 0xaa, 0x19, 0x26, 0xee, 0x4e, 0xf8, 0x7a, - 0x01, 0x2e, 0x3f, 0xae, 0xdb, 0x08, 0x5b, 0xa9, 0x7b, 0xe7, 0xac, 0xb6, 0xf2, 0xf1, 0xfd, 0x90, - 0x2c, 0x41, 0x69, 0xb0, 0x47, 0xfd, 0x60, 0x56, 0x09, 0x26, 0xdf, 0x52, 0x4b, 0x10, 0x1f, 0x8d, - 0x1a, 0x35, 0x35, 0x1b, 0xc9, 0x47, 0x54, 0xac, 0xc2, 0xb2, 0xf4, 0x99, 0xef, 0x47, 0xfe, 0x6d, - 0x68, 0x59, 0x36, 0x15, 0x19, 0x83, 0x72, 0xc2, 0xa1, 0xac, 0xd6, 0x8c, 0x3a, 0xf7, 0x34, 0x7d, - 0xa2, 0x34, 0x63, 0x0b, 0x4b, 0xf4, 0x52, 0x3a, 0xfc, 0xa0, 0x75, 0x85, 0x5b, 0x64, 0x4a, 0xd9, - 0x5b, 0x64, 0x62, 0x13, 0xac, 0xda, 0x22, 0xf3, 0x77, 0x15, 0x78, 0x3e, 0xfb, 0x1b, 0x8a, 0x77, - 0xdd, 0x67, 0x9e, 0x6f, 0xb9, 0x8e, 0x9e, 0xb4, 0xa3, 0xad, 0x64, 0x8a, 0x8c, 0x41, 0xf9, 0x4f, - 0x75, 0x12, 0xf6, 0x0f, 0x72, 0xc2, 0x0d, 0x56, 0x81, 0x9a, 0xf7, 0x23, 0x11, 0xfb, 0xa2, 0x72, - 0xa7, 0x27, 0x28, 0xc4, 0xc9, 0x75, 0x21, 0xbf, 0x9f, 0x83, 0x7a, 0x3f, 0xe5, 0x67, 0x9f, 0xe0, - 0x66, 0xce, 0xcb, 0x87, 0xa3, 0x46, 0x7d, 0x73, 0x82, 0x3e, 0x9c, 0x58, 0x13, 0xf2, 0x2b, 0x50, - 0x1b, 0x88, 0x7e, 0xe1, 0x73, 0xe6, 0x98, 0xc1, 0x7e, 0xce, 0xe9, 0x7b, 0x7f, 0x2b, 0xc2, 0x0a, - 0x52, 0xa0, 0x3a, 0xb3, 0x1a, 0x15, 0x60, 0x5c, 0xe3, 0x33, 0xbe, 0x7b, 0xf3, 0x1a, 0x54, 0x7c, - 0xc6, 0xb9, 0xe5, 0x74, 0x7d, 0xb9, 0x7a, 0xab, 0xaa, 0xb1, 0xd2, 0xd6, 0x34, 0x0c, 0x4b, 0xc9, - 0xff, 0x81, 0xaa, 0x8c, 0xfb, 0x2c, 0x7b, 0x5d, 0xbf, 0x5e, 0x95, 0xd9, 0x48, 0x69, 0x57, 0xdb, - 0x01, 0x11, 0xa3, 0x72, 0xf2, 0x2a, 0xcc, 0xab, 0x8c, 0xac, 0xde, 0xc5, 0xad, 0xd6, 0x58, 0x32, - 0x19, 0xd5, 0x8c, 0xd1, 0x31, 0xc1, 0x25, 0xd6, 0x53, 0x2c, 0x0c, 0x8e, 0xa5, 0xd7, 0x53, 0x51, - 0xd8, 0x0c, 0x63, 0x5c, 0xe4, 0x45, 0x28, 0x70, 0xdb, 0x97, 0x6b, 0xa8, 0x4a, 0xe4, 0xf7, 0x6e, - 0x6d, 0xb4, 0x51, 0xd0, 0x8d, 0xff, 0xca, 0xc1, 0x99, 0xd4, 0x66, 0x35, 0x21, 0x32, 0xf4, 0x6c, - 0x6d, 0x46, 0x42, 0x91, 0x6d, 0xdc, 0x40, 0x41, 0x27, 0xef, 0x68, 0xaf, 0x30, 0x3f, 0xe3, 0x81, - 0x95, 0x3b, 0x94, 0xfb, 0xc2, 0x0d, 0x1c, 0x73, 0x08, 0x6f, 0xa4, 0x1a, 0xa7, 0x90, 0x8c, 0xb5, - 0x3d, 0xbe, 0x81, 0x62, 0x0b, 0xce, 0xe2, 0xd3, 0x2c, 0x38, 0x8d, 0xbf, 0x29, 0x40, 0xed, 0x4d, - 0x77, 0xe7, 0xa7, 0x64, 0x03, 0x4d, 0xb6, 0x45, 0xce, 0xff, 0x04, 0x2d, 0xf2, 0x36, 0xbc, 0xc0, - 0xb9, 0x58, 0xf5, 0xbb, 0x4e, 0xc7, 0x5f, 0xde, 0xe5, 0xcc, 0x5b, 0xb3, 0x1c, 0xcb, 0xdf, 0x63, - 0x1d, 0x1d, 0xb9, 0xfb, 0xc0, 0xe1, 0xa8, 0xf1, 0xc2, 0xd6, 0xd6, 0x46, 0x16, 0x0b, 0x4e, 0x92, - 0x95, 0x23, 0x84, 0x9a, 0x3d, 0x77, 0x77, 0x57, 0xed, 0x6e, 0x50, 0x39, 0x1e, 0x35, 0x42, 0x62, - 0x74, 0x4c, 0x70, 0x19, 0xdf, 0xcf, 0x41, 0x75, 0x9d, 0xee, 0xf6, 0x68, 0xdb, 0x72, 0x7a, 0xe4, - 0x25, 0x98, 0xdb, 0xf1, 0xdc, 0x1e, 0xf3, 0x7c, 0xbd, 0xdb, 0x40, 0xae, 0x7a, 0x9a, 0x8a, 0x84, - 0x41, 0x99, 0x58, 0x86, 0x71, 0x77, 0x60, 0x99, 0xe9, 0x05, 0xeb, 0x96, 0x20, 0xa2, 0x2a, 0x23, - 0xf7, 0xd5, 0x38, 0x2a, 0xcc, 0xb8, 0xdb, 0x7f, 0x6b, 0xa3, 0xad, 0xb2, 0xb7, 0xc1, 0x08, 0x24, - 0x2f, 0x27, 0x3c, 0x8f, 0xea, 0x24, 0x5f, 0xc1, 0xf8, 0x71, 0x1e, 0x6a, 0xea, 0xd5, 0xd4, 0xe2, - 0xec, 0x38, 0x5f, 0xee, 0x35, 0x19, 0x5d, 0xf7, 0x87, 0x7d, 0xe6, 0xc9, 0x45, 0xb0, 0x1e, 0x72, - 0xf1, 0x68, 0x49, 0x54, 0x18, 0x46, 0xd8, 0x23, 0x52, 0xd0, 0x3a, 0xc5, 0x13, 0x6c, 0x9d, 0xd2, - 0xe3, 0x5a, 0x47, 0x9e, 0xf4, 0xa0, 0xbe, 0xad, 0xe7, 0xaf, 0x19, 0x4e, 0x7a, 0x2c, 0xb7, 0x37, - 0xf4, 0x49, 0x8f, 0xe5, 0xf6, 0x06, 0x4a, 0x50, 0xe3, 0x8f, 0x73, 0x50, 0xdd, 0xb0, 0x76, 0x99, - 0x79, 0x60, 0xda, 0x8c, 0xbc, 0x05, 0xf5, 0x0e, 0xb3, 0x19, 0x67, 0xb7, 0x3c, 0x6a, 0xb2, 0x16, - 0xf3, 0x2c, 0x79, 0x70, 0x4c, 0x74, 0x61, 0x69, 0x24, 0x4a, 0x6a, 0x2e, 0x5e, 0x9d, 0xc0, 0x83, - 0x13, 0xa5, 0xc9, 0x6d, 0x98, 0xef, 0x30, 0xdf, 0xf2, 0x58, 0xa7, 0x15, 0x73, 0x75, 0x5f, 0x0a, - 0x0c, 0xdf, 0x6a, 0xac, 0xec, 0xd1, 0xa8, 0x71, 0xaa, 0x65, 0x0d, 0x98, 0x6d, 0x39, 0x4c, 0xf9, - 0xbc, 0x09, 0x51, 0xa3, 0x04, 0x85, 0x0d, 0xb7, 0x6b, 0x7c, 0xb5, 0x00, 0xe1, 0x51, 0x40, 0xf2, - 0xb5, 0x1c, 0xd4, 0xa8, 0xe3, 0xb8, 0x5c, 0x1f, 0xb3, 0x53, 0x89, 0x03, 0x9c, 0xf9, 0xc4, 0xe1, - 0xe2, 0x72, 0x04, 0xaa, 0x62, 0xce, 0x61, 0x1c, 0x3c, 0x56, 0x82, 0x71, 0xdd, 0x64, 0x98, 0x0a, - 0x83, 0x6f, 0xce, 0x5e, 0x8b, 0xa7, 0x08, 0x7a, 0x2f, 0x7c, 0x0a, 0xce, 0xa6, 0x2b, 0x7b, 0x94, - 0xa8, 0xd9, 0x2c, 0x01, 0xb7, 0x2f, 0x57, 0xa1, 0x76, 0x87, 0x72, 0x6b, 0x9f, 0xc9, 0xf5, 0xdd, - 0xc9, 0x38, 0xec, 0xbf, 0x9b, 0x83, 0xe7, 0x93, 0x01, 0xe9, 0x13, 0xf4, 0xda, 0x17, 0x0e, 0x47, - 0x8d, 0xe7, 0x31, 0x53, 0x1b, 0x4e, 0xa8, 0x85, 0xf4, 0xdf, 0xc7, 0xe2, 0xdb, 0x27, 0xed, 0xbf, - 0xb7, 0x27, 0x29, 0xc4, 0xc9, 0x75, 0xf9, 0x69, 0xf1, 0xdf, 0x9f, 0xed, 0xa3, 0x59, 0xa9, 0xd5, - 0xc5, 0xdc, 0x33, 0xb3, 0xba, 0xa8, 0x3c, 0x13, 0xde, 0xdc, 0x20, 0xb6, 0xba, 0xa8, 0xce, 0x18, - 0x64, 0xd5, 0x39, 0x5c, 0x85, 0x36, 0x69, 0x95, 0x22, 0x77, 0x57, 0x06, 0x8e, 0x37, 0x31, 0xa1, - 0xb4, 0x43, 0x7d, 0xcb, 0xd4, 0xbe, 0x6d, 0x73, 0xfa, 0x98, 0x47, 0x70, 0x86, 0x49, 0x05, 0xb0, - 0xe4, 0x23, 0x2a, 0xec, 0xe8, 0xac, 0x54, 0x7e, 0xa6, 0xb3, 0x52, 0x64, 0x05, 0x8a, 0x8e, 0x30, - 0xb6, 0x85, 0x23, 0x9f, 0x8e, 0xba, 0xb3, 0xce, 0x0e, 0x50, 0x0a, 0x1b, 0xdf, 0xc9, 0x03, 0x88, - 0xd7, 0xd7, 0x3e, 0xd4, 0x13, 0x56, 0x3a, 0x1f, 0x86, 0x39, 0x7f, 0x28, 0x43, 0xc1, 0x7a, 0x2a, - 0x8e, 0x22, 0xd3, 0x8a, 0x8c, 0x41, 0xb9, 0x70, 0xb3, 0x3e, 0x3f, 0x64, 0xc3, 0x20, 0xd0, 0x14, - 0xba, 0x59, 0x9f, 0x16, 0x44, 0x54, 0x65, 0x27, 0xe7, 0x25, 0x05, 0x4b, 0xb2, 0xd2, 0x09, 0x2d, - 0xc9, 0x8c, 0x2f, 0xe6, 0x01, 0xa2, 0xec, 0x01, 0xf9, 0x56, 0x0e, 0x2e, 0x86, 0xa3, 0x8c, 0xab, - 0x13, 0x38, 0x2b, 0x36, 0xb5, 0xfa, 0x33, 0xaf, 0x92, 0xb2, 0x46, 0xb8, 0x34, 0x3b, 0xad, 0x2c, - 0x75, 0x98, 0x5d, 0x0b, 0x82, 0x50, 0x61, 0xfd, 0x01, 0x3f, 0x58, 0xb5, 0x3c, 0xdd, 0xed, 0x32, - 0x8f, 0xb0, 0xdc, 0xd4, 0x3c, 0x4a, 0x54, 0x9f, 0xb6, 0x90, 0x23, 0x27, 0x28, 0xc1, 0x10, 0xc7, - 0xf8, 0x66, 0x1e, 0xce, 0x67, 0xd4, 0x8e, 0xbc, 0x0e, 0x67, 0x75, 0xfa, 0x24, 0x3a, 0x86, 0x9e, - 0x8b, 0x8e, 0xa1, 0xb7, 0x53, 0x65, 0x38, 0xc6, 0x4d, 0xde, 0x01, 0xa0, 0xa6, 0xc9, 0x7c, 0x7f, - 0xd3, 0xed, 0x04, 0x4e, 0xdf, 0x6b, 0x62, 0xc5, 0xba, 0x1c, 0x52, 0x1f, 0x8d, 0x1a, 0x1f, 0xcd, - 0xca, 0x83, 0xa5, 0xde, 0x3e, 0x12, 0xc0, 0x18, 0x24, 0xf9, 0x1c, 0x80, 0x3a, 0x17, 0x15, 0xee, - 0xe4, 0x7c, 0x42, 0x98, 0x7e, 0x31, 0x38, 0xb3, 0xb3, 0xf8, 0xe9, 0x21, 0x75, 0xb8, 0xc5, 0x0f, - 0xd4, 0xc6, 0xfd, 0x7b, 0x21, 0x0a, 0xc6, 0x10, 0x8d, 0xbf, 0xca, 0x43, 0x25, 0x70, 0x46, 0xdf, - 0x87, 0x44, 0x4c, 0x37, 0x91, 0x88, 0x99, 0xfe, 0x20, 0x6c, 0x50, 0xe5, 0x89, 0xa9, 0x17, 0x37, - 0x95, 0x7a, 0xb9, 0x35, 0xbb, 0xaa, 0xc7, 0x27, 0x5b, 0xbe, 0x9d, 0x87, 0xd3, 0x01, 0xab, 0x3e, - 0x28, 0xf0, 0x71, 0x38, 0xe5, 0x31, 0xda, 0x69, 0x52, 0x6e, 0xee, 0xc9, 0xcf, 0xa7, 0x8e, 0x09, - 0x9c, 0x3b, 0x1c, 0x35, 0x4e, 0x61, 0xbc, 0x00, 0x93, 0x7c, 0x59, 0x27, 0x0c, 0xf2, 0x33, 0x9e, - 0x30, 0x28, 0x1c, 0xe5, 0x84, 0x01, 0xa1, 0x50, 0x13, 0x35, 0xda, 0xb2, 0xfa, 0xcc, 0x1d, 0x06, - 0x37, 0x6f, 0x1c, 0x75, 0x67, 0xb2, 0x9c, 0xdd, 0x31, 0x82, 0xc1, 0x38, 0xa6, 0xf1, 0xf7, 0x39, - 0x98, 0x8f, 0xda, 0xeb, 0xc4, 0xd3, 0x51, 0xbb, 0xc9, 0x74, 0xd4, 0xf2, 0xcc, 0xdd, 0x61, 0x42, - 0x02, 0xea, 0xb7, 0xca, 0xd1, 0x6b, 0xc9, 0x94, 0xd3, 0x0e, 0x2c, 0x58, 0x99, 0x59, 0x98, 0x98, - 0xb5, 0x09, 0x77, 0xd8, 0xdd, 0x9e, 0xc8, 0x89, 0x8f, 0x41, 0x21, 0x43, 0xa8, 0xec, 0x33, 0x8f, - 0x5b, 0x26, 0x0b, 0xde, 0xef, 0xd6, 0xcc, 0xde, 0x91, 0xda, 0x5d, 0x10, 0xb5, 0xe9, 0x3d, 0xad, - 0x00, 0x43, 0x55, 0x64, 0x07, 0x4a, 0xac, 0xd3, 0x65, 0xc1, 0x01, 0x8d, 0x4f, 0xce, 0x74, 0xe6, - 0x27, 0x6a, 0x4f, 0xf1, 0xe4, 0xa3, 0x82, 0x26, 0x3e, 0x54, 0xed, 0x60, 0xf9, 0xae, 0xfb, 0xe1, - 0xf4, 0xbe, 0x4e, 0x18, 0x08, 0x88, 0x76, 0xb8, 0x86, 0x24, 0x8c, 0xf4, 0x90, 0x5e, 0x78, 0x9a, - 0xa9, 0x74, 0x4c, 0xc6, 0xe3, 0x31, 0x27, 0x9a, 0x7c, 0xa8, 0x3e, 0xa0, 0x9c, 0x79, 0x7d, 0xea, - 0xf5, 0xb4, 0xe3, 0x3f, 0xfd, 0x1b, 0xde, 0x0f, 0x90, 0xa2, 0x37, 0x0c, 0x49, 0x18, 0xe9, 0x21, - 0x2e, 0x54, 0xb9, 0xf6, 0x64, 0x83, 0xe3, 0xb5, 0xd3, 0x2b, 0x0d, 0x7c, 0x62, 0x5f, 0x45, 0xcd, - 0xc3, 0x47, 0x8c, 0x74, 0x18, 0x8f, 0x0a, 0x91, 0x79, 0x7c, 0xbf, 0xf3, 0x8f, 0xaf, 0x26, 0xf3, - 0x8f, 0x57, 0xd2, 0xf9, 0xc7, 0x54, 0x34, 0xe6, 0xe8, 0x19, 0x48, 0x0a, 0x35, 0x9b, 0xfa, 0x7c, - 0x7b, 0xd0, 0xa1, 0x5c, 0x07, 0xaf, 0x6b, 0x4b, 0xff, 0xfb, 0xe9, 0xac, 0x97, 0xb0, 0x87, 0x51, - 0xd0, 0x65, 0x23, 0x82, 0xc1, 0x38, 0x26, 0x79, 0x05, 0x6a, 0xfb, 0x72, 0x44, 0xaa, 0xa3, 0x1a, - 0x25, 0x69, 0xce, 0xa5, 0x85, 0xbd, 0x17, 0x91, 0x31, 0xce, 0x23, 0x44, 0x94, 0x27, 0xa0, 0x44, - 0xca, 0x91, 0x48, 0x3b, 0x22, 0x63, 0x9c, 0x47, 0x26, 0x42, 0x2c, 0xa7, 0xa7, 0x04, 0xe6, 0xa4, - 0x80, 0x4a, 0x84, 0x04, 0x44, 0x8c, 0xca, 0xc9, 0x35, 0xa8, 0x0c, 0x3b, 0xbb, 0x8a, 0xb7, 0x22, - 0x79, 0xa5, 0xff, 0xb5, 0xbd, 0xba, 0xa6, 0x8f, 0x8e, 0x04, 0xa5, 0xc6, 0x7f, 0xe4, 0x80, 0x8c, - 0x67, 0xcc, 0xc9, 0x1e, 0x94, 0x1d, 0x19, 0x55, 0x99, 0xf9, 0x7a, 0x80, 0x58, 0x70, 0x46, 0x8d, - 0x31, 0x4d, 0xd0, 0xf8, 0xc4, 0x81, 0x0a, 0x7b, 0xc8, 0x99, 0xe7, 0x50, 0x5b, 0xbb, 0x1e, 0xc7, - 0x73, 0x15, 0x81, 0x72, 0x38, 0x35, 0x32, 0x86, 0x3a, 0x8c, 0x1f, 0xe5, 0xa1, 0x16, 0xe3, 0x7b, - 0xd2, 0x62, 0x45, 0x6e, 0x48, 0x55, 0xc1, 0x8c, 0x6d, 0xcf, 0xd6, 0xdd, 0x34, 0xb6, 0x21, 0x55, - 0x17, 0xe1, 0x06, 0xc6, 0xf9, 0xc8, 0x12, 0x40, 0x9f, 0xfa, 0x9c, 0x79, 0x77, 0xa2, 0xa3, 0x60, - 0xa1, 0xfb, 0xb5, 0x19, 0x96, 0x60, 0x8c, 0x8b, 0x5c, 0xd5, 0x17, 0x55, 0x14, 0x93, 0xa7, 0xf2, - 0x26, 0xdc, 0x42, 0x51, 0x3a, 0x86, 0x5b, 0x28, 0x48, 0x17, 0xce, 0x06, 0xb5, 0x0e, 0x4a, 0x8f, - 0x76, 0xd0, 0x4b, 0x39, 0xe3, 0x29, 0x08, 0x1c, 0x03, 0x35, 0xbe, 0x93, 0x83, 0x53, 0x89, 0xa5, - 0xb4, 0x3a, 0x4f, 0x17, 0xec, 0xf7, 0x48, 0x9c, 0xa7, 0x8b, 0x6d, 0xd3, 0x78, 0x19, 0xca, 0xaa, - 0x81, 0x74, 0xc3, 0x87, 0x66, 0x44, 0x35, 0x21, 0xea, 0x52, 0x61, 0x10, 0x74, 0xb0, 0x2e, 0x6d, - 0x10, 0x74, 0x34, 0x0f, 0x83, 0x72, 0xf2, 0x11, 0xa8, 0x04, 0xb5, 0xd3, 0x2d, 0x1d, 0xdd, 0x51, - 0xa2, 0xe9, 0x18, 0x72, 0x18, 0x5f, 0xcb, 0x83, 0x0c, 0x59, 0x93, 0x8f, 0x43, 0xb5, 0xcf, 0xcc, - 0x3d, 0xea, 0x58, 0x7e, 0x70, 0x08, 0x58, 0x2c, 0xa0, 0xaa, 0x9b, 0x01, 0xf1, 0x91, 0x00, 0x58, - 0x6e, 0x6f, 0xc8, 0x7d, 0x05, 0x11, 0x2f, 0x31, 0xa1, 0xdc, 0xf5, 0x7d, 0x3a, 0xb0, 0x66, 0xbe, - 0x91, 0x49, 0x9d, 0x5f, 0x54, 0x83, 0x48, 0xfd, 0x46, 0x0d, 0x4d, 0x4c, 0x28, 0x0d, 0x6c, 0x6a, - 0x39, 0xda, 0xa3, 0x6e, 0xce, 0x14, 0xa8, 0x6f, 0x09, 0x24, 0x15, 0x2a, 0x90, 0x3f, 0x51, 0x61, - 0x1b, 0x7f, 0x99, 0x83, 0x6a, 0x58, 0x1e, 0xf6, 0xd6, 0xdc, 0xc4, 0xde, 0x3a, 0x7e, 0x48, 0x31, - 0x7f, 0xdc, 0x87, 0x14, 0xaf, 0x43, 0x75, 0x8f, 0x3a, 0x1d, 0x7f, 0x8f, 0xf6, 0xd4, 0x28, 0xab, - 0x44, 0x93, 0xeb, 0x1b, 0x41, 0x01, 0x46, 0x3c, 0xc6, 0x97, 0x4a, 0xa0, 0x2e, 0xcc, 0x11, 0xfd, - 0xa0, 0x63, 0xf9, 0x2a, 0xa5, 0x99, 0x93, 0x92, 0x61, 0x3f, 0x58, 0xd5, 0x74, 0x0c, 0x39, 0xc8, - 0x25, 0x28, 0xf4, 0x2d, 0x47, 0x87, 0x89, 0x65, 0x94, 0x60, 0xd3, 0x72, 0x50, 0xd0, 0x64, 0x11, - 0x7d, 0xa8, 0xb3, 0x72, 0xaa, 0x88, 0x3e, 0x44, 0x41, 0x13, 0x8b, 0x05, 0xdb, 0x75, 0x7b, 0x3b, - 0xd4, 0xec, 0x05, 0xa9, 0x8c, 0xa2, 0xb4, 0xc6, 0x72, 0xb1, 0xb0, 0x91, 0x2c, 0xc2, 0x34, 0xaf, - 0x10, 0x37, 0x5d, 0xd7, 0xee, 0xb8, 0x0f, 0x9c, 0x40, 0xbc, 0x14, 0x89, 0xaf, 0x24, 0x8b, 0x30, - 0xcd, 0x4b, 0xb6, 0xe1, 0x85, 0xf7, 0x98, 0xe7, 0xea, 0x11, 0xd0, 0xb6, 0x19, 0x1b, 0x04, 0x30, - 0x6a, 0xc2, 0x91, 0x29, 0xc4, 0xcf, 0x66, 0xb3, 0xe0, 0x24, 0x59, 0x99, 0x99, 0xa4, 0x5e, 0x97, - 0xf1, 0x96, 0xe7, 0x8a, 0xb5, 0xb0, 0xe5, 0x74, 0x03, 0xd8, 0xb9, 0x08, 0x76, 0x2b, 0x9b, 0x05, - 0x27, 0xc9, 0x92, 0x4d, 0x38, 0xa7, 0x8a, 0x62, 0x6b, 0x20, 0x3d, 0x77, 0x35, 0x0e, 0x47, 0x8d, - 0x0f, 0xac, 0xb2, 0x81, 0xc7, 0x4c, 0x31, 0xe1, 0x6e, 0xa5, 0xd9, 0x70, 0x5c, 0x52, 0x5e, 0x63, - 0xa7, 0xc3, 0xf7, 0x2d, 0xe6, 0xc9, 0x4f, 0x2e, 0x63, 0x81, 0x7a, 0xa1, 0x85, 0xa9, 0x32, 0x1c, - 0xe3, 0x26, 0x6f, 0x41, 0x3d, 0x0e, 0xbb, 0xbc, 0x4f, 0x2d, 0x9b, 0xee, 0x58, 0xb6, 0xc5, 0x0f, - 0xe4, 0xc6, 0x82, 0x53, 0x2a, 0xb8, 0xbc, 0x35, 0x81, 0x07, 0x27, 0x4a, 0x1b, 0xbf, 0x5d, 0x00, - 0x79, 0xe3, 0x19, 0xb9, 0x0f, 0x05, 0xdb, 0xed, 0xea, 0x29, 0x76, 0xfa, 0xc8, 0xd5, 0x86, 0xdb, - 0x55, 0x1d, 0x6f, 0xc3, 0xed, 0xa2, 0x40, 0x14, 0xf6, 0xa0, 0x47, 0x77, 0x7b, 0x54, 0x8f, 0xb8, - 0xe9, 0xed, 0x41, 0x98, 0xf5, 0xd5, 0x47, 0xca, 0xc5, 0x23, 0x2a, 0x6c, 0xe1, 0xa8, 0xee, 0x04, - 0x57, 0x18, 0xcd, 0x6c, 0x78, 0xc2, 0xcb, 0x90, 0x94, 0x57, 0x13, 0x3e, 0x62, 0xa4, 0x43, 0x98, - 0xd2, 0x61, 0x47, 0xde, 0x3c, 0x57, 0x9c, 0xd1, 0x94, 0x6e, 0xaf, 0xca, 0x77, 0x92, 0xa6, 0x54, - 0xfd, 0x46, 0x0d, 0x6d, 0xfc, 0x49, 0x0e, 0x4e, 0xb5, 0x6d, 0xab, 0x63, 0x39, 0xdd, 0x93, 0x3b, - 0xc4, 0x4f, 0xee, 0x42, 0xc9, 0xb7, 0xad, 0x0e, 0x9b, 0xf2, 0x64, 0xb1, 0xfc, 0x18, 0xa2, 0x96, - 0x0c, 0x15, 0x8e, 0xf1, 0x87, 0x45, 0xd0, 0xd7, 0xf4, 0x91, 0x21, 0x54, 0xbb, 0xc1, 0x31, 0x67, - 0x5d, 0xe5, 0x37, 0x66, 0x38, 0x0e, 0x93, 0x38, 0x30, 0xad, 0xbe, 0x4e, 0x48, 0xc4, 0x48, 0x13, - 0x61, 0xc9, 0x3e, 0xb7, 0x3a, 0x63, 0x9f, 0x53, 0xea, 0xc6, 0x7b, 0x1d, 0x85, 0xe2, 0x1e, 0xe7, - 0x83, 0x99, 0x77, 0x6d, 0x47, 0x1b, 0xb2, 0x55, 0x58, 0x56, 0x3c, 0xa3, 0x84, 0x16, 0x2a, 0x1c, - 0x7a, 0x0c, 0xf7, 0x65, 0x44, 0x21, 0x71, 0x1d, 0x31, 0xa7, 0xdc, 0x47, 0x09, 0x4d, 0x7e, 0x09, - 0x6a, 0xdc, 0xa3, 0x8e, 0xbf, 0xeb, 0x7a, 0x7d, 0xe6, 0x69, 0x67, 0x6e, 0x6d, 0x86, 0xfe, 0xbc, - 0x15, 0xa1, 0xa9, 0x28, 0x56, 0x82, 0x84, 0x71, 0x6d, 0x46, 0x1f, 0xf4, 0x32, 0x8c, 0x98, 0x89, - 0x7b, 0x49, 0x54, 0xe6, 0xfa, 0xfa, 0xd3, 0xf5, 0xc5, 0xf0, 0xf2, 0x89, 0xd8, 0x41, 0xcc, 0xcc, - 0x0b, 0x48, 0x8c, 0x7f, 0xcc, 0x43, 0x61, 0x6b, 0xa3, 0xad, 0xce, 0x15, 0xc9, 0x4b, 0x7f, 0x58, - 0xbb, 0x67, 0x0d, 0xee, 0x31, 0xcf, 0xda, 0x3d, 0xd0, 0x73, 0x6f, 0xec, 0x5c, 0x51, 0x9a, 0x03, - 0x33, 0xa4, 0xc8, 0xdb, 0x30, 0x6f, 0xd2, 0x15, 0xe6, 0xf1, 0x69, 0x3c, 0x0b, 0xb9, 0xdd, 0x65, - 0x65, 0x39, 0x12, 0xc7, 0x04, 0x18, 0xd9, 0x06, 0x30, 0x23, 0xe8, 0x23, 0x65, 0x46, 0xd4, 0x45, - 0x2c, 0x11, 0x70, 0x0c, 0x88, 0x20, 0x54, 0x7b, 0x82, 0x55, 0xa2, 0x16, 0x8f, 0x82, 0x2a, 0x07, - 0xdd, 0x7a, 0x20, 0x8b, 0x11, 0x8c, 0xf1, 0x6f, 0x39, 0x88, 0x16, 0xf5, 0xc4, 0x87, 0x72, 0x47, - 0x1e, 0xf1, 0xd7, 0xc3, 0x7e, 0xfa, 0xe0, 0x48, 0xf2, 0xfa, 0x23, 0xe5, 0x8b, 0x25, 0x69, 0xa8, - 0x55, 0x91, 0x2e, 0x14, 0xde, 0x75, 0x77, 0x66, 0x1e, 0xf5, 0xb1, 0xcd, 0x62, 0x6a, 0x25, 0x1c, - 0x23, 0xa0, 0xd0, 0x60, 0x7c, 0x29, 0x0f, 0xb5, 0x58, 0x9f, 0x9e, 0xf9, 0x16, 0x93, 0x87, 0xa9, - 0x5b, 0x4c, 0x5a, 0xd3, 0x47, 0x59, 0xa2, 0x5a, 0x9d, 0xf4, 0x45, 0x26, 0x7f, 0x9d, 0x87, 0xc2, - 0xf6, 0xea, 0x9a, 0x98, 0x7c, 0xc3, 0x4d, 0x63, 0x33, 0x27, 0x1a, 0xa3, 0xfb, 0x1a, 0x65, 0x4f, - 0x0b, 0x1f, 0x31, 0xd2, 0x41, 0xf6, 0x60, 0x6e, 0x67, 0x68, 0xd9, 0xdc, 0x72, 0x66, 0xde, 0xa2, - 0x18, 0x5c, 0xfa, 0xa2, 0xf7, 0x56, 0x29, 0x54, 0x0c, 0xe0, 0x49, 0x17, 0xe6, 0xba, 0xea, 0xcc, - 0x90, 0x1e, 0x7b, 0xaf, 0x4f, 0x3f, 0x7b, 0x29, 0x1c, 0xa5, 0x48, 0x3f, 0x60, 0x80, 0x6e, 0x7c, - 0x01, 0xf4, 0xe4, 0x4f, 0xfc, 0x93, 0x69, 0xcd, 0x70, 0x2d, 0x92, 0xd5, 0xa2, 0xc6, 0xbf, 0xe7, - 0x20, 0x69, 0xa5, 0xdf, 0xff, 0x8f, 0xda, 0x4b, 0x7f, 0xd4, 0xd5, 0xe3, 0x18, 0x03, 0xd9, 0xdf, - 0xd5, 0xf8, 0x8b, 0x3c, 0x94, 0xf5, 0x45, 0xc6, 0x27, 0x9f, 0xcd, 0x62, 0x89, 0x6c, 0xd6, 0xca, - 0x8c, 0xd7, 0xba, 0x4e, 0xcc, 0x65, 0xf5, 0x53, 0xb9, 0xac, 0x59, 0xef, 0x8f, 0x7d, 0x42, 0x26, - 0xeb, 0x6f, 0x73, 0x70, 0x5a, 0x31, 0xde, 0x76, 0x7c, 0x4e, 0x1d, 0x53, 0x3a, 0xc5, 0x2a, 0xb2, - 0x38, 0x73, 0xa8, 0x56, 0xa7, 0x15, 0xa4, 0xbb, 0xaa, 0x7e, 0xa3, 0x86, 0x16, 0x8b, 0xe5, 0x3d, - 0xd7, 0xe7, 0xd2, 0xdc, 0xe6, 0x93, 0x41, 0x93, 0x37, 0x34, 0x1d, 0x43, 0x8e, 0x74, 0x34, 0xa6, - 0x34, 0x39, 0x1a, 0x63, 0xfc, 0x51, 0x0e, 0xe6, 0xe3, 0x37, 0xe7, 0x4e, 0x9f, 0x98, 0x4b, 0xe5, - 0xc5, 0xf2, 0x27, 0x90, 0x17, 0xfb, 0x5e, 0x0e, 0x20, 0xa8, 0xec, 0x89, 0x67, 0xc5, 0x3a, 0xc9, - 0xac, 0xd8, 0xcc, 0x9f, 0x35, 0x3b, 0x27, 0xf6, 0xed, 0x52, 0xf0, 0x4a, 0x32, 0x23, 0xf6, 0x95, - 0x1c, 0x9c, 0xa6, 0x89, 0x2c, 0xd3, 0xcc, 0x9e, 0x44, 0x2a, 0x69, 0x15, 0xde, 0x34, 0x9c, 0xa4, - 0x63, 0x4a, 0x2d, 0xb9, 0x01, 0xf3, 0x03, 0x1d, 0xfa, 0xbf, 0x13, 0xf5, 0xba, 0x70, 0xb7, 0x7a, - 0x2b, 0x56, 0x86, 0x09, 0xce, 0x27, 0x64, 0xf5, 0x0a, 0xc7, 0x92, 0xd5, 0x8b, 0x6f, 0x1d, 0x2c, - 0x3e, 0x76, 0xeb, 0xa0, 0x03, 0xd5, 0x5d, 0xcf, 0xed, 0xcb, 0xc4, 0x99, 0xbe, 0x9b, 0x72, 0xc6, - 0x64, 0x5c, 0x38, 0xa9, 0xac, 0x05, 0xb8, 0x18, 0xa9, 0x10, 0xd3, 0x34, 0x77, 0x95, 0xb6, 0xf2, - 0x71, 0x68, 0x0b, 0x87, 0xee, 0x96, 0x42, 0xc5, 0x00, 0x3e, 0x99, 0x1c, 0x9b, 0x7b, 0x7f, 0x92, - 0x63, 0xc6, 0x3f, 0xe4, 0x03, 0x7b, 0xd1, 0x4e, 0x1d, 0x60, 0xcb, 0x4d, 0x38, 0xc0, 0xa6, 0x8f, - 0x62, 0xc7, 0xd3, 0x47, 0x2f, 0x43, 0xd9, 0x63, 0xd4, 0x77, 0x1d, 0x7d, 0xa9, 0x41, 0x68, 0x6d, - 0x51, 0x52, 0x51, 0x97, 0xc6, 0xd3, 0x4c, 0xf9, 0x27, 0xa4, 0x99, 0x3e, 0x12, 0xeb, 0x10, 0x2a, - 0x9f, 0x1f, 0x8e, 0xed, 0x8c, 0x4e, 0x21, 0x63, 0xd0, 0xfa, 0xef, 0x42, 0x4a, 0xe9, 0x18, 0xb4, - 0xfe, 0x2b, 0x8f, 0x90, 0x83, 0x74, 0x60, 0xde, 0xa6, 0x3e, 0x97, 0x51, 0xa9, 0xce, 0x32, 0x9f, - 0x22, 0x87, 0x15, 0x0e, 0x9b, 0x8d, 0x18, 0x0e, 0x26, 0x50, 0x8d, 0xdf, 0xcc, 0x41, 0xd4, 0xe4, - 0x47, 0x8c, 0x8e, 0xbe, 0x05, 0x95, 0x3e, 0x7d, 0xb8, 0xca, 0x6c, 0x7a, 0x30, 0xcb, 0x55, 0x69, - 0x9b, 0x1a, 0x03, 0x43, 0x34, 0x63, 0x94, 0x03, 0x7d, 0xbc, 0x9b, 0x30, 0x28, 0xed, 0x5a, 0x0f, - 0x75, 0x7d, 0x66, 0xf1, 0x54, 0x62, 0x17, 0x34, 0xaa, 0xf8, 0x82, 0x24, 0xa0, 0x42, 0x27, 0x7d, - 0x98, 0xf3, 0x55, 0xf8, 0x47, 0xbf, 0xca, 0xf4, 0xab, 0xf2, 0x44, 0x18, 0x49, 0x9f, 0x0d, 0x57, - 0x24, 0x0c, 0x74, 0x34, 0x17, 0xbf, 0xfb, 0x83, 0x2b, 0xcf, 0x7d, 0xef, 0x07, 0x57, 0x9e, 0xfb, - 0xfe, 0x0f, 0xae, 0x3c, 0xf7, 0xc5, 0xc3, 0x2b, 0xb9, 0xef, 0x1e, 0x5e, 0xc9, 0x7d, 0xef, 0xf0, - 0x4a, 0xee, 0xfb, 0x87, 0x57, 0x72, 0xff, 0x72, 0x78, 0x25, 0xf7, 0x1b, 0xff, 0x7a, 0xe5, 0xb9, - 0xcf, 0x56, 0x02, 0xcc, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x55, 0x26, 0x4c, 0xb4, 0x9e, 0x68, - 0x00, 0x00, + // 6107 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x7d, 0x5d, 0x6c, 0x1c, 0xc9, + 0x75, 0xee, 0xce, 0x2f, 0x67, 0xce, 0x50, 0x7f, 0x25, 0x69, 0x77, 0x44, 0x6b, 0x35, 0x72, 0x1b, + 0xbb, 0x57, 0xbe, 0xd7, 0xa6, 0xee, 0xf2, 0xae, 0xaf, 0xe5, 0x24, 0xf6, 0x2e, 0x87, 0x14, 0xb5, + 0x5a, 0x92, 0xd2, 0xf8, 0x0c, 0x29, 0xad, 0xbd, 0x89, 0x37, 0xc5, 0x9e, 0xe2, 0xb0, 0x77, 0x7a, + 0xba, 0xc7, 0xdd, 0x35, 0x94, 0xb8, 0x89, 0x11, 0xff, 0x3c, 0xd8, 0x46, 0x1c, 0x38, 0x40, 0x10, + 0xc0, 0x48, 0xe0, 0x00, 0x01, 0x02, 0x04, 0x08, 0x60, 0x20, 0x0f, 0xf1, 0x4b, 0xf2, 0x90, 0xe4, + 0x25, 0x70, 0xf2, 0x90, 0xf8, 0x21, 0x40, 0x1c, 0x24, 0x18, 0xc4, 0xcc, 0x53, 0x1e, 0x12, 0x18, + 0xf0, 0x8b, 0x21, 0x04, 0x48, 0x50, 0x3f, 0xfd, 0x3b, 0x3d, 0x92, 0x38, 0x43, 0xae, 0x65, 0xe4, + 0x6d, 0xfa, 0x9c, 0x53, 0xdf, 0xa9, 0xae, 0xae, 0x3a, 0x75, 0xea, 0x9c, 0xaa, 0x1a, 0xb8, 0xd5, + 0xb5, 0xf8, 0xde, 0x70, 0x67, 0xd1, 0x74, 0xfb, 0xd7, 0x9d, 0x61, 0x9f, 0x0e, 0x3c, 0xf7, 0x5d, + 0xf9, 0x63, 0xd7, 0x76, 0x1f, 0x5c, 0x1f, 0xf4, 0xba, 0xd7, 0xe9, 0xc0, 0xf2, 0x23, 0xca, 0xfe, + 0x2b, 0xd4, 0x1e, 0xec, 0xd1, 0x57, 0xae, 0x77, 0x99, 0xc3, 0x3c, 0xca, 0x59, 0x67, 0x71, 0xe0, + 0xb9, 0xdc, 0x25, 0x1f, 0x8f, 0x80, 0x16, 0x03, 0xa0, 0xc5, 0xa0, 0xd8, 0xe2, 0xa0, 0xd7, 0x5d, + 0x14, 0x40, 0x11, 0x25, 0x00, 0x5a, 0xf8, 0x68, 0xac, 0x06, 0x5d, 0xb7, 0xeb, 0x5e, 0x97, 0x78, + 0x3b, 0xc3, 0x5d, 0xf9, 0x24, 0x1f, 0xe4, 0x2f, 0xa5, 0x67, 0xc1, 0xe8, 0xdd, 0xf0, 0x17, 0x2d, + 0x57, 0x54, 0xeb, 0xba, 0xe9, 0x7a, 0xec, 0xfa, 0xfe, 0x58, 0x5d, 0x16, 0x5e, 0x8d, 0x64, 0xfa, + 0xd4, 0xdc, 0xb3, 0x1c, 0xe6, 0x1d, 0x04, 0xef, 0x72, 0xdd, 0x63, 0xbe, 0x3b, 0xf4, 0x4c, 0x76, + 0xa4, 0x52, 0xfe, 0xf5, 0x3e, 0xe3, 0x34, 0x4b, 0xd7, 0xf5, 0x49, 0xa5, 0xbc, 0xa1, 0xc3, 0xad, + 0xfe, 0xb8, 0x9a, 0xff, 0xff, 0xa4, 0x02, 0xbe, 0xb9, 0xc7, 0xfa, 0x34, 0x5d, 0xce, 0xf8, 0xa7, + 0x2a, 0x9c, 0x5f, 0xde, 0xf1, 0xb9, 0x47, 0x4d, 0xde, 0x72, 0x3b, 0x5b, 0xac, 0x3f, 0xb0, 0x29, + 0x67, 0xa4, 0x07, 0x15, 0x51, 0xb7, 0x0e, 0xe5, 0xb4, 0x9e, 0xbb, 0x9a, 0xbb, 0x56, 0x5b, 0x5a, + 0x5e, 0x9c, 0xf2, 0x5b, 0x2c, 0x6e, 0x6a, 0xa0, 0xe6, 0xfc, 0xe1, 0xa8, 0x51, 0x09, 0x9e, 0x30, + 0x54, 0x40, 0xbe, 0x95, 0x83, 0x79, 0xc7, 0xed, 0xb0, 0x36, 0xb3, 0x99, 0xc9, 0x5d, 0xaf, 0x9e, + 0xbf, 0x5a, 0xb8, 0x56, 0x5b, 0xfa, 0xdc, 0xd4, 0x1a, 0x33, 0xde, 0x68, 0xf1, 0x4e, 0x4c, 0xc1, + 0x4d, 0x87, 0x7b, 0x07, 0xcd, 0x0b, 0xdf, 0x1b, 0x35, 0x9e, 0x3b, 0x1c, 0x35, 0xe6, 0xe3, 0x2c, + 0x4c, 0xd4, 0x84, 0x6c, 0x43, 0x8d, 0xbb, 0xb6, 0x68, 0x32, 0xcb, 0x75, 0xfc, 0x7a, 0x41, 0x56, + 0xec, 0xca, 0xa2, 0x6a, 0x6d, 0xa1, 0x7e, 0x51, 0x74, 0x97, 0xc5, 0xfd, 0x57, 0x16, 0xb7, 0x42, + 0xb1, 0xe6, 0x79, 0x0d, 0x5c, 0x8b, 0x68, 0x3e, 0xc6, 0x71, 0x08, 0x83, 0x33, 0x3e, 0x33, 0x87, + 0x9e, 0xc5, 0x0f, 0x56, 0x5c, 0x87, 0xb3, 0x87, 0xbc, 0x5e, 0x94, 0xad, 0xfc, 0x72, 0x16, 0x74, + 0xcb, 0xed, 0xb4, 0x93, 0xd2, 0xcd, 0xf3, 0x87, 0xa3, 0xc6, 0x99, 0x14, 0x11, 0xd3, 0x98, 0xc4, + 0x81, 0xb3, 0x56, 0x9f, 0x76, 0x59, 0x6b, 0x68, 0xdb, 0x6d, 0x66, 0x7a, 0x8c, 0xfb, 0xf5, 0x92, + 0x7c, 0x85, 0x6b, 0x59, 0x7a, 0x36, 0x5c, 0x93, 0xda, 0x77, 0x77, 0xde, 0x65, 0x26, 0x47, 0xb6, + 0xcb, 0x3c, 0xe6, 0x98, 0xac, 0x59, 0xd7, 0x2f, 0x73, 0xf6, 0x76, 0x0a, 0x09, 0xc7, 0xb0, 0xc9, + 0x2d, 0x38, 0x37, 0xf0, 0x2c, 0x57, 0x56, 0xc1, 0xa6, 0xbe, 0x7f, 0x87, 0xf6, 0x59, 0xbd, 0x7c, + 0x35, 0x77, 0xad, 0xda, 0xbc, 0xa4, 0x61, 0xce, 0xb5, 0xd2, 0x02, 0x38, 0x5e, 0x86, 0x5c, 0x83, + 0x4a, 0x40, 0xac, 0xcf, 0x5d, 0xcd, 0x5d, 0x2b, 0xa9, 0xbe, 0x13, 0x94, 0xc5, 0x90, 0x4b, 0xd6, + 0xa0, 0x42, 0x77, 0x77, 0x2d, 0x47, 0x48, 0x56, 0x64, 0x13, 0x5e, 0xce, 0x7a, 0xb5, 0x65, 0x2d, + 0xa3, 0x70, 0x82, 0x27, 0x0c, 0xcb, 0x92, 0x37, 0x81, 0xf8, 0xcc, 0xdb, 0xb7, 0x4c, 0xb6, 0x6c, + 0x9a, 0xee, 0xd0, 0xe1, 0xb2, 0xee, 0x55, 0x59, 0xf7, 0x05, 0x5d, 0x77, 0xd2, 0x1e, 0x93, 0xc0, + 0x8c, 0x52, 0xe4, 0x75, 0x38, 0xab, 0x87, 0x5d, 0xd4, 0x0a, 0x20, 0x91, 0x2e, 0x88, 0x86, 0xc4, + 0x14, 0x0f, 0xc7, 0xa4, 0x49, 0x07, 0x2e, 0xd3, 0x21, 0x77, 0xfb, 0x02, 0x32, 0xa9, 0x74, 0xcb, + 0xed, 0x31, 0xa7, 0x5e, 0xbb, 0x9a, 0xbb, 0x56, 0x69, 0x5e, 0x3d, 0x1c, 0x35, 0x2e, 0x2f, 0x3f, + 0x46, 0x0e, 0x1f, 0x8b, 0x42, 0xee, 0x42, 0xb5, 0xe3, 0xf8, 0x2d, 0xd7, 0xb6, 0xcc, 0x83, 0xfa, + 0xbc, 0xac, 0xe0, 0x2b, 0xfa, 0x55, 0xab, 0xab, 0x77, 0xda, 0x8a, 0xf1, 0x68, 0xd4, 0xb8, 0x3c, + 0x6e, 0x1d, 0x17, 0x43, 0x3e, 0x46, 0x18, 0x64, 0x53, 0x02, 0xae, 0xb8, 0xce, 0xae, 0xd5, 0xad, + 0x9f, 0x92, 0x5f, 0xe3, 0xea, 0x84, 0x0e, 0xbd, 0x7a, 0xa7, 0xad, 0xe4, 0x9a, 0xa7, 0xb4, 0x3a, + 0xf5, 0x88, 0x11, 0xc2, 0xc2, 0x6b, 0x70, 0x6e, 0x6c, 0xd4, 0x92, 0xb3, 0x50, 0xe8, 0xb1, 0x03, + 0x69, 0x94, 0xaa, 0x28, 0x7e, 0x92, 0x0b, 0x50, 0xda, 0xa7, 0xf6, 0x90, 0xd5, 0xf3, 0x92, 0xa6, + 0x1e, 0x7e, 0x2e, 0x7f, 0x23, 0x67, 0x8c, 0xaa, 0x70, 0x3a, 0xb0, 0x05, 0xf7, 0x98, 0xc7, 0xd9, + 0x43, 0x72, 0x15, 0x8a, 0x8e, 0xf8, 0x1e, 0xb2, 0x7c, 0x73, 0x5e, 0xbf, 0x6e, 0x51, 0x7e, 0x07, + 0xc9, 0x21, 0x26, 0x94, 0x95, 0x2d, 0x97, 0x78, 0xb5, 0xa5, 0xd7, 0xa6, 0x36, 0x43, 0x6d, 0x09, + 0xd3, 0x84, 0xc3, 0x51, 0xa3, 0xac, 0x7e, 0xa3, 0x86, 0x26, 0x6f, 0x43, 0xd1, 0xb7, 0x9c, 0x5e, + 0xbd, 0x20, 0x55, 0x7c, 0x72, 0x7a, 0x15, 0x96, 0xd3, 0x6b, 0x56, 0xc4, 0x1b, 0x88, 0x5f, 0x28, + 0x41, 0xc9, 0x7d, 0x28, 0x0c, 0x3b, 0xbb, 0xda, 0xa2, 0xfc, 0xc2, 0xd4, 0xd8, 0xdb, 0xab, 0x6b, + 0xcd, 0xb9, 0xc3, 0x51, 0xa3, 0xb0, 0xbd, 0xba, 0x86, 0x02, 0x91, 0x7c, 0x33, 0x07, 0xe7, 0x4c, + 0xd7, 0xe1, 0x54, 0xcc, 0x2f, 0x81, 0x65, 0xad, 0x97, 0xa4, 0x9e, 0x37, 0xa7, 0xd6, 0xb3, 0x92, + 0x46, 0x6c, 0x5e, 0x14, 0x86, 0x62, 0x8c, 0x8c, 0xe3, 0xba, 0xc9, 0xef, 0xe6, 0xe0, 0xa2, 0x18, + 0xc0, 0x63, 0xc2, 0xd2, 0xec, 0x1c, 0x6f, 0xad, 0x2e, 0x1d, 0x8e, 0x1a, 0x17, 0x6f, 0x67, 0x29, + 0xc3, 0xec, 0x3a, 0x88, 0xda, 0x9d, 0xa7, 0xe3, 0x73, 0x91, 0x34, 0x69, 0xb5, 0xa5, 0x8d, 0xe3, + 0x9c, 0xdf, 0x9a, 0x1f, 0xd0, 0x5d, 0x39, 0x6b, 0x3a, 0xc7, 0xac, 0x5a, 0x90, 0x9b, 0x30, 0xb7, + 0xef, 0xda, 0xc3, 0x3e, 0xf3, 0xeb, 0x15, 0x39, 0x29, 0x2c, 0x64, 0x8d, 0xd5, 0x7b, 0x52, 0xa4, + 0x79, 0x46, 0xc3, 0xcf, 0xa9, 0x67, 0x1f, 0x83, 0xb2, 0xc4, 0x82, 0xb2, 0x6d, 0xf5, 0x2d, 0xee, + 0x4b, 0x6b, 0x59, 0x5b, 0xba, 0x39, 0xf5, 0x6b, 0xa9, 0x21, 0xba, 0x21, 0xc1, 0xd4, 0xa8, 0x51, + 0xbf, 0x51, 0x2b, 0x20, 0x26, 0x94, 0x7c, 0x93, 0xda, 0xca, 0x9a, 0xd6, 0x96, 0x3e, 0x35, 0xfd, + 0xb0, 0x11, 0x28, 0xcd, 0x53, 0xfa, 0x9d, 0x4a, 0xf2, 0x11, 0x15, 0x36, 0xf9, 0x25, 0x38, 0x9d, + 0xf8, 0x9a, 0x7e, 0xbd, 0x26, 0x5b, 0xe7, 0xc5, 0xac, 0xd6, 0x09, 0xa5, 0x9a, 0xcf, 0x6b, 0xb0, + 0xd3, 0x89, 0x1e, 0xe2, 0x63, 0x0a, 0x8c, 0xac, 0x43, 0xc5, 0xb7, 0x3a, 0xcc, 0xa4, 0x9e, 0x5f, + 0x9f, 0x7f, 0x1a, 0xe0, 0xb3, 0x1a, 0xb8, 0xd2, 0xd6, 0xc5, 0x30, 0x04, 0x30, 0xee, 0xc3, 0xa9, + 0xe5, 0x21, 0xdf, 0x73, 0x3d, 0xeb, 0x3d, 0xe9, 0x59, 0x90, 0x35, 0x28, 0x71, 0x39, 0x43, 0x28, + 0xa7, 0xed, 0xa5, 0x2c, 0x68, 0x35, 0x5b, 0xaf, 0xb3, 0x83, 0xc0, 0xb0, 0x36, 0xab, 0xa2, 0x11, + 0xd4, 0x8c, 0xa1, 0x8a, 0x1b, 0xbf, 0x9f, 0x83, 0x6a, 0x93, 0xfa, 0x96, 0x29, 0xe0, 0xc9, 0x0a, + 0x14, 0x87, 0x3e, 0xf3, 0x8e, 0x06, 0x2a, 0xad, 0xd2, 0xb6, 0xcf, 0x3c, 0x94, 0x85, 0xc9, 0x5d, + 0xa8, 0x0c, 0xa8, 0xef, 0x3f, 0x70, 0xbd, 0x8e, 0xb6, 0xac, 0x4f, 0x09, 0xa4, 0xa6, 0x7e, 0x5d, + 0x14, 0x43, 0x10, 0xa3, 0x06, 0xd5, 0xa6, 0x4d, 0xcd, 0xde, 0x9e, 0x6b, 0x33, 0xe3, 0xb3, 0x50, + 0x6e, 0x0e, 0x77, 0x77, 0x99, 0xf7, 0x14, 0x16, 0x7e, 0x11, 0x8a, 0xfc, 0x60, 0xa0, 0xe7, 0x8b, + 0x70, 0x76, 0x2f, 0x6e, 0x1d, 0x0c, 0xd8, 0xa3, 0x51, 0x03, 0x14, 0x8e, 0x78, 0x42, 0x29, 0x67, + 0xfc, 0x38, 0x07, 0xe7, 0x15, 0x51, 0xcf, 0xa2, 0x6a, 0x7e, 0x22, 0x0c, 0x4a, 0x1e, 0xeb, 0x58, + 0xbe, 0x6e, 0x97, 0xd5, 0xa9, 0xbb, 0x23, 0x0a, 0x14, 0x3d, 0x1d, 0xca, 0x6f, 0x21, 0x09, 0xa8, + 0xd0, 0xc9, 0x10, 0xaa, 0xef, 0x32, 0xee, 0x73, 0x8f, 0xd1, 0xbe, 0x6e, 0xb9, 0x37, 0xa6, 0x56, + 0xf5, 0x26, 0xe3, 0x6d, 0x89, 0x14, 0x9f, 0x7d, 0x43, 0x22, 0x46, 0x9a, 0x8c, 0x9f, 0x14, 0xa1, + 0x1a, 0xf6, 0x42, 0xf2, 0x21, 0x28, 0x49, 0x77, 0x4f, 0x37, 0x6b, 0x38, 0x74, 0xa4, 0x57, 0x88, + 0x8a, 0x47, 0x5e, 0x82, 0x39, 0xd3, 0xed, 0xf7, 0xa9, 0xd3, 0x91, 0x2e, 0x7c, 0xb5, 0x59, 0x13, + 0x16, 0x63, 0x45, 0x91, 0x30, 0xe0, 0x91, 0xcb, 0x50, 0xa4, 0x5e, 0x57, 0x79, 0xd3, 0x55, 0xd5, + 0x4f, 0x96, 0xbd, 0xae, 0x8f, 0x92, 0x4a, 0x3e, 0x01, 0x05, 0xe6, 0xec, 0xd7, 0x8b, 0x93, 0x4d, + 0xd2, 0x4d, 0x67, 0xff, 0x1e, 0xf5, 0x9a, 0x35, 0x5d, 0x87, 0xc2, 0x4d, 0x67, 0x1f, 0x45, 0x19, + 0xb2, 0x01, 0x73, 0xcc, 0xd9, 0x5f, 0xf3, 0xdc, 0xbe, 0x76, 0x73, 0x3f, 0x38, 0xa1, 0xb8, 0x10, + 0xd1, 0xb3, 0x73, 0x68, 0xd8, 0x34, 0x19, 0x03, 0x08, 0xf2, 0x19, 0x98, 0x57, 0x36, 0x6e, 0x53, + 0xb8, 0x4c, 0x7e, 0xbd, 0x2c, 0x21, 0x1b, 0x93, 0x8d, 0xa4, 0x94, 0x8b, 0x96, 0x15, 0x31, 0xa2, + 0x8f, 0x09, 0x28, 0xf2, 0x19, 0xa8, 0x06, 0x2b, 0x46, 0x5f, 0xcf, 0x06, 0x99, 0x1e, 0x39, 0x6a, + 0x21, 0x64, 0x9f, 0x1f, 0x5a, 0x1e, 0xeb, 0x33, 0x87, 0xfb, 0xcd, 0x73, 0x81, 0x8f, 0x16, 0x70, + 0x7d, 0x8c, 0xd0, 0xc8, 0xce, 0xf8, 0xd2, 0x42, 0xf9, 0xc5, 0x1f, 0x9a, 0x30, 0xda, 0xa6, 0x58, + 0x57, 0x7c, 0x0e, 0xce, 0x84, 0xbe, 0xbf, 0x76, 0x1f, 0x95, 0xa7, 0xfc, 0xaa, 0x28, 0x7e, 0x3b, + 0xc9, 0x7a, 0x34, 0x6a, 0xbc, 0x98, 0xe1, 0x40, 0x46, 0x02, 0x98, 0x06, 0x33, 0xfe, 0xbc, 0x00, + 0xe3, 0xd3, 0x7f, 0xb2, 0xd1, 0x72, 0xc7, 0xdd, 0x68, 0xe9, 0x17, 0x52, 0xc6, 0xe1, 0x86, 0x2e, + 0x36, 0xfb, 0x4b, 0x65, 0x7d, 0x98, 0xc2, 0x71, 0x7f, 0x98, 0x67, 0x65, 0xec, 0x18, 0x5f, 0x2b, + 0xc2, 0xe9, 0x55, 0xca, 0xfa, 0xae, 0xf3, 0x44, 0x67, 0x28, 0xf7, 0x4c, 0x38, 0x43, 0xd7, 0xa0, + 0xe2, 0xb1, 0x81, 0x6d, 0x99, 0xd4, 0x97, 0x9f, 0x5e, 0xaf, 0x38, 0x51, 0xd3, 0x30, 0xe4, 0x4e, + 0x70, 0x82, 0x0b, 0xcf, 0xa4, 0x13, 0x5c, 0xfc, 0xe9, 0x3b, 0xc1, 0xc6, 0xb7, 0x0b, 0x50, 0xbc, + 0xd9, 0xe9, 0x32, 0x31, 0x31, 0xef, 0x8a, 0xee, 0x95, 0x9a, 0x98, 0x65, 0xc7, 0x91, 0x1c, 0xb2, + 0x00, 0x79, 0xee, 0xea, 0x91, 0x07, 0x9a, 0x9f, 0xdf, 0x72, 0x31, 0xcf, 0x5d, 0xf2, 0x1e, 0x80, + 0xe9, 0x3a, 0x1d, 0x2b, 0x08, 0xc4, 0xcc, 0xf6, 0x62, 0x6b, 0xae, 0xf7, 0x80, 0x7a, 0x9d, 0x95, + 0x10, 0xb1, 0x79, 0xfa, 0x70, 0xd4, 0x80, 0xe8, 0x19, 0x63, 0xda, 0x48, 0x37, 0x74, 0x71, 0x55, + 0x83, 0xae, 0x4c, 0xad, 0x57, 0x34, 0xc4, 0x63, 0x1c, 0xdc, 0x57, 0xa0, 0x36, 0xa0, 0x1e, 0xb5, + 0x6d, 0x66, 0x5b, 0x7e, 0x5f, 0xae, 0xac, 0x4a, 0xcd, 0x33, 0x87, 0xa3, 0x46, 0xad, 0x15, 0x91, + 0x31, 0x2e, 0x43, 0x5e, 0x83, 0xb2, 0xeb, 0xac, 0x0d, 0x6d, 0x5b, 0x07, 0x5a, 0xfe, 0x97, 0x80, + 0xbd, 0x2b, 0x29, 0x8f, 0x46, 0x8d, 0x4b, 0xca, 0x6f, 0x11, 0x4f, 0xf7, 0x3d, 0x8b, 0x5b, 0x4e, + 0xb7, 0xcd, 0x3d, 0xca, 0x59, 0xf7, 0x00, 0x75, 0x31, 0xe3, 0x37, 0x72, 0x00, 0x51, 0xb5, 0xc8, + 0x27, 0xe1, 0xcc, 0x8e, 0x2c, 0xb3, 0x49, 0x1f, 0x6e, 0x30, 0xa7, 0xcb, 0xf7, 0xe4, 0x07, 0x2b, + 0x2a, 0x0b, 0xd4, 0x4c, 0xb2, 0x30, 0x2d, 0x4b, 0x5e, 0x87, 0xb3, 0x8a, 0xb4, 0xed, 0x53, 0x8d, + 0x29, 0x3f, 0xe8, 0x29, 0x15, 0xfb, 0x68, 0xa6, 0x78, 0x38, 0x26, 0x6d, 0x50, 0xa8, 0xad, 0x59, + 0x0f, 0x59, 0xe7, 0xbe, 0xe5, 0x74, 0xdc, 0x07, 0x04, 0xa1, 0x6c, 0x47, 0xd5, 0xa8, 0x2d, 0x2d, + 0xc6, 0xcc, 0x52, 0x18, 0xea, 0x8c, 0x9a, 0xbc, 0xcf, 0x38, 0x15, 0x86, 0x6a, 0x75, 0xa8, 0x83, + 0x71, 0xaa, 0x99, 0x55, 0x6d, 0x35, 0x92, 0xf1, 0x2a, 0x9c, 0x1b, 0xeb, 0x00, 0xa4, 0x01, 0xa5, + 0x1e, 0x3b, 0xb8, 0x2d, 0x5c, 0x67, 0xe1, 0x96, 0x48, 0x3f, 0x6c, 0x5d, 0x10, 0x50, 0xd1, 0x8d, + 0xff, 0xcc, 0x41, 0x65, 0x6d, 0xe8, 0x98, 0xd2, 0xd1, 0x7e, 0xb2, 0x97, 0x19, 0x78, 0x39, 0xf9, + 0x4c, 0x2f, 0x67, 0x08, 0xe5, 0xde, 0x83, 0xd0, 0x0b, 0xaa, 0x2d, 0x6d, 0x4e, 0xdf, 0x95, 0x75, + 0x95, 0x16, 0xd7, 0x25, 0x9e, 0x8a, 0x6d, 0x9e, 0xd6, 0x15, 0x2a, 0xaf, 0xdf, 0x97, 0x4a, 0xb5, + 0xb2, 0x85, 0x4f, 0x40, 0x2d, 0x26, 0x76, 0xa4, 0x60, 0xca, 0x97, 0x8a, 0x50, 0xbe, 0xd5, 0x6e, + 0x2f, 0xb7, 0x6e, 0x93, 0x8f, 0x41, 0x4d, 0x87, 0xbd, 0xee, 0x44, 0x6d, 0x10, 0x46, 0x3d, 0xdb, + 0x11, 0x0b, 0xe3, 0x72, 0xc2, 0x87, 0xf4, 0x18, 0xb5, 0xfb, 0x7a, 0x84, 0x87, 0x3e, 0x24, 0x0a, + 0x22, 0x2a, 0x1e, 0xf9, 0x08, 0x54, 0xc4, 0x72, 0x41, 0x36, 0x6e, 0x41, 0xca, 0x85, 0x0b, 0xa0, + 0x6d, 0x4d, 0xc7, 0x50, 0x82, 0xdc, 0x80, 0x0a, 0x1d, 0xf2, 0x3d, 0xe1, 0xac, 0xcb, 0xb1, 0x59, + 0x6d, 0x5e, 0x96, 0x01, 0x3e, 0x4d, 0x7b, 0x34, 0x6a, 0xcc, 0xaf, 0x63, 0xf3, 0x63, 0xc1, 0x33, + 0x86, 0xd2, 0x84, 0xc2, 0xe9, 0x60, 0x25, 0xa1, 0x96, 0x1c, 0x3a, 0x8e, 0xf1, 0x94, 0x8b, 0x12, + 0x22, 0x96, 0x7a, 0xad, 0x04, 0x00, 0xa6, 0x00, 0xc9, 0xdb, 0x30, 0xdf, 0x63, 0x07, 0x9c, 0xee, + 0x68, 0x05, 0xe5, 0xa3, 0x28, 0x38, 0x2b, 0x5c, 0xc8, 0xf5, 0x58, 0x71, 0x4c, 0x80, 0x11, 0x1f, + 0x2e, 0xf4, 0x98, 0xb7, 0xc3, 0x3c, 0x57, 0xaf, 0x1c, 0xb4, 0x92, 0xb9, 0xa3, 0x28, 0xa9, 0x1f, + 0x8e, 0x1a, 0x17, 0xd6, 0x33, 0x60, 0x30, 0x13, 0xdc, 0xf8, 0x49, 0x0e, 0xce, 0xdc, 0x52, 0x29, + 0x04, 0xd7, 0x53, 0x4e, 0x00, 0xb9, 0x04, 0x05, 0x6f, 0x30, 0x94, 0x9d, 0xa0, 0xa0, 0xe2, 0x45, + 0xd8, 0xda, 0x46, 0x41, 0x23, 0x6f, 0x41, 0xa5, 0xa3, 0xc7, 0xa1, 0x5e, 0xb8, 0x1c, 0x75, 0xf4, + 0xca, 0x49, 0x38, 0x78, 0xc2, 0x10, 0x4d, 0xac, 0x34, 0xfa, 0x7e, 0xb7, 0x6d, 0xbd, 0xa7, 0x3a, + 0x49, 0x49, 0xad, 0x34, 0x36, 0x15, 0x09, 0x03, 0x9e, 0x98, 0xd5, 0x7b, 0xec, 0x60, 0x45, 0x38, + 0xdd, 0xb2, 0x7b, 0xe8, 0x59, 0x7d, 0x5d, 0xd3, 0x30, 0xe4, 0x8a, 0xd1, 0xaf, 0xfa, 0x7d, 0x49, + 0x1a, 0x3b, 0x39, 0xfa, 0xef, 0x09, 0x82, 0x1e, 0x02, 0xc6, 0x37, 0xf3, 0xf0, 0xfc, 0x2d, 0xc6, + 0x95, 0x53, 0xb3, 0xca, 0x06, 0xb6, 0x7b, 0x20, 0x3c, 0x4b, 0x64, 0x9f, 0x27, 0xaf, 0x03, 0x58, + 0xfe, 0x4e, 0x7b, 0xdf, 0x94, 0xdd, 0x50, 0x8d, 0x86, 0xab, 0xba, 0xd3, 0xc2, 0xed, 0x76, 0x53, + 0x73, 0x1e, 0x25, 0x9e, 0x30, 0x56, 0x26, 0x5a, 0x5d, 0xe5, 0x1f, 0xb3, 0xba, 0x6a, 0x03, 0x0c, + 0x22, 0xff, 0x54, 0x8d, 0x8d, 0xff, 0x17, 0xa8, 0x39, 0x8a, 0x6b, 0x1a, 0x83, 0x99, 0xc1, 0x63, + 0x34, 0xfe, 0xb4, 0x00, 0x0b, 0xb7, 0x18, 0x0f, 0x17, 0x8f, 0x7a, 0xdc, 0xb7, 0x07, 0xcc, 0x14, + 0xad, 0xf2, 0xd5, 0x1c, 0x94, 0x6d, 0xba, 0xc3, 0x6c, 0x5f, 0x5a, 0xd4, 0xda, 0xd2, 0x3b, 0x53, + 0x9b, 0xb8, 0xc9, 0x5a, 0x16, 0x37, 0xa4, 0x86, 0x94, 0xd1, 0x53, 0x44, 0xd4, 0xea, 0x85, 0xb9, + 0x32, 0xed, 0xa1, 0xcf, 0x99, 0xd7, 0x72, 0x3d, 0xae, 0xdd, 0xbb, 0xd0, 0x5c, 0xad, 0x44, 0x2c, + 0x8c, 0xcb, 0x91, 0x25, 0x00, 0xd3, 0xb6, 0x98, 0xc3, 0x65, 0x29, 0xd5, 0xcd, 0x48, 0xd0, 0xde, + 0x2b, 0x21, 0x07, 0x63, 0x52, 0x42, 0x55, 0xdf, 0x75, 0x2c, 0xee, 0x2a, 0x55, 0xc5, 0xa4, 0xaa, + 0xcd, 0x88, 0x85, 0x71, 0x39, 0x59, 0x8c, 0x71, 0xcf, 0x32, 0x7d, 0x59, 0xac, 0x94, 0x2a, 0x16, + 0xb1, 0x30, 0x2e, 0x27, 0xac, 0x79, 0xec, 0xfd, 0x8f, 0x64, 0xcd, 0xff, 0xac, 0x02, 0x57, 0x12, + 0xcd, 0xca, 0x29, 0x67, 0xbb, 0x43, 0xbb, 0xcd, 0x78, 0xf0, 0x01, 0xa7, 0xb4, 0xf2, 0xbf, 0x1e, + 0x7d, 0x77, 0x95, 0xc7, 0x33, 0x8f, 0xe7, 0xbb, 0x8f, 0x55, 0xf0, 0xa9, 0xbe, 0xfd, 0x75, 0xa8, + 0x3a, 0x94, 0xfb, 0x72, 0x20, 0xe9, 0x31, 0x13, 0x2e, 0x05, 0xef, 0x04, 0x0c, 0x8c, 0x64, 0x48, + 0x0b, 0x2e, 0xe8, 0x26, 0xbe, 0xf9, 0x70, 0xe0, 0x7a, 0x9c, 0x79, 0xaa, 0xac, 0x9e, 0x5d, 0x74, + 0xd9, 0x0b, 0x9b, 0x19, 0x32, 0x98, 0x59, 0x92, 0x6c, 0xc2, 0x79, 0x53, 0xe5, 0x36, 0x98, 0xed, + 0xd2, 0x4e, 0x00, 0x58, 0x92, 0x80, 0xe1, 0x4a, 0x65, 0x65, 0x5c, 0x04, 0xb3, 0xca, 0xa5, 0x7b, + 0x73, 0x79, 0xaa, 0xde, 0x3c, 0x37, 0x4d, 0x6f, 0xae, 0x4c, 0xd7, 0x9b, 0xab, 0x4f, 0xd7, 0x9b, + 0x45, 0xcb, 0x8b, 0x7e, 0xc4, 0x3c, 0x31, 0x5b, 0xab, 0x09, 0x27, 0x96, 0x3a, 0x0b, 0x5b, 0xbe, + 0x9d, 0x21, 0x83, 0x99, 0x25, 0xc9, 0x0e, 0x2c, 0x28, 0xfa, 0x4d, 0xc7, 0xf4, 0x0e, 0x06, 0x62, + 0xe6, 0x88, 0xe1, 0xd6, 0x24, 0xae, 0xa1, 0x71, 0x17, 0xda, 0x13, 0x25, 0xf1, 0x31, 0x28, 0xe4, + 0xe7, 0xe1, 0x94, 0xfa, 0x4a, 0x9b, 0x74, 0x20, 0x61, 0x55, 0x22, 0xed, 0xa2, 0x86, 0x3d, 0xb5, + 0x12, 0x67, 0x62, 0x52, 0x96, 0x2c, 0xc3, 0x99, 0xc1, 0xbe, 0x29, 0x7e, 0xde, 0xde, 0xbd, 0xc3, + 0x58, 0x87, 0x75, 0x64, 0xda, 0xac, 0xda, 0x7c, 0x21, 0x88, 0x3b, 0xb4, 0x92, 0x6c, 0x4c, 0xcb, + 0x93, 0x1b, 0x30, 0xef, 0x73, 0xea, 0x71, 0x1d, 0x65, 0xab, 0x9f, 0x56, 0x89, 0xc6, 0x20, 0x08, + 0xd5, 0x8e, 0xf1, 0x30, 0x21, 0x39, 0x8b, 0xf5, 0x78, 0xa4, 0x26, 0x43, 0x19, 0xa6, 0x4c, 0x99, + 0xfd, 0xaf, 0xa4, 0xcd, 0xfe, 0xdb, 0xb3, 0x0c, 0xff, 0x0c, 0x0d, 0x4f, 0x35, 0xec, 0xdf, 0x04, + 0xe2, 0xe9, 0xa0, 0xaa, 0x5a, 0x8e, 0xc6, 0x2c, 0x7f, 0x98, 0xce, 0xc5, 0x31, 0x09, 0xcc, 0x28, + 0x45, 0xda, 0x70, 0xd1, 0x67, 0x0e, 0xb7, 0x1c, 0x66, 0x27, 0xe1, 0xd4, 0x94, 0xf0, 0xa2, 0x86, + 0xbb, 0xd8, 0xce, 0x12, 0xc2, 0xec, 0xb2, 0xb3, 0x34, 0xfe, 0x3f, 0x57, 0xe5, 0xbc, 0xab, 0x9a, + 0xe6, 0xd8, 0xcc, 0xf6, 0x57, 0xd3, 0x66, 0xfb, 0x9d, 0xd9, 0xbf, 0xdb, 0x74, 0x26, 0x7b, 0x09, + 0x40, 0x7e, 0x85, 0xb8, 0xcd, 0x0e, 0x2d, 0x15, 0x86, 0x1c, 0x8c, 0x49, 0x89, 0x51, 0x18, 0xb4, + 0x73, 0xdc, 0x5c, 0x87, 0xa3, 0xb0, 0x1d, 0x67, 0x62, 0x52, 0x76, 0xa2, 0xc9, 0x2f, 0x4d, 0x6d, + 0xf2, 0xdf, 0x04, 0x92, 0x08, 0x86, 0x28, 0xbc, 0x72, 0x72, 0x37, 0xc1, 0xed, 0x31, 0x09, 0xcc, + 0x28, 0x35, 0xa1, 0x2b, 0xcf, 0x1d, 0x6f, 0x57, 0xae, 0x4c, 0xdf, 0x95, 0xc9, 0x3b, 0x70, 0x49, + 0xaa, 0xd2, 0xed, 0x93, 0x04, 0x56, 0xc6, 0xff, 0x83, 0x1a, 0xf8, 0x12, 0x4e, 0x12, 0xc4, 0xc9, + 0x18, 0xe2, 0xfb, 0x98, 0x1e, 0xeb, 0x08, 0xe5, 0xd4, 0x9e, 0x3c, 0x31, 0xac, 0x64, 0xc8, 0x60, + 0x66, 0x49, 0xd1, 0xc5, 0xb8, 0xe8, 0x86, 0x74, 0xc7, 0x66, 0x1d, 0xbd, 0x9b, 0x22, 0xec, 0x62, + 0x5b, 0x1b, 0x6d, 0xcd, 0xc1, 0x98, 0x54, 0x96, 0xad, 0x9e, 0x3f, 0xa2, 0xad, 0xbe, 0x25, 0x23, + 0x87, 0xbb, 0x89, 0x29, 0x41, 0x1b, 0xfc, 0x70, 0x7f, 0xcc, 0x4a, 0x5a, 0x00, 0xc7, 0xcb, 0xc8, + 0xa9, 0xd2, 0xf4, 0xac, 0x01, 0xf7, 0x93, 0x58, 0xa7, 0x53, 0x53, 0x65, 0x86, 0x0c, 0x66, 0x96, + 0x14, 0x4e, 0xca, 0x1e, 0xa3, 0x36, 0xdf, 0x4b, 0x02, 0x9e, 0x49, 0x3a, 0x29, 0x6f, 0x8c, 0x8b, + 0x60, 0x56, 0xb9, 0x59, 0xcc, 0xdb, 0x37, 0xf2, 0x70, 0xfe, 0x16, 0xd3, 0xfb, 0x35, 0x5a, 0x6e, + 0x27, 0xb0, 0x6b, 0xff, 0x43, 0x57, 0x59, 0x5f, 0xc9, 0xc3, 0xdc, 0x2d, 0xcf, 0x1d, 0x0e, 0x9a, + 0x07, 0xa4, 0x0b, 0xe5, 0x07, 0x32, 0x2a, 0xa6, 0x63, 0x61, 0xd3, 0x6f, 0x4d, 0x51, 0xc1, 0xb5, + 0xc8, 0x04, 0xab, 0x67, 0xd4, 0xf0, 0xa2, 0xa5, 0x7a, 0xec, 0x80, 0xa9, 0x44, 0x6d, 0x25, 0x6a, + 0xa9, 0x75, 0x41, 0x44, 0xc5, 0x23, 0xef, 0xc2, 0x9c, 0xcf, 0x5d, 0x2f, 0x30, 0xd2, 0xb3, 0x84, + 0x45, 0x5b, 0xcd, 0x4f, 0xb7, 0x15, 0x94, 0x5a, 0xc8, 0xeb, 0x07, 0x0c, 0x14, 0x18, 0xdf, 0xce, + 0x01, 0xbc, 0xb1, 0xb5, 0xd5, 0xd2, 0x31, 0x87, 0x0e, 0x14, 0xe9, 0x30, 0x0c, 0x09, 0xae, 0x4d, + 0x9f, 0x3b, 0x88, 0x27, 0xcf, 0x75, 0x8c, 0x6e, 0xc8, 0xf7, 0x50, 0xa2, 0x93, 0x0f, 0xc3, 0x9c, + 0x9e, 0x21, 0x75, 0x3b, 0x84, 0xf9, 0x0e, 0x3d, 0x8b, 0x62, 0xc0, 0x37, 0x7e, 0x94, 0x87, 0xe7, + 0x6f, 0x3b, 0x9c, 0x79, 0x6d, 0xce, 0x06, 0x89, 0x5c, 0x31, 0xf9, 0xe5, 0xb1, 0xad, 0x94, 0xff, + 0xf7, 0xe9, 0x82, 0x20, 0x6a, 0x27, 0xde, 0x26, 0xe3, 0x34, 0xb2, 0x4d, 0x11, 0x2d, 0xb6, 0x7f, + 0x72, 0x08, 0x45, 0x7f, 0xc0, 0x4c, 0x1d, 0x62, 0x69, 0x4f, 0xdd, 0x1a, 0xd9, 0x2f, 0x20, 0xc6, + 0x5f, 0x14, 0xe0, 0x94, 0xa3, 0x51, 0xaa, 0x23, 0x5f, 0x80, 0xb2, 0xcf, 0x29, 0x1f, 0x06, 0xd1, + 0xf8, 0xed, 0xe3, 0x56, 0x2c, 0xc1, 0xa3, 0x3e, 0xaa, 0x9e, 0x51, 0x2b, 0x35, 0x7e, 0x94, 0x83, + 0x85, 0xec, 0x82, 0x1b, 0x96, 0xcf, 0xc9, 0x2f, 0x8e, 0x35, 0xfb, 0x53, 0xc6, 0x9e, 0x44, 0x69, + 0xd9, 0xe8, 0x61, 0xdc, 0x31, 0xa0, 0xc4, 0x9a, 0x9c, 0x43, 0xc9, 0xe2, 0xac, 0x1f, 0xf8, 0x4a, + 0x77, 0x8f, 0xf9, 0xd5, 0x63, 0xb6, 0x49, 0x68, 0x41, 0xa5, 0xcc, 0xf8, 0x5a, 0x7e, 0xd2, 0x2b, + 0x8b, 0xcf, 0x42, 0xec, 0xe4, 0x7e, 0x84, 0xf5, 0xd9, 0xf6, 0x23, 0x24, 0x2b, 0x34, 0xbe, 0x2d, + 0xe1, 0x57, 0xc7, 0xb7, 0x25, 0xdc, 0x9d, 0x7d, 0x5b, 0x42, 0xaa, 0x19, 0x26, 0xee, 0x4e, 0xf8, + 0x46, 0x01, 0x2e, 0x3f, 0xae, 0xdb, 0x08, 0x5b, 0xa9, 0x7b, 0xe7, 0xac, 0xb6, 0xf2, 0xf1, 0xfd, + 0x90, 0x2c, 0x41, 0x69, 0xb0, 0x47, 0xfd, 0x60, 0x56, 0x09, 0x26, 0xdf, 0x52, 0x4b, 0x10, 0x1f, + 0x8d, 0x1a, 0x35, 0x35, 0x1b, 0xc9, 0x47, 0x54, 0xa2, 0xc2, 0xb2, 0xf4, 0x99, 0xef, 0x47, 0xfe, + 0x6d, 0x68, 0x59, 0x36, 0x15, 0x19, 0x03, 0x3e, 0xe1, 0x50, 0x56, 0x6b, 0x46, 0x9d, 0x7b, 0x9a, + 0x3e, 0x51, 0x9a, 0xb1, 0x85, 0x25, 0x7a, 0x29, 0x1d, 0x7e, 0xd0, 0xba, 0xc2, 0x2d, 0x32, 0xa5, + 0xec, 0x2d, 0x32, 0xb1, 0x09, 0x56, 0x6d, 0x91, 0xf9, 0xbb, 0x0a, 0x3c, 0x9f, 0xfd, 0x0d, 0xc5, + 0xbb, 0xee, 0x33, 0xcf, 0xb7, 0x5c, 0x47, 0x4f, 0xda, 0xd1, 0x56, 0x32, 0x45, 0xc6, 0x80, 0xff, + 0x33, 0x9d, 0x84, 0xfd, 0xc3, 0x9c, 0x70, 0x83, 0x55, 0xa0, 0xe6, 0xfd, 0x48, 0xc4, 0xbe, 0xa8, + 0xdc, 0xe9, 0x09, 0x0a, 0x71, 0x72, 0x5d, 0xc8, 0x1f, 0xe4, 0xa0, 0xde, 0x4f, 0xf9, 0xd9, 0x27, + 0xb8, 0x99, 0xf3, 0xf2, 0xe1, 0xa8, 0x51, 0xdf, 0x9c, 0xa0, 0x0f, 0x27, 0xd6, 0x84, 0xfc, 0x1a, + 0xd4, 0x06, 0xa2, 0x5f, 0xf8, 0x9c, 0x39, 0x66, 0xb0, 0x9f, 0x73, 0xfa, 0xde, 0xdf, 0x8a, 0xb0, + 0x82, 0x14, 0xa8, 0xce, 0xac, 0x46, 0x0c, 0x8c, 0x6b, 0x7c, 0xc6, 0x77, 0x6f, 0x5e, 0x83, 0x8a, + 0xcf, 0x38, 0xb7, 0x9c, 0xae, 0x2f, 0x57, 0x6f, 0x55, 0x35, 0x56, 0xda, 0x9a, 0x86, 0x21, 0x97, + 0xfc, 0x1f, 0xa8, 0xca, 0xb8, 0xcf, 0xb2, 0xd7, 0xf5, 0xeb, 0x55, 0x99, 0x8d, 0x94, 0x76, 0xb5, + 0x1d, 0x10, 0x31, 0xe2, 0x93, 0x57, 0x61, 0x5e, 0x65, 0x64, 0xf5, 0x2e, 0x6e, 0xb5, 0xc6, 0x92, + 0xc9, 0xa8, 0x66, 0x8c, 0x8e, 0x09, 0x29, 0xb1, 0x9e, 0x62, 0x61, 0x70, 0x2c, 0xbd, 0x9e, 0x8a, + 0xc2, 0x66, 0x18, 0x93, 0x22, 0x2f, 0x42, 0x81, 0xdb, 0xbe, 0x5c, 0x43, 0x55, 0x22, 0xbf, 0x77, + 0x6b, 0xa3, 0x8d, 0x82, 0x6e, 0xfc, 0x57, 0x0e, 0xce, 0xa4, 0x36, 0xab, 0x89, 0x22, 0x43, 0xcf, + 0xd6, 0x66, 0x24, 0x2c, 0xb2, 0x8d, 0x1b, 0x28, 0xe8, 0xe4, 0x1d, 0xed, 0x15, 0xe6, 0x67, 0x3c, + 0xb0, 0x72, 0x87, 0x72, 0x5f, 0xb8, 0x81, 0x63, 0x0e, 0xe1, 0x8d, 0x54, 0xe3, 0x14, 0x92, 0xb1, + 0xb6, 0xc7, 0x37, 0x50, 0x6c, 0xc1, 0x59, 0x7c, 0x9a, 0x05, 0xa7, 0xf1, 0x37, 0x05, 0xa8, 0xbd, + 0xe9, 0xee, 0xfc, 0x8c, 0x6c, 0xa0, 0xc9, 0xb6, 0xc8, 0xf9, 0x9f, 0xa2, 0x45, 0xde, 0x86, 0x17, + 0x38, 0x17, 0xab, 0x7e, 0xd7, 0xe9, 0xf8, 0xcb, 0xbb, 0x9c, 0x79, 0x6b, 0x96, 0x63, 0xf9, 0x7b, + 0xac, 0xa3, 0x23, 0x77, 0x1f, 0x38, 0x1c, 0x35, 0x5e, 0xd8, 0xda, 0xda, 0xc8, 0x12, 0xc1, 0x49, + 0x65, 0xe5, 0x08, 0xa1, 0x66, 0xcf, 0xdd, 0xdd, 0x55, 0xbb, 0x1b, 0x54, 0x8e, 0x47, 0x8d, 0x90, + 0x18, 0x1d, 0x13, 0x52, 0xc6, 0x0f, 0x72, 0x50, 0x5d, 0xa7, 0xbb, 0x3d, 0xda, 0xb6, 0x9c, 0x1e, + 0x79, 0x09, 0xe6, 0x76, 0x3c, 0xb7, 0xc7, 0x3c, 0x5f, 0xef, 0x36, 0x90, 0xab, 0x9e, 0xa6, 0x22, + 0x61, 0xc0, 0x13, 0xcb, 0x30, 0xee, 0x0e, 0x2c, 0x33, 0xbd, 0x60, 0xdd, 0x12, 0x44, 0x54, 0x3c, + 0x72, 0x5f, 0x8d, 0xa3, 0xc2, 0x8c, 0xbb, 0xfd, 0xb7, 0x36, 0xda, 0x2a, 0x7b, 0x1b, 0x8c, 0x40, + 0xf2, 0x72, 0xc2, 0xf3, 0xa8, 0x4e, 0xf2, 0x15, 0x8c, 0x9f, 0xe4, 0xa1, 0xa6, 0x5e, 0x4d, 0x2d, + 0xce, 0x8e, 0xf3, 0xe5, 0x5e, 0x93, 0xd1, 0x75, 0x7f, 0xd8, 0x67, 0x9e, 0x5c, 0x04, 0xeb, 0x21, + 0x17, 0x8f, 0x96, 0x44, 0xcc, 0x30, 0xc2, 0x1e, 0x91, 0x82, 0xd6, 0x29, 0x9e, 0x60, 0xeb, 0x94, + 0x1e, 0xd7, 0x3a, 0xf2, 0xa4, 0x07, 0xf5, 0x6d, 0x3d, 0x7f, 0xcd, 0x70, 0xd2, 0x63, 0xb9, 0xbd, + 0xa1, 0x4f, 0x7a, 0x2c, 0xb7, 0x37, 0x50, 0x82, 0x1a, 0x7f, 0x9c, 0x83, 0xea, 0x86, 0xb5, 0xcb, + 0xcc, 0x03, 0xd3, 0x66, 0xe4, 0x2d, 0xa8, 0x77, 0x98, 0xcd, 0x38, 0xbb, 0xe5, 0x51, 0x93, 0xb5, + 0x98, 0x67, 0xc9, 0x83, 0x63, 0xa2, 0x0b, 0x4b, 0x23, 0x51, 0x52, 0x73, 0xf1, 0xea, 0x04, 0x19, + 0x9c, 0x58, 0x9a, 0xdc, 0x86, 0xf9, 0x0e, 0xf3, 0x2d, 0x8f, 0x75, 0x5a, 0x31, 0x57, 0xf7, 0xa5, + 0xc0, 0xf0, 0xad, 0xc6, 0x78, 0x8f, 0x46, 0x8d, 0x53, 0x2d, 0x6b, 0xc0, 0x6c, 0xcb, 0x61, 0xca, + 0xe7, 0x4d, 0x14, 0x35, 0x4a, 0x50, 0xd8, 0x70, 0xbb, 0xc6, 0xd7, 0x0a, 0x10, 0x1e, 0x05, 0x24, + 0x5f, 0xcf, 0x41, 0x8d, 0x3a, 0x8e, 0xcb, 0xf5, 0x31, 0x3b, 0x95, 0x38, 0xc0, 0x99, 0x4f, 0x1c, + 0x2e, 0x2e, 0x47, 0xa0, 0x2a, 0xe6, 0x1c, 0xc6, 0xc1, 0x63, 0x1c, 0x8c, 0xeb, 0x26, 0xc3, 0x54, + 0x18, 0x7c, 0x73, 0xf6, 0x5a, 0x3c, 0x45, 0xd0, 0x7b, 0xe1, 0x53, 0x70, 0x36, 0x5d, 0xd9, 0xa3, + 0x44, 0xcd, 0x66, 0x09, 0xb8, 0x7d, 0xa5, 0x0a, 0xb5, 0x3b, 0x94, 0x5b, 0xfb, 0x4c, 0xae, 0xef, + 0x4e, 0xc6, 0x61, 0xff, 0xbd, 0x1c, 0x3c, 0x9f, 0x0c, 0x48, 0x9f, 0xa0, 0xd7, 0xbe, 0x70, 0x38, + 0x6a, 0x3c, 0x8f, 0x99, 0xda, 0x70, 0x42, 0x2d, 0xa4, 0xff, 0x3e, 0x16, 0xdf, 0x3e, 0x69, 0xff, + 0xbd, 0x3d, 0x49, 0x21, 0x4e, 0xae, 0xcb, 0xcf, 0x8a, 0xff, 0xfe, 0x6c, 0x1f, 0xcd, 0x4a, 0xad, + 0x2e, 0xe6, 0x9e, 0x99, 0xd5, 0x45, 0xe5, 0x99, 0xf0, 0xe6, 0x06, 0xb1, 0xd5, 0x45, 0x75, 0xc6, + 0x20, 0xab, 0xce, 0xe1, 0x2a, 0xb4, 0x49, 0xab, 0x14, 0xb9, 0xbb, 0x32, 0x70, 0xbc, 0x89, 0x09, + 0xa5, 0x1d, 0xea, 0x5b, 0xa6, 0xf6, 0x6d, 0x9b, 0xd3, 0xc7, 0x3c, 0x82, 0x33, 0x4c, 0x2a, 0x80, + 0x25, 0x1f, 0x51, 0x61, 0x47, 0x67, 0xa5, 0xf2, 0x33, 0x9d, 0x95, 0x22, 0x2b, 0x50, 0x74, 0x84, + 0xb1, 0x2d, 0x1c, 0xf9, 0x74, 0xd4, 0x9d, 0x75, 0x76, 0x80, 0xb2, 0xb0, 0xf1, 0xdd, 0x3c, 0x80, + 0x78, 0x7d, 0xed, 0x43, 0x3d, 0x61, 0xa5, 0xf3, 0x61, 0x98, 0xf3, 0x87, 0x32, 0x14, 0xac, 0xa7, + 0xe2, 0x28, 0x32, 0xad, 0xc8, 0x18, 0xf0, 0x85, 0x9b, 0xf5, 0xf9, 0x21, 0x1b, 0x06, 0x81, 0xa6, + 0xd0, 0xcd, 0xfa, 0xb4, 0x20, 0xa2, 0xe2, 0x9d, 0x9c, 0x97, 0x14, 0x2c, 0xc9, 0x4a, 0x27, 0xb4, + 0x24, 0x33, 0xbe, 0x98, 0x07, 0x88, 0xb2, 0x07, 0xe4, 0xdb, 0x39, 0xb8, 0x18, 0x8e, 0x32, 0xae, + 0x4e, 0xe0, 0xac, 0xd8, 0xd4, 0xea, 0xcf, 0xbc, 0x4a, 0xca, 0x1a, 0xe1, 0xd2, 0xec, 0xb4, 0xb2, + 0xd4, 0x61, 0x76, 0x2d, 0x08, 0x42, 0x85, 0xf5, 0x07, 0xfc, 0x60, 0xd5, 0xf2, 0x74, 0xb7, 0xcb, + 0x3c, 0xc2, 0x72, 0x53, 0xcb, 0xa8, 0xa2, 0xfa, 0xb4, 0x85, 0x1c, 0x39, 0x01, 0x07, 0x43, 0x1c, + 0xe3, 0x5b, 0x79, 0x38, 0x9f, 0x51, 0x3b, 0xf2, 0x3a, 0x9c, 0xd5, 0xe9, 0x93, 0xe8, 0x18, 0x7a, + 0x2e, 0x3a, 0x86, 0xde, 0x4e, 0xf1, 0x70, 0x4c, 0x9a, 0xbc, 0x03, 0x40, 0x4d, 0x93, 0xf9, 0xfe, + 0xa6, 0xdb, 0x09, 0x9c, 0xbe, 0xd7, 0xc4, 0x8a, 0x75, 0x39, 0xa4, 0x3e, 0x1a, 0x35, 0x3e, 0x9a, + 0x95, 0x07, 0x4b, 0xbd, 0x7d, 0x54, 0x00, 0x63, 0x90, 0xe4, 0x73, 0x00, 0xea, 0x5c, 0x54, 0xb8, + 0x93, 0xf3, 0x09, 0x61, 0xfa, 0xc5, 0xe0, 0xcc, 0xce, 0xe2, 0xa7, 0x87, 0xd4, 0xe1, 0x16, 0x3f, + 0x50, 0x1b, 0xf7, 0xef, 0x85, 0x28, 0x18, 0x43, 0x34, 0xfe, 0x2a, 0x0f, 0x95, 0xc0, 0x19, 0x7d, + 0x1f, 0x12, 0x31, 0xdd, 0x44, 0x22, 0x66, 0xfa, 0x83, 0xb0, 0x41, 0x95, 0x27, 0xa6, 0x5e, 0xdc, + 0x54, 0xea, 0xe5, 0xd6, 0xec, 0xaa, 0x1e, 0x9f, 0x6c, 0xf9, 0x4e, 0x1e, 0x4e, 0x07, 0xa2, 0xfa, + 0xa0, 0xc0, 0xc7, 0xe1, 0x94, 0xc7, 0x68, 0xa7, 0x49, 0xb9, 0xb9, 0x27, 0x3f, 0x9f, 0x3a, 0x26, + 0x70, 0xee, 0x70, 0xd4, 0x38, 0x85, 0x71, 0x06, 0x26, 0xe5, 0xb2, 0x4e, 0x18, 0xe4, 0x67, 0x3c, + 0x61, 0x50, 0x38, 0xca, 0x09, 0x03, 0x42, 0xa1, 0x26, 0x6a, 0xb4, 0x65, 0xf5, 0x99, 0x3b, 0x0c, + 0x6e, 0xde, 0x38, 0xea, 0xce, 0x64, 0x39, 0xbb, 0x63, 0x04, 0x83, 0x71, 0x4c, 0xe3, 0xef, 0x73, + 0x30, 0x1f, 0xb5, 0xd7, 0x89, 0xa7, 0xa3, 0x76, 0x93, 0xe9, 0xa8, 0xe5, 0x99, 0xbb, 0xc3, 0x84, + 0x04, 0xd4, 0x6f, 0x97, 0xa3, 0xd7, 0x92, 0x29, 0xa7, 0x1d, 0x58, 0xb0, 0x32, 0xb3, 0x30, 0x31, + 0x6b, 0x13, 0xee, 0xb0, 0xbb, 0x3d, 0x51, 0x12, 0x1f, 0x83, 0x42, 0x86, 0x50, 0xd9, 0x67, 0x1e, + 0xb7, 0x4c, 0x16, 0xbc, 0xdf, 0xad, 0x99, 0xbd, 0x23, 0xb5, 0xbb, 0x20, 0x6a, 0xd3, 0x7b, 0x5a, + 0x01, 0x86, 0xaa, 0xc8, 0x0e, 0x94, 0x58, 0xa7, 0xcb, 0x82, 0x03, 0x1a, 0x9f, 0x9c, 0xe9, 0xcc, + 0x4f, 0xd4, 0x9e, 0xe2, 0xc9, 0x47, 0x05, 0x4d, 0x7c, 0xa8, 0xda, 0xc1, 0xf2, 0x5d, 0xf7, 0xc3, + 0xe9, 0x7d, 0x9d, 0x30, 0x10, 0x10, 0xed, 0x70, 0x0d, 0x49, 0x18, 0xe9, 0x21, 0xbd, 0xf0, 0x34, + 0x53, 0xe9, 0x98, 0x8c, 0xc7, 0x63, 0x4e, 0x34, 0xf9, 0x50, 0x7d, 0x40, 0x39, 0xf3, 0xfa, 0xd4, + 0xeb, 0x69, 0xc7, 0x7f, 0xfa, 0x37, 0xbc, 0x1f, 0x20, 0x45, 0x6f, 0x18, 0x92, 0x30, 0xd2, 0x43, + 0x5c, 0xa8, 0x72, 0xed, 0xc9, 0x06, 0xc7, 0x6b, 0xa7, 0x57, 0x1a, 0xf8, 0xc4, 0xbe, 0x8a, 0x9a, + 0x87, 0x8f, 0x18, 0xe9, 0x30, 0x1e, 0x15, 0x22, 0xf3, 0xf8, 0x7e, 0xe7, 0x1f, 0x5f, 0x4d, 0xe6, + 0x1f, 0xaf, 0xa4, 0xf3, 0x8f, 0xa9, 0x68, 0xcc, 0xd1, 0x33, 0x90, 0x14, 0x6a, 0x36, 0xf5, 0xf9, + 0xf6, 0xa0, 0x43, 0xb9, 0x0e, 0x5e, 0xd7, 0x96, 0xfe, 0xf7, 0xd3, 0x59, 0x2f, 0x61, 0x0f, 0xa3, + 0xa0, 0xcb, 0x46, 0x04, 0x83, 0x71, 0x4c, 0xf2, 0x0a, 0xd4, 0xf6, 0xe5, 0x88, 0x54, 0x47, 0x35, + 0x4a, 0xd2, 0x9c, 0x4b, 0x0b, 0x7b, 0x2f, 0x22, 0x63, 0x5c, 0x46, 0x14, 0x51, 0x9e, 0x80, 0x2a, + 0x52, 0x8e, 0x8a, 0xb4, 0x23, 0x32, 0xc6, 0x65, 0x64, 0x22, 0xc4, 0x72, 0x7a, 0xaa, 0xc0, 0x9c, + 0x2c, 0xa0, 0x12, 0x21, 0x01, 0x11, 0x23, 0x3e, 0xb9, 0x06, 0x95, 0x61, 0x67, 0x57, 0xc9, 0x56, + 0xa4, 0xac, 0xf4, 0xbf, 0xb6, 0x57, 0xd7, 0xf4, 0xd1, 0x91, 0x80, 0x6b, 0xfc, 0x47, 0x0e, 0xc8, + 0x78, 0xc6, 0x9c, 0xec, 0x41, 0xd9, 0x91, 0x51, 0x95, 0x99, 0xaf, 0x07, 0x88, 0x05, 0x67, 0xd4, + 0x18, 0xd3, 0x04, 0x8d, 0x4f, 0x1c, 0xa8, 0xb0, 0x87, 0x9c, 0x79, 0x0e, 0xb5, 0xb5, 0xeb, 0x71, + 0x3c, 0x57, 0x11, 0x28, 0x87, 0x53, 0x23, 0x63, 0xa8, 0xc3, 0xf8, 0x71, 0x1e, 0x6a, 0x31, 0xb9, + 0x27, 0x2d, 0x56, 0xe4, 0x86, 0x54, 0x15, 0xcc, 0xd8, 0xf6, 0x6c, 0xdd, 0x4d, 0x63, 0x1b, 0x52, + 0x35, 0x0b, 0x37, 0x30, 0x2e, 0x47, 0x96, 0x00, 0xfa, 0xd4, 0xe7, 0xcc, 0xbb, 0x13, 0x1d, 0x05, + 0x0b, 0xdd, 0xaf, 0xcd, 0x90, 0x83, 0x31, 0x29, 0x72, 0x55, 0x5f, 0x54, 0x51, 0x4c, 0x9e, 0xca, + 0x9b, 0x70, 0x0b, 0x45, 0xe9, 0x18, 0x6e, 0xa1, 0x20, 0x5d, 0x38, 0x1b, 0xd4, 0x3a, 0xe0, 0x1e, + 0xed, 0xa0, 0x97, 0x72, 0xc6, 0x53, 0x10, 0x38, 0x06, 0x6a, 0x7c, 0x37, 0x07, 0xa7, 0x12, 0x4b, + 0x69, 0x75, 0x9e, 0x2e, 0xd8, 0xef, 0x91, 0x38, 0x4f, 0x17, 0xdb, 0xa6, 0xf1, 0x32, 0x94, 0x55, + 0x03, 0xe9, 0x86, 0x0f, 0xcd, 0x88, 0x6a, 0x42, 0xd4, 0x5c, 0x61, 0x10, 0x74, 0xb0, 0x2e, 0x6d, + 0x10, 0x74, 0x34, 0x0f, 0x03, 0x3e, 0xf9, 0x08, 0x54, 0x82, 0xda, 0xe9, 0x96, 0x8e, 0xee, 0x28, + 0xd1, 0x74, 0x0c, 0x25, 0x8c, 0x6f, 0x15, 0xf4, 0xf0, 0x50, 0xa9, 0xbc, 0x60, 0x85, 0xfb, 0x2b, + 0xc2, 0x09, 0x0b, 0xfb, 0xd0, 0xb1, 0x5e, 0xa1, 0x11, 0xf6, 0xad, 0x18, 0x11, 0xe3, 0xda, 0x44, + 0xa3, 0xc4, 0x36, 0xae, 0x54, 0xe3, 0xb6, 0x55, 0x6e, 0x34, 0xd1, 0x5c, 0xbd, 0xb9, 0x7f, 0x2c, + 0xfd, 0x10, 0xdf, 0xdc, 0x1f, 0x31, 0xd3, 0xa9, 0x87, 0x5b, 0x70, 0x4e, 0xb8, 0x84, 0x6b, 0x9e, + 0xdb, 0x6f, 0xb2, 0xae, 0xe5, 0x38, 0x96, 0xd3, 0xd5, 0xa9, 0xbf, 0x30, 0x7f, 0x81, 0x69, 0x01, + 0x1c, 0x2f, 0x13, 0xac, 0xce, 0x4b, 0xc7, 0xbd, 0x3a, 0x37, 0xbe, 0x9e, 0x07, 0x99, 0x4d, 0x20, + 0x1f, 0x87, 0x6a, 0x9f, 0x99, 0x7b, 0xd4, 0xb1, 0xfc, 0xe0, 0x7c, 0xb6, 0x58, 0xdb, 0x56, 0x37, + 0x03, 0xe2, 0x23, 0xf1, 0x6d, 0x97, 0xdb, 0x1b, 0x72, 0xcb, 0x47, 0x24, 0x4b, 0x4c, 0x28, 0x77, + 0x7d, 0x9f, 0x0e, 0xac, 0x99, 0x2f, 0xcb, 0x52, 0x47, 0x4b, 0x95, 0x7d, 0x53, 0xbf, 0x51, 0x43, + 0x13, 0x13, 0x4a, 0x03, 0x9b, 0x5a, 0x8e, 0x5e, 0xec, 0x34, 0x67, 0xca, 0xa1, 0xb4, 0x04, 0x92, + 0x8a, 0xe2, 0xc8, 0x9f, 0xa8, 0xb0, 0x8d, 0xbf, 0xcc, 0x41, 0x35, 0xe4, 0x87, 0x86, 0x24, 0x37, + 0xd1, 0x90, 0x8c, 0x9f, 0x1f, 0xcd, 0x1f, 0xf7, 0xf9, 0xd1, 0xeb, 0x50, 0xdd, 0xa3, 0x4e, 0xc7, + 0xdf, 0xa3, 0x3d, 0x65, 0x00, 0x2b, 0x91, 0xdf, 0xf3, 0x46, 0xc0, 0xc0, 0x48, 0xc6, 0xf8, 0x72, + 0x09, 0xd4, 0x5d, 0x46, 0x62, 0x88, 0x76, 0x2c, 0x5f, 0x65, 0x9b, 0x73, 0xb2, 0x64, 0x38, 0x44, + 0x57, 0x35, 0x1d, 0x43, 0x09, 0x72, 0x09, 0x0a, 0x7d, 0xcb, 0xd1, 0x11, 0x7c, 0xd9, 0x45, 0x36, + 0x2d, 0x07, 0x05, 0x4d, 0xb2, 0xe8, 0x43, 0x9d, 0x30, 0x55, 0x2c, 0xfa, 0x10, 0x05, 0x4d, 0xac, + 0xe3, 0x6c, 0xd7, 0xed, 0xed, 0x50, 0xb3, 0x17, 0x64, 0x99, 0x8a, 0x72, 0xa2, 0x94, 0xeb, 0xb8, + 0x8d, 0x24, 0x0b, 0xd3, 0xb2, 0xa2, 0xb8, 0xe9, 0xba, 0x76, 0xc7, 0x7d, 0xe0, 0x04, 0xc5, 0x4b, + 0x51, 0xf1, 0x95, 0x24, 0x0b, 0xd3, 0xb2, 0x64, 0x1b, 0x5e, 0x78, 0x8f, 0x79, 0xae, 0x36, 0x4e, + 0x6d, 0x9b, 0xb1, 0x41, 0x00, 0xa3, 0x7c, 0x01, 0x99, 0xdd, 0xfd, 0x6c, 0xb6, 0x08, 0x4e, 0x2a, + 0x2b, 0x93, 0xc6, 0xd4, 0xeb, 0x32, 0xde, 0xf2, 0x5c, 0x93, 0xf9, 0xbe, 0xe5, 0x74, 0x03, 0xd8, + 0xb9, 0x08, 0x76, 0x2b, 0x5b, 0x04, 0x27, 0x95, 0x25, 0x9b, 0x70, 0x4e, 0xb1, 0x62, 0xcb, 0x53, + 0xed, 0x56, 0x34, 0x0e, 0x47, 0x8d, 0x0f, 0xac, 0xb2, 0x81, 0xc7, 0x4c, 0xe1, 0x0b, 0x6d, 0xa5, + 0xc5, 0x70, 0xbc, 0xa4, 0xbc, 0x61, 0x50, 0x67, 0x56, 0x5a, 0xcc, 0x93, 0x9f, 0x5c, 0x86, 0x69, + 0xf5, 0x1a, 0x18, 0x53, 0x3c, 0x1c, 0x93, 0x26, 0x6f, 0x41, 0x3d, 0x0e, 0xbb, 0xbc, 0x4f, 0x2d, + 0x9b, 0xee, 0x58, 0xb6, 0xc5, 0x0f, 0xe4, 0x9e, 0x8f, 0x53, 0x2a, 0xee, 0xbf, 0x35, 0x41, 0x06, + 0x27, 0x96, 0x36, 0x7e, 0xa7, 0x00, 0xf2, 0x32, 0x3a, 0x61, 0xb6, 0x6c, 0x37, 0xb0, 0xec, 0xd3, + 0x9b, 0xad, 0x0d, 0xb7, 0xab, 0x3a, 0xde, 0x86, 0xdb, 0x45, 0x81, 0x28, 0xec, 0x41, 0x8f, 0xee, + 0xf6, 0xa8, 0x1e, 0x71, 0xd3, 0xdb, 0x83, 0x30, 0x21, 0xaf, 0x4f, 0xfb, 0x8b, 0x47, 0x54, 0xd8, + 0x62, 0x0d, 0xb1, 0x13, 0xdc, 0x2e, 0x35, 0xb3, 0xe1, 0x09, 0xef, 0xa9, 0x52, 0x0e, 0x67, 0xf8, + 0x88, 0x91, 0x0e, 0x61, 0x4a, 0x87, 0x1d, 0x79, 0x29, 0x60, 0x71, 0x46, 0x53, 0xba, 0xbd, 0x2a, + 0xdf, 0x49, 0x9a, 0x52, 0xf5, 0x1b, 0x35, 0xb4, 0xf1, 0x27, 0x39, 0x38, 0xd5, 0xb6, 0xad, 0x8e, + 0xe5, 0x74, 0x4f, 0xee, 0x7e, 0x05, 0x72, 0x17, 0x4a, 0xbe, 0x6d, 0x75, 0xd8, 0x94, 0x87, 0xbe, + 0xe5, 0xc7, 0x10, 0xb5, 0x64, 0xa8, 0x70, 0x8c, 0xef, 0x94, 0x40, 0xdf, 0xa0, 0x48, 0x86, 0x50, + 0xed, 0x06, 0x27, 0xd0, 0x75, 0x95, 0xdf, 0x98, 0xe1, 0xa4, 0x52, 0xe2, 0x2c, 0xbb, 0xfa, 0x3a, + 0x21, 0x11, 0x23, 0x4d, 0x84, 0x25, 0xfb, 0xdc, 0xea, 0x8c, 0x7d, 0x4e, 0xa9, 0x1b, 0xef, 0x75, + 0x14, 0x8a, 0x7b, 0x9c, 0x0f, 0x66, 0xde, 0x50, 0x1f, 0xed, 0x95, 0x57, 0x11, 0x73, 0xf1, 0x8c, + 0x12, 0x5a, 0xa8, 0x70, 0xe8, 0x31, 0x5c, 0x65, 0x12, 0x65, 0x2b, 0x74, 0x32, 0x83, 0x72, 0x1f, + 0x25, 0x34, 0xf9, 0x52, 0x0e, 0xe6, 0xbd, 0x98, 0xcb, 0xa7, 0x5d, 0x97, 0x19, 0x37, 0x24, 0x27, + 0xfc, 0x47, 0xb5, 0xe1, 0x26, 0x4e, 0xc7, 0x84, 0x4a, 0xe1, 0x5f, 0x72, 0x8f, 0x3a, 0xfe, 0xae, + 0xeb, 0xf5, 0x99, 0xa7, 0x5d, 0xf2, 0xb5, 0x19, 0xc6, 0xd4, 0x56, 0x84, 0xa6, 0x82, 0x9c, 0x09, + 0x12, 0xc6, 0xb5, 0x19, 0x7d, 0xd0, 0xab, 0x74, 0x62, 0x26, 0xae, 0xad, 0x51, 0x1b, 0x1b, 0xae, + 0x3f, 0xdd, 0x78, 0x08, 0xef, 0x26, 0x89, 0x9d, 0xd3, 0xcd, 0xbc, 0x9f, 0xc6, 0xf8, 0xc7, 0x3c, + 0x08, 0xa7, 0x4e, 0x1d, 0x3b, 0x93, 0x77, 0x42, 0xb1, 0x76, 0xcf, 0x1a, 0xdc, 0x63, 0x9e, 0xb5, + 0x7b, 0xa0, 0xe7, 0xff, 0xd8, 0xb1, 0xb3, 0xb4, 0x04, 0x66, 0x94, 0x22, 0x6f, 0xc3, 0xbc, 0x49, + 0x57, 0x98, 0xc7, 0xa7, 0xf1, 0x6e, 0xe4, 0xc7, 0x59, 0x59, 0x8e, 0x8a, 0x63, 0x02, 0x8c, 0x6c, + 0x03, 0x98, 0x11, 0xf4, 0x91, 0x12, 0x67, 0xea, 0x9e, 0x9e, 0x08, 0x38, 0x06, 0x44, 0x10, 0xaa, + 0x3d, 0x21, 0x2a, 0x51, 0x8b, 0x47, 0x41, 0x95, 0x03, 0x7f, 0x3d, 0x28, 0x8b, 0x11, 0x8c, 0xf1, + 0x6f, 0x39, 0x88, 0x62, 0x3e, 0xc4, 0x87, 0x72, 0x47, 0xde, 0x00, 0xa1, 0x4d, 0xcf, 0xf4, 0xb1, + 0xb3, 0xe4, 0xed, 0x58, 0xca, 0x1f, 0x4c, 0xd2, 0x50, 0xab, 0x22, 0x5d, 0x28, 0xbc, 0xeb, 0xee, + 0xcc, 0x6c, 0x79, 0x62, 0x7b, 0x09, 0x55, 0xa0, 0x24, 0x46, 0x40, 0xa1, 0xc1, 0xf8, 0x72, 0x1e, + 0x6a, 0xb1, 0x3e, 0x3d, 0xf3, 0x25, 0x37, 0x0f, 0x53, 0x97, 0xdc, 0xb4, 0xa6, 0x5f, 0xbb, 0x44, + 0xb5, 0x3a, 0xe9, 0x7b, 0x6e, 0xfe, 0x3a, 0x0f, 0x85, 0xed, 0xd5, 0x35, 0xe1, 0x00, 0x84, 0x7b, + 0x0a, 0x67, 0xce, 0x43, 0x47, 0xd7, 0x79, 0xca, 0x9e, 0x16, 0x3e, 0x62, 0xa4, 0x83, 0xec, 0xc1, + 0xdc, 0xce, 0xd0, 0xb2, 0xb9, 0xe5, 0xcc, 0xbc, 0x83, 0x35, 0xb8, 0x13, 0x48, 0x6f, 0xbd, 0x53, + 0xa8, 0x18, 0xc0, 0x93, 0x2e, 0xcc, 0x75, 0xd5, 0x91, 0x32, 0x3d, 0xf6, 0x5e, 0x9f, 0x7e, 0x06, + 0x55, 0x38, 0x4a, 0x91, 0x7e, 0xc0, 0x00, 0xdd, 0xf8, 0x02, 0x68, 0x07, 0x84, 0xf8, 0x27, 0xd3, + 0x9a, 0xe1, 0x7a, 0x28, 0xab, 0x45, 0x8d, 0x7f, 0xcf, 0x41, 0xd2, 0x4a, 0xbf, 0xff, 0x1f, 0xb5, + 0x97, 0xfe, 0xa8, 0xab, 0xc7, 0x31, 0x06, 0xb2, 0xbf, 0xab, 0xf1, 0x17, 0x79, 0x28, 0xeb, 0x7b, + 0xae, 0x4f, 0x3e, 0xd9, 0xc9, 0x12, 0xc9, 0xce, 0x95, 0x19, 0x6f, 0xfd, 0x9d, 0x98, 0xea, 0xec, + 0xa7, 0x52, 0x9d, 0xb3, 0x5e, 0x2f, 0xfc, 0x84, 0x44, 0xe7, 0xdf, 0xe6, 0xe0, 0xb4, 0x12, 0xbc, + 0xed, 0xf8, 0x9c, 0x3a, 0xa6, 0x74, 0xcc, 0x55, 0xe0, 0x79, 0xe6, 0x48, 0xbe, 0xce, 0x3a, 0x49, + 0x97, 0x59, 0xfd, 0x46, 0x0d, 0x2d, 0x16, 0xec, 0x7b, 0xae, 0xcf, 0xa5, 0xb9, 0xcd, 0x27, 0x63, + 0x6a, 0x6f, 0x68, 0x3a, 0x86, 0x12, 0xe9, 0x60, 0x5d, 0x69, 0x72, 0xb0, 0xce, 0xf8, 0xa3, 0x1c, + 0xcc, 0xc7, 0x2f, 0x56, 0x9e, 0x3e, 0x6f, 0x9b, 0x4a, 0x9b, 0xe6, 0x4f, 0x20, 0x6d, 0xfa, 0xfd, + 0x1c, 0x40, 0x50, 0xd9, 0x13, 0x4f, 0x9a, 0x76, 0x92, 0x49, 0xd3, 0x99, 0x3f, 0x6b, 0x76, 0xca, + 0xf4, 0x3b, 0xa5, 0xe0, 0x95, 0x64, 0xc2, 0xf4, 0xab, 0x39, 0x38, 0x4d, 0x13, 0x49, 0xc8, 0x99, + 0x3d, 0x89, 0x54, 0x4e, 0x33, 0xbc, 0x88, 0x3a, 0x49, 0xc7, 0x94, 0x5a, 0x72, 0x03, 0xe6, 0x07, + 0x3a, 0x33, 0x74, 0x27, 0xea, 0x75, 0xe1, 0x61, 0x86, 0x56, 0x8c, 0x87, 0x09, 0xc9, 0x27, 0x24, + 0x7d, 0x0b, 0xc7, 0x92, 0xf4, 0x8d, 0xef, 0x2c, 0x2d, 0x3e, 0x76, 0x67, 0xa9, 0x03, 0xd5, 0x5d, + 0xcf, 0xed, 0xcb, 0xbc, 0xaa, 0xbe, 0xba, 0x74, 0xc6, 0x5c, 0x6d, 0x38, 0xa9, 0xac, 0x05, 0xb8, + 0x18, 0xa9, 0x10, 0xd3, 0x34, 0x77, 0x95, 0xb6, 0xf2, 0x71, 0x68, 0x0b, 0x87, 0xee, 0x96, 0x42, + 0xc5, 0x00, 0x3e, 0x99, 0x3b, 0x9d, 0x7b, 0x7f, 0x72, 0xa7, 0xc6, 0x3f, 0xe4, 0x03, 0x7b, 0xd1, + 0x4e, 0x9d, 0x6f, 0xcc, 0x4d, 0x38, 0xdf, 0xa8, 0x4f, 0xea, 0xc7, 0xb3, 0x8b, 0x2f, 0x43, 0xd9, + 0x63, 0xd4, 0x77, 0x1d, 0x7d, 0xe7, 0x45, 0x68, 0x6d, 0x51, 0x52, 0x51, 0x73, 0xe3, 0x59, 0xc8, + 0xfc, 0x13, 0xb2, 0x90, 0x1f, 0x89, 0x75, 0x08, 0xb5, 0xdd, 0x23, 0x1c, 0xdb, 0x19, 0x9d, 0x42, + 0xa6, 0x28, 0xf4, 0xbf, 0xc9, 0x94, 0xd2, 0x29, 0x0a, 0xfd, 0x4f, 0x2f, 0xa1, 0x04, 0xe9, 0xc0, + 0xbc, 0x4d, 0x7d, 0x2e, 0x23, 0x63, 0x9d, 0x65, 0x3e, 0x45, 0x8a, 0x33, 0x1c, 0x36, 0x1b, 0x31, + 0x1c, 0x4c, 0xa0, 0x1a, 0xbf, 0x95, 0x83, 0xa8, 0xc9, 0x8f, 0x18, 0xa1, 0x7d, 0x0b, 0x2a, 0x7d, + 0xfa, 0x70, 0x95, 0xd9, 0xf4, 0x60, 0x96, 0x9b, 0xf4, 0x36, 0x35, 0x06, 0x86, 0x68, 0xc6, 0x28, + 0x07, 0xfa, 0xf4, 0x3f, 0x61, 0x50, 0xda, 0xb5, 0x1e, 0xea, 0xfa, 0xcc, 0xe2, 0xa9, 0xc4, 0xee, + 0xef, 0x54, 0x31, 0x0e, 0x49, 0x40, 0x85, 0x4e, 0xfa, 0x30, 0xe7, 0xab, 0x10, 0x94, 0x7e, 0x95, + 0xe9, 0x57, 0xe5, 0x89, 0x50, 0x96, 0xbe, 0x3a, 0x40, 0x91, 0x30, 0xd0, 0xd1, 0x5c, 0xfc, 0xde, + 0x0f, 0xaf, 0x3c, 0xf7, 0xfd, 0x1f, 0x5e, 0x79, 0xee, 0x07, 0x3f, 0xbc, 0xf2, 0xdc, 0x17, 0x0f, + 0xaf, 0xe4, 0xbe, 0x77, 0x78, 0x25, 0xf7, 0xfd, 0xc3, 0x2b, 0xb9, 0x1f, 0x1c, 0x5e, 0xc9, 0xfd, + 0xcb, 0xe1, 0x95, 0xdc, 0x6f, 0xfe, 0xeb, 0x95, 0xe7, 0x3e, 0x5b, 0x09, 0x30, 0xff, 0x3b, 0x00, + 0x00, 0xff, 0xff, 0xaf, 0x9a, 0x79, 0x96, 0xbd, 0x6a, 0x00, 0x00, } func (m *AbstractPodTemplate) Marshal() (dAtA []byte, err error) { @@ -5670,6 +5705,69 @@ func (m *RedisSettings) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RedisStreamsSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RedisStreamsSource) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RedisStreamsSource) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TLS != nil { + { + size, err := m.TLS.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + i-- + if m.ReadFromBeginning { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + i -= len(m.ConsumerGroup) + copy(dAtA[i:], m.ConsumerGroup) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ConsumerGroup))) + i-- + dAtA[i] = 0x1a + i -= len(m.Stream) + copy(dAtA[i:], m.Stream) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Stream))) + i-- + dAtA[i] = 0x12 + { + size, err := m.RedisConfig.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *SASL) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -5996,6 +6094,18 @@ func (m *Source) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x32 + } + if m.RedisStreams != nil { + { + size, err := m.RedisStreams.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x2a } if m.Nats != nil { @@ -8013,6 +8123,26 @@ func (m *RedisSettings) Size() (n int) { return n } +func (m *RedisStreamsSource) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.RedisConfig.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Stream) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ConsumerGroup) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + if m.TLS != nil { + l = m.TLS.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *SASL) Size() (n int) { if m == nil { return 0 @@ -8151,6 +8281,10 @@ func (m *Source) Size() (n int) { l = m.Nats.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.RedisStreams != nil { + l = m.RedisStreams.Size() + n += 1 + l + sovGenerated(uint64(l)) + } if m.UDTransformer != nil { l = m.UDTransformer.Size() n += 1 + l + sovGenerated(uint64(l)) @@ -9278,6 +9412,20 @@ func (this *RedisSettings) String() string { }, "") return s } +func (this *RedisStreamsSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RedisStreamsSource{`, + `RedisConfig:` + strings.Replace(strings.Replace(this.RedisConfig.String(), "RedisConfig", "RedisConfig", 1), `&`, ``, 1) + `,`, + `Stream:` + fmt.Sprintf("%v", this.Stream) + `,`, + `ConsumerGroup:` + fmt.Sprintf("%v", this.ConsumerGroup) + `,`, + `ReadFromBeginning:` + fmt.Sprintf("%v", this.ReadFromBeginning) + `,`, + `TLS:` + strings.Replace(this.TLS.String(), "TLS", "TLS", 1) + `,`, + `}`, + }, "") + return s +} func (this *SASL) String() string { if this == nil { return "nil" @@ -9354,6 +9502,7 @@ func (this *Source) String() string { `Kafka:` + strings.Replace(this.Kafka.String(), "KafkaSource", "KafkaSource", 1) + `,`, `HTTP:` + strings.Replace(this.HTTP.String(), "HTTPSource", "HTTPSource", 1) + `,`, `Nats:` + strings.Replace(this.Nats.String(), "NatsSource", "NatsSource", 1) + `,`, + `RedisStreams:` + strings.Replace(this.RedisStreams.String(), "RedisStreamsSource", "RedisStreamsSource", 1) + `,`, `UDTransformer:` + strings.Replace(this.UDTransformer.String(), "UDTransformer", "UDTransformer", 1) + `,`, `}`, }, "") @@ -19962,6 +20111,209 @@ func (m *RedisSettings) Unmarshal(dAtA []byte) error { } return nil } +func (m *RedisStreamsSource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RedisStreamsSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RedisStreamsSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedisConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RedisConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Stream", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Stream = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsumerGroup", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsumerGroup = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadFromBeginning", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ReadFromBeginning = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TLS", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TLS == nil { + m.TLS = &TLS{} + } + if err := m.TLS.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *SASL) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -20995,6 +21347,42 @@ func (m *Source) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedisStreams", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RedisStreams == nil { + m.RedisStreams = &RedisStreamsSource{} + } + if err := m.RedisStreams.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UDTransformer", wireType) } diff --git a/pkg/apis/numaflow/v1alpha1/generated.proto b/pkg/apis/numaflow/v1alpha1/generated.proto index e13a349361..d583c22f7f 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.proto +++ b/pkg/apis/numaflow/v1alpha1/generated.proto @@ -934,6 +934,21 @@ message RedisSettings { optional string sentinel = 4; } +message RedisStreamsSource { + // RedisConfig contains connectivity info + optional RedisConfig redisConfig = 1; + + optional string stream = 2; + + optional string consumerGroup = 3; + + // if true, stream starts being read from the beginning; otherwise, the latest + optional bool readFromBeginning = 4; + + // +optional + optional TLS tls = 5; +} + message SASL { // SASL mechanism to use optional string mechanism = 1; @@ -1040,7 +1055,10 @@ message Source { optional NatsSource nats = 4; // +optional - optional UDTransformer transformer = 5; + optional RedisStreamsSource redisStreams = 5; + + // +optional + optional UDTransformer transformer = 6; } // Status is a common structure which can be used for Status field. diff --git a/pkg/apis/numaflow/v1alpha1/openapi_generated.go b/pkg/apis/numaflow/v1alpha1/openapi_generated.go index fedbd8797e..073fd3c340 100644 --- a/pkg/apis/numaflow/v1alpha1/openapi_generated.go +++ b/pkg/apis/numaflow/v1alpha1/openapi_generated.go @@ -80,6 +80,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisBufferService": schema_pkg_apis_numaflow_v1alpha1_RedisBufferService(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisConfig": schema_pkg_apis_numaflow_v1alpha1_RedisConfig(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisSettings": schema_pkg_apis_numaflow_v1alpha1_RedisSettings(ref), + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisStreamsSource": schema_pkg_apis_numaflow_v1alpha1_RedisStreamsSource(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASL": schema_pkg_apis_numaflow_v1alpha1_SASL(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLPlain": schema_pkg_apis_numaflow_v1alpha1_SASLPlain(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale": schema_pkg_apis_numaflow_v1alpha1_Scale(ref), @@ -3164,6 +3165,88 @@ func schema_pkg_apis_numaflow_v1alpha1_RedisSettings(ref common.ReferenceCallbac } } +func schema_pkg_apis_numaflow_v1alpha1_RedisStreamsSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "Redis URL", + Type: []string{"string"}, + Format: "", + }, + }, + "sentinelUrl": { + SchemaProps: spec.SchemaProps{ + Description: "Sentinel URL, will be ignored if Redis URL is provided", + Type: []string{"string"}, + Format: "", + }, + }, + "masterName": { + SchemaProps: spec.SchemaProps{ + Description: "Only required when Sentinel is used", + Type: []string{"string"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "Redis user", + Type: []string{"string"}, + Format: "", + }, + }, + "password": { + SchemaProps: spec.SchemaProps{ + Description: "Redis password secret selector", + Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), + }, + }, + "sentinelPassword": { + SchemaProps: spec.SchemaProps{ + Description: "Sentinel password secret selector", + Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), + }, + }, + "stream": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "consumerGroup": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readFromBeginning": { + SchemaProps: spec.SchemaProps{ + Description: "if true, stream starts being read from the beginning; otherwise, the latest", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "tls": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.TLS"), + }, + }, + }, + Required: []string{"stream", "consumerGroup", "readFromBeginning"}, + }, + }, + Dependencies: []string{ + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.TLS", "k8s.io/api/core/v1.SecretKeySelector"}, + } +} + func schema_pkg_apis_numaflow_v1alpha1_SASL(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -3401,6 +3484,11 @@ func schema_pkg_apis_numaflow_v1alpha1_Source(ref common.ReferenceCallback) comm Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.NatsSource"), }, }, + "redisStreams": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisStreamsSource"), + }, + }, "transformer": { SchemaProps: spec.SchemaProps{ Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDTransformer"), @@ -3410,7 +3498,7 @@ func schema_pkg_apis_numaflow_v1alpha1_Source(ref common.ReferenceCallback) comm }, }, Dependencies: []string{ - "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.GeneratorSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.HTTPSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.NatsSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDTransformer"}, + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.GeneratorSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.HTTPSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.NatsSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RedisStreamsSource", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDTransformer"}, } } diff --git a/pkg/apis/numaflow/v1alpha1/redis_streams_source.go b/pkg/apis/numaflow/v1alpha1/redis_streams_source.go new file mode 100644 index 0000000000..60f449e7a8 --- /dev/null +++ b/pkg/apis/numaflow/v1alpha1/redis_streams_source.go @@ -0,0 +1,28 @@ +/* +Copyright 2022 The Numaproj 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 + +type RedisStreamsSource struct { + // RedisConfig contains connectivity info + RedisConfig `json:",inline" protobuf:"bytes,1,opt,name=redisConfig"` + Stream string `json:"stream" protobuf:"bytes,2,opt,name=stream"` + ConsumerGroup string `json:"consumerGroup" protobuf:"bytes,3,opt,name=consumerGroup"` + // if true, stream starts being read from the beginning; otherwise, the latest + ReadFromBeginning bool `json:"readFromBeginning" protobuf:"bytes,4,opt,name=readFromBeginning"` + // +optional + TLS *TLS `json:"tls" protobuf:"bytes,5,opt,name=tls"` +} diff --git a/pkg/apis/numaflow/v1alpha1/source.go b/pkg/apis/numaflow/v1alpha1/source.go index 6af2edfc8e..919a6edcbc 100644 --- a/pkg/apis/numaflow/v1alpha1/source.go +++ b/pkg/apis/numaflow/v1alpha1/source.go @@ -35,7 +35,9 @@ type Source struct { // +optional Nats *NatsSource `json:"nats,omitempty" protobuf:"bytes,4,opt,name=nats"` // +optional - UDTransformer *UDTransformer `json:"transformer,omitempty" protobuf:"bytes,5,opt,name=transformer"` + RedisStreams *RedisStreamsSource `json:"redisStreams,omitempty" protobuf:"bytes,5,opt,name=redisStreams"` + // +optional + UDTransformer *UDTransformer `json:"transformer,omitempty" protobuf:"bytes,6,opt,name=transformer"` } func (s Source) getContainers(req getContainerReq) ([]corev1.Container, error) { diff --git a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go index 857df59bff..67cad8e838 100644 --- a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go @@ -1537,6 +1537,28 @@ func (in *RedisSettings) DeepCopy() *RedisSettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisStreamsSource) DeepCopyInto(out *RedisStreamsSource) { + *out = *in + in.RedisConfig.DeepCopyInto(&out.RedisConfig) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLS) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisStreamsSource. +func (in *RedisStreamsSource) DeepCopy() *RedisStreamsSource { + if in == nil { + return nil + } + out := new(RedisStreamsSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SASL) DeepCopyInto(out *SASL) { *out = *in @@ -1735,6 +1757,11 @@ func (in *Source) DeepCopyInto(out *Source) { *out = new(NatsSource) (*in).DeepCopyInto(*out) } + if in.RedisStreams != nil { + in, out := &in.RedisStreams, &out.RedisStreams + *out = new(RedisStreamsSource) + (*in).DeepCopyInto(*out) + } if in.UDTransformer != nil { in, out := &in.UDTransformer, &out.UDTransformer *out = new(UDTransformer) diff --git a/pkg/isb/stores/redis/read.go b/pkg/isb/stores/redis/read.go index 0c0ec41519..7527240171 100644 --- a/pkg/isb/stores/redis/read.go +++ b/pkg/isb/stores/redis/read.go @@ -18,7 +18,6 @@ package redis import ( "context" - "errors" "fmt" "sync" "time" @@ -34,14 +33,8 @@ import ( // BufferRead is the read queue implementation powered by RedisClient. type BufferRead struct { - Name string - Stream string - Group string - Consumer string + *redisclient.RedisStreamsRead *BufferReadInfo - *redisclient.RedisClient - options - log *zap.SugaredLogger } // BufferReadInfo will contain the buffer information from the reader point of view. @@ -55,33 +48,73 @@ type BufferReadInfo struct { var _ isb.BufferReader = (*BufferRead)(nil) // NewBufferRead returns a new redis buffer reader. -func NewBufferRead(ctx context.Context, client *redisclient.RedisClient, name string, group string, consumer string, opts ...Option) isb.BufferReader { - options := &options{ - infoRefreshInterval: time.Second, - readTimeOut: time.Second, - checkBackLog: true, +func NewBufferRead(ctx context.Context, client *redisclient.RedisClient, name string, group string, consumer string, opts ...redisclient.Option) isb.BufferReader { + options := &redisclient.Options{ + InfoRefreshInterval: time.Second, + ReadTimeOut: time.Second, + CheckBackLog: true, } for _, o := range opts { - o.apply(options) + o.Apply(options) } rqr := &BufferRead{ - Name: name, - Stream: redisclient.GetRedisStreamName(name), - Group: group, - Consumer: consumer, - RedisClient: client, + RedisStreamsRead: &redisclient.RedisStreamsRead{ + Name: name, + Stream: redisclient.GetRedisStreamName(name), + Group: group, + Consumer: consumer, + RedisClient: client, + Options: *options, + Metrics: redisclient.Metrics{ + ReadErrorsInc: func() { + labels := map[string]string{"buffer": name} + isbReadErrors.With(labels).Inc() + }, + }, + }, BufferReadInfo: &BufferReadInfo{ rwLock: new(sync.RWMutex), isEmpty: true, lag: atomic.NewDuration(0), refreshEmptyError: atomic.NewUint32(0), }, - options: *options, // checkBackLog is set to true as on start up we need to start from the beginning } - rqr.log = logging.FromContext(ctx).With("BufferReader", rqr.GetName()) + + // this function describes how to derive messages from the XSstream + rqr.XStreamToMessages = func(xstreams []redis.XStream, messages []*isb.ReadMessage, labels map[string]string) ([]*isb.ReadMessage, error) { + // for each XMessage in []XStream + for _, xstream := range xstreams { + for _, message := range xstream.Messages { + var readOffset = message.ID + + // our messages have only one field/value pair (i.e., header/payload) + if len(message.Values) != 1 { + if rqr.Metrics.ReadErrorsInc != nil { + rqr.Metrics.ReadErrorsInc() + } + return messages, fmt.Errorf("expected only 1 pair of field/value in stream %+v", message.Values) + } + for f, v := range message.Values { + msg, err := getHeaderAndBody(f, v) + if err != nil { + return messages, fmt.Errorf("%w", err) + } + readMessage := isb.ReadMessage{ + Message: msg, + ReadOffset: isb.SimpleStringOffset(func() string { return readOffset }), + } + messages = append(messages, &readMessage) + } + } + } + + return messages, nil + } + + rqr.Log = logging.FromContext(ctx).With("BufferReader", rqr.GetName()) // updateIsEmptyFlag is used to update isEmpty flag once rqr.updateIsEmptyFlag(ctx) @@ -90,11 +123,21 @@ func NewBufferRead(ctx context.Context, client *redisclient.RedisClient, name st return rqr } +func getHeaderAndBody(field string, value interface{}) (msg isb.Message, err error) { + err = msg.Header.UnmarshalBinary([]byte(field)) + if err != nil { + return msg, fmt.Errorf("header unmarshal error %w", err) + } + + msg.Body.Payload = []byte(value.(string)) + return msg, nil +} + // refreshIsEmptyFlag is used to refresh the changes for isEmpty func (br *BufferRead) refreshIsEmptyFlag(ctx context.Context) { - ticker := time.NewTicker(br.options.infoRefreshInterval) + ticker := time.NewTicker(br.Options.InfoRefreshInterval) defer ticker.Stop() - br.log.Infow("refreshIsEmptyFlag has started") + br.Log.Infow("refreshIsEmptyFlag has started") for { select { case <-ctx.Done(): @@ -196,7 +239,7 @@ func (br *BufferRead) updateIsEmptyFlag(_ context.Context) { // string comparison of lastGenerated and lastDelivered if lastGenerated == lastDelivered { - br.log.Debugw("Is Empty") + br.Log.Debugw("Is Empty") br.setIsEmptyFlag(true) isbIsEmpty.With(labels).Inc() return @@ -214,117 +257,10 @@ func (br *BufferRead) setIsEmptyFlag(flag bool) { // setError is used to set error cases func (br *BufferRead) setError(errMsg string, err error) { - br.log.Errorw(errMsg, zap.Error(err)) + br.Log.Errorw(errMsg, zap.Error(err)) br.BufferReadInfo.refreshEmptyError.Inc() br.setIsEmptyFlag(false) } - -// Read reads the messages from the stream. -// During a restart, we need to make sure all the un-acknowledged messages are reprocessed. -// we need to replace `>` with `0-0` during restarts. We might run into data loss otherwise. -func (br *BufferRead) Read(_ context.Context, count int64) ([]*isb.ReadMessage, error) { - var messages = make([]*isb.ReadMessage, 0, count) - var xstreams []redis.XStream - var err error - // start with 0-0 if checkBackLog is true - labels := map[string]string{"buffer": br.GetName()} - if br.options.checkBackLog { - xstreams, err = br.processXReadResult("0-0", count) - if err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, redis.Nil) { - br.log.Debugw("checkBacklog true, redis.Nil", zap.Error(err)) - return messages, nil - } - isbReadErrors.With(labels).Inc() - // we should try to do our best effort to convert our data here, if there is data available in xstream from the previous loop - messages, errMsg := br.convertXStreamToMessages(xstreams, messages, labels) - br.log.Errorw("checkBacklog true, convertXStreamToMessages failed", zap.Error(errMsg)) - return messages, fmt.Errorf("XReadGroup failed, %w", err) - } - - // NOTE: If all messages have been delivered and acknowledged, the XREADGROUP 0-0 call returns an empty - // list of messages in the stream. At this point we want to read everything from last delivered which would be > - if len(xstreams) == 1 && len(xstreams[0].Messages) == 0 { - br.log.Infow("We have delivered and acknowledged all PENDING msgs, setting checkBacklog to false") - br.checkBackLog = false - } - } - if !br.options.checkBackLog { - xstreams, err = br.processXReadResult(">", count) - if err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, redis.Nil) { - br.log.Debugw("checkBacklog false, redis.Nil", zap.Error(err)) - return messages, nil - } - isbReadErrors.With(labels).Inc() - // we should try to do our best effort to convert our data here, if there is data available in xstream from the previous loop - messages, errMsg := br.convertXStreamToMessages(xstreams, messages, labels) - br.log.Errorw("checkBacklog false, convertXStreamToMessages failed", zap.Error(errMsg)) - return messages, fmt.Errorf("XReadGroup failed, %w", err) - } - } - - // for each XMessage in []XStream - return br.convertXStreamToMessages(xstreams, messages, labels) -} - -// Ack acknowledges the offset to the read queue. Ack is always pipelined, if you want to avoid it then -// send array of 1 element. -func (br *BufferRead) Ack(_ context.Context, offsets []isb.Offset) []error { - errs := make([]error, len(offsets)) - strOffsets := []string{} - for _, o := range offsets { - strOffsets = append(strOffsets, o.String()) - } - if err := br.Client.XAck(redisclient.RedisContext, br.Stream, br.Group, strOffsets...).Err(); err != nil { - for i := 0; i < len(offsets); i++ { - errs[i] = err - } - } - return errs -} - -// processXReadResult is used to process the results of XREADGROUP -func (br *BufferRead) processXReadResult(startIndex string, count int64) ([]redis.XStream, error) { - result := br.Client.XReadGroup(redisclient.RedisContext, &redis.XReadGroupArgs{ - Group: br.Group, - Consumer: br.Consumer, - Streams: []string{br.Stream, startIndex}, - Count: count, - Block: br.options.readTimeOut, - }) - return result.Result() -} - -// convertXStreamToMessages is used to convert xstreams to messages -func (br *BufferRead) convertXStreamToMessages(xstreams []redis.XStream, messages []*isb.ReadMessage, labels map[string]string) ([]*isb.ReadMessage, error) { - // for each XMessage in []XStream - for _, xstream := range xstreams { - for _, message := range xstream.Messages { - var readOffset = message.ID - - // our messages have only one field/value pair (i.e., header/payload) - if len(message.Values) != 1 { - isbReadErrors.With(labels).Inc() - return messages, fmt.Errorf("expected only 1 pair of field/value in stream %+v", message.Values) - } - for f, v := range message.Values { - msg, err := getHeaderAndBody(f, v) - if err != nil { - return messages, fmt.Errorf("%w", err) - } - readMessage := isb.ReadMessage{ - Message: msg, - ReadOffset: isb.SimpleStringOffset(func() string { return readOffset }), - } - messages = append(messages, &readMessage) - } - } - } - - return messages, nil -} - func (br *BufferRead) Pending(_ context.Context) (int64, error) { // TODO: not implemented return isb.PendingNotAvailable, nil @@ -334,28 +270,3 @@ func (br *BufferRead) Rate(_ context.Context) (float64, error) { // TODO: not implemented return isb.RateNotAvailable, nil } - -func getHeaderAndBody(field string, value interface{}) (msg isb.Message, err error) { - err = msg.Header.UnmarshalBinary([]byte(field)) - if err != nil { - return msg, fmt.Errorf("header unmarshal error %w", err) - } - - msg.Body.Payload = []byte(value.(string)) - return msg, nil -} - -// GetName returns name for the buffer. -func (br *BufferRead) GetName() string { - return br.Name -} - -// GetStreamName returns the stream name. -func (br *BufferRead) GetStreamName() string { - return br.Stream -} - -// GetGroupName gets the name of the consumer group. -func (br *BufferRead) GetGroupName() string { - return br.Group -} diff --git a/pkg/isb/stores/redis/read_test.go b/pkg/isb/stores/redis/read_test.go index 3c0c98ee33..99dc11a3b9 100644 --- a/pkg/isb/stores/redis/read_test.go +++ b/pkg/isb/stores/redis/read_test.go @@ -108,11 +108,11 @@ func TestRedisCheckBacklog(t *testing.T) { readMessages, err := rqr.Read(ctx, count) // check if backlog is set to false - assert.False(t, rqr.checkBackLog) + assert.False(t, rqr.CheckBackLog) assert.NoErrorf(t, err, "rqr.Read failed, %s", err) assert.Len(t, readMessages, int(count)) - rqr.options.checkBackLog = true + rqr.Options.CheckBackLog = true vertex := &dfv1.Vertex{Spec: dfv1.VertexSpec{ PipelineName: "testPipeline", @@ -121,7 +121,7 @@ func TestRedisCheckBacklog(t *testing.T) { }, }} - rqw, _ := NewBufferWrite(ctx, client, "toStream", "toGroup", WithInfoRefreshInterval(2*time.Millisecond), WithLagDuration(time.Minute)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, "toStream", "toGroup", redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithLagDuration(time.Minute)).(*BufferWrite) err = client.CreateStreamGroup(ctx, rqw.GetStreamName(), "toGroup", redisclient.ReadFromEarliest) assert.NoError(t, err) @@ -308,7 +308,7 @@ func (suite *ReadWritePerformance) SetupSuite() { toGroup := "ReadWritePerformance-group-to" consumer := "ReadWritePerformance-con-0" count := int64(10000) - rqw, _ := NewBufferWrite(ctx, client, toStream, toGroup, WithInfoRefreshInterval(2*time.Millisecond), WithLagDuration(time.Minute), WithMaxLength(20000)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, toStream, toGroup, redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithLagDuration(time.Minute), redisclient.WithMaxLength(20000)).(*BufferWrite) rqr, _ := NewBufferRead(ctx, client, fromStream, fromGroup, consumer).(*BufferRead) toSteps := map[string]isb.BufferWriter{ @@ -398,7 +398,7 @@ func (suite *ReadWritePerformance) TestReadWriteLatency() { // TestReadWriteLatencyPipelining is performs wthe latency test during a forward. func (suite *ReadWritePerformance) TestReadWriteLatencyPipelining() { - suite.rqw, _ = NewBufferWrite(suite.ctx, suite.rclient, "ReadWritePerformance-to", "ReadWritePerformance-group-to", WithInfoRefreshInterval(2*time.Second), WithLagDuration(time.Minute), WithoutPipelining(), WithMaxLength(20000)).(*BufferWrite) + suite.rqw, _ = NewBufferWrite(suite.ctx, suite.rclient, "ReadWritePerformance-to", "ReadWritePerformance-group-to", redisclient.WithInfoRefreshInterval(2*time.Second), redisclient.WithLagDuration(time.Minute), redisclient.WithoutPipelining(), redisclient.WithMaxLength(20000)).(*BufferWrite) _ = NewBufferRead(suite.ctx, suite.rclient, "ReadWritePerformance-to", "ReadWritePerformance-group-to", "consumer-0") vertex := &dfv1.Vertex{Spec: dfv1.VertexSpec{ diff --git a/pkg/isb/stores/redis/write.go b/pkg/isb/stores/redis/write.go index 297bd76aa5..1c704b41c6 100644 --- a/pkg/isb/stores/redis/write.go +++ b/pkg/isb/stores/redis/write.go @@ -46,7 +46,7 @@ type BufferWrite struct { Group string *BufferWriteInfo *redisclient.RedisClient - options + redisclient.Options log *zap.SugaredLogger } @@ -65,19 +65,19 @@ type BufferWriteInfo struct { var _ isb.BufferWriter = (*BufferWrite)(nil) // NewBufferWrite returns a new redis queue writer. -func NewBufferWrite(ctx context.Context, client *redisclient.RedisClient, name string, group string, opts ...Option) isb.BufferWriter { - options := &options{ - pipelining: true, - infoRefreshInterval: time.Second, - lagDuration: time.Duration(0), - maxLength: dfv1.DefaultBufferLength, - bufferUsageLimit: dfv1.DefaultBufferUsageLimit, - refreshBufferWriteInfo: true, - bufferFullWritingStrategy: dfv1.RetryUntilSuccess, +func NewBufferWrite(ctx context.Context, client *redisclient.RedisClient, name string, group string, opts ...redisclient.Option) isb.BufferWriter { + options := &redisclient.Options{ + Pipelining: true, + InfoRefreshInterval: time.Second, + LagDuration: time.Duration(0), + MaxLength: dfv1.DefaultBufferLength, + BufferUsageLimit: dfv1.DefaultBufferUsageLimit, + RefreshBufferWriteInfo: true, + BufferFullWritingStrategy: dfv1.RetryUntilSuccess, } for _, o := range opts { - o.apply(options) + o.Apply(options) } // check whether the script exists, if not then load @@ -91,20 +91,20 @@ func NewBufferWrite(ctx context.Context, client *redisclient.RedisClient, name s consumerLag: atomic.NewDuration(0), minId: atomic.NewString("0-0"), // During start up if we set pending count to 0 we are saying nothing is pending. - pendingCount: atomic.NewInt64(options.maxLength), - bufferLength: atomic.NewInt64(options.maxLength), + pendingCount: atomic.NewInt64(options.MaxLength), + bufferLength: atomic.NewInt64(options.MaxLength), hasUnprocessedData: atomic.NewBool(true), }, RedisClient: client, - options: *options, } + rqw.Options = *options rqw.log = logging.FromContext(ctx).With("bufferWriter", rqw.GetName()) // setWriteInfo is used to update isFull flag and minId once rqw.setWriteInfo(ctx) - if rqw.options.refreshBufferWriteInfo { + if rqw.Options.RefreshBufferWriteInfo { // refresh isFull flag at a periodic interval go rqw.refreshWriteInfo(ctx) } @@ -114,7 +114,7 @@ func NewBufferWrite(ctx context.Context, client *redisclient.RedisClient, name s // refreshWriteInfo is used to refresh the changes func (bw *BufferWrite) refreshWriteInfo(ctx context.Context) { - ticker := time.NewTicker(bw.options.infoRefreshInterval) + ticker := time.NewTicker(bw.Options.InfoRefreshInterval) defer ticker.Stop() bw.log.Infow("refreshWriteInfo has started") for { @@ -161,7 +161,7 @@ func (bw *BufferWrite) Write(_ context.Context, messages []isb.Message) ([]isb.O isbIsFull.With(labels).Inc() // when buffer is full, we need to decide whether to discard the message or not. - switch bw.bufferFullWritingStrategy { + switch bw.BufferFullWritingStrategy { case dfv1.DiscardLatest: // user explicitly wants to discard the message when buffer if full. // return no retryable error as a callback to let caller know that the message is discarded. @@ -174,7 +174,7 @@ func (bw *BufferWrite) Write(_ context.Context, messages []isb.Message) ([]isb.O return nil, errs } // Maybe just do pipelined write, always? - if !bw.pipelining { + if !bw.Pipelining { for idx, message := range messages { // Reference the Payload in Body directly when writing to Redis ISB to avoid extra marshaling. // TODO: revisit directly Payload reference when Body structure changes diff --git a/pkg/isb/stores/redis/write_info.go b/pkg/isb/stores/redis/write_info.go index c8d56076da..27fde2ca5e 100644 --- a/pkg/isb/stores/redis/write_info.go +++ b/pkg/isb/stores/redis/write_info.go @@ -60,7 +60,7 @@ func (bw *BufferWrite) updateIsFullAndLag(ctx context.Context) { // consumerLag as metric isbConsumerLag.With(labels).Set(float64(consumerLag)) - lagDuration := bw.lagDuration.Milliseconds() + lagDuration := bw.LagDuration.Milliseconds() // if specified consumerLag duration is 0, that means we do not want to use consumerLag to determine isFull. // if lagDuration is specified we use that to compare against the consumer consumerLag (lastGenerated - lastDelivered) @@ -87,7 +87,7 @@ func (bw *BufferWrite) updateIsFullAndLag(ctx context.Context) { } isbBufferUsage.With(labels).Set(usage) - if usage >= bw.bufferUsageLimit { + if usage >= bw.BufferUsageLimit { bw.log.Infow("usage is greater than bufferUsageLimit", zap.Float64("usage", usage)) bw.setIsFull(true) return @@ -108,7 +108,7 @@ func (bw *BufferWrite) getUsage(ctx context.Context) (float64, error) { // set stream length bw.setBufferLength(streamLen) - maxLen := bw.maxLength + maxLen := bw.MaxLength var usage = float64(streamLen) / float64(maxLen) return usage, err diff --git a/pkg/isb/stores/redis/write_test.go b/pkg/isb/stores/redis/write_test.go index 9935dffabb..8a263affe7 100644 --- a/pkg/isb/stores/redis/write_test.go +++ b/pkg/isb/stores/redis/write_test.go @@ -40,7 +40,7 @@ func TestRedisQWrite_Write(t *testing.T) { client := redisclient.NewRedisClient(redisOptions) ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) defer cancel() - rqw, _ := NewBufferWrite(ctx, client, "rediswrite", "test", WithLagDuration(2*time.Millisecond), WithInfoRefreshInterval(2*time.Millisecond)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, "rediswrite", "test", redisclient.WithLagDuration(2*time.Millisecond), redisclient.WithInfoRefreshInterval(2*time.Millisecond)).(*BufferWrite) for rqw.IsFull() { select { @@ -91,7 +91,7 @@ func TestRedisQWrite_WithPipeline(t *testing.T) { ctx := context.Background() stream := "withPipeline" count := int64(100) - rqw, _ := NewBufferWrite(ctx, client, stream, "test", WithoutPipelining()).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, stream, "test", redisclient.WithoutPipelining()).(*BufferWrite) streamName := rqw.GetStreamName() defer func() { _ = client.DeleteKeys(ctx, streamName) }() group := "withPipeline-group" @@ -144,7 +144,7 @@ func TestRedisQWrite_WithInfoRefreshInterval(t *testing.T) { stream := "withInfoRefreshInterval" count := int64(10) group := "withInfoRefreshInterval-group" - rqw, _ := NewBufferWrite(ctx, client, stream, group, WithInfoRefreshInterval(2*time.Millisecond), WithLagDuration(2*time.Millisecond), WithMaxLength(10)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, stream, group, redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithLagDuration(2*time.Millisecond), redisclient.WithMaxLength(10)).(*BufferWrite) err := client.CreateStreamGroup(ctx, rqw.GetStreamName(), group, redisclient.ReadFromEarliest) if err != nil { t.Fatalf("error creating consumer group: %s", err) @@ -184,7 +184,7 @@ func TestRedisQWrite_WithInfoRefreshInterval_WithBufferFullWritingStrategyIsDisc stream := "withInfoRefreshInterval" count := int64(10) group := "withInfoRefreshInterval-group" - rqw, _ := NewBufferWrite(ctx, client, stream, group, WithInfoRefreshInterval(2*time.Millisecond), WithLagDuration(2*time.Millisecond), WithMaxLength(10), WithBufferFullWritingStrategy(dfv1.DiscardLatest)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, stream, group, redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithLagDuration(2*time.Millisecond), redisclient.WithMaxLength(10), redisclient.WithBufferFullWritingStrategy(dfv1.DiscardLatest)).(*BufferWrite) err := client.CreateStreamGroup(ctx, rqw.GetStreamName(), group, redisclient.ReadFromEarliest) if err != nil { t.Fatalf("error creating consumer group: %s", err) @@ -282,7 +282,7 @@ func Test_updateIsFullFlag(t *testing.T) { group := "getConsumerLag-group" count := int64(9) - rqw, _ := NewBufferWrite(ctx, client, stream, group, WithInfoRefreshInterval(2*time.Millisecond), WithMaxLength(10)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, stream, group, redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithMaxLength(10)).(*BufferWrite) err := client.CreateStreamGroup(ctx, rqw.GetStreamName(), group, redisclient.ReadFromEarliest) defer func() { _ = client.DeleteStreamGroup(ctx, rqw.GetStreamName(), group) }() @@ -365,9 +365,9 @@ func TestNewInterStepDataForwardRedis(t *testing.T) { toStream := "toStep" // fromStep, we also have to create a fromStepWrite here to write data to the from stream - fromStep, _ := NewBufferRead(ctx, client, fromStream, fromGroup, consumer, WithInfoRefreshInterval(2*time.Millisecond)).(*BufferRead) + fromStep, _ := NewBufferRead(ctx, client, fromStream, fromGroup, consumer, redisclient.WithInfoRefreshInterval(2*time.Millisecond)).(*BufferRead) _ = client.CreateStreamGroup(ctx, fromStep.GetStreamName(), fromGroup, redisclient.ReadFromEarliest) - fromStepWrite, _ := NewBufferWrite(ctx, client, fromStream, fromGroup, WithInfoRefreshInterval(2*time.Second), WithLagDuration(1*time.Millisecond)).(*BufferWrite) + fromStepWrite, _ := NewBufferWrite(ctx, client, fromStream, fromGroup, redisclient.WithInfoRefreshInterval(2*time.Second), redisclient.WithLagDuration(1*time.Millisecond)).(*BufferWrite) defer func() { _ = client.DeleteKeys(ctx, fromStepWrite.GetStreamName()) }() defer func() { _ = client.DeleteKeys(ctx, fromStep.GetStreamName()) }() defer func() { _ = client.DeleteStreamGroup(ctx, fromStep.GetStreamName(), fromGroup) }() @@ -375,7 +375,7 @@ func TestNewInterStepDataForwardRedis(t *testing.T) { // toStep, we also have to create a toStepRead here to read from the toStep to1Read, _ := NewBufferRead(ctx, client, toStream, toGroup, consumer).(*BufferRead) _ = client.CreateStreamGroup(ctx, to1Read.GetStreamName(), toGroup, redisclient.ReadFromEarliest) - to1, _ := NewBufferWrite(ctx, client, toStream, toGroup, WithLagDuration(1*time.Millisecond), WithInfoRefreshInterval(2*time.Second), WithMaxLength(17)).(*BufferWrite) + to1, _ := NewBufferWrite(ctx, client, toStream, toGroup, redisclient.WithLagDuration(1*time.Millisecond), redisclient.WithInfoRefreshInterval(2*time.Second), redisclient.WithMaxLength(17)).(*BufferWrite) defer func() { _ = client.DeleteKeys(ctx, to1.GetStreamName()) }() defer func() { _ = client.DeleteKeys(ctx, to1.GetStreamName()) }() defer func() { _ = client.DeleteStreamGroup(ctx, to1.GetStreamName(), toGroup) }() @@ -418,12 +418,12 @@ func TestReadTimeout(t *testing.T) { toStream := "to-st" // fromStep, we also have to create a fromStepWrite here to write data to the from stream - fromStep, _ := NewBufferRead(ctx, client, fromStream, fromGroup, consumer, WithInfoRefreshInterval(2*time.Millisecond)).(*BufferRead) + fromStep, _ := NewBufferRead(ctx, client, fromStream, fromGroup, consumer, redisclient.WithInfoRefreshInterval(2*time.Millisecond)).(*BufferRead) _ = client.CreateStreamGroup(ctx, fromStep.GetStreamName(), fromGroup, redisclient.ReadFromEarliest) defer func() { _ = client.DeleteKeys(ctx, fromStep.GetStreamName()) }() defer func() { _ = client.DeleteStreamGroup(ctx, fromStep.GetStreamName(), fromGroup) }() - to1, _ := NewBufferWrite(ctx, client, toStream, toGroup, WithLagDuration(1*time.Millisecond), WithInfoRefreshInterval(2*time.Second)).(*BufferWrite) + to1, _ := NewBufferWrite(ctx, client, toStream, toGroup, redisclient.WithLagDuration(1*time.Millisecond), redisclient.WithInfoRefreshInterval(2*time.Second)).(*BufferWrite) _ = client.CreateStreamGroup(ctx, to1.GetStreamName(), toGroup, redisclient.ReadFromEarliest) defer func() { _ = client.DeleteKeys(ctx, to1.GetStreamName()) }() @@ -459,7 +459,7 @@ func TestXTrimOnIsFull(t *testing.T) { group := "trim-group" buffer := "trim" - rqw, _ := NewBufferWrite(ctx, client, buffer, group, WithLagDuration(1*time.Millisecond), WithInfoRefreshInterval(2*time.Millisecond), WithMaxLength(10)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, buffer, group, redisclient.WithLagDuration(1*time.Millisecond), redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithMaxLength(10)).(*BufferWrite) err := client.CreateStreamGroup(ctx, rqw.GetStreamName(), group, redisclient.ReadFromEarliest) assert.NoError(t, err) @@ -524,7 +524,7 @@ func TestSetWriteInfo(t *testing.T) { group := "setWriteInfo-group" buffer := "setWriteInfo" - rqw, _ := NewBufferWrite(ctx, client, buffer, group, WithLagDuration(1*time.Millisecond), WithInfoRefreshInterval(2*time.Millisecond), WithRefreshBufferWriteInfo(false)).(*BufferWrite) + rqw, _ := NewBufferWrite(ctx, client, buffer, group, redisclient.WithLagDuration(1*time.Millisecond), redisclient.WithInfoRefreshInterval(2*time.Millisecond), redisclient.WithRefreshBufferWriteInfo(false)).(*BufferWrite) err := client.CreateStreamGroup(ctx, rqw.GetStreamName(), group, redisclient.ReadFromEarliest) assert.NoError(t, err) diff --git a/pkg/isbsvc/redis_service.go b/pkg/isbsvc/redis_service.go index 36d21f80d5..d6c25b6390 100644 --- a/pkg/isbsvc/redis_service.go +++ b/pkg/isbsvc/redis_service.go @@ -129,7 +129,7 @@ func (r *isbsRedisSvc) GetBufferInfo(ctx context.Context, buffer dfv1.Buffer) (* return nil, fmt.Errorf("buffer infomation inquiry is not supported for type %q", buffer.Type) } group := fmt.Sprintf("%s-group", buffer.Name) - rqw := redis2.NewBufferWrite(ctx, redisclient.NewInClusterRedisClient(), buffer.Name, group, redis2.WithRefreshBufferWriteInfo(false)) + rqw := redis2.NewBufferWrite(ctx, redisclient.NewInClusterRedisClient(), buffer.Name, group, redisclient.WithRefreshBufferWriteInfo(false)) var bufferWrite = rqw.(*redis2.BufferWrite) bufferInfo := &BufferInfo{ diff --git a/pkg/isb/stores/redis/options.go b/pkg/shared/clients/redis/options.go similarity index 61% rename from pkg/isb/stores/redis/options.go rename to pkg/shared/clients/redis/options.go index ecc988eb2b..0e24343c26 100644 --- a/pkg/isb/stores/redis/options.go +++ b/pkg/shared/clients/redis/options.go @@ -22,38 +22,38 @@ import ( dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" ) -// options for writing to redis -type options struct { - // pipelining enables redis pipeline - pipelining bool - // infoRefreshInterval refreshes the info at this interval - infoRefreshInterval time.Duration - // lagDuration is the minimum permissable consumerLag that we consider as a valid consumerLag. - lagDuration time.Duration - // readTimeOut is the timeout needed for read timeout - readTimeOut time.Duration - // checkBackLog is used to read all the PENDING entries from the stream - checkBackLog bool - // maxLength is the maximum length of the stream before it reaches full - maxLength int64 - // bufferUsageLimit is the limit of buffer usage before we declare it as full - bufferUsageLimit float64 - // refreshBufferWriteInfo is used to determine if we refresh buffer write info - refreshBufferWriteInfo bool - // bufferFullWritingStrategy is the writing strategy when buffer is full - bufferFullWritingStrategy dfv1.BufferFullWritingStrategy +// Options for writing to redis +type Options struct { + // Pipelining enables redis pipeline + Pipelining bool + // InfoRefreshInterval refreshes the info at this interval + InfoRefreshInterval time.Duration + // LagDuration is the minimum permissable consumerLag that we consider as a valid consumerLag. + LagDuration time.Duration + // ReadTimeOut is the timeout needed for read timeout + ReadTimeOut time.Duration + // CheckBackLog is used to read all the PENDING entries from the stream + CheckBackLog bool + // MaxLength is the maximum length of the stream before it reaches full + MaxLength int64 + // BufferUsageLimit is the limit of buffer usage before we declare it as full + BufferUsageLimit float64 + // RefreshBufferWriteInfo is used to determine if we refresh buffer write info + RefreshBufferWriteInfo bool + // BufferFullWritingStrategy is the writing strategy when buffer is full + BufferFullWritingStrategy dfv1.BufferFullWritingStrategy } // Option to apply different options type Option interface { - apply(*options) + Apply(*Options) } // pipelining option type pipelining bool -func (p pipelining) apply(opts *options) { - opts.pipelining = bool(p) +func (p pipelining) Apply(opts *Options) { + opts.Pipelining = bool(p) } // WithoutPipelining turns on redis pipelining @@ -64,8 +64,8 @@ func WithoutPipelining() Option { // infoRefreshInterval option type infoRefreshInterval time.Duration -func (i infoRefreshInterval) apply(o *options) { - o.infoRefreshInterval = time.Duration(i) +func (i infoRefreshInterval) Apply(o *Options) { + o.InfoRefreshInterval = time.Duration(i) } // WithInfoRefreshInterval sets the refresh interval @@ -76,8 +76,8 @@ func WithInfoRefreshInterval(t time.Duration) Option { // lagDuration option type lagDuration time.Duration -func (l lagDuration) apply(o *options) { - o.lagDuration = time.Duration(l) +func (l lagDuration) Apply(o *Options) { + o.LagDuration = time.Duration(l) } // WithLagDuration sets the consumerLag duration @@ -88,8 +88,8 @@ func WithLagDuration(t time.Duration) Option { // readTimeOut option type readTimeOut time.Duration -func (r readTimeOut) apply(o *options) { - o.readTimeOut = time.Duration(r) +func (r readTimeOut) Apply(o *Options) { + o.ReadTimeOut = time.Duration(r) } // WithReadTimeOut sets the readTimeOut @@ -100,8 +100,8 @@ func WithReadTimeOut(t time.Duration) Option { // checkBackLog option type checkBackLog bool -func (b checkBackLog) apply(o *options) { - o.checkBackLog = bool(b) +func (b checkBackLog) Apply(o *Options) { + o.CheckBackLog = bool(b) } // WithCheckBacklog sets the checkBackLog option @@ -112,8 +112,8 @@ func WithCheckBacklog(b bool) Option { // maxLength option type maxLength int64 -func (m maxLength) apply(o *options) { - o.maxLength = int64(m) +func (m maxLength) Apply(o *Options) { + o.MaxLength = int64(m) } // WithMaxLength sets the maxLength @@ -124,8 +124,8 @@ func WithMaxLength(m int64) Option { // usageLimit option type bufferUsageLimit float64 -func (u bufferUsageLimit) apply(o *options) { - o.bufferUsageLimit = float64(u) +func (u bufferUsageLimit) Apply(o *Options) { + o.BufferUsageLimit = float64(u) } // WithBufferUsageLimit sets the bufferUsageLimit @@ -136,8 +136,8 @@ func WithBufferUsageLimit(u float64) Option { // refreshBufferWriteInfo option type refreshBufferWriteInfo bool -func (r refreshBufferWriteInfo) apply(o *options) { - o.refreshBufferWriteInfo = bool(r) +func (r refreshBufferWriteInfo) Apply(o *Options) { + o.RefreshBufferWriteInfo = bool(r) } // WithRefreshBufferWriteInfo sets the refreshBufferWriteInfo @@ -148,8 +148,8 @@ func WithRefreshBufferWriteInfo(r bool) Option { // WithBufferFullWritingStrategy option type bufferFullWritingStrategy dfv1.BufferFullWritingStrategy -func (s bufferFullWritingStrategy) apply(o *options) { - o.bufferFullWritingStrategy = dfv1.BufferFullWritingStrategy(s) +func (s bufferFullWritingStrategy) Apply(o *Options) { + o.BufferFullWritingStrategy = dfv1.BufferFullWritingStrategy(s) } // WithBufferFullWritingStrategy sets the BufferFullWritingStrategy diff --git a/pkg/shared/clients/redis/redis_client.go b/pkg/shared/clients/redis/redis_client.go index 22c212b132..c1952dbf6f 100644 --- a/pkg/shared/clients/redis/redis_client.go +++ b/pkg/shared/clients/redis/redis_client.go @@ -29,6 +29,7 @@ import ( ) const ReadFromEarliest = "0-0" +const ReadFromLatest = "$" // RedisContext is used to pass the context specifically for REDIS operations. // A cancelled context during SIGTERM or Ctrl-C that is propagated down will throw a context cancelled error because redis uses context to obtain connection from the connection pool. diff --git a/pkg/shared/clients/redis/redis_reader.go b/pkg/shared/clients/redis/redis_reader.go new file mode 100644 index 0000000000..7de0aa6303 --- /dev/null +++ b/pkg/shared/clients/redis/redis_reader.go @@ -0,0 +1,150 @@ +/* +Copyright 2022 The Numaproj 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 redis + +import ( + "context" + "errors" + "fmt" + + "github.com/go-redis/redis/v8" + "github.com/numaproj/numaflow/pkg/isb" + "go.uber.org/zap" +) + +// RedisStreamsRead is the read queue implementation powered by RedisClient. +type RedisStreamsRead struct { + Name string + Stream string + Group string + Consumer string + + *RedisClient + Options + Log *zap.SugaredLogger + Metrics Metrics + + XStreamToMessages func(xstreams []redis.XStream, messages []*isb.ReadMessage, labels map[string]string) ([]*isb.ReadMessage, error) +} + +type Metrics struct { + ReadErrorsInc MetricsIncrementFunc + ReadsAdd MetricsAddFunc + AcksAdd MetricsAddFunc +} + +// need a function type which increments a particular counter +type MetricsIncrementFunc func() +type MetricsAddFunc func(int) + +func (br *RedisStreamsRead) GetName() string { + return br.Name +} + +// GetStreamName returns the stream name. +func (br *RedisStreamsRead) GetStreamName() string { + return br.Stream +} + +// GetGroupName gets the name of the consumer group. +func (br *RedisStreamsRead) GetGroupName() string { + return br.Group +} + +// Read reads the messages from the stream. +// During a restart, we need to make sure all the un-acknowledged messages are reprocessed. +// we need to replace `>` with `0-0` during restarts. We might run into data loss otherwise. +func (br *RedisStreamsRead) Read(_ context.Context, count int64) ([]*isb.ReadMessage, error) { + var messages = make([]*isb.ReadMessage, 0, count) + var xstreams []redis.XStream + var err error + // start with 0-0 if CheckBackLog is true + labels := map[string]string{"buffer": br.GetName()} + if br.Options.CheckBackLog { + xstreams, err = br.processXReadResult("0-0", count) + if err != nil { + return br.processReadError(xstreams, messages, err) + } + + // NOTE: If all messages have been delivered and acknowledged, the XREADGROUP 0-0 call returns an empty + // list of messages in the stream. At this point we want to read everything from last delivered which would be > + if len(xstreams) == 1 && len(xstreams[0].Messages) == 0 { + br.Log.Infow("We have delivered and acknowledged all PENDING msgs, setting checkBacklog to false") + br.CheckBackLog = false + } + } + if !br.Options.CheckBackLog { + xstreams, err = br.processXReadResult(">", count) + if err != nil { + return br.processReadError(xstreams, messages, err) + } + } + + // for each XMessage in []XStream + msgs, err := br.XStreamToMessages(xstreams, messages, labels) + if br.Metrics.ReadsAdd != nil { + br.Metrics.ReadsAdd(len(messages)) + } + br.Log.Debugf("Received %d messages over Redis Streams Source, err=%v", len(msgs), err) + return msgs, err +} + +func (br *RedisStreamsRead) processReadError(xstreams []redis.XStream, messages []*isb.ReadMessage, err error) ([]*isb.ReadMessage, error) { + if errors.Is(err, context.Canceled) || errors.Is(err, redis.Nil) { + br.Log.Debugf("redis.Nil/context cancelled, checkBackLog=%v, err=%v", br.Options.CheckBackLog, err) + return messages, nil + } + + if br.Metrics.ReadErrorsInc != nil { + br.Metrics.ReadErrorsInc() + } + // we should try to do our best effort to convert our data here, if there is data available in xstream from the previous loop + messages, errMsg := br.XStreamToMessages(xstreams, messages, map[string]string{"buffer": br.GetName()}) + br.Log.Errorf("convertXStreamToMessages failed, checkBackLog=%v, err=%s", br.Options.CheckBackLog, errMsg) + return messages, fmt.Errorf("XReadGroup failed, %w", err) +} + +// Ack acknowledges the offset to the read queue. Ack is always pipelined, if you want to avoid it then +// send array of 1 element. +func (br *RedisStreamsRead) Ack(_ context.Context, offsets []isb.Offset) []error { + errs := make([]error, len(offsets)) + strOffsets := []string{} + for _, o := range offsets { + strOffsets = append(strOffsets, o.String()) + } + if err := br.Client.XAck(RedisContext, br.Stream, br.Group, strOffsets...).Err(); err != nil { + for i := 0; i < len(offsets); i++ { + errs[i] = err + } + } + if br.Metrics.AcksAdd != nil { + br.Metrics.AcksAdd(len(offsets)) + } + return errs +} + +// processXReadResult is used to process the results of XREADGROUP +func (br *RedisStreamsRead) processXReadResult(startIndex string, count int64) ([]redis.XStream, error) { + result := br.Client.XReadGroup(RedisContext, &redis.XReadGroupArgs{ + Group: br.Group, + Consumer: br.Consumer, + Streams: []string{br.Stream, startIndex}, + Count: count, + Block: br.Options.ReadTimeOut, + }) + return result.Result() +} diff --git a/pkg/sinks/sink.go b/pkg/sinks/sink.go index a9284be29c..bf1964ad76 100644 --- a/pkg/sinks/sink.go +++ b/pkg/sinks/sink.go @@ -64,9 +64,9 @@ func (u *SinkProcessor) Start(ctx context.Context) error { redisClient := redisclient.NewInClusterRedisClient() fromGroup := fromBufferName + "-group" consumer := fmt.Sprintf("%s-%v", u.VertexInstance.Vertex.Name, u.VertexInstance.Replica) - readOptions := []redisisb.Option{} + readOptions := []redisclient.Option{} if x := u.VertexInstance.Vertex.Spec.Limits; x != nil && x.ReadTimeout != nil { - readOptions = append(readOptions, redisisb.WithReadTimeOut(x.ReadTimeout.Duration)) + readOptions = append(readOptions, redisclient.WithReadTimeOut(x.ReadTimeout.Duration)) } reader = redisisb.NewBufferRead(ctx, redisClient, fromBufferName, fromGroup, consumer, readOptions...) case dfv1.ISBSvcTypeJetStream: diff --git a/pkg/sources/redisstreams/metrics.go b/pkg/sources/redisstreams/metrics.go new file mode 100644 index 0000000000..a63363ab91 --- /dev/null +++ b/pkg/sources/redisstreams/metrics.go @@ -0,0 +1,45 @@ +/* +Copyright 2022 The Numaproj 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 redisstreams + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/numaproj/numaflow/pkg/metrics" +) + +// Total number of Redis Streams messages Read +var redisStreamsSourceReadCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Subsystem: "redis_streams_source", + Name: "read_total", + Help: "Total number of Redis Streams messages Read", +}, []string{metrics.LabelVertex, metrics.LabelPipeline}) + +// Total number of Redis Streams messages Acknowledged +var redisStreamsSourceAckCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Subsystem: "redis_streams_source", + Name: "ack_total", + Help: "Total number of Redis Streams messages Acknowledged", +}, []string{metrics.LabelVertex, metrics.LabelPipeline}) + +// Total number of Redis Read Errors +var redisStreamsSourceReadErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + Subsystem: "redis_streams_source", + Name: "read_error_total", + Help: "Total number of Redis IsEmpty Errors", +}, []string{metrics.LabelVertex, metrics.LabelPipeline}) diff --git a/pkg/sources/redisstreams/redisstream.go b/pkg/sources/redisstreams/redisstream.go new file mode 100644 index 0000000000..67d8db456b --- /dev/null +++ b/pkg/sources/redisstreams/redisstream.go @@ -0,0 +1,282 @@ +/* +Copyright 2022 The Numaproj 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 redisstreams + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/go-redis/redis/v8" + dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" + "github.com/numaproj/numaflow/pkg/forward" + "github.com/numaproj/numaflow/pkg/forward/applier" + "github.com/numaproj/numaflow/pkg/isb" + "github.com/numaproj/numaflow/pkg/metrics" + redisclient "github.com/numaproj/numaflow/pkg/shared/clients/redis" + "github.com/numaproj/numaflow/pkg/shared/logging" + sharedutil "github.com/numaproj/numaflow/pkg/shared/util" + "github.com/numaproj/numaflow/pkg/watermark/fetch" + "github.com/numaproj/numaflow/pkg/watermark/processor" + "github.com/numaproj/numaflow/pkg/watermark/publish" + "github.com/numaproj/numaflow/pkg/watermark/store" + "github.com/numaproj/numaflow/pkg/watermark/wmb" + "go.uber.org/zap" +) + +type redisStreamsSource struct { + // shared code to read from Redis Streams + *redisclient.RedisStreamsRead + // name of the pipeline + pipelineName string + // forwarder that writes the consumed data to destination + forwarder *forward.InterStepDataForward + // context cancel function + cancelfn context.CancelFunc + // source watermark publisher + sourcePublishWM publish.Publisher +} + +type Option func(*redisStreamsSource) error + +// WithLogger is used to return logger information +func WithLogger(l *zap.SugaredLogger) Option { + return func(o *redisStreamsSource) error { + o.Log = l + return nil + } +} + +// WithReadTimeOut sets the read timeout +func WithReadTimeOut(t time.Duration) Option { + return func(o *redisStreamsSource) error { + o.Options.ReadTimeOut = t + return nil + } +} + +func New( + vertexInstance *dfv1.VertexInstance, + writers []isb.BufferWriter, + fsd forward.ToWhichStepDecider, + mapApplier applier.MapApplier, + fetchWM fetch.Fetcher, + publishWM map[string]publish.Publisher, + publishWMStores store.WatermarkStorer, + opts ...Option) (*redisStreamsSource, error) { + + // create RedisClient to connect to Redis + vertexSpec := vertexInstance.Vertex.Spec + redisSpec := vertexSpec.Source.RedisStreams + redisClient, err := newRedisClient(redisSpec) + if err != nil { + return nil, err + } + + readerOpts := &redisclient.Options{ + InfoRefreshInterval: time.Second, + ReadTimeOut: time.Second, + CheckBackLog: true, + } + + // RedisStreamsReader handles reading from Redis Streams + redisStreamsReader := &redisclient.RedisStreamsRead{ + Name: vertexSpec.Name, + Stream: redisSpec.Stream, + Group: redisSpec.ConsumerGroup, + Consumer: fmt.Sprintf("%s-%v", vertexSpec.Name, vertexInstance.Replica), + RedisClient: redisClient, + Options: *readerOpts, + Metrics: redisclient.Metrics{ + ReadErrorsInc: func() { + redisStreamsSourceReadErrors.With(map[string]string{metrics.LabelVertex: vertexSpec.Name, metrics.LabelPipeline: vertexSpec.PipelineName}).Inc() + }, + ReadsAdd: func(count int) { + redisStreamsSourceReadCount.With(map[string]string{metrics.LabelVertex: vertexSpec.Name, metrics.LabelPipeline: vertexSpec.PipelineName}).Add(float64(count)) + }, + AcksAdd: func(count int) { + redisStreamsSourceAckCount.With(map[string]string{metrics.LabelVertex: vertexSpec.Name, metrics.LabelPipeline: vertexSpec.PipelineName}).Add(float64(count)) + }, + }, + } + + redisStreamsSource := &redisStreamsSource{ + RedisStreamsRead: redisStreamsReader, + pipelineName: vertexSpec.PipelineName, + } + + for _, o := range opts { + operr := o(redisStreamsSource) + if operr != nil { + return nil, operr + } + } + if redisStreamsReader.Log == nil { + redisStreamsReader.Log = logging.NewLogger() + } + + // Create InterStepDataForward + destinations := make(map[string]isb.BufferWriter, len(writers)) + for _, w := range writers { + destinations[w.GetName()] = w + } + forwardOpts := []forward.Option{forward.WithVertexType(dfv1.VertexTypeSource), forward.WithLogger(redisStreamsSource.Log), forward.WithSourceWatermarkPublisher(redisStreamsSource)} + if x := vertexInstance.Vertex.Spec.Limits; x != nil { + if x.ReadBatchSize != nil { + forwardOpts = append(forwardOpts, forward.WithReadBatchSize(int64(*x.ReadBatchSize))) + } + } + forwarder, err := forward.NewInterStepDataForward(vertexInstance.Vertex, redisStreamsSource, destinations, fsd, mapApplier, fetchWM, publishWM, forwardOpts...) + if err != nil { + redisStreamsSource.Log.Errorw("Error instantiating the forwarder", zap.Error(err)) + return nil, err + } + redisStreamsSource.forwarder = forwarder + + // Create Watermark Publisher + ctx, cancel := context.WithCancel(context.Background()) + redisStreamsSource.cancelfn = cancel + entityName := fmt.Sprintf("%s-%d", vertexInstance.Vertex.Name, vertexInstance.Replica) + processorEntity := processor.NewProcessorEntity(entityName) + redisStreamsSource.sourcePublishWM = publish.NewPublish(ctx, processorEntity, publishWMStores, publish.IsSource(), publish.WithDelay(vertexInstance.Vertex.Spec.Watermark.GetMaxDelay())) + + // create the ConsumerGroup here if not already created + err = redisStreamsSource.createConsumerGroup(ctx, redisSpec) + if err != nil { + return nil, err + } + + // function which takes the messages that have been read from the Stream and turns them into ReadMessages + redisStreamsSource.XStreamToMessages = func(xstreams []redis.XStream, messages []*isb.ReadMessage, labels map[string]string) ([]*isb.ReadMessage, error) { + // for each XMessage in []XStream + for _, xstream := range xstreams { + for _, message := range xstream.Messages { + var readOffset = message.ID + + valIndex := 0 + for f, v := range message.Values { + + // need to make sure the ID is unique: we can make it a combination of the original + // message ID from Redis (timestamp+sequence num) plus its ordinal in the key/value pairs + // contained in the message + id := fmt.Sprintf("%s-%d", message.ID, valIndex) + valIndex = valIndex + 1 + + isbMsg := isb.Message{ + Header: isb.Header{ + MessageInfo: isb.MessageInfo{EventTime: time.Now()}, //doesn't seem like Redis offers a timestamp + ID: id, // assumption is that this only needs to be unique for this source vertex + Key: f, + }, + Body: isb.Body{Payload: []byte(v.(string))}, + } + + readMsg := &isb.ReadMessage{ + ReadOffset: isb.SimpleStringOffset(func() string { return readOffset }), // assumption is that this is just used for ack, so doesn't need to include stream name + Message: isbMsg, + } + messages = append(messages, readMsg) + } + } + } + + return messages, nil + } + + return redisStreamsSource, nil +} + +// create a RedisClient from the RedisStreamsSource spec +func newRedisClient(sourceSpec *dfv1.RedisStreamsSource) (*redisclient.RedisClient, error) { + password, _ := sharedutil.GetSecretFromVolume(sourceSpec.Password) + opts := &redis.UniversalOptions{ + Username: sourceSpec.User, + Password: password, + MasterName: sourceSpec.MasterName, + MaxRedirects: 3, + } + if opts.MasterName != "" { + urls := sourceSpec.SentinelURL + if urls != "" { + opts.Addrs = strings.Split(urls, ",") + } + sentinelPassword, _ := sharedutil.GetSecretFromVolume(sourceSpec.SentinelPassword) + opts.SentinelPassword = os.Getenv(sentinelPassword) + } else { + urls := sourceSpec.URL + if urls != "" { + opts.Addrs = strings.Split(urls, ",") + } + } + + return redisclient.NewRedisClient(opts), nil +} + +func (rsSource *redisStreamsSource) createConsumerGroup(ctx context.Context, sourceSpec *dfv1.RedisStreamsSource) error { + // user can configure to read stream either from the beginning or from the most recent messages + readFrom := redisclient.ReadFromLatest + if sourceSpec.ReadFromBeginning { + readFrom = redisclient.ReadFromEarliest + } + rsSource.Log.Infof("Creating Redis Stream group %q on Stream %q (readFrom=%v)", rsSource.Group, rsSource.Stream, readFrom) + err := rsSource.RedisClient.CreateStreamGroup(ctx, rsSource.Stream, rsSource.Group, readFrom) + if err != nil { + if redisclient.IsAlreadyExistError(err) { + rsSource.Log.Infow("Consumer Group on Stream already exists.", zap.String("group", rsSource.Group), zap.String("stream", rsSource.Stream)) + } else { + return fmt.Errorf("failed to create consumer group %q on redis stream %q: err=%v", rsSource.Group, rsSource.Stream, err) + } + } + return nil +} + +func (rsSource *redisStreamsSource) PublishSourceWatermarks(msgs []*isb.ReadMessage) { + var oldest time.Time + for _, m := range msgs { + if oldest.IsZero() || m.EventTime.Before(oldest) { + oldest = m.EventTime + } + } + if len(msgs) > 0 && !oldest.IsZero() { + rsSource.sourcePublishWM.PublishWatermark(wmb.Watermark(oldest), nil) // Source publisher does not care about the offset + } +} + +func (rsSource *redisStreamsSource) Close() error { + rsSource.Log.Info("Shutting down redis source server...") + rsSource.cancelfn() + if err := rsSource.sourcePublishWM.Close(); err != nil { + rsSource.Log.Errorw("Failed to close source vertex watermark publisher", zap.Error(err)) + } + rsSource.Log.Info("Redis source server shutdown") + return nil +} + +func (rsSource *redisStreamsSource) Stop() { + rsSource.Log.Info("Stopping redis streams source reader...") + rsSource.forwarder.Stop() +} + +func (rsSource *redisStreamsSource) ForceStop() { + rsSource.Stop() +} + +func (rsSource *redisStreamsSource) Start() <-chan struct{} { + return rsSource.forwarder.Start() +} diff --git a/pkg/sources/source.go b/pkg/sources/source.go index 2df0ea3fe2..3da7769fd9 100644 --- a/pkg/sources/source.go +++ b/pkg/sources/source.go @@ -40,6 +40,7 @@ import ( "github.com/numaproj/numaflow/pkg/sources/http" "github.com/numaproj/numaflow/pkg/sources/kafka" "github.com/numaproj/numaflow/pkg/sources/nats" + "github.com/numaproj/numaflow/pkg/sources/redisstreams" "github.com/numaproj/numaflow/pkg/sources/transformer" "github.com/numaproj/numaflow/pkg/watermark/fetch" "github.com/numaproj/numaflow/pkg/watermark/generic" @@ -69,14 +70,14 @@ func (sp *SourceProcessor) Start(ctx context.Context) error { switch sp.ISBSvcType { case dfv1.ISBSvcTypeRedis: for _, e := range sp.VertexInstance.Vertex.Spec.ToEdges { - writeOpts := []redisisb.Option{ - redisisb.WithBufferFullWritingStrategy(e.BufferFullWritingStrategy()), + writeOpts := []redisclient.Option{ + redisclient.WithBufferFullWritingStrategy(e.BufferFullWritingStrategy()), } if x := e.Limits; x != nil && x.BufferMaxLength != nil { - writeOpts = append(writeOpts, redisisb.WithMaxLength(int64(*x.BufferMaxLength))) + writeOpts = append(writeOpts, redisclient.WithMaxLength(int64(*x.BufferMaxLength))) } if x := e.Limits; x != nil && x.BufferUsageLimit != nil { - writeOpts = append(writeOpts, redisisb.WithBufferUsageLimit(float64(*x.BufferUsageLimit)/100)) + writeOpts = append(writeOpts, redisclient.WithBufferUsageLimit(float64(*x.BufferUsageLimit)/100)) } buffers := dfv1.GenerateEdgeBufferNames(sp.VertexInstance.Vertex.Namespace, sp.VertexInstance.Vertex.Spec.PipelineName, e) for _, buffer := range buffers { @@ -216,6 +217,14 @@ func (sp *SourceProcessor) getSourcer( readOptions = append(readOptions, nats.WithReadTimeout(l.ReadTimeout.Duration)) } return nats.New(sp.VertexInstance, writers, fsd, mapApplier, fetchWM, publishWM, publishWMStores, readOptions...) + } else if x := src.RedisStreams; x != nil { + readOptions := []redisstreams.Option{ + redisstreams.WithLogger(logger), + } + if l := sp.VertexInstance.Vertex.Spec.Limits; l != nil && l.ReadTimeout != nil { + readOptions = append(readOptions, redisstreams.WithReadTimeOut(l.ReadTimeout.Duration)) + } + return redisstreams.New(sp.VertexInstance, writers, fsd, mapApplier, fetchWM, publishWM, publishWMStores, readOptions...) } return nil, fmt.Errorf("invalid source spec") } diff --git a/pkg/udf/common.go b/pkg/udf/common.go index aa7dd89121..9e2c0cf58f 100644 --- a/pkg/udf/common.go +++ b/pkg/udf/common.go @@ -33,22 +33,22 @@ func buildRedisBufferIO(ctx context.Context, fromBufferName string, vertexInstan writers := make(map[string]isb.BufferWriter) redisClient := redisclient.NewInClusterRedisClient() fromGroup := fromBufferName + "-group" - readerOpts := []redisisb.Option{} + readerOpts := []redisclient.Option{} if x := vertexInstance.Vertex.Spec.Limits; x != nil && x.ReadTimeout != nil { - readerOpts = append(readerOpts, redisisb.WithReadTimeOut(x.ReadTimeout.Duration)) + readerOpts = append(readerOpts, redisclient.WithReadTimeOut(x.ReadTimeout.Duration)) } consumer := fmt.Sprintf("%s-%v", vertexInstance.Vertex.Name, vertexInstance.Replica) reader := redisisb.NewBufferRead(ctx, redisClient, fromBufferName, fromGroup, consumer, readerOpts...) for _, e := range vertexInstance.Vertex.Spec.ToEdges { - writeOpts := []redisisb.Option{ - redisisb.WithBufferFullWritingStrategy(e.BufferFullWritingStrategy()), + writeOpts := []redisclient.Option{ + redisclient.WithBufferFullWritingStrategy(e.BufferFullWritingStrategy()), } if x := e.Limits; x != nil && x.BufferMaxLength != nil { - writeOpts = append(writeOpts, redisisb.WithMaxLength(int64(*x.BufferMaxLength))) + writeOpts = append(writeOpts, redisclient.WithMaxLength(int64(*x.BufferMaxLength))) } if x := e.Limits; x != nil && x.BufferUsageLimit != nil { - writeOpts = append(writeOpts, redisisb.WithBufferUsageLimit(float64(*x.BufferUsageLimit)/100)) + writeOpts = append(writeOpts, redisclient.WithBufferUsageLimit(float64(*x.BufferUsageLimit)/100)) } buffers := dfv1.GenerateEdgeBufferNames(vertexInstance.Vertex.Namespace, vertexInstance.Vertex.Spec.PipelineName, e) for _, buffer := range buffers { diff --git a/test/e2e-api/redis.go b/test/e2e-api/redis.go index c9fa314443..381d7dc84b 100644 --- a/test/e2e-api/redis.go +++ b/test/e2e-api/redis.go @@ -18,11 +18,15 @@ package main import ( "context" + "encoding/json" "fmt" - "github.com/go-redis/redis/v8" "log" "net/http" "net/url" + "strconv" + "time" + + "github.com/go-redis/redis/v8" ) var redisClient *redis.Client @@ -59,4 +63,72 @@ func init() { w.WriteHeader(200) _, _ = w.Write([]byte(count)) }) + + http.HandleFunc("/redis/pump-stream", func(w http.ResponseWriter, r *http.Request) { + stream := r.URL.Query().Get("stream") + keysValuesJsonEncoded := r.URL.Query().Get("keysvalues") + keysValuesJson, err := url.QueryUnescape(keysValuesJsonEncoded) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + var keysValues map[string]string + err = json.Unmarshal([]byte(keysValuesJson), &keysValues) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + valueMap := make(map[string]interface{}) + for k, v := range keysValues { + valueMap[k] = interface{}(v) + } + + size, err := strconv.Atoi(r.URL.Query().Get("size")) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + duration, err := time.ParseDuration(r.URL.Query().Get("sleep")) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + ns := r.URL.Query().Get("n") + if ns == "" { + ns = "-1" + } + n, err := strconv.Atoi(ns) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/octet-stream") + w.WriteHeader(200) + + start := time.Now() + _, _ = fmt.Fprintf(w, "sending %d messages of size %d to %q\n", n, size, stream) + + for i := 0; i < n || n < 0; i++ { + select { + case <-r.Context().Done(): + return + default: + result := redisClient.XAdd(r.Context(), &redis.XAddArgs{Stream: stream, Values: valueMap}) + if result.Err() != nil { + http.Error(w, result.Err().Error(), http.StatusFailedDependency) + return + } + time.Sleep(duration) + } + } + _, _ = fmt.Fprintf(w, "sent %d messages of size %d at %.0f TPS to %q\n", n, size, float64(n)/time.Since(start).Seconds(), stream) + }) } diff --git a/test/fixtures/redis.go b/test/fixtures/redis.go index 7f0b4c6e7e..bfeba4e91f 100644 --- a/test/fixtures/redis.go +++ b/test/fixtures/redis.go @@ -18,8 +18,10 @@ package fixtures import ( "fmt" + "log" "net/url" "strconv" + "time" ) // GetMsgCountContains returns number of occurrences of the targetStr in redis that are written by pipelineName, sinkName. @@ -31,3 +33,26 @@ func GetMsgCountContains(pipelineName, sinkName, targetStr string) int { } return count } + +// function to invoke Redis Source +func PumpRedisStream(stream string, n int, opts ...interface{}) { + var sleep time.Duration + var keysValuesJson string + var size int + for _, opt := range opts { + switch v := opt.(type) { + case time.Duration: + sleep = v + case string: + keysValuesJson = v + case int: + size = v + default: + panic(fmt.Errorf("unexpected option type %T", opt)) + } + } + keysValuesJsonEncoded := url.QueryEscape(keysValuesJson) + log.Printf("Pumping Redis stream %q sleeping %v with %d messages sized %d, keys/values=%q\n", stream, sleep, n, size, keysValuesJson) + InvokeE2EAPI("/redis/pump-stream?stream=%s&sleep=%v&n=%d&keysvalues=%s&size=%d", stream, sleep, n, keysValuesJsonEncoded, size) + +} diff --git a/test/fixtures/util.go b/test/fixtures/util.go index 5aab3c298e..0ecf2bc6a4 100644 --- a/test/fixtures/util.go +++ b/test/fixtures/util.go @@ -453,7 +453,6 @@ func podLogContains(ctx context.Context, client kubernetes.Interface, namespace, return s.Err() } data := s.Bytes() - fmt.Println(string(data)) if exp.Match(data) { result <- true } diff --git a/test/redis-source-e2e/redis_source_test.go b/test/redis-source-e2e/redis_source_test.go new file mode 100644 index 0000000000..9758d503ed --- /dev/null +++ b/test/redis-source-e2e/redis_source_test.go @@ -0,0 +1,82 @@ +//go:build test + +/* +Copyright 2022 The Numaproj 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 redis_source_e2e + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "github.com/numaproj/numaflow/test/fixtures" +) + +//go:generate kubectl -n numaflow-system delete statefulset nats --ignore-not-found=true +//go:generate kubectl apply -k ../../config/apps/redis -n numaflow-system +type RedisSourceSuite struct { + fixtures.E2ESuite +} + +func (rss *RedisSourceSuite) TestRedisSource() { + + // need to wait for Redis to be fully up and running + // tried to wait for Pod to be in Running phase, but that doesn't seem to be sufficient for it being ready + // so for now just using a timer + time.Sleep(10 * time.Second) + + keysValues := map[string]string{"test-msg-1": "test-val-1", "test-msg-2": "test-val-2"} + keysValuesJson, err := json.Marshal(keysValues) + if err != nil { + rss.Fail(err.Error()) + } + + // can do 2 tests + // 1. start from the beginning of the Stream + // 2. start from the most recent messages of the Stream + for _, tt := range []struct { + stream string + manifest string + expectedNumMsgs int // expected result + }{ + {"test-stream-a", "@testdata/redis-source-pipeline-from-beginning.yaml", 101}, + {"test-stream-b", "@testdata/redis-source-pipeline-from-end.yaml", 100}, + } { + // send some messages before creating the Pipeline so we can test both "ReadFromBeginning" and "ReadFromLatest" + fixtures.PumpRedisStream(tt.stream, 1, 20*time.Millisecond, 10, string(keysValuesJson)) + + w := rss.Given().Pipeline(tt.manifest). + When(). + CreatePipelineAndWait() + + // wait for all the pods to come up + w.Expect().VertexPodsRunning() + + fixtures.PumpRedisStream(tt.stream, 100, 20*time.Millisecond, 10, string(keysValuesJson)) + time.Sleep(20 * time.Second) + w.Expect().SinkContains("out", "test-val-1", fixtures.WithContainCount(tt.expectedNumMsgs)) + w.Expect().SinkContains("out", "test-val-2", fixtures.WithContainCount(tt.expectedNumMsgs)) + + w.DeletePipelineAndWait() + } + +} +func TestRedisSourceSuite(t *testing.T) { + suite.Run(t, new(RedisSourceSuite)) +} diff --git a/test/redis-source-e2e/testdata/redis-source-pipeline-from-beginning.yaml b/test/redis-source-e2e/testdata/redis-source-pipeline-from-beginning.yaml new file mode 100644 index 0000000000..7e69c06fed --- /dev/null +++ b/test/redis-source-e2e/testdata/redis-source-pipeline-from-beginning.yaml @@ -0,0 +1,33 @@ +apiVersion: numaflow.numaproj.io/v1alpha1 +kind: Pipeline +metadata: + name: redis-source-e2e +spec: + vertices: + - name: in + containerTemplate: + env: + - name: NUMAFLOW_DEBUG + value: "true" + scale: + min: 2 + source: + redisStreams: + url: "redis:6379" + stream: test-stream-a + consumerGroup: my-group + readFromBeginning: true + - name: p1 + udf: + builtin: + name: cat + - name: out + sink: + udsink: + container: + image: quay.io/numaio/numaflow-sink/redis-e2e-test-sink:latest + edges: + - from: in + to: p1 + - from: p1 + to: out diff --git a/test/redis-source-e2e/testdata/redis-source-pipeline-from-end.yaml b/test/redis-source-e2e/testdata/redis-source-pipeline-from-end.yaml new file mode 100644 index 0000000000..bebe3b7eaa --- /dev/null +++ b/test/redis-source-e2e/testdata/redis-source-pipeline-from-end.yaml @@ -0,0 +1,33 @@ +apiVersion: numaflow.numaproj.io/v1alpha1 +kind: Pipeline +metadata: + name: redis-source-e2e +spec: + vertices: + - name: in + containerTemplate: + env: + - name: NUMAFLOW_DEBUG + value: "true" + scale: + min: 2 + source: + redisStreams: + url: "redis:6379" + stream: test-stream-b + consumerGroup: my-group + readFromBeginning: false + - name: p1 + udf: + builtin: + name: cat + - name: out + sink: + udsink: + container: + image: quay.io/numaio/numaflow-sink/redis-e2e-test-sink:latest + edges: + - from: in + to: p1 + - from: p1 + to: out