github.com/m3db/m3@v1.5.0/src/query/storage/m3/config.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package m3 22 23 import ( 24 goerrors "errors" 25 "fmt" 26 "sync" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/client" 30 "github.com/m3db/m3/src/dbnode/encoding" 31 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 32 "github.com/m3db/m3/src/query/stores/m3db" 33 xerrors "github.com/m3db/m3/src/x/errors" 34 "github.com/m3db/m3/src/x/ident" 35 "github.com/m3db/m3/src/x/instrument" 36 ) 37 38 var ( 39 errNotAggregatedClusterNamespace = goerrors.New("not an aggregated cluster namespace") 40 errNoNamespaceInitializerSet = goerrors.New("no namespace initializer set") 41 ) 42 43 // ClustersStaticConfiguration is a set of static cluster configurations. 44 type ClustersStaticConfiguration []ClusterStaticConfiguration 45 46 // NewClientFromConfig is a method that can be set on 47 // ClusterStaticConfiguration to allow overriding the client initialization. 48 type NewClientFromConfig func( 49 cfg client.Configuration, 50 params client.ConfigurationParameters, 51 custom ...client.CustomAdminOption, 52 ) (client.Client, error) 53 54 // ClusterStaticConfiguration is a static cluster configuration. 55 type ClusterStaticConfiguration struct { 56 NewClientFromConfig NewClientFromConfig `yaml:"-"` 57 Namespaces []ClusterStaticNamespaceConfiguration `yaml:"namespaces"` 58 Client client.Configuration `yaml:"client"` 59 } 60 61 func (c ClusterStaticConfiguration) newClient( 62 params client.ConfigurationParameters, 63 custom ...client.CustomAdminOption, 64 ) (client.Client, error) { 65 if c.NewClientFromConfig != nil { 66 return c.NewClientFromConfig(c.Client, params, custom...) 67 } 68 return c.Client.NewAdminClient(params, custom...) 69 } 70 71 // ClusterStaticNamespaceConfiguration describes the namespaces in a 72 // static cluster. 73 type ClusterStaticNamespaceConfiguration struct { 74 // Namespace is namespace in the cluster that is specified. 75 Namespace string `yaml:"namespace"` 76 77 // Type is the type of values stored by the namespace, current 78 // supported values are "unaggregated" or "aggregated". 79 Type storagemetadata.MetricsType `yaml:"type"` 80 81 // Retention is the length of which values are stored by the namespace. 82 Retention time.Duration `yaml:"retention" validate:"nonzero"` 83 84 // Resolution is the frequency of which values are stored by the namespace. 85 Resolution time.Duration `yaml:"resolution" validate:"min=0"` 86 87 // Downsample is the configuration for downsampling options to use with 88 // the namespace. 89 Downsample *DownsampleClusterStaticNamespaceConfiguration `yaml:"downsample"` 90 91 // ReadOnly prevents any writes to this namespace. 92 ReadOnly bool `yaml:"readOnly"` 93 94 // DataLatency is the duration after which the data is available in this namespace. 95 DataLatency time.Duration `yaml:"dataLatency"` 96 } 97 98 func (c ClusterStaticNamespaceConfiguration) metricsType() (storagemetadata.MetricsType, error) { 99 unset := storagemetadata.MetricsType(0) 100 101 if c.Type != unset { 102 // New field value set 103 return c.Type, nil 104 } 105 106 // Both are unset 107 return storagemetadata.DefaultMetricsType, nil 108 } 109 110 func (c ClusterStaticNamespaceConfiguration) downsampleOptions() ( 111 ClusterNamespaceDownsampleOptions, 112 error, 113 ) { 114 nsType, err := c.metricsType() 115 if err != nil { 116 return ClusterNamespaceDownsampleOptions{}, err 117 } 118 if nsType != storagemetadata.AggregatedMetricsType { 119 return ClusterNamespaceDownsampleOptions{}, errNotAggregatedClusterNamespace 120 } 121 if c.Downsample == nil { 122 return DefaultClusterNamespaceDownsampleOptions, nil 123 } 124 125 return c.Downsample.downsampleOptions(), nil 126 } 127 128 // DownsampleClusterStaticNamespaceConfiguration is configuration 129 // specified for downsampling options on an aggregated cluster namespace. 130 type DownsampleClusterStaticNamespaceConfiguration struct { 131 All bool `yaml:"all"` 132 } 133 134 func (c DownsampleClusterStaticNamespaceConfiguration) downsampleOptions() ClusterNamespaceDownsampleOptions { 135 return ClusterNamespaceDownsampleOptions(c) 136 } 137 138 type unaggregatedClusterNamespaceConfiguration struct { 139 client client.Client 140 namespace ClusterStaticNamespaceConfiguration 141 result clusterConnectResult 142 } 143 144 type aggregatedClusterNamespacesConfiguration struct { 145 client client.Client 146 namespaces []ClusterStaticNamespaceConfiguration 147 result clusterConnectResult 148 } 149 150 type clusterConnectResult struct { 151 session client.Session 152 err error 153 } 154 155 // ClustersStaticConfigurationOptions are options to use when 156 // constructing clusters from config. 157 type ClustersStaticConfigurationOptions struct { 158 AsyncSessions bool 159 ProvidedSession client.Session 160 CustomAdminOptions []client.CustomAdminOption 161 EncodingOptions encoding.Options 162 } 163 164 // NewStaticClusters instantiates a new Clusters instance based on 165 // static configuration. 166 func (c ClustersStaticConfiguration) NewStaticClusters( 167 instrumentOpts instrument.Options, 168 opts ClustersStaticConfigurationOptions, 169 clusterNamespacesWatcher ClusterNamespacesWatcher, 170 ) (Clusters, error) { 171 var ( 172 numUnaggregatedClusterNamespaces int 173 numAggregatedClusterNamespaces int 174 unaggregatedClusterNamespaceCfg = &unaggregatedClusterNamespaceConfiguration{} 175 aggregatedClusterNamespacesCfgs []*aggregatedClusterNamespacesConfiguration 176 unaggregatedClusterNamespace UnaggregatedClusterNamespaceDefinition 177 aggregatedClusterNamespaces []AggregatedClusterNamespaceDefinition 178 ) 179 for _, clusterCfg := range c { 180 var ( 181 result client.Client 182 err error 183 ) 184 185 if opts.ProvidedSession == nil { 186 // NB(r): Only create client session if not already provided. 187 result, err = clusterCfg.newClient(client.ConfigurationParameters{ 188 InstrumentOptions: instrumentOpts, 189 EncodingOptions: opts.EncodingOptions, 190 }, opts.CustomAdminOptions...) 191 if err != nil { 192 return nil, err 193 } 194 } 195 196 aggregatedClusterNamespacesCfg := &aggregatedClusterNamespacesConfiguration{ 197 client: result, 198 } 199 200 for _, n := range clusterCfg.Namespaces { 201 nsType, err := n.metricsType() 202 if err != nil { 203 return nil, err 204 } 205 206 switch nsType { 207 case storagemetadata.UnaggregatedMetricsType: 208 numUnaggregatedClusterNamespaces++ 209 if numUnaggregatedClusterNamespaces > 1 { 210 return nil, fmt.Errorf("only one unaggregated cluster namespace "+ 211 "can be specified: specified %d", numUnaggregatedClusterNamespaces) 212 } 213 214 unaggregatedClusterNamespaceCfg.client = result 215 unaggregatedClusterNamespaceCfg.namespace = n 216 217 case storagemetadata.AggregatedMetricsType: 218 numAggregatedClusterNamespaces++ 219 220 aggregatedClusterNamespacesCfg.namespaces = 221 append(aggregatedClusterNamespacesCfg.namespaces, n) 222 223 default: 224 return nil, fmt.Errorf("unknown storage metrics type: %v", nsType) 225 } 226 } 227 228 if len(aggregatedClusterNamespacesCfg.namespaces) > 0 { 229 aggregatedClusterNamespacesCfgs = 230 append(aggregatedClusterNamespacesCfgs, aggregatedClusterNamespacesCfg) 231 } 232 } 233 234 if numUnaggregatedClusterNamespaces != 1 { 235 return nil, fmt.Errorf("one unaggregated cluster namespace "+ 236 "must be specified: specified %d", numUnaggregatedClusterNamespaces) 237 } 238 239 // Connect to all clusters in parallel. 240 var wg sync.WaitGroup 241 wg.Add(1) 242 go func() { 243 defer wg.Done() 244 cfg := unaggregatedClusterNamespaceCfg 245 if opts.ProvidedSession != nil { 246 cfg.result.session = opts.ProvidedSession 247 } else if !opts.AsyncSessions { 248 cfg.result.session, cfg.result.err = cfg.client.DefaultSession() 249 } else { 250 cfg.result.session = m3db.NewAsyncSession(func() (client.Client, error) { 251 return cfg.client, nil 252 }, nil) 253 } 254 }() 255 for _, cfg := range aggregatedClusterNamespacesCfgs { 256 cfg := cfg // Capture var 257 wg.Add(1) 258 go func() { 259 defer wg.Done() 260 if opts.ProvidedSession != nil { 261 cfg.result.session = opts.ProvidedSession 262 } else if !opts.AsyncSessions { 263 cfg.result.session, cfg.result.err = cfg.client.DefaultSession() 264 } else { 265 cfg.result.session = m3db.NewAsyncSession(func() (client.Client, error) { 266 return cfg.client, nil 267 }, nil) 268 } 269 }() 270 } 271 272 // Wait for connections. 273 wg.Wait() 274 275 if unaggregatedClusterNamespaceCfg.result.err != nil { 276 return nil, fmt.Errorf("could not connect to unaggregated cluster: %v", 277 unaggregatedClusterNamespaceCfg.result.err) 278 } 279 280 unaggregatedClusterNamespace = UnaggregatedClusterNamespaceDefinition{ 281 NamespaceID: ident.StringID(unaggregatedClusterNamespaceCfg.namespace.Namespace), 282 Session: unaggregatedClusterNamespaceCfg.result.session, 283 Retention: unaggregatedClusterNamespaceCfg.namespace.Retention, 284 } 285 286 for i, cfg := range aggregatedClusterNamespacesCfgs { 287 if cfg.result.err != nil { 288 return nil, fmt.Errorf("could not connect to aggregated cluster #%d: %v", 289 i, cfg.result.err) 290 } 291 292 for _, n := range cfg.namespaces { 293 downsampleOpts, err := n.downsampleOptions() 294 if err != nil { 295 return nil, fmt.Errorf("error parse downsample options for cluster #%d namespace %s: %v", 296 i, n.Namespace, err) 297 } 298 299 def := AggregatedClusterNamespaceDefinition{ 300 NamespaceID: ident.StringID(n.Namespace), 301 Session: cfg.result.session, 302 Retention: n.Retention, 303 Resolution: n.Resolution, 304 Downsample: &downsampleOpts, 305 ReadOnly: n.ReadOnly, 306 DataLatency: n.DataLatency, 307 } 308 aggregatedClusterNamespaces = append(aggregatedClusterNamespaces, def) 309 } 310 } 311 312 clusters, err := NewClusters(unaggregatedClusterNamespace, 313 aggregatedClusterNamespaces...) 314 if err != nil { 315 return nil, err 316 } 317 318 if err := clusterNamespacesWatcher.Update(clusters.ClusterNamespaces()); err != nil { 319 return nil, err 320 } 321 322 return clusters, nil 323 } 324 325 // NB(nate): exists primarily for testing. 326 type newClustersFn func(DynamicClusterOptions) (Clusters, error) 327 328 // NewDynamicClusters instantiates a new Clusters instance that pulls 329 // cluster information from etcd. 330 func (c ClustersStaticConfiguration) NewDynamicClusters( 331 instrumentOpts instrument.Options, 332 opts ClustersStaticConfigurationOptions, 333 clusterNamespacesWatcher ClusterNamespacesWatcher, 334 ) (Clusters, error) { 335 return c.newDynamicClusters(NewDynamicClusters, instrumentOpts, opts, clusterNamespacesWatcher) 336 } 337 338 func (c ClustersStaticConfiguration) newDynamicClusters( 339 newFn newClustersFn, 340 instrumentOpts instrument.Options, 341 opts ClustersStaticConfigurationOptions, 342 clusterNamespacesWatcher ClusterNamespacesWatcher, 343 ) (Clusters, error) { 344 clients := make([]client.Client, 0, len(c)) 345 for _, clusterCfg := range c { 346 clusterClient, err := clusterCfg.newClient(client.ConfigurationParameters{ 347 InstrumentOptions: instrumentOpts, 348 EncodingOptions: opts.EncodingOptions, 349 }, opts.CustomAdminOptions...) 350 if err != nil { 351 return nil, err 352 } 353 clients = append(clients, clusterClient) 354 } 355 356 // Connect to all clusters in parallel 357 var ( 358 wg sync.WaitGroup 359 cfgs = make([]DynamicClusterNamespaceConfiguration, len(clients)) 360 361 errLock sync.Mutex 362 multiErr xerrors.MultiError 363 ) 364 for i, clusterClient := range clients { 365 i := i 366 clusterClient := clusterClient 367 nsInit := clusterClient.Options().NamespaceInitializer() 368 369 // TODO(nate): move this validation to client.Options once static configuration of namespaces 370 // is no longer allowed. 371 if nsInit == nil { 372 return nil, errNoNamespaceInitializerSet 373 } 374 375 wg.Add(1) 376 go func() { 377 defer wg.Done() 378 379 cfgs[i].nsInitializer = nsInit 380 if opts.ProvidedSession != nil { 381 cfgs[i].session = opts.ProvidedSession 382 } else if !opts.AsyncSessions { 383 var err error 384 session, err := clusterClient.DefaultSession() 385 if err != nil { 386 errLock.Lock() 387 multiErr = multiErr.Add(err) 388 errLock.Unlock() 389 } 390 cfgs[i].session = session 391 } else { 392 cfgs[i].session = m3db.NewAsyncSession(func() (client.Client, error) { 393 return clusterClient, nil 394 }, nil) 395 } 396 }() 397 } 398 399 wg.Wait() 400 401 if !multiErr.Empty() { 402 // Close any created sessions on failure. 403 for _, cfg := range cfgs { 404 if cfg.session != nil { 405 // Returns an error if session is already closed which, in this case, 406 // is fine 407 _ = cfg.session.Close() 408 } 409 } 410 return nil, multiErr.FinalError() 411 } 412 413 dcOpts := NewDynamicClusterOptions(). 414 SetDynamicClusterNamespaceConfiguration(cfgs). 415 SetClusterNamespacesWatcher(clusterNamespacesWatcher). 416 SetInstrumentOptions(instrumentOpts) 417 418 return newFn(dcOpts) 419 }