2017-11-21 19:25:08 +00:00
package cluster
import (
2019-03-01 18:09:45 +00:00
"context"
2017-11-21 19:25:08 +00:00
"fmt"
"strings"
2017-11-28 17:45:24 +00:00
2019-03-01 18:09:45 +00:00
"github.com/rancher/rke/log"
2020-02-04 19:27:52 +00:00
"github.com/rancher/rke/metadata"
2019-06-11 22:31:01 +00:00
"github.com/rancher/rke/pki"
2017-11-28 17:45:24 +00:00
"github.com/rancher/rke/services"
2019-03-01 18:09:45 +00:00
"github.com/rancher/rke/util"
2020-02-04 19:27:52 +00:00
"k8s.io/api/core/v1"
2018-08-25 20:03:34 +00:00
"k8s.io/apimachinery/pkg/util/validation"
2017-11-21 19:25:08 +00:00
)
2019-03-01 18:09:45 +00:00
func ( c * Cluster ) ValidateCluster ( ctx context . Context ) error {
// validate kubernetes version
// Version Check
if err := validateVersion ( ctx , c ) ; err != nil {
return err
}
2018-02-12 17:52:12 +00:00
// validate duplicate nodes
if err := validateDuplicateNodes ( c ) ; err != nil {
return err
}
2017-11-28 17:45:24 +00:00
// validate hosts options
if err := validateHostsOptions ( c ) ; err != nil {
return err
}
// validate Auth options
if err := validateAuthOptions ( c ) ; err != nil {
return err
}
// validate Network options
if err := validateNetworkOptions ( c ) ; err != nil {
return err
2017-11-21 19:25:08 +00:00
}
2018-02-01 21:28:31 +00:00
// validate Ingress options
if err := validateIngressOptions ( c ) ; err != nil {
return err
}
2017-11-21 19:25:08 +00:00
// validate services options
2017-12-01 21:06:13 +00:00
return validateServicesOptions ( c )
2017-11-21 19:25:08 +00:00
}
2017-11-28 17:45:24 +00:00
func validateAuthOptions ( c * Cluster ) error {
2018-12-28 16:41:37 +00:00
for strategy , enabled := range c . AuthnStrategies {
if ! enabled {
continue
}
strategy = strings . ToLower ( strategy )
if strategy != AuthnX509Provider && strategy != AuthnWebhookProvider {
return fmt . Errorf ( "Authentication strategy [%s] is not supported" , strategy )
}
}
if ! c . AuthnStrategies [ AuthnX509Provider ] {
return fmt . Errorf ( "Authentication strategy must contain [%s]" , AuthnX509Provider )
2017-11-28 17:45:24 +00:00
}
return nil
}
func validateNetworkOptions ( c * Cluster ) error {
2018-11-28 00:23:15 +00:00
if c . Network . Plugin != NoNetworkPlugin && c . Network . Plugin != FlannelNetworkPlugin && c . Network . Plugin != CalicoNetworkPlugin && c . Network . Plugin != CanalNetworkPlugin && c . Network . Plugin != WeaveNetworkPlugin {
2017-11-28 17:45:24 +00:00
return fmt . Errorf ( "Network plugin [%s] is not supported" , c . Network . Plugin )
}
2019-12-23 00:22:13 +00:00
if c . Network . Plugin == FlannelNetworkPlugin && c . Network . MTU != 0 {
2019-12-06 16:59:15 +00:00
return fmt . Errorf ( "Network plugin [%s] does not support configuring MTU" , FlannelNetworkPlugin )
}
2017-11-28 17:45:24 +00:00
return nil
}
func validateHostsOptions ( c * Cluster ) error {
for i , host := range c . Nodes {
if len ( host . Address ) == 0 {
2018-02-26 23:27:18 +00:00
return fmt . Errorf ( "Address for host (%d) is not provided" , i + 1 )
2017-11-28 17:45:24 +00:00
}
if len ( host . User ) == 0 {
return fmt . Errorf ( "User for host (%d) is not provided" , i + 1 )
}
if len ( host . Role ) == 0 {
return fmt . Errorf ( "Role for host (%d) is not provided" , i + 1 )
}
2018-08-25 20:03:34 +00:00
if errs := validation . IsDNS1123Subdomain ( host . HostnameOverride ) ; len ( errs ) > 0 {
return fmt . Errorf ( "Hostname_override [%s] for host (%d) is not valid: %v" , host . HostnameOverride , i + 1 , errs )
}
2017-11-28 17:45:24 +00:00
for _ , role := range host . Role {
if role != services . ETCDRole && role != services . ControlRole && role != services . WorkerRole {
return fmt . Errorf ( "Role [%s] for host (%d) is not recognized" , role , i + 1 )
}
}
}
return nil
}
func validateServicesOptions ( c * Cluster ) error {
2017-11-21 19:25:08 +00:00
servicesOptions := map [ string ] string {
"etcd_image" : c . Services . Etcd . Image ,
"kube_api_image" : c . Services . KubeAPI . Image ,
"kube_api_service_cluster_ip_range" : c . Services . KubeAPI . ServiceClusterIPRange ,
"kube_controller_image" : c . Services . KubeController . Image ,
"kube_controller_service_cluster_ip_range" : c . Services . KubeController . ServiceClusterIPRange ,
"kube_controller_cluster_cidr" : c . Services . KubeController . ClusterCIDR ,
"scheduler_image" : c . Services . Scheduler . Image ,
"kubelet_image" : c . Services . Kubelet . Image ,
"kubelet_cluster_dns_service" : c . Services . Kubelet . ClusterDNSServer ,
"kubelet_cluster_domain" : c . Services . Kubelet . ClusterDomain ,
"kubelet_infra_container_image" : c . Services . Kubelet . InfraContainerImage ,
"kubeproxy_image" : c . Services . Kubeproxy . Image ,
}
for optionName , OptionValue := range servicesOptions {
if len ( OptionValue ) == 0 {
return fmt . Errorf ( "%s can't be empty" , strings . Join ( strings . Split ( optionName , "_" ) , " " ) )
}
}
2018-02-14 20:58:35 +00:00
// Validate external etcd information
if len ( c . Services . Etcd . ExternalURLs ) > 0 {
if len ( c . Services . Etcd . CACert ) == 0 {
return fmt . Errorf ( "External CA Certificate for etcd can't be empty" )
}
if len ( c . Services . Etcd . Cert ) == 0 {
return fmt . Errorf ( "External Client Certificate for etcd can't be empty" )
}
if len ( c . Services . Etcd . Key ) == 0 {
return fmt . Errorf ( "External Client Key for etcd can't be empty" )
}
if len ( c . Services . Etcd . Path ) == 0 {
return fmt . Errorf ( "External etcd path can't be empty" )
}
}
2018-12-13 08:46:47 +00:00
// validate etcd s3 backup backend configurations
if err := validateEtcdBackupOptions ( c ) ; err != nil {
return err
}
return nil
}
func validateEtcdBackupOptions ( c * Cluster ) error {
2019-03-11 23:04:08 +00:00
if c . Services . Etcd . BackupConfig != nil {
2019-01-14 20:20:10 +00:00
if c . Services . Etcd . BackupConfig . S3BackupConfig != nil {
if len ( c . Services . Etcd . BackupConfig . S3BackupConfig . Endpoint ) == 0 {
return fmt . Errorf ( "etcd s3 backup backend endpoint can't be empty" )
}
if len ( c . Services . Etcd . BackupConfig . S3BackupConfig . BucketName ) == 0 {
return fmt . Errorf ( "etcd s3 backup backend bucketName can't be empty" )
}
2019-06-25 20:12:17 +00:00
if len ( c . Services . Etcd . BackupConfig . S3BackupConfig . CustomCA ) != 0 {
if isValid , err := pki . IsValidCertStr ( c . Services . Etcd . BackupConfig . S3BackupConfig . CustomCA ) ; ! isValid {
2019-06-11 22:31:01 +00:00
return fmt . Errorf ( "invalid S3 endpoint CA certificate: %v" , err )
}
}
2018-12-13 08:46:47 +00:00
}
}
2017-11-21 19:25:08 +00:00
return nil
}
2018-02-01 21:28:31 +00:00
func validateIngressOptions ( c * Cluster ) error {
// Should be changed when adding more ingress types
2018-02-07 00:30:25 +00:00
if c . Ingress . Provider != DefaultIngressController && c . Ingress . Provider != "none" {
return fmt . Errorf ( "Ingress controller %s is incorrect" , c . Ingress . Provider )
2018-02-01 21:28:31 +00:00
}
2019-08-23 22:52:31 +00:00
if c . Ingress . DNSPolicy != "" &&
! ( c . Ingress . DNSPolicy == string ( v1 . DNSClusterFirst ) ||
c . Ingress . DNSPolicy == string ( v1 . DNSClusterFirstWithHostNet ) ||
c . Ingress . DNSPolicy == string ( v1 . DNSNone ) ||
c . Ingress . DNSPolicy == string ( v1 . DNSDefault ) ) {
return fmt . Errorf ( "DNSPolicy %s was not a valid DNS Policy" , c . Ingress . DNSPolicy )
}
2018-02-01 21:28:31 +00:00
return nil
}
2018-02-21 23:13:08 +00:00
func ValidateHostCount ( c * Cluster ) error {
if len ( c . EtcdHosts ) == 0 && len ( c . Services . Etcd . ExternalURLs ) == 0 {
2019-08-07 15:35:19 +00:00
failedEtcdHosts := [ ] string { }
for _ , host := range c . InactiveHosts {
if host . IsEtcd {
failedEtcdHosts = append ( failedEtcdHosts , host . Address )
2018-08-29 21:49:01 +00:00
}
return fmt . Errorf ( "Cluster must have at least one etcd plane host: failed to connect to the following etcd host(s) %v" , failedEtcdHosts )
}
return fmt . Errorf ( "Cluster must have at least one etcd plane host: please specify one or more etcd in cluster config" )
2018-02-21 23:13:08 +00:00
}
if len ( c . EtcdHosts ) > 0 && len ( c . Services . Etcd . ExternalURLs ) > 0 {
return fmt . Errorf ( "Cluster can't have both internal and external etcd" )
}
return nil
}
2018-02-12 17:52:12 +00:00
func validateDuplicateNodes ( c * Cluster ) error {
for i := range c . Nodes {
for j := range c . Nodes {
if i == j {
continue
}
if c . Nodes [ i ] . Address == c . Nodes [ j ] . Address {
return fmt . Errorf ( "Cluster can't have duplicate node: %s" , c . Nodes [ i ] . Address )
}
if c . Nodes [ i ] . HostnameOverride == c . Nodes [ j ] . HostnameOverride {
return fmt . Errorf ( "Cluster can't have duplicate node: %s" , c . Nodes [ i ] . HostnameOverride )
}
}
}
return nil
}
2019-03-01 18:09:45 +00:00
func validateVersion ( ctx context . Context , c * Cluster ) error {
_ , err := util . StrToSemVer ( c . Version )
if err != nil {
return fmt . Errorf ( "%s is not valid semver" , c . Version )
}
2019-05-28 18:51:53 +00:00
_ , ok := metadata . K8sVersionToRKESystemImages [ c . Version ]
2019-03-01 18:09:45 +00:00
if ! ok {
if err := validateSystemImages ( c ) ; err != nil {
return fmt . Errorf ( "%s is an unsupported Kubernetes version and system images are not populated: %v" , c . Version , err )
}
return nil
}
2019-05-28 18:51:53 +00:00
if _ , ok := metadata . K8sBadVersions [ c . Version ] ; ok {
2019-03-01 18:09:45 +00:00
log . Warnf ( ctx , "%s version exists but its recommended to install this version - see 'rke config --system-images --all' for versions supported with this release" , c . Version )
2019-05-28 18:51:53 +00:00
return fmt . Errorf ( "%s is an unsupported Kubernetes version and system images are not populated: %v" , c . Version , err )
2019-03-01 18:09:45 +00:00
}
return nil
}
func validateSystemImages ( c * Cluster ) error {
if err := validateKubernetesImages ( c ) ; err != nil {
return err
}
if err := validateNetworkImages ( c ) ; err != nil {
return err
}
if err := validateDNSImages ( c ) ; err != nil {
return err
}
if err := validateMetricsImages ( c ) ; err != nil {
return err
}
if err := validateIngressImages ( c ) ; err != nil {
return err
}
return nil
}
func validateKubernetesImages ( c * Cluster ) error {
if len ( c . SystemImages . Etcd ) == 0 {
return fmt . Errorf ( "etcd image is not populated" )
}
if len ( c . SystemImages . Kubernetes ) == 0 {
return fmt . Errorf ( "kubernetes image is not populated" )
}
if len ( c . SystemImages . PodInfraContainer ) == 0 {
return fmt . Errorf ( "pod infrastructure container image is not populated" )
}
if len ( c . SystemImages . Alpine ) == 0 {
return fmt . Errorf ( "alpine image is not populated" )
}
if len ( c . SystemImages . NginxProxy ) == 0 {
return fmt . Errorf ( "nginx proxy image is not populated" )
}
if len ( c . SystemImages . CertDownloader ) == 0 {
return fmt . Errorf ( "certificate downloader image is not populated" )
}
if len ( c . SystemImages . KubernetesServicesSidecar ) == 0 {
return fmt . Errorf ( "kubernetes sidecar image is not populated" )
}
return nil
}
func validateNetworkImages ( c * Cluster ) error {
// check network provider images
if c . Network . Plugin == FlannelNetworkPlugin {
if len ( c . SystemImages . Flannel ) == 0 {
return fmt . Errorf ( "flannel image is not populated" )
}
if len ( c . SystemImages . FlannelCNI ) == 0 {
return fmt . Errorf ( "flannel cni image is not populated" )
}
} else if c . Network . Plugin == CanalNetworkPlugin {
if len ( c . SystemImages . CanalNode ) == 0 {
return fmt . Errorf ( "canal image is not populated" )
}
if len ( c . SystemImages . CanalCNI ) == 0 {
return fmt . Errorf ( "canal cni image is not populated" )
}
if len ( c . SystemImages . CanalFlannel ) == 0 {
return fmt . Errorf ( "flannel image is not populated" )
}
} else if c . Network . Plugin == CalicoNetworkPlugin {
if len ( c . SystemImages . CalicoCNI ) == 0 {
return fmt . Errorf ( "calico cni image is not populated" )
}
if len ( c . SystemImages . CalicoCtl ) == 0 {
return fmt . Errorf ( "calico ctl image is not populated" )
}
if len ( c . SystemImages . CalicoNode ) == 0 {
return fmt . Errorf ( "calico image is not populated" )
}
if len ( c . SystemImages . CalicoControllers ) == 0 {
return fmt . Errorf ( "calico controllers image is not populated" )
}
} else if c . Network . Plugin == WeaveNetworkPlugin {
if len ( c . SystemImages . WeaveCNI ) == 0 {
return fmt . Errorf ( "weave cni image is not populated" )
}
if len ( c . SystemImages . WeaveNode ) == 0 {
return fmt . Errorf ( "weave image is not populated" )
}
}
return nil
}
func validateDNSImages ( c * Cluster ) error {
// check dns provider images
if c . DNS . Provider == "kube-dns" {
if len ( c . SystemImages . KubeDNS ) == 0 {
return fmt . Errorf ( "kubedns image is not populated" )
}
if len ( c . SystemImages . DNSmasq ) == 0 {
return fmt . Errorf ( "dnsmasq image is not populated" )
}
if len ( c . SystemImages . KubeDNSSidecar ) == 0 {
return fmt . Errorf ( "kubedns sidecar image is not populated" )
}
if len ( c . SystemImages . KubeDNSAutoscaler ) == 0 {
return fmt . Errorf ( "kubedns autoscaler image is not populated" )
}
} else if c . DNS . Provider == "coredns" {
if len ( c . SystemImages . CoreDNS ) == 0 {
return fmt . Errorf ( "coredns image is not populated" )
}
if len ( c . SystemImages . CoreDNSAutoscaler ) == 0 {
return fmt . Errorf ( "coredns autoscaler image is not populated" )
}
}
return nil
}
func validateMetricsImages ( c * Cluster ) error {
// checl metrics server image
if c . Monitoring . Provider != "none" {
if len ( c . SystemImages . MetricsServer ) == 0 {
return fmt . Errorf ( "metrics server images is not populated" )
}
}
return nil
}
func validateIngressImages ( c * Cluster ) error {
// check ingress images
if c . Ingress . Provider != "none" {
if len ( c . SystemImages . Ingress ) == 0 {
return fmt . Errorf ( "ingress image is not populated" )
}
if len ( c . SystemImages . IngressBackend ) == 0 {
return fmt . Errorf ( "ingress backend image is not populated" )
}
}
return nil
}