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