| /* |
| * Copyright 2019-2024 Open Networking Foundation (ONF) and the ONF Contributors |
| |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package etcd |
| |
| import ( |
| "context" |
| "fmt" |
| "net/url" |
| "os" |
| "strings" |
| "time" |
| |
| "go.etcd.io/etcd/embed" |
| ) |
| |
| const ( |
| serverStartUpTimeout = 10 * time.Second // Maximum time allowed to wait for the Etcd server to be ready |
| defaultLocalPersistentStorage = "voltha.test.embed.etcd" |
| ) |
| |
| // EtcdServer represents an embedded Etcd server. It is used for testing only. |
| type EtcdServer struct { |
| server *embed.Etcd |
| } |
| |
| func islogLevelValid(logLevel string) bool { |
| valid := []string{"debug", "info", "warn", "error", "panic", "fatal"} |
| for _, l := range valid { |
| if l == logLevel { |
| return true |
| } |
| } |
| return false |
| } |
| |
| /* |
| * MKConfig creates an embedded Etcd config |
| * :param configName: A name for this config |
| * :param clientPort: The port the etcd client will connect to (do not use 2379 for unit test) |
| * :param peerPort: The port the etcd server will listen for its peers (do not use 2380 for unit test) |
| * :param localPersistentStorageDir: The name of a local directory which will hold the Etcd server data |
| * :param logLevel: One of debug, info, warn, error, panic, or fatal. Default 'info'. |
| */ |
| func MKConfig(ctx context.Context, configName string, clientPort, peerPort int, localPersistentStorageDir string, logLevel string) *embed.Config { |
| cfg := embed.NewConfig() |
| cfg.Name = configName |
| cfg.Dir = localPersistentStorageDir |
| // cfg.Logger = "zap" |
| if !islogLevelValid(logLevel) { |
| logger.Fatalf(ctx, "Invalid log level -%s", logLevel) |
| } |
| // cfg.LogLevel = logLevel |
| cfg.Debug = strings.EqualFold(logLevel, "debug") |
| cfg.LogPkgLevels = "*=C" |
| cfg.SetupLogging() |
| |
| acurl, err := url.Parse(fmt.Sprintf("http://localhost:%d", clientPort)) |
| if err != nil { |
| logger.Fatalf(ctx, "Invalid client port -%d", clientPort) |
| } |
| cfg.ACUrls = []url.URL{*acurl} |
| cfg.LCUrls = []url.URL{*acurl} |
| |
| apurl, err := url.Parse(fmt.Sprintf("http://localhost:%d", peerPort)) |
| if err != nil { |
| logger.Fatalf(ctx, "Invalid peer port -%d", peerPort) |
| } |
| cfg.LPUrls = []url.URL{*apurl} |
| cfg.APUrls = []url.URL{*apurl} |
| |
| cfg.ClusterState = embed.ClusterStateFlagNew |
| cfg.InitialCluster = cfg.Name + "=" + apurl.String() |
| |
| return cfg |
| } |
| |
| // getDefaultCfg specifies the default config |
| func getDefaultCfg() *embed.Config { |
| cfg := embed.NewConfig() |
| cfg.Debug = false |
| cfg.LogPkgLevels = "*=C" |
| cfg.SetupLogging() |
| cfg.Dir = defaultLocalPersistentStorage |
| return cfg |
| } |
| |
| // StartEtcdServer creates and starts an embedded Etcd server. A local directory to store data is created for the |
| // embedded server lifetime (for the duration of a unit test. The server runs at localhost:2379. |
| func StartEtcdServer(ctx context.Context, cfg *embed.Config) *EtcdServer { |
| // If the server is already running, just return |
| if cfg == nil { |
| cfg = getDefaultCfg() |
| } |
| // Remove the local directory as |
| // a safeguard for the case where a prior test failed |
| if err := os.RemoveAll(cfg.Dir); err != nil { |
| logger.Fatalf(ctx, "Failure removing local directory %s", cfg.Dir) |
| } |
| e, err := embed.StartEtcd(cfg) |
| if err != nil { |
| logger.Fatal(ctx, err) |
| } |
| select { |
| case <-e.Server.ReadyNotify(): |
| logger.Debug(ctx, "Embedded Etcd server is ready!") |
| case <-time.After(serverStartUpTimeout): |
| e.Server.HardStop() // trigger a shutdown |
| e.Close() |
| logger.Fatal(ctx, "Embedded Etcd server took too long to start!") |
| case err := <-e.Err(): |
| e.Server.HardStop() // trigger a shutdown |
| e.Close() |
| logger.Fatalf(ctx, "Embedded Etcd server errored out - %s", err) |
| } |
| return &EtcdServer{server: e} |
| } |
| |
| // Stop closes the embedded Etcd server and removes the local data directory as well |
| func (es *EtcdServer) Stop(ctx context.Context) { |
| if es != nil { |
| storage := es.server.Config().Dir |
| es.server.Server.HardStop() |
| es.server.Close() |
| if err := os.RemoveAll(storage); err != nil { |
| logger.Fatalf(ctx, "Failure removing local directory %s", es.server.Config().Dir) |
| } |
| } |
| } |