github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/pipeline/config_test.go (about) 1 package pipeline 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "testing" 7 8 "github.com/observiq/carbon/operator" 9 _ "github.com/observiq/carbon/operator/builtin" 10 "github.com/observiq/carbon/operator/builtin/transformer" 11 "github.com/observiq/carbon/testutil" 12 "github.com/stretchr/testify/require" 13 yaml "gopkg.in/yaml.v2" 14 ) 15 16 func TestParamsWithID(t *testing.T) { 17 expectedID := "test" 18 params := Params{ 19 "id": expectedID, 20 } 21 actualID := params.ID() 22 require.Equal(t, expectedID, actualID) 23 } 24 25 func TestParamsWithoutID(t *testing.T) { 26 params := Params{} 27 actualID := params.ID() 28 require.Equal(t, "", actualID) 29 } 30 31 func TestParamsWithType(t *testing.T) { 32 expectedType := "test" 33 params := Params{ 34 "type": expectedType, 35 } 36 actualType := params.Type() 37 require.Equal(t, expectedType, actualType) 38 } 39 40 func TestParamsWithoutType(t *testing.T) { 41 params := Params{} 42 actualType := params.Type() 43 require.Equal(t, "", actualType) 44 } 45 46 func TestParamsWithOutputs(t *testing.T) { 47 params := Params{ 48 "output": "test", 49 } 50 actualOutput := params.Outputs() 51 require.Equal(t, []string{"test"}, actualOutput) 52 } 53 54 func TestParamsWithoutOutputs(t *testing.T) { 55 params := Params{} 56 actualOutput := params.Outputs() 57 require.Equal(t, []string{}, actualOutput) 58 } 59 60 func TestParamsNamespacedID(t *testing.T) { 61 params := Params{ 62 "id": "test-id", 63 } 64 result := params.NamespacedID("namespace") 65 require.Equal(t, "namespace.test-id", result) 66 } 67 68 func TestParamsNamespacedOutputs(t *testing.T) { 69 params := Params{ 70 "output": "test-output", 71 } 72 result := params.NamespacedOutputs("namespace") 73 require.Equal(t, []string{"namespace.test-output"}, result) 74 } 75 76 func TestParamsTemplateInput(t *testing.T) { 77 params := Params{ 78 "id": "test-id", 79 } 80 result := params.TemplateInput("namespace") 81 require.Equal(t, "namespace.test-id", result) 82 } 83 84 func TestParamsTemplateOutput(t *testing.T) { 85 params := Params{ 86 "output": "test-output", 87 } 88 result := params.TemplateOutput("namespace", []string{}) 89 require.Equal(t, "[namespace.test-output]", result) 90 } 91 92 func TestParamsTemplateDefault(t *testing.T) { 93 params := Params{} 94 result := params.TemplateOutput("namespace", []string{"test-output"}) 95 require.Equal(t, "[test-output]", result) 96 } 97 98 func TestParamsNamespaceExclusions(t *testing.T) { 99 params := Params{ 100 "id": "test-id", 101 "output": "test-output", 102 } 103 result := params.NamespaceExclusions("namespace") 104 require.Equal(t, []string{"namespace.test-id", "namespace.test-output"}, result) 105 } 106 107 func TestParamsGetExistingString(t *testing.T) { 108 params := Params{ 109 "key": "string", 110 } 111 result := params.getString("key") 112 require.Equal(t, "string", result) 113 } 114 115 func TestParamsGetMissingString(t *testing.T) { 116 params := Params{} 117 result := params.getString("missing") 118 require.Equal(t, "", result) 119 } 120 121 func TestParamsGetInvalidString(t *testing.T) { 122 params := Params{ 123 "key": true, 124 } 125 result := params.getString("key") 126 require.Equal(t, "", result) 127 } 128 129 func TestParamsGetStringArrayMissing(t *testing.T) { 130 params := Params{} 131 result := params.getStringArray("missing") 132 require.Equal(t, []string{}, result) 133 } 134 135 func TestParamsGetStringArrayFromString(t *testing.T) { 136 params := Params{ 137 "key": "string", 138 } 139 result := params.getStringArray("key") 140 require.Equal(t, []string{"string"}, result) 141 } 142 143 func TestParamsGetStringArrayFromArray(t *testing.T) { 144 params := Params{ 145 "key": []string{"one", "two"}, 146 } 147 result := params.getStringArray("key") 148 require.Equal(t, []string{"one", "two"}, result) 149 } 150 151 func TestParamsGetStringArrayFromInterface(t *testing.T) { 152 params := Params{ 153 "key": []interface{}{"one", "two"}, 154 } 155 result := params.getStringArray("key") 156 require.Equal(t, []string{"one", "two"}, result) 157 } 158 159 func TestParamsGetStringArrayFromInvalid(t *testing.T) { 160 params := Params{ 161 "key": true, 162 } 163 result := params.getStringArray("key") 164 require.Equal(t, []string{}, result) 165 } 166 167 func TestValidParams(t *testing.T) { 168 params := Params{ 169 "id": "test_id", 170 "type": "test_type", 171 } 172 err := params.Validate() 173 require.NoError(t, err) 174 } 175 176 func TestInvalidParams(t *testing.T) { 177 paramsWithoutType := Params{ 178 "id": "test_id", 179 } 180 err := paramsWithoutType.Validate() 181 require.Error(t, err) 182 } 183 184 type invalidMarshaller struct{} 185 186 func (i invalidMarshaller) MarshalYAML() (interface{}, error) { 187 return nil, fmt.Errorf("failed") 188 } 189 190 func TestBuildBuiltinFromParamsWithUnsupportedYaml(t *testing.T) { 191 params := Params{ 192 "id": "noop", 193 "type": "noop", 194 "output": "test", 195 "field": invalidMarshaller{}, 196 } 197 _, err := params.BuildConfigs(operator.PluginRegistry{}, "test_namespace", []string{}) 198 require.Error(t, err) 199 require.Contains(t, err.Error(), "failed to parse config map as yaml") 200 } 201 202 func TestBuildBuiltinFromParamsWithUnknownField(t *testing.T) { 203 params := Params{ 204 "id": "noop", 205 "type": "noop", 206 "unknown": true, 207 "output": "test_output", 208 } 209 _, err := params.BuildConfigs(operator.PluginRegistry{}, "test_namespace", []string{}) 210 require.Error(t, err) 211 } 212 213 func TestBuildBuiltinFromValidParams(t *testing.T) { 214 params := Params{ 215 "id": "noop", 216 "type": "noop", 217 "output": "test_output", 218 } 219 configs, err := params.BuildConfigs(operator.PluginRegistry{}, "test_namespace", []string{}) 220 221 require.NoError(t, err) 222 require.Equal(t, 1, len(configs)) 223 require.IsType(t, &transformer.NoopOperatorConfig{}, configs[0].Builder) 224 require.Equal(t, "test_namespace.noop", configs[0].ID()) 225 } 226 227 func TestBuildPluginFromValidParams(t *testing.T) { 228 registry := operator.PluginRegistry{} 229 pluginTemplate := ` 230 pipeline: 231 - id: plugin_noop 232 type: noop 233 output: {{.output}} 234 ` 235 err := registry.Add("plugin", pluginTemplate) 236 require.NoError(t, err) 237 238 params := Params{ 239 "id": "plugin", 240 "type": "plugin", 241 "output": "test_output", 242 } 243 244 configs, err := params.BuildConfigs(registry, "test_namespace", []string{}) 245 require.NoError(t, err) 246 require.Equal(t, 1, len(configs)) 247 require.IsType(t, &transformer.NoopOperatorConfig{}, configs[0].Builder) 248 require.Equal(t, "test_namespace.plugin.plugin_noop", configs[0].ID()) 249 } 250 251 func TestBuildValidPipeline(t *testing.T) { 252 context := testutil.NewBuildContext(t) 253 pluginTemplate := ` 254 pipeline: 255 - id: plugin_generate 256 type: generate_input 257 count: 1 258 entry: 259 record: 260 message: test 261 output: {{.output}} 262 ` 263 err := context.PluginRegistry.Add("plugin", pluginTemplate) 264 require.NoError(t, err) 265 266 pipelineConfig := Config{ 267 Params{ 268 "id": "plugin", 269 "type": "plugin", 270 "output": "drop_output", 271 }, 272 Params{ 273 "id": "drop_output", 274 "type": "drop_output", 275 }, 276 } 277 278 _, err = pipelineConfig.BuildPipeline(context) 279 require.NoError(t, err) 280 } 281 282 func TestBuildInvalidPipelineInvalidType(t *testing.T) { 283 context := testutil.NewBuildContext(t) 284 285 pipelineConfig := Config{ 286 Params{ 287 "id": "plugin", 288 "type": "plugin", 289 "output": "drop_output", 290 }, 291 Params{ 292 "id": "drop_output", 293 "type": "drop_output", 294 }, 295 } 296 297 _, err := pipelineConfig.BuildPipeline(context) 298 require.Error(t, err) 299 require.Contains(t, err.Error(), "unsupported `type` for operator config") 300 } 301 302 func TestBuildInvalidPipelineInvalidParam(t *testing.T) { 303 context := testutil.NewBuildContext(t) 304 pluginTemplate := ` 305 pipeline: 306 - id: plugin_generate 307 type: generate_input 308 count: invalid_value 309 record: 310 message: test 311 output: {{.output}} 312 ` 313 err := context.PluginRegistry.Add("plugin", pluginTemplate) 314 require.NoError(t, err) 315 316 pipelineConfig := Config{ 317 Params{ 318 "id": "plugin", 319 "type": "plugin", 320 "output": "drop_output", 321 }, 322 Params{ 323 "id": "drop_output", 324 "type": "drop_output", 325 }, 326 } 327 328 _, err = pipelineConfig.BuildPipeline(context) 329 require.Error(t, err) 330 require.Contains(t, err.Error(), "build operator configs") 331 } 332 333 func TestBuildInvalidPipelineInvalidOperator(t *testing.T) { 334 pipelineConfig := Config{ 335 Params{ 336 "id": "tcp_input", 337 "type": "tcp_input", 338 "output": "drop_output", 339 }, 340 Params{ 341 "id": "drop_output", 342 "type": "drop_output", 343 }, 344 } 345 346 context := testutil.NewBuildContext(t) 347 _, err := pipelineConfig.BuildPipeline(context) 348 require.Error(t, err) 349 require.Contains(t, err.Error(), "missing required parameter 'listen_address'") 350 } 351 352 func TestBuildInvalidPipelineInvalidGraph(t *testing.T) { 353 pipelineConfig := Config{ 354 Params{ 355 "id": "generate_input", 356 "type": "generate_input", 357 "count": 1, 358 "entry": map[string]interface{}{ 359 "record": map[string]interface{}{ 360 "message": "test", 361 }, 362 }, 363 "output": "invalid_output", 364 }, 365 Params{ 366 "id": "drop_output", 367 "type": "drop_output", 368 }, 369 } 370 371 context := testutil.NewBuildContext(t) 372 _, err := pipelineConfig.BuildPipeline(context) 373 require.Error(t, err) 374 require.Contains(t, err.Error(), "does not exist") 375 } 376 377 func TestBuildPipelineDefaultOutputInPlugin(t *testing.T) { 378 context := testutil.NewBuildContext(t) 379 pluginTemplate := ` 380 pipeline: 381 - id: plugin_generate1 382 type: generate_input 383 entry: 384 record: test 385 output: {{.output}} 386 - id: plugin_generate2 387 type: generate_input 388 entry: 389 record: test 390 output: {{.output}} 391 ` 392 err := context.PluginRegistry.Add("plugin", pluginTemplate) 393 require.NoError(t, err) 394 395 config := Config{ 396 { 397 "id": "my_plugin", 398 "type": "plugin", 399 }, 400 { 401 "id": "my_drop", 402 "type": "drop_output", 403 }, 404 } 405 406 configs, err := config.buildOperatorConfigs(context.PluginRegistry) 407 require.NoError(t, err) 408 require.Len(t, configs, 3) 409 410 operators, err := config.buildOperators(configs, context) 411 require.Len(t, operators, 3) 412 413 for _, operator := range operators { 414 if !operator.CanOutput() { 415 continue 416 } 417 if err := operator.SetOutputs(operators); err != nil { 418 require.NoError(t, err) 419 } 420 } 421 422 require.Len(t, operators[0].Outputs(), 1) 423 require.Equal(t, "$.my_drop", operators[0].Outputs()[0].ID()) 424 require.Len(t, operators[1].Outputs(), 1) 425 require.Equal(t, "$.my_drop", operators[1].Outputs()[0].ID()) 426 require.Len(t, operators[2].Outputs(), 0) 427 } 428 429 func TestMultiRoundtripParams(t *testing.T) { 430 cases := []Params{ 431 map[string]interface{}{"foo": "bar"}, 432 map[string]interface{}{ 433 "foo": map[string]interface{}{ 434 "bar": "baz", 435 }, 436 }, 437 map[string]interface{}{ 438 "123": map[string]interface{}{ 439 "234": "345", 440 }, 441 }, 442 map[string]interface{}{ 443 "array": []string{ 444 "foo", 445 "bar", 446 }, 447 }, 448 map[string]interface{}{ 449 "array": []map[string]interface{}{ 450 { 451 "foo": "bar", 452 }, 453 }, 454 }, 455 } 456 457 for _, tc := range cases { 458 // To YAML 459 marshalledYaml, err := yaml.Marshal(tc) 460 require.NoError(t, err) 461 462 // From YAML 463 var unmarshalledYaml Params 464 err = yaml.Unmarshal(marshalledYaml, &unmarshalledYaml) 465 require.NoError(t, err) 466 467 // To JSON 468 marshalledJson, err := json.Marshal(unmarshalledYaml) 469 require.NoError(t, err) 470 471 // From JSON 472 var unmarshalledJson Params 473 err = json.Unmarshal(marshalledJson, &unmarshalledJson) 474 require.NoError(t, err) 475 476 // Back to YAML 477 marshalledYaml2, err := yaml.Marshal(unmarshalledJson) 478 require.NoError(t, err) 479 require.Equal(t, marshalledYaml, marshalledYaml2) 480 } 481 }