blob: 59d52159f69c786b13d33762df4cfbb921873b38 [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001/*
Joey Armstrong653504f2024-01-18 12:58:56 -05002* Copyright 2019-2024 Open Networking Foundation (ONF) and the ONF Contributors
khenaidood948f772021-08-11 17:49:24 -04003
Mahir Gunyel4b93c072023-07-21 11:55:08 +03004* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
khenaidood948f772021-08-11 17:49:24 -04007
Mahir Gunyel4b93c072023-07-21 11:55:08 +03008* http://www.apache.org/licenses/LICENSE-2.0
khenaidood948f772021-08-11 17:49:24 -04009
Mahir Gunyel4b93c072023-07-21 11:55:08 +030010* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
khenaidood948f772021-08-11 17:49:24 -040015 */
16package etcd
17
18import (
19 "context"
20 "fmt"
21 "net/url"
22 "os"
23 "strings"
24 "time"
25
26 "go.etcd.io/etcd/embed"
27)
28
29const (
30 serverStartUpTimeout = 10 * time.Second // Maximum time allowed to wait for the Etcd server to be ready
31 defaultLocalPersistentStorage = "voltha.test.embed.etcd"
32)
33
Mahir Gunyel4b93c072023-07-21 11:55:08 +030034// EtcdServer represents an embedded Etcd server. It is used for testing only.
khenaidood948f772021-08-11 17:49:24 -040035type EtcdServer struct {
36 server *embed.Etcd
37}
38
39func islogLevelValid(logLevel string) bool {
40 valid := []string{"debug", "info", "warn", "error", "panic", "fatal"}
41 for _, l := range valid {
42 if l == logLevel {
43 return true
44 }
45 }
46 return false
47}
48
49/*
50* MKConfig creates an embedded Etcd config
51* :param configName: A name for this config
52* :param clientPort: The port the etcd client will connect to (do not use 2379 for unit test)
53* :param peerPort: The port the etcd server will listen for its peers (do not use 2380 for unit test)
54* :param localPersistentStorageDir: The name of a local directory which will hold the Etcd server data
55* :param logLevel: One of debug, info, warn, error, panic, or fatal. Default 'info'.
56 */
57func MKConfig(ctx context.Context, configName string, clientPort, peerPort int, localPersistentStorageDir string, logLevel string) *embed.Config {
58 cfg := embed.NewConfig()
59 cfg.Name = configName
60 cfg.Dir = localPersistentStorageDir
61 // cfg.Logger = "zap"
62 if !islogLevelValid(logLevel) {
63 logger.Fatalf(ctx, "Invalid log level -%s", logLevel)
64 }
65 // cfg.LogLevel = logLevel
66 cfg.Debug = strings.EqualFold(logLevel, "debug")
67 cfg.LogPkgLevels = "*=C"
68 cfg.SetupLogging()
69
70 acurl, err := url.Parse(fmt.Sprintf("http://localhost:%d", clientPort))
71 if err != nil {
72 logger.Fatalf(ctx, "Invalid client port -%d", clientPort)
73 }
74 cfg.ACUrls = []url.URL{*acurl}
75 cfg.LCUrls = []url.URL{*acurl}
76
77 apurl, err := url.Parse(fmt.Sprintf("http://localhost:%d", peerPort))
78 if err != nil {
79 logger.Fatalf(ctx, "Invalid peer port -%d", peerPort)
80 }
81 cfg.LPUrls = []url.URL{*apurl}
82 cfg.APUrls = []url.URL{*apurl}
83
84 cfg.ClusterState = embed.ClusterStateFlagNew
85 cfg.InitialCluster = cfg.Name + "=" + apurl.String()
86
87 return cfg
88}
89
Mahir Gunyel4b93c072023-07-21 11:55:08 +030090// getDefaultCfg specifies the default config
khenaidood948f772021-08-11 17:49:24 -040091func getDefaultCfg() *embed.Config {
92 cfg := embed.NewConfig()
93 cfg.Debug = false
94 cfg.LogPkgLevels = "*=C"
95 cfg.SetupLogging()
96 cfg.Dir = defaultLocalPersistentStorage
97 return cfg
98}
99
Mahir Gunyel4b93c072023-07-21 11:55:08 +0300100// StartEtcdServer creates and starts an embedded Etcd server. A local directory to store data is created for the
101// embedded server lifetime (for the duration of a unit test. The server runs at localhost:2379.
khenaidood948f772021-08-11 17:49:24 -0400102func StartEtcdServer(ctx context.Context, cfg *embed.Config) *EtcdServer {
103 // If the server is already running, just return
104 if cfg == nil {
105 cfg = getDefaultCfg()
106 }
107 // Remove the local directory as
108 // a safeguard for the case where a prior test failed
109 if err := os.RemoveAll(cfg.Dir); err != nil {
110 logger.Fatalf(ctx, "Failure removing local directory %s", cfg.Dir)
111 }
112 e, err := embed.StartEtcd(cfg)
113 if err != nil {
114 logger.Fatal(ctx, err)
115 }
116 select {
117 case <-e.Server.ReadyNotify():
118 logger.Debug(ctx, "Embedded Etcd server is ready!")
119 case <-time.After(serverStartUpTimeout):
120 e.Server.HardStop() // trigger a shutdown
121 e.Close()
122 logger.Fatal(ctx, "Embedded Etcd server took too long to start!")
123 case err := <-e.Err():
124 e.Server.HardStop() // trigger a shutdown
125 e.Close()
126 logger.Fatalf(ctx, "Embedded Etcd server errored out - %s", err)
127 }
128 return &EtcdServer{server: e}
129}
130
Mahir Gunyel4b93c072023-07-21 11:55:08 +0300131// Stop closes the embedded Etcd server and removes the local data directory as well
khenaidood948f772021-08-11 17:49:24 -0400132func (es *EtcdServer) Stop(ctx context.Context) {
133 if es != nil {
134 storage := es.server.Config().Dir
135 es.server.Server.HardStop()
136 es.server.Close()
137 if err := os.RemoveAll(storage); err != nil {
138 logger.Fatalf(ctx, "Failure removing local directory %s", es.server.Config().Dir)
139 }
140 }
141}