From fe6cad931f622dfdbea26d1b4bb055f1112990af Mon Sep 17 00:00:00 2001
From: John D <john@boostchicken.dev>
Date: Tue, 12 Jul 2022 04:54:03 -0700
Subject: [PATCH] Allow changing of spankind

Signed-off-by: John D <john@boostchicken.dev>
---
 processor/spanprocessor/README.md    | 14 ++++++++++++++
 processor/spanprocessor/config.go    |  7 +++++++
 processor/spanprocessor/factory.go   | 17 +++++++++++++++--
 processor/spanprocessor/span.go      | 21 +++++++++++++++++++++
 processor/spanprocessor/span_test.go | 27 +++++++++++++++++++++++++++
 unreleased/span_kind_processor.yaml  | 16 ++++++++++++++++
 6 files changed, 100 insertions(+), 2 deletions(-)
 create mode 100755 unreleased/span_kind_processor.yaml

diff --git a/processor/spanprocessor/README.md b/processor/spanprocessor/README.md
index 594faae85ee2..c651fd722384 100644
--- a/processor/spanprocessor/README.md
+++ b/processor/spanprocessor/README.md
@@ -123,6 +123,20 @@ span/set_status:
     description: "some error description"
 ```
 
+### Set kind for span
+
+The following setting is required:
+
+- `kind`: Represents span kind. One of the following values "Internal", "Client", "Server", "Producer", "Consumer", "Unspecified".
+
+Example:
+
+```yaml
+# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind
+span/set_kind:
+  spankind:
+    kind: Client
+```
 
 Refer to [config.yaml](./testdata/config.yaml) for detailed
 examples on using the processor.
diff --git a/processor/spanprocessor/config.go b/processor/spanprocessor/config.go
index 06948d5caa3d..17682d0c9392 100644
--- a/processor/spanprocessor/config.go
+++ b/processor/spanprocessor/config.go
@@ -38,6 +38,8 @@ type Config struct {
 
 	// SetStatus specifies status which should be set for this span.
 	SetStatus *Status `mapstructure:"status"`
+
+	SetKind *Kind `mapstructure:"spankind"`
 }
 
 // Name specifies the attributes to use to re-name a span.
@@ -92,6 +94,11 @@ type Status struct {
 	Description string `mapstructure:"description"`
 }
 
+type Kind struct {
+	// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind
+	Kind string `mapstructure:"kind"`
+}
+
 var _ config.Processor = (*Config)(nil)
 
 // Validate checks if the processor configuration is valid
diff --git a/processor/spanprocessor/factory.go b/processor/spanprocessor/factory.go
index 835dfa4e934d..151c2e4c374f 100644
--- a/processor/spanprocessor/factory.go
+++ b/processor/spanprocessor/factory.go
@@ -36,6 +36,13 @@ const (
 	statusCodeUnset = "Unset"
 	statusCodeError = "Error"
 	statusCodeOk    = "Ok"
+
+	spanKindInternal    = "Internal"
+	spanKindClient      = "Client"
+	spanKindServer      = "Server"
+	spanKindProducer    = "Producer"
+	spanKindConsumer    = "Consumer"
+	spanKindUnspecified = "Unspecified"
 )
 
 var processorCapabilities = consumer.Capabilities{MutatesData: true}
@@ -45,8 +52,9 @@ var processorCapabilities = consumer.Capabilities{MutatesData: true}
 // TODO https://github.com/open-telemetry/opentelemetry-collector/issues/215
 //	Move this to the error package that allows for span name and field to be specified.
 var (
-	errMissingRequiredField       = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\" or \"setStatus\" must be specified")
+	errMissingRequiredField       = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\" or \"setStatus\" must be specified or \"spankind\" must be specified")
 	errIncorrectStatusCode        = errors.New("error creating \"span\" processor: \"status\" must have specified \"code\" as \"Ok\" or \"Error\" or \"Unset\"")
+	errIncorrectKind              = errors.New("error creating \"span\" processor: \"kind\" must have specified \"Internal\", \"Client\", \"Server\", \"Producer\", \"Consumer\" or \"Unspecified\"")
 	errIncorrectStatusDescription = errors.New("error creating \"span\" processor: \"description\" can be specified only for \"code\" \"Error\"")
 )
 
@@ -76,7 +84,7 @@ func createTracesProcessor(
 	oCfg := cfg.(*Config)
 	if len(oCfg.Rename.FromAttributes) == 0 &&
 		(oCfg.Rename.ToAttributes == nil || len(oCfg.Rename.ToAttributes.Rules) == 0) &&
-		oCfg.SetStatus == nil {
+		oCfg.SetStatus == nil && oCfg.SetKind == nil {
 		return nil, errMissingRequiredField
 	}
 
@@ -89,6 +97,11 @@ func createTracesProcessor(
 		}
 	}
 
+	if oCfg.SetKind != nil {
+		if oCfg.SetKind.Kind != spanKindInternal && oCfg.SetKind.Kind != spanKindClient && oCfg.SetKind.Kind != spanKindServer && oCfg.SetKind.Kind != spanKindProducer && oCfg.SetKind.Kind != spanKindConsumer && oCfg.SetKind.Kind != spanKindUnspecified {
+			return nil, errIncorrectKind
+		}
+	}
 	sp, err := newSpanProcessor(*oCfg)
 	if err != nil {
 		return nil, err
diff --git a/processor/spanprocessor/span.go b/processor/spanprocessor/span.go
index 16da5f7271e4..5c0b5e063796 100644
--- a/processor/spanprocessor/span.go
+++ b/processor/spanprocessor/span.go
@@ -99,6 +99,7 @@ func (sp *spanProcessor) processTraces(_ context.Context, td ptrace.Traces) (ptr
 				sp.processFromAttributes(s)
 				sp.processToAttributes(s)
 				sp.processUpdateStatus(s)
+				sp.processUpdateKind(s)
 			}
 		}
 	}
@@ -239,3 +240,23 @@ func (sp *spanProcessor) processUpdateStatus(span ptrace.Span) {
 		}
 	}
 }
+
+func (sp *spanProcessor) processUpdateKind(span ptrace.Span) {
+	cfg := sp.config.SetKind
+	if cfg != nil {
+		switch cfg.Kind {
+		case spanKindInternal:
+			span.SetKind(ptrace.SpanKindInternal)
+		case spanKindClient:
+			span.SetKind(ptrace.SpanKindClient)
+		case spanKindServer:
+			span.SetKind(ptrace.SpanKindServer)
+		case spanKindConsumer:
+			span.SetKind(ptrace.SpanKindConsumer)
+		case spanKindProducer:
+			span.SetKind(ptrace.SpanKindProducer)
+		default:
+			span.SetKind(ptrace.SpanKindUnspecified)
+		}
+	}
+}
diff --git a/processor/spanprocessor/span_test.go b/processor/spanprocessor/span_test.go
index d3e1413e37f4..a413a55ec2aa 100644
--- a/processor/spanprocessor/span_test.go
+++ b/processor/spanprocessor/span_test.go
@@ -605,6 +605,15 @@ func generateTraceDataSetStatus(code ptrace.StatusCode, description string, attr
 	return td
 }
 
+func generateTraceDataSetKind(kind ptrace.SpanKind, attrs map[string]interface{}) ptrace.Traces {
+	td := ptrace.NewTraces()
+	rs := td.ResourceSpans().AppendEmpty()
+	span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
+	span.SetKind(kind)
+	pcommon.NewMapFromRaw(attrs).Sort().CopyTo(span.Attributes())
+	return td
+}
+
 func TestSpanProcessor_setStatusCode(t *testing.T) {
 	factory := NewFactory()
 	cfg := factory.CreateDefaultConfig()
@@ -624,6 +633,24 @@ func TestSpanProcessor_setStatusCode(t *testing.T) {
 	assert.EqualValues(t, generateTraceDataSetStatus(ptrace.StatusCodeError, "Set custom error message", nil), td)
 }
 
+func TestSpanProcessor_setKind(t *testing.T) {
+	factory := NewFactory()
+	cfg := factory.CreateDefaultConfig()
+	oCfg := cfg.(*Config)
+	oCfg.SetKind = &Kind{
+		Kind: spanKindServer,
+	}
+	tp, err := factory.CreateTracesProcessor(context.Background(), componenttest.NewNopProcessorCreateSettings(), oCfg, consumertest.NewNop())
+	require.Nil(t, err)
+	require.NotNil(t, tp)
+
+	td := generateTraceDataSetKind(ptrace.SpanKindClient, nil)
+
+	assert.NoError(t, tp.ConsumeTraces(context.Background(), td))
+
+	assert.EqualValues(t, generateTraceDataSetKind(ptrace.SpanKindServer, nil), td)
+}
+
 func TestSpanProcessor_setStatusCodeConditionally(t *testing.T) {
 	factory := NewFactory()
 	cfg := factory.CreateDefaultConfig()
diff --git a/unreleased/span_kind_processor.yaml b/unreleased/span_kind_processor.yaml
new file mode 100755
index 000000000000..2d7c4fb4863a
--- /dev/null
+++ b/unreleased/span_kind_processor.yaml
@@ -0,0 +1,16 @@
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: enhancement
+
+# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
+component: spanprocessor
+
+# A brief description of the change.  Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Allow changing of SpanKind
+
+# One or more tracking issues related to the change
+issues: [6883]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext: