Merge "Add Routes to Pod"
[ovn4nfv-k8s-plugin.git] / internal / pkg / config / config.go
1 package config
2
3 import (
4         "fmt"
5         "os"
6         "path/filepath"
7         "reflect"
8
9         "github.com/sirupsen/logrus"
10         "github.com/urfave/cli"
11         gcfg "gopkg.in/gcfg.v1"
12
13        "k8s.io/client-go/kubernetes"
14         "k8s.io/client-go/rest"
15         "k8s.io/client-go/tools/clientcmd"
16 )
17
18 // The following are global config parameters that other modules may access directly
19 var (
20
21         // Default holds parsed config file parameters and command-line overrides
22         Default = DefaultConfig{
23                 MTU: 1400,
24         }
25
26         // Logging holds logging-related parsed config file parameters and command-line overrides
27         Logging = LoggingConfig{
28                 File:  "", // do not log to a file by default
29                 Level: 4,
30         }
31
32         // CNI holds CNI-related parsed config file parameters and command-line overrides
33         CNI = CNIConfig{
34                 ConfDir: "/etc/cni/net.d",
35                 Plugin:  "ovn4nfvk8s-cni",
36         }
37
38         // Kubernetes holds Kubernetes-related parsed config file parameters
39         Kubernetes = KubernetesConfig{}
40 )
41
42 // DefaultConfig holds parsed config file parameters and command-line overrides
43 type DefaultConfig struct {
44         // MTU value used for the overlay networks.
45         MTU int `gcfg:"mtu"`
46 }
47
48 // LoggingConfig holds logging-related parsed config file parameters and command-line overrides
49 type LoggingConfig struct {
50         // File is the path of the file to log to
51         File string `gcfg:"logfile"`
52         // Level is the logging verbosity level
53         Level int `gcfg:"loglevel"`
54 }
55
56 // CNIConfig holds CNI-related parsed config file parameters and command-line overrides
57 type CNIConfig struct {
58         // ConfDir specifies the CNI config directory in which to write the overlay CNI config file
59         ConfDir string `gcfg:"conf-dir"`
60         // Plugin specifies the name of the CNI plugin
61         Plugin string `gcfg:"plugin"`
62 }
63
64 // KubernetesConfig holds Kubernetes-related parsed config file parameters and command-line overrides
65 type KubernetesConfig struct {
66         Kubeconfig string `gcfg:"kubeconfig"`
67 }
68
69 // Config is used to read the structured config file and to cache config in testcases
70 type config struct {
71         Default    DefaultConfig
72         Logging    LoggingConfig
73         CNI        CNIConfig
74         Kubernetes KubernetesConfig
75 }
76
77 // copy members of struct 'src' into the corresponding field in struct 'dst'
78 // if the field in 'src' is a non-zero int or a non-zero-length string. This
79 // function should be called with pointers to structs.
80 func overrideFields(dst, src interface{}) {
81         dstStruct := reflect.ValueOf(dst).Elem()
82         srcStruct := reflect.ValueOf(src).Elem()
83         if dstStruct.Kind() != srcStruct.Kind() || dstStruct.Kind() != reflect.Struct {
84                 panic("mismatched value types")
85         }
86         if dstStruct.NumField() != srcStruct.NumField() {
87                 panic("mismatched struct types")
88         }
89
90         for i := 0; i < dstStruct.NumField(); i++ {
91                 dstField := dstStruct.Field(i)
92                 srcField := srcStruct.Field(i)
93                 if dstField.Kind() != srcField.Kind() {
94                         panic("mismatched struct fields")
95                 }
96                 switch srcField.Kind() {
97                 case reflect.String:
98                         if srcField.String() != "" {
99                                 dstField.Set(srcField)
100                         }
101                 case reflect.Int:
102                         if srcField.Int() != 0 {
103                                 dstField.Set(srcField)
104                         }
105                 default:
106                         panic(fmt.Sprintf("unhandled struct field type: %v", srcField.Kind()))
107                 }
108         }
109 }
110
111 var cliConfig config
112
113 // Flags are general command-line flags. Apps should add these flags to their
114 // own urfave/cli flags and call InitConfig() early in the application.
115 var Flags = []cli.Flag{
116         cli.StringFlag{
117                 Name:  "config-file",
118                 Usage: "configuration file path (default: /etc/openvswitch/ovn4nfv_k8s.conf)",
119         },
120
121         // Generic options
122         cli.IntFlag{
123                 Name:        "mtu",
124                 Usage:       "MTU value used for the overlay networks (default: 1400)",
125                 Destination: &cliConfig.Default.MTU,
126         },
127
128         // Logging options
129         cli.IntFlag{
130                 Name:        "loglevel",
131                 Usage:       "log verbosity and level: 5=debug, 4=info, 3=warn, 2=error, 1=fatal (default: 4)",
132                 Destination: &cliConfig.Logging.Level,
133         },
134         cli.StringFlag{
135                 Name:        "logfile",
136                 Usage:       "path of a file to direct log output to",
137                 Destination: &cliConfig.Logging.File,
138         },
139
140         // CNI options
141         cli.StringFlag{
142                 Name:        "cni-conf-dir",
143                 Usage:       "the CNI config directory in which to write the overlay CNI config file (default: /etc/cni/net.d)",
144                 Destination: &cliConfig.CNI.ConfDir,
145         },
146         cli.StringFlag{
147                 Name:        "cni-plugin",
148                 Usage:       "the name of the CNI plugin (default: ovn4nfvk8s-cni)",
149                 Destination: &cliConfig.CNI.Plugin,
150         },
151
152         // Kubernetes-related options
153         cli.StringFlag{
154                 Name:        "k8s-kubeconfig",
155                 Usage:       "absolute path to the Kubernetes kubeconfig file",
156                 Destination: &cliConfig.Kubernetes.Kubeconfig,
157         },
158 }
159
160 func buildKubernetesConfig(cli, file *config) error {
161
162         // Copy config file values over default values
163         overrideFields(&Kubernetes, &file.Kubernetes)
164         // And CLI overrides over config file and default values
165         overrideFields(&Kubernetes, &cli.Kubernetes)
166
167         if Kubernetes.Kubeconfig == "" || !pathExists(Kubernetes.Kubeconfig) {
168                 return fmt.Errorf("kubernetes kubeconfig file %q not found", Kubernetes.Kubeconfig)
169         }
170         return nil
171 }
172
173 // getConfigFilePath returns config file path and 'true' if the config file is
174 // the fallback path (eg not given by the user), 'false' if given explicitly
175 // by the user
176 func getConfigFilePath(ctx *cli.Context) (string, bool) {
177         configFile := ctx.String("config-file")
178         if configFile != "" {
179                 return configFile, false
180         }
181
182         // default
183         return filepath.Join("/etc", "openvswitch", "ovn4nfv_k8s.conf"), true
184
185 }
186
187 // InitConfig reads the config file and common command-line options and
188 // constructs the global config object from them. It returns the config file
189 // path (if explicitly specified) or an error
190 func InitConfig(ctx *cli.Context) (string, error) {
191         return InitConfigWithPath(ctx, "")
192 }
193
194 // InitConfigWithPath reads the given config file (or if empty, reads the config file
195 // specified by command-line arguments, or empty, the default config file) and
196 // common command-line options and constructs the global config object from
197 // them. It returns the config file path (if explicitly specified) or an error
198 func InitConfigWithPath(ctx *cli.Context, configFile string) (string, error) {
199         var cfg config
200         var retConfigFile string
201         var configFileIsDefault bool
202
203         // If no specific config file was given, try to find one from command-line
204         // arguments, or the platform-specific default config file path
205         if configFile == "" {
206                 configFile, configFileIsDefault = getConfigFilePath(ctx)
207         }
208
209         logrus.SetOutput(os.Stderr)
210
211         if !configFileIsDefault {
212                 // Only return explicitly specified config file
213                 retConfigFile = configFile
214         }
215
216         f, err := os.Open(configFile)
217         // Failure to find a default config file is not a hard error
218         if err != nil && !configFileIsDefault {
219                 return "", fmt.Errorf("failed to open config file %s: %v", configFile, err)
220         }
221         if f != nil {
222                 defer f.Close()
223
224                 // Parse ovn4nfvk8s config file.
225                 if err = gcfg.ReadInto(&cfg, f); err != nil {
226                         return "", fmt.Errorf("failed to parse config file %s: %v", f.Name(), err)
227                 }
228                 logrus.Infof("Parsed config file %s", f.Name())
229                 logrus.Infof("Parsed config: %+v", cfg)
230         }
231
232         // Build config that needs no special processing
233         overrideFields(&Default, &cfg.Default)
234         overrideFields(&Default, &cliConfig.Default)
235         overrideFields(&CNI, &cfg.CNI)
236         overrideFields(&CNI, &cliConfig.CNI)
237
238         // Logging setup
239         overrideFields(&Logging, &cfg.Logging)
240         overrideFields(&Logging, &cliConfig.Logging)
241         logrus.SetLevel(logrus.Level(Logging.Level))
242         if Logging.File != "" {
243                 var file *os.File
244                 file, err = os.OpenFile(Logging.File, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0660)
245                 if err != nil {
246                         logrus.Errorf("failed to open logfile %s (%v). Ignoring..", Logging.File, err)
247                 } else {
248                         logrus.SetOutput(file)
249                 }
250         }
251
252         if err = buildKubernetesConfig(&cliConfig, &cfg); err != nil {
253                 return "", err
254         }
255         logrus.Debugf("Default config: %+v", Default)
256         logrus.Debugf("Logging config: %+v", Logging)
257         logrus.Debugf("CNI config: %+v", CNI)
258         logrus.Debugf("Kubernetes config: %+v", Kubernetes)
259
260         return retConfigFile, nil
261 }
262
263 func pathExists(path string) bool {
264         _, err := os.Stat(path)
265         if err != nil && os.IsNotExist(err) {
266                 return false
267         }
268         return true
269 }
270
271 // NewClientset creates a Kubernetes clientset
272 func NewClientset(conf *KubernetesConfig) (*kubernetes.Clientset, error) {
273         var kconfig *rest.Config
274         var err error
275
276         if conf.Kubeconfig != "" {
277                 // uses the current context in kubeconfig
278                 kconfig, err = clientcmd.BuildConfigFromFlags("", conf.Kubeconfig)
279         }
280         if err != nil {
281                 return nil, err
282         }
283
284         return kubernetes.NewForConfig(kconfig)
285 }