Browse code

diagnostics: introduce cluster ServiceExternalIPs

Luke Meyer authored on 2016/03/24 04:33:10
Showing 7 changed files
... ...
@@ -20,7 +20,7 @@ import (
20 20
 var (
21 21
 	// availableClusterDiagnostics contains the names of cluster diagnostics that can be executed
22 22
 	// during a single run of diagnostics. Add more diagnostics to the list as they are defined.
23
-	availableClusterDiagnostics = sets.NewString(clustdiags.NodeDefinitionsName, clustdiags.ClusterRegistryName, clustdiags.ClusterRouterName, clustdiags.ClusterRolesName, clustdiags.ClusterRoleBindingsName, clustdiags.MasterNodeName, clustdiags.MetricsApiProxyName)
23
+	availableClusterDiagnostics = sets.NewString(clustdiags.NodeDefinitionsName, clustdiags.ClusterRegistryName, clustdiags.ClusterRouterName, clustdiags.ClusterRolesName, clustdiags.ClusterRoleBindingsName, clustdiags.MasterNodeName, clustdiags.MetricsApiProxyName, clustdiags.ServiceExternalIPsName)
24 24
 )
25 25
 
26 26
 // buildClusterDiagnostics builds cluster Diagnostic objects if a cluster-admin client can be extracted from the rawConfig passed in.
... ...
@@ -44,24 +44,28 @@ func (o DiagnosticsOptions) buildClusterDiagnostics(rawConfig *clientcmdapi.Conf
44 44
 
45 45
 	diagnostics := []types.Diagnostic{}
46 46
 	for _, diagnosticName := range requestedDiagnostics {
47
+		var d types.Diagnostic
47 48
 		switch diagnosticName {
48 49
 		case clustdiags.NodeDefinitionsName:
49
-			diagnostics = append(diagnostics, &clustdiags.NodeDefinitions{KubeClient: kclusterClient, OsClient: clusterClient})
50
+			d = &clustdiags.NodeDefinitions{KubeClient: kclusterClient, OsClient: clusterClient}
50 51
 		case clustdiags.MasterNodeName:
51
-			diagnostics = append(diagnostics, &clustdiags.MasterNode{KubeClient: kclusterClient, OsClient: clusterClient, ServerUrl: serverUrl, MasterConfigFile: o.MasterConfigLocation})
52
+			d = &clustdiags.MasterNode{KubeClient: kclusterClient, OsClient: clusterClient, ServerUrl: serverUrl, MasterConfigFile: o.MasterConfigLocation}
52 53
 		case clustdiags.ClusterRegistryName:
53
-			diagnostics = append(diagnostics, &clustdiags.ClusterRegistry{KubeClient: kclusterClient, OsClient: clusterClient, PreventModification: o.PreventModification})
54
+			d = &clustdiags.ClusterRegistry{KubeClient: kclusterClient, OsClient: clusterClient, PreventModification: o.PreventModification}
54 55
 		case clustdiags.ClusterRouterName:
55
-			diagnostics = append(diagnostics, &clustdiags.ClusterRouter{KubeClient: kclusterClient, OsClient: clusterClient})
56
+			d = &clustdiags.ClusterRouter{KubeClient: kclusterClient, OsClient: clusterClient}
56 57
 		case clustdiags.ClusterRolesName:
57
-			diagnostics = append(diagnostics, &clustdiags.ClusterRoles{ClusterRolesClient: clusterClient, SARClient: clusterClient})
58
+			d = &clustdiags.ClusterRoles{ClusterRolesClient: clusterClient, SARClient: clusterClient}
58 59
 		case clustdiags.ClusterRoleBindingsName:
59
-			diagnostics = append(diagnostics, &clustdiags.ClusterRoleBindings{ClusterRoleBindingsClient: clusterClient, SARClient: clusterClient})
60
+			d = &clustdiags.ClusterRoleBindings{ClusterRoleBindingsClient: clusterClient, SARClient: clusterClient}
60 61
 		case clustdiags.MetricsApiProxyName:
61
-			diagnostics = append(diagnostics, &clustdiags.MetricsApiProxy{KubeClient: kclusterClient})
62
+			d = &clustdiags.MetricsApiProxy{KubeClient: kclusterClient}
63
+		case clustdiags.ServiceExternalIPsName:
64
+			d = &clustdiags.ServiceExternalIPs{MasterConfigFile: o.MasterConfigLocation, KclusterClient: kclusterClient}
62 65
 		default:
63 66
 			return nil, false, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
64 67
 		}
68
+		diagnostics = append(diagnostics, d)
65 69
 	}
66 70
 	return diagnostics, true, nil
67 71
 }
... ...
@@ -36,26 +36,20 @@ func (o DiagnosticsOptions) buildHostDiagnostics() ([]types.Diagnostic, bool, er
36 36
 	diagnostics := []types.Diagnostic{}
37 37
 	systemdUnits := systemddiags.GetSystemdUnits(o.Logger)
38 38
 	for _, diagnosticName := range requestedDiagnostics {
39
+		var d types.Diagnostic
39 40
 		switch diagnosticName {
40 41
 		case systemddiags.AnalyzeLogsName:
41
-			diagnostics = append(diagnostics, systemddiags.AnalyzeLogs{SystemdUnits: systemdUnits})
42
-
42
+			d = systemddiags.AnalyzeLogs{SystemdUnits: systemdUnits}
43 43
 		case systemddiags.UnitStatusName:
44
-			diagnostics = append(diagnostics, systemddiags.UnitStatus{SystemdUnits: systemdUnits})
45
-
44
+			d = systemddiags.UnitStatus{SystemdUnits: systemdUnits}
46 45
 		case hostdiags.MasterConfigCheckName:
47
-			if len(o.MasterConfigLocation) > 0 {
48
-				diagnostics = append(diagnostics, hostdiags.MasterConfigCheck{MasterConfigFile: o.MasterConfigLocation})
49
-			}
50
-
46
+			d = hostdiags.MasterConfigCheck{MasterConfigFile: o.MasterConfigLocation}
51 47
 		case hostdiags.NodeConfigCheckName:
52
-			if len(o.NodeConfigLocation) > 0 {
53
-				diagnostics = append(diagnostics, hostdiags.NodeConfigCheck{NodeConfigFile: o.NodeConfigLocation})
54
-			}
55
-
48
+			d = hostdiags.NodeConfigCheck{NodeConfigFile: o.NodeConfigLocation}
56 49
 		default:
57 50
 			return diagnostics, false, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
58 51
 		}
52
+		diagnostics = append(diagnostics, d)
59 53
 	}
60 54
 
61 55
 	return diagnostics, true, nil
62 56
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+package cluster
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"net"
6
+	"strings"
7
+
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
10
+
11
+	hostdiag "github.com/openshift/origin/pkg/diagnostics/host"
12
+	"github.com/openshift/origin/pkg/diagnostics/types"
13
+	"github.com/openshift/origin/pkg/service/admission"
14
+)
15
+
16
+// ServiceExternalIPs is a Diagnostic to check for the services in the cluster
17
+// that do not comply with an updated master ExternalIPNetworkCIDR setting.
18
+// Background: https://github.com/openshift/origin/issues/7808
19
+type ServiceExternalIPs struct {
20
+	MasterConfigFile string
21
+	KclusterClient   *kclient.Client
22
+}
23
+
24
+const ServiceExternalIPsName = "ServiceExternalIPs"
25
+
26
+func (d *ServiceExternalIPs) Name() string {
27
+	return ServiceExternalIPsName
28
+}
29
+
30
+func (d *ServiceExternalIPs) Description() string {
31
+	return "Check for existing services with ExternalIPs that are disallowed by master config"
32
+}
33
+
34
+func (d *ServiceExternalIPs) CanRun() (bool, error) {
35
+	if len(d.MasterConfigFile) == 0 {
36
+		return false, errors.New("No master config file was detected")
37
+	}
38
+	if d.KclusterClient == nil {
39
+		return false, errors.New("Client config must include a cluster-admin context to run this diagnostic")
40
+	}
41
+
42
+	return true, nil
43
+}
44
+
45
+func (d *ServiceExternalIPs) Check() types.DiagnosticResult {
46
+	r := types.NewDiagnosticResult(ServiceExternalIPsName)
47
+	masterConfig, err := hostdiag.GetMasterConfig(r, d.MasterConfigFile)
48
+	if err != nil {
49
+		r.Info("DH2004", "Unreadable master config; skipping this diagnostic.")
50
+		return r
51
+	}
52
+
53
+	admit, reject := []*net.IPNet{}, []*net.IPNet{}
54
+	if cidrs := masterConfig.NetworkConfig.ExternalIPNetworkCIDRs; cidrs != nil {
55
+		reject, admit, err = admission.ParseCIDRRules(cidrs)
56
+		if err != nil {
57
+			r.Error("DH2007", err, fmt.Sprintf("Could not parse master config NetworkConfig.ExternalIPNetworkCIDRs: (%[1]T) %[1]v", err))
58
+			return r
59
+		}
60
+	}
61
+	services, err := d.KclusterClient.Services("").List(kapi.ListOptions{})
62
+	if err != nil {
63
+		r.Error("DH2005", err, fmt.Sprintf("Error while listing cluster services: (%[1]T) %[1]v", err))
64
+		return r
65
+	}
66
+
67
+	errList := []string{}
68
+	for _, service := range services.Items {
69
+		if len(service.Spec.ExternalIPs) == 0 {
70
+			continue
71
+		}
72
+		if len(admit) == 0 {
73
+			errList = append(errList, fmt.Sprintf("Service %s.%s specifies ExternalIPs %v, but none are permitted.", service.Namespace, service.Name, service.Spec.ExternalIPs))
74
+			continue
75
+		}
76
+		for _, ipString := range service.Spec.ExternalIPs {
77
+			ip := net.ParseIP(ipString)
78
+			if ip == nil {
79
+				continue // we don't really care for the purposes of this diagnostic
80
+			}
81
+			if admission.NetworkSlice(reject).Contains(ip) || !admission.NetworkSlice(admit).Contains(ip) {
82
+				errList = append(errList, fmt.Sprintf("Service %s.%s specifies ExternalIP %s that is not permitted by the master ExternalIPNetworkCIDRs setting.", service.Namespace, service.Name, ipString))
83
+			}
84
+		}
85
+	}
86
+	if len(errList) > 0 {
87
+		r.Error("DH2006", nil, `The following problems were found with service ExternalIPs in the cluster.
88
+These services were created before the master ExternalIPNetworkCIDRs setting changed to exclude them.
89
+The default ExternalIPNetworkCIDRs now excludes all ExternalIPs on services.
90
+`+strings.Join(errList, "\n"))
91
+	}
92
+
93
+	return r
94
+}
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"errors"
5 5
 	"fmt"
6 6
 
7
-	configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
8 7
 	configvalidation "github.com/openshift/origin/pkg/cmd/server/api/validation"
9 8
 	"github.com/openshift/origin/pkg/diagnostics/types"
10 9
 )
... ...
@@ -25,23 +24,18 @@ func (d MasterConfigCheck) Description() string {
25 25
 }
26 26
 func (d MasterConfigCheck) CanRun() (bool, error) {
27 27
 	if len(d.MasterConfigFile) == 0 {
28
-		return false, errors.New("must have master config file")
28
+		return false, errors.New("No master config file was detected")
29 29
 	}
30 30
 
31 31
 	return true, nil
32 32
 }
33 33
 func (d MasterConfigCheck) Check() types.DiagnosticResult {
34 34
 	r := types.NewDiagnosticResult(MasterConfigCheckName)
35
-
36
-	r.Debug("DH0001", fmt.Sprintf("Looking for master config file at '%s'", d.MasterConfigFile))
37
-	masterConfig, err := configapilatest.ReadAndResolveMasterConfig(d.MasterConfigFile)
35
+	masterConfig, err := GetMasterConfig(r, d.MasterConfigFile)
38 36
 	if err != nil {
39
-		r.Error("DH0002", err, fmt.Sprintf("Could not read master config file '%s':\n(%T) %[2]v", d.MasterConfigFile, err))
40 37
 		return r
41 38
 	}
42 39
 
43
-	r.Info("DH0003", fmt.Sprintf("Found a master config file: %[1]s", d.MasterConfigFile))
44
-
45 40
 	results := configvalidation.ValidateMasterConfig(masterConfig, nil)
46 41
 	if len(results.Errors) > 0 {
47 42
 		errText := fmt.Sprintf("Validation of master config file '%s' failed:\n", d.MasterConfigFile)
... ...
@@ -25,7 +25,7 @@ func (d NodeConfigCheck) Description() string {
25 25
 }
26 26
 func (d NodeConfigCheck) CanRun() (bool, error) {
27 27
 	if len(d.NodeConfigFile) == 0 {
28
-		return false, errors.New("must have node config file")
28
+		return false, errors.New("No node config file was detected")
29 29
 	}
30 30
 
31 31
 	return true, nil
32 32
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package host
1
+
2
+import (
3
+	"fmt"
4
+
5
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
6
+	configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
7
+	"github.com/openshift/origin/pkg/diagnostics/types"
8
+)
9
+
10
+// this would be bad practice if there were ever a need to load more than one master config for diagnostics.
11
+// however we will proceed with the assumption that will never be necessary.
12
+var (
13
+	masterConfigLoaded    = false
14
+	masterConfig          *configapi.MasterConfig
15
+	masterConfigLoadError error
16
+)
17
+
18
+func GetMasterConfig(r types.DiagnosticResult, masterConfigFile string) (*configapi.MasterConfig, error) {
19
+	if masterConfigLoaded { // no need to do this more than once
20
+		return masterConfig, masterConfigLoadError
21
+	}
22
+	r.Debug("DH0001", fmt.Sprintf("Looking for master config file at '%s'", masterConfigFile))
23
+	masterConfigLoaded = true
24
+	masterConfig, masterConfigLoadError = configapilatest.ReadAndResolveMasterConfig(masterConfigFile)
25
+	if masterConfigLoadError != nil {
26
+		r.Error("DH0002", masterConfigLoadError, fmt.Sprintf("Could not read master config file '%s':\n(%T) %[2]v", masterConfigFile, masterConfigLoadError))
27
+	} else {
28
+		r.Debug("DH0003", fmt.Sprintf("Found a master config file: %[1]s", masterConfigFile))
29
+	}
30
+	return masterConfig, masterConfigLoadError
31
+}
... ...
@@ -59,11 +59,11 @@ func NewExternalIPRanger(reject, admit []*net.IPNet) *externalIPRanger {
59 59
 	}
60 60
 }
61 61
 
62
-// networkSlice is a helper for checking whether an IP is contained in a range
62
+// NetworkSlice is a helper for checking whether an IP is contained in a range
63 63
 // of networks.
64
-type networkSlice []*net.IPNet
64
+type NetworkSlice []*net.IPNet
65 65
 
66
-func (s networkSlice) Contains(ip net.IP) bool {
66
+func (s NetworkSlice) Contains(ip net.IP) bool {
67 67
 	for _, cidr := range s {
68 68
 		if cidr.Contains(ip) {
69 69
 			return true
... ...
@@ -97,7 +97,7 @@ func (r *externalIPRanger) Admit(a kadmission.Attributes) error {
97 97
 				errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIPs must be a valid address"))
98 98
 				continue
99 99
 			}
100
-			if networkSlice(r.reject).Contains(ip) || !networkSlice(r.admit).Contains(ip) {
100
+			if NetworkSlice(r.reject).Contains(ip) || !NetworkSlice(r.admit).Contains(ip) {
101 101
 				errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIP is not allowed"))
102 102
 				continue
103 103
 			}