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  }