1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package config 16 17import ( 18 "errors" 19 "fmt" 20 "testing" 21 22 "github.com/stretchr/testify/assert" 23) 24 25var errInvalidRecvConfig = errors.New("invalid receiver config") 26var errInvalidExpConfig = errors.New("invalid exporter config") 27var errInvalidProcConfig = errors.New("invalid processor config") 28var errInvalidExtConfig = errors.New("invalid extension config") 29 30type nopRecvConfig struct { 31 ReceiverSettings 32} 33 34func (nc *nopRecvConfig) Validate() error { 35 if nc.ID() != NewID("nop") { 36 return errInvalidRecvConfig 37 } 38 return nil 39} 40 41type nopExpConfig struct { 42 ExporterSettings 43} 44 45func (nc *nopExpConfig) Validate() error { 46 if nc.ID() != NewID("nop") { 47 return errInvalidExpConfig 48 } 49 return nil 50} 51 52type nopProcConfig struct { 53 ProcessorSettings 54} 55 56func (nc *nopProcConfig) Validate() error { 57 if nc.ID() != NewID("nop") { 58 return errInvalidProcConfig 59 } 60 return nil 61} 62 63type nopExtConfig struct { 64 ExtensionSettings 65} 66 67func (nc *nopExtConfig) Validate() error { 68 if nc.ID() != NewID("nop") { 69 return errInvalidExtConfig 70 } 71 return nil 72} 73 74func TestConfigValidate(t *testing.T) { 75 var testCases = []struct { 76 name string // test case name (also file name containing config yaml) 77 cfgFn func() *Config 78 expected error 79 }{ 80 { 81 name: "valid", 82 cfgFn: generateConfig, 83 expected: nil, 84 }, 85 { 86 name: "missing-exporters", 87 cfgFn: func() *Config { 88 cfg := generateConfig() 89 cfg.Exporters = nil 90 return cfg 91 }, 92 expected: errMissingExporters, 93 }, 94 { 95 name: "missing-receivers", 96 cfgFn: func() *Config { 97 cfg := generateConfig() 98 cfg.Receivers = nil 99 return cfg 100 }, 101 expected: errMissingReceivers, 102 }, 103 { 104 name: "invalid-extension-reference", 105 cfgFn: func() *Config { 106 cfg := generateConfig() 107 cfg.Service.Extensions = append(cfg.Service.Extensions, NewIDWithName("nop", "2")) 108 return cfg 109 }, 110 expected: errors.New(`service references extension "nop/2" which does not exist`), 111 }, 112 { 113 name: "invalid-receiver-reference", 114 cfgFn: func() *Config { 115 cfg := generateConfig() 116 pipe := cfg.Service.Pipelines["traces"] 117 pipe.Receivers = append(pipe.Receivers, NewIDWithName("nop", "2")) 118 return cfg 119 }, 120 expected: errors.New(`pipeline "traces" references receiver "nop/2" which does not exist`), 121 }, 122 { 123 name: "invalid-processor-reference", 124 cfgFn: func() *Config { 125 cfg := generateConfig() 126 pipe := cfg.Service.Pipelines["traces"] 127 pipe.Processors = append(pipe.Processors, NewIDWithName("nop", "2")) 128 return cfg 129 }, 130 expected: errors.New(`pipeline "traces" references processor "nop/2" which does not exist`), 131 }, 132 { 133 name: "invalid-exporter-reference", 134 cfgFn: func() *Config { 135 cfg := generateConfig() 136 pipe := cfg.Service.Pipelines["traces"] 137 pipe.Exporters = append(pipe.Exporters, NewIDWithName("nop", "2")) 138 return cfg 139 }, 140 expected: errors.New(`pipeline "traces" references exporter "nop/2" which does not exist`), 141 }, 142 { 143 name: "missing-pipeline-receivers", 144 cfgFn: func() *Config { 145 cfg := generateConfig() 146 pipe := cfg.Service.Pipelines["traces"] 147 pipe.Receivers = nil 148 return cfg 149 }, 150 expected: errors.New(`pipeline "traces" must have at least one receiver`), 151 }, 152 { 153 name: "missing-pipeline-exporters", 154 cfgFn: func() *Config { 155 cfg := generateConfig() 156 pipe := cfg.Service.Pipelines["traces"] 157 pipe.Exporters = nil 158 return cfg 159 }, 160 expected: errors.New(`pipeline "traces" must have at least one exporter`), 161 }, 162 { 163 name: "missing-pipelines", 164 cfgFn: func() *Config { 165 cfg := generateConfig() 166 cfg.Service.Pipelines = nil 167 return cfg 168 }, 169 expected: errMissingServicePipelines, 170 }, 171 { 172 name: "invalid-receiver-config", 173 cfgFn: func() *Config { 174 cfg := generateConfig() 175 cfg.Receivers[NewID("nop")] = &nopRecvConfig{ 176 ReceiverSettings: NewReceiverSettings(NewID("invalid_rec_type")), 177 } 178 return cfg 179 }, 180 expected: fmt.Errorf(`receiver "nop" has invalid configuration: %w`, errInvalidRecvConfig), 181 }, 182 { 183 name: "invalid-exporter-config", 184 cfgFn: func() *Config { 185 cfg := generateConfig() 186 cfg.Exporters[NewID("nop")] = &nopExpConfig{ 187 ExporterSettings: NewExporterSettings(NewID("invalid_rec_type")), 188 } 189 return cfg 190 }, 191 expected: fmt.Errorf(`exporter "nop" has invalid configuration: %w`, errInvalidExpConfig), 192 }, 193 { 194 name: "invalid-processor-config", 195 cfgFn: func() *Config { 196 cfg := generateConfig() 197 cfg.Processors[NewID("nop")] = &nopProcConfig{ 198 ProcessorSettings: NewProcessorSettings(NewID("invalid_rec_type")), 199 } 200 return cfg 201 }, 202 expected: fmt.Errorf(`processor "nop" has invalid configuration: %w`, errInvalidProcConfig), 203 }, 204 { 205 name: "invalid-extension-config", 206 cfgFn: func() *Config { 207 cfg := generateConfig() 208 cfg.Extensions[NewID("nop")] = &nopExtConfig{ 209 ExtensionSettings: NewExtensionSettings(NewID("invalid_rec_type")), 210 } 211 return cfg 212 }, 213 expected: fmt.Errorf(`extension "nop" has invalid configuration: %w`, errInvalidExtConfig), 214 }, 215 } 216 217 for _, test := range testCases { 218 t.Run(test.name, func(t *testing.T) { 219 cfg := test.cfgFn() 220 assert.Equal(t, test.expected, cfg.Validate()) 221 }) 222 } 223} 224 225func generateConfig() *Config { 226 return &Config{ 227 Receivers: map[ComponentID]Receiver{ 228 NewID("nop"): &nopRecvConfig{ 229 ReceiverSettings: NewReceiverSettings(NewID("nop")), 230 }, 231 }, 232 Exporters: map[ComponentID]Exporter{ 233 NewID("nop"): &nopExpConfig{ 234 ExporterSettings: NewExporterSettings(NewID("nop")), 235 }, 236 }, 237 Processors: map[ComponentID]Processor{ 238 NewID("nop"): &nopProcConfig{ 239 ProcessorSettings: NewProcessorSettings(NewID("nop")), 240 }, 241 }, 242 Extensions: map[ComponentID]Extension{ 243 NewID("nop"): &nopExtConfig{ 244 ExtensionSettings: NewExtensionSettings(NewID("nop")), 245 }, 246 }, 247 Service: Service{ 248 Extensions: []ComponentID{NewID("nop")}, 249 Pipelines: map[string]*Pipeline{ 250 "traces": { 251 Name: "traces", 252 InputType: TracesDataType, 253 Receivers: []ComponentID{NewID("nop")}, 254 Processors: []ComponentID{NewID("nop")}, 255 Exporters: []ComponentID{NewID("nop")}, 256 }, 257 }, 258 }, 259 } 260} 261