-
Notifications
You must be signed in to change notification settings - Fork 66
/
config.go
347 lines (309 loc) · 12.7 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package cluster
import (
"context"
"fmt"
"net"
"os"
"strconv"
"strings"
"github.com/apparentlymart/go-cidr/cidr"
oconfig "github.com/openshift/api/config/v1"
configclient "github.com/openshift/client-go/config/clientset/versioned"
operatorv1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
"golang.org/x/mod/semver"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)
//+kubebuilder:rbac:groups=config.openshift.io,resources=infrastructures,verbs=get
//+kubebuilder:rbac:groups=config.openshift.io;operator.openshift.io,resources=networks,verbs=get
const (
ovnKubernetesNetwork = "OVNKubernetes"
// baseK8sVersion specifies the base k8s version supported by the operator. (For eg. All versions in the format
// 1.20.x are supported for baseK8sVersion 1.20)
baseK8sVersion = "v1.29"
// MachineAPINamespace is the name of the namespace in which machine objects and userData secret is created.
MachineAPINamespace = "openshift-machine-api"
)
var (
// WatchedEnvironmentVars is a list of the WMCO watched environment variables
WatchedEnvironmentVars = []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"}
// clusterWideProxyVars is a map of the global egress proxy variables and values from WMCO's environment
clusterWideProxyVars map[string]string
)
// Network interface contains methods to interact with cluster network objects
type Network interface {
Validate() error
GetServiceCIDR() string
VXLANPort() string
}
// Config interface contains methods to expose cluster config related information
type Config interface {
// Validate checks if the cluster configurations are as required.
Validate() error
// Platform returns cloud provider on which OpenShift is running
Platform() oconfig.PlatformType
// Network returns network configuration for the OpenShift cluster
Network() Network
}
// networkType holds information for a required network type
type networkType struct {
// name describes value of the Network Type
name string
// operatorClient is the OpenShift operator client, we will use to interact with OpenShift operator objects
operatorClient operatorv1.OperatorV1Interface
}
// config encapsulates cluster configuration
type config struct {
// oclient is the OpenShift config client that will be used to interact with the OpenShift API
oclient configclient.Interface
// operatorClient is the OpenShift operator client that that will be used to interact with operator APIs
operatorClient operatorv1.OperatorV1Interface
// network is the interface containing information on cluster network
network Network
// platform indicates the cloud on which OpenShift cluster is running
// TODO: Remove this once we figure out how to be provider agnostic
platform oconfig.PlatformType
}
func (c *config) Platform() oconfig.PlatformType {
return c.platform
}
func (c *config) Network() Network {
return c.network
}
// NewConfig returns a Config struct pertaining to the cluster configuration
func NewConfig(restConfig *rest.Config) (Config, error) {
// get OpenShift API config client.
oclient, err := configclient.NewForConfig(restConfig)
if err != nil {
return nil, fmt.Errorf("could not create config clientset: %w", err)
}
// get OpenShift API operator client
operatorClient, err := operatorv1.NewForConfig(restConfig)
if err != nil {
return nil, fmt.Errorf("could not create operator clientset: %w", err)
}
// get cluster network configurations
network, err := networkConfigurationFactory(oclient, operatorClient)
if err != nil {
return nil, fmt.Errorf("error getting cluster network: %w", err)
}
// get the platform type here
infra, err := oclient.ConfigV1().Infrastructures().Get(context.TODO(), "cluster", meta.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting cluster network: %w", err)
}
platformStatus := infra.Status.PlatformStatus
if platformStatus == nil {
return nil, fmt.Errorf("error getting infrastructure status")
}
if len(platformStatus.Type) == 0 {
return nil, fmt.Errorf("error getting platform type")
}
return &config{
oclient: oclient,
operatorClient: operatorClient,
network: network,
platform: platformStatus.Type,
}, nil
}
// validateK8sVersion checks for valid k8s version in the cluster. It returns an error for all versions that are not in
// range of given base version(x.y.z) and x.y+1.z version.
func (c *config) validateK8sVersion() error {
versionInfo, err := c.oclient.Discovery().ServerVersion()
if err != nil {
return fmt.Errorf("error retrieving server version: %w", err)
}
// split the version in the form Major.Minor. For e.g v1.18.0-rc.1 -> v1.18
clusterBaseVersion := semver.MajorMinor(versionInfo.GitVersion)
// Convert base version to float and add 1 to Minor version
baseVersion, err := strconv.ParseFloat(strings.TrimPrefix(baseK8sVersion, "v"), 64)
if err != nil {
return fmt.Errorf("error converting %s k8s version to float: %w", baseK8sVersion, err)
}
maxK8sVersion := fmt.Sprintf("v%.2f", baseVersion+0.01)
// validate cluster version is in the range of baseK8sVersion and maxK8sVersion
if semver.Compare(clusterBaseVersion, baseK8sVersion) >= 0 && semver.Compare(clusterBaseVersion, maxK8sVersion) <= 0 {
return nil
}
return fmt.Errorf("unsupported server version: %v. Supported versions are %v.x to %v.x", versionInfo.GitVersion,
baseK8sVersion, maxK8sVersion)
}
// Validate method checks if the cluster configurations are as required. It throws an error if the configuration could not
// be validated.
func (c *config) Validate() error {
err := c.validateK8sVersion()
if err != nil {
return fmt.Errorf("error validating k8s version: %w", err)
}
if err = c.network.Validate(); err != nil {
return fmt.Errorf("error validating network configuration: %w", err)
}
return nil
}
// clusterNetworkCfg struct holds the information for the cluster network
type clusterNetworkCfg struct {
// serviceCIDR holds the value for cluster network service CIDR
serviceCIDR string
// vxlanPort is the port to be used for VXLAN communication
vxlanPort string
}
// ovnKubernetes contains information specific to network type OVNKubernetes
type ovnKubernetes struct {
networkType
clusterNetworkConfig *clusterNetworkCfg
}
// networkConfigurationFactory is a factory method that returns information specific to network type
func networkConfigurationFactory(oclient configclient.Interface, operatorClient operatorv1.OperatorV1Interface) (Network, error) {
network, err := getNetworkType(oclient)
if err != nil {
return nil, fmt.Errorf("error getting cluster network type: %w", err)
}
// retrieve serviceCIDR using cluster config required for cni configurations
serviceCIDR, err := getServiceNetworkCIDR(oclient)
if err != nil || serviceCIDR == "" {
return nil, fmt.Errorf("error getting service network CIDR: %w", err)
}
// retrieve the VXLAN port using cluster config
vxlanPort, err := getVXLANPort(operatorClient)
if err != nil {
return nil, fmt.Errorf("error getting the custom vxlan port: %w", err)
}
clusterNetworkCfg, err := NewClusterNetworkCfg(serviceCIDR, vxlanPort)
if err != nil {
return nil, fmt.Errorf("error getting cluster network config: %w", err)
}
switch network {
case ovnKubernetesNetwork:
return &ovnKubernetes{
networkType{
name: network,
operatorClient: operatorClient,
},
clusterNetworkCfg,
}, nil
default:
return nil, fmt.Errorf("%s : network type not supported", network)
}
}
// NewClusterNetworkCfg assigns a serviceCIDR value and returns a pointer to the clusterNetworkCfg struct
func NewClusterNetworkCfg(serviceCIDR, vxlanPort string) (*clusterNetworkCfg, error) {
if serviceCIDR == "" {
return nil, fmt.Errorf("can't instantiate cluster network config" +
"with empty service CIDR value")
}
return &clusterNetworkCfg{
serviceCIDR: serviceCIDR,
vxlanPort: vxlanPort,
}, nil
}
// GetServiceCIDR returns the serviceCIDR string
func (ovn *ovnKubernetes) GetServiceCIDR() string {
return ovn.clusterNetworkConfig.serviceCIDR
}
// GetVXLANPort gets the VXLAN port to be used for VXLAN tunnel establishment
func (ovn *ovnKubernetes) VXLANPort() string {
return ovn.clusterNetworkConfig.vxlanPort
}
// Validate for OVN Kubernetes checks for network type and hybrid overlay.
func (ovn *ovnKubernetes) Validate() error {
// check if hybrid overlay is enabled for the cluster
networkCR, err := ovn.operatorClient.Networks().Get(context.TODO(), "cluster", meta.GetOptions{})
if err != nil {
return fmt.Errorf("error getting cluster network.operator object: %w", err)
}
defaultNetwork := networkCR.Spec.DefaultNetwork
if defaultNetwork.OVNKubernetesConfig == nil || defaultNetwork.OVNKubernetesConfig.HybridOverlayConfig == nil {
return fmt.Errorf("cluster is not configured for OVN hybrid networking")
}
if len(networkCR.Spec.DefaultNetwork.OVNKubernetesConfig.HybridOverlayConfig.HybridClusterNetwork) == 0 {
return fmt.Errorf("invalid OVN hybrid networking configuration")
}
return nil
}
// getNetworkType returns network type of the cluster
func getNetworkType(oclient configclient.Interface) (string, error) {
// Get the cluster network object so that we can find the network type
networkCR, err := oclient.ConfigV1().Networks().Get(context.TODO(), "cluster", meta.GetOptions{})
if err != nil {
return "", fmt.Errorf("error getting cluster network object: %w", err)
}
return networkCR.Spec.NetworkType, nil
}
// getServiceNetworkCIDR gets the serviceCIDR using cluster config required for cni configuration
func getServiceNetworkCIDR(oclient configclient.Interface) (string, error) {
// Get the cluster network object so that we can find the service network
networkCR, err := oclient.ConfigV1().Networks().Get(context.TODO(), "cluster", meta.GetOptions{})
if err != nil {
return "", fmt.Errorf("error getting cluster network object: %w", err)
}
if len(networkCR.Spec.ServiceNetwork) == 0 {
return "", fmt.Errorf("error getting cluster service CIDR," + "received empty value for service networks")
}
serviceCIDR := networkCR.Spec.ServiceNetwork[0]
if err := ValidateCIDR(serviceCIDR); err != nil {
return "", fmt.Errorf("invalid cluster service CIDR: %w", err)
}
return serviceCIDR, nil
}
// getVXLANPort gets the VXLAN port to establish tunnel as a string. The return type doesn't matter as we want to pass
// this argument to a powershell command
func getVXLANPort(operatorClient operatorv1.OperatorV1Interface) (string, error) {
// Get the cluster network object so that we can find the service network
networkCR, err := operatorClient.Networks().Get(context.TODO(), "cluster", meta.GetOptions{})
if err != nil {
return "", fmt.Errorf("error getting cluster network object: %w", err)
}
var vxlanPort *uint32
if networkCR.Spec.DefaultNetwork.OVNKubernetesConfig != nil &&
networkCR.Spec.DefaultNetwork.OVNKubernetesConfig.HybridOverlayConfig != nil &&
networkCR.Spec.DefaultNetwork.OVNKubernetesConfig.HybridOverlayConfig.HybridOverlayVXLANPort != nil {
vxlanPort = networkCR.Spec.DefaultNetwork.OVNKubernetesConfig.HybridOverlayConfig.HybridOverlayVXLANPort
return fmt.Sprint(*vxlanPort), nil
}
return "", nil
}
// ValidateCIDR uses the parseCIDR from network package to validate the format of the CIDR
func ValidateCIDR(cidr string) error {
_, _, err := net.ParseCIDR(cidr)
if err != nil || cidr == "" {
return fmt.Errorf("received invalid CIDR value %s: %w", cidr, err)
}
return nil
}
// GetDNS parses a subnet in CIDR format as defined by RFC 4632 and RFC 4291
// and returns the IP address of the Cluster DNS.
// Example: 172.30.0.0/16 returns 172.30.0.10
func GetDNS(subnet string) (string, error) {
_, network, err := net.ParseCIDR(subnet)
if err != nil {
return "", err
}
// clusterDNS is the 10th IP for the given subnet
// this is a widespread convention set by upstream Kubernetes
clusterDNS, err := cidr.Host(network, 10)
if err != nil {
return "", err
}
return clusterDNS.String(), nil
}
// IsProxyEnabled returns whether a global egress proxy is active in the cluster
func IsProxyEnabled() bool {
return len(GetProxyVars()) > 0
}
// GetProxyVars returns a map of the proxy variables and values from the WMCO container's environment. The presence of
// any implies a proxy is enabled, as OLM would have injected them into the operator spec. Returns an empty map otherwise.
func GetProxyVars() map[string]string {
if clusterWideProxyVars != nil {
return clusterWideProxyVars
}
// if clusterWideProxyVars is not already cached, initialize it. We never expect these values to change during
// runtime as OLM restarts the operator when the global cluster proxy config changes
clusterWideProxyVars = make(map[string]string, len(WatchedEnvironmentVars))
for _, envVar := range WatchedEnvironmentVars {
value, found := os.LookupEnv(envVar)
if found {
clusterWideProxyVars[envVar] = value
}
}
return clusterWideProxyVars
}