<commit_before>/*
 * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package storageclass

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/minio/minio/cmd/config"
	"github.com/minio/minio/pkg/env"
)

// Standard constants for all storage class
const (
	// Reduced redundancy storage class
	RRS = "REDUCED_REDUNDANCY"
	// Standard storage class
	STANDARD = "STANDARD"
)

// Standard constats for config info storage class
const (
	ClassStandard = "standard"
	ClassRRS      = "rrs"

	// Reduced redundancy storage class environment variable
	RRSEnv = "MINIO_STORAGE_CLASS_RRS"
	// Standard storage class environment variable
	StandardEnv = "MINIO_STORAGE_CLASS_STANDARD"

	// Supported storage class scheme is EC
	schemePrefix = "EC"

	// Min parity disks
	minParityDisks = 2

	// Default RRS parity is always minimum parity.
	defaultRRSParity = minParityDisks
)

// DefaultKVS - default storage class config
var (
	DefaultKVS = config.KVS{
		config.KV{
			Key:   ClassStandard,
			Value: "",
		},
		config.KV{
			Key:   ClassRRS,
			Value: "EC:2",
		},
	}
)

// StorageClass - holds storage class information
type StorageClass struct {
	Parity int
}

// Config storage class configuration
type Config struct {
	Standard StorageClass `json:"standard"`
	RRS      StorageClass `json:"rrs"`
}

// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
func (sCfg *Config) UnmarshalJSON(data []byte) error {
	type Alias Config
	aux := &struct {
		*Alias
	}{
		Alias: (*Alias)(sCfg),
	}
	return json.Unmarshal(data, &aux)
}

// IsValid - returns true if input string is a valid
// storage class kind supported.
func IsValid(sc string) bool {
	return sc == RRS || sc == STANDARD
}

// UnmarshalText unmarshals storage class from its textual form into
// storageClass structure.
func (sc *StorageClass) UnmarshalText(b []byte) error {
	scStr := string(b)
	if scStr == "" {
		return nil
	}
	s, err := parseStorageClass(scStr)
	if err != nil {
		return err
	}
	sc.Parity = s.Parity
	return nil
}

// MarshalText - marshals storage class string.
func (sc *StorageClass) MarshalText() ([]byte, error) {
	if sc.Parity != 0 {
		return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
	}
	return []byte(""), nil
}

func (sc *StorageClass) String() string {
	if sc.Parity != 0 {
		return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
	}
	return ""
}

// Parses given storageClassEnv and returns a storageClass structure.
// Supported Storage Class format is "Scheme:Number of parity disks".
// Currently only supported scheme is "EC".
func parseStorageClass(storageClassEnv string) (sc StorageClass, err error) {
	s := strings.Split(storageClassEnv, ":")

	// only two elements allowed in the string - "scheme" and "number of parity disks"
	if len(s) > 2 {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv)
	} else if len(s) < 2 {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too few sections in " + storageClassEnv)
	}

	// only allowed scheme is "EC"
	if s[0] != schemePrefix {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Unsupported scheme " + s[0] + ". Supported scheme is EC")
	}

	// Number of parity disks should be integer
	parityDisks, err := strconv.Atoi(s[1])
	if err != nil {
		return StorageClass{}, config.ErrStorageClassValue(err)
	}

	return StorageClass{
		Parity: parityDisks,
	}, nil
}

// Validates the parity disks.
func validateParity(ssParity, rrsParity, drivesPerSet int) (err error) {
	if ssParity == 0 && rrsParity == 0 {
		return nil
	}

	// SS parity disks should be greater than or equal to minParityDisks.
	// Parity below minParityDisks is not supported.
	if ssParity < minParityDisks {
		return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
			ssParity, minParityDisks)
	}

	// RRS parity disks should be greater than or equal to minParityDisks.
	// Parity below minParityDisks is not supported.
	if rrsParity < minParityDisks {
		return fmt.Errorf("Reduced redundancy storage class parity %d should be greater than or equal to %d", rrsParity, minParityDisks)
	}

	if ssParity > drivesPerSet/2 {
		return fmt.Errorf("Standard storage class parity %d should be less than or equal to %d", ssParity, drivesPerSet/2)
	}

	if rrsParity > drivesPerSet/2 {
		return fmt.Errorf("Reduced redundancy storage class parity %d should be less than  or equal to %d", rrsParity, drivesPerSet/2)
	}

	if ssParity > 0 && rrsParity > 0 {
		if ssParity < rrsParity {
			return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
		}
	}
	return nil
}

// GetParityForSC - Returns the data and parity drive count based on storage class
// If storage class is set using the env vars MINIO_STORAGE_CLASS_RRS and MINIO_STORAGE_CLASS_STANDARD
// or config.json fields
// -- corresponding values are returned
// If storage class is not set during startup, default values are returned
// -- Default for Reduced Redundancy Storage class is, parity = 2 and data = N-Parity
// -- Default for Standard Storage class is, parity = N/2, data = N/2
// If storage class is empty
// -- standard storage class is assumed and corresponding data and parity is returned
func (sCfg Config) GetParityForSC(sc string) (parity int) {
	switch strings.TrimSpace(sc) {
	case RRS:
		// set the rrs parity if available
		if sCfg.RRS.Parity == 0 {
			return defaultRRSParity
		}
		return sCfg.RRS.Parity
	default:
		return sCfg.Standard.Parity
	}
}

// Enabled returns if etcd is enabled.
func Enabled(kvs config.KVS) bool {
	ssc := kvs.Get(ClassStandard)
	rrsc := kvs.Get(ClassRRS)
	return ssc != "" || rrsc != ""
}

// LookupConfig - lookup storage class config and override with valid environment settings if any.
func LookupConfig(kvs config.KVS, drivesPerSet int) (cfg Config, err error) {
	cfg = Config{}
	cfg.Standard.Parity = drivesPerSet / 2
	cfg.RRS.Parity = defaultRRSParity

	if err = config.CheckValidKeys(config.StorageClassSubSys, kvs, DefaultKVS); err != nil {
		return cfg, err
	}

	ssc := env.Get(StandardEnv, kvs.Get(ClassStandard))
	rrsc := env.Get(RRSEnv, kvs.Get(ClassRRS))
	// Check for environment variables and parse into storageClass struct
	if ssc != "" {
		cfg.Standard, err = parseStorageClass(ssc)
		if err != nil {
			return cfg, err
		}
	}
	if cfg.Standard.Parity == 0 {
		cfg.Standard.Parity = drivesPerSet / 2
	}

	if rrsc != "" {
		cfg.RRS, err = parseStorageClass(rrsc)
		if err != nil {
			return cfg, err
		}
	}
	if cfg.RRS.Parity == 0 {
		cfg.RRS.Parity = defaultRRSParity
	}

	// Validation is done after parsing both the storage classes. This is needed because we need one
	// storage class value to deduce the correct value of the other storage class.
	if err = validateParity(cfg.Standard.Parity, cfg.RRS.Parity, drivesPerSet); err != nil {
		return cfg, err
	}

	return cfg, nil
}
<commit_msg>fix: storageClass shouldn't set the value upon failure (#10271)<commit_after>/*
 * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package storageclass

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/minio/minio/cmd/config"
	"github.com/minio/minio/pkg/env"
)

// Standard constants for all storage class
const (
	// Reduced redundancy storage class
	RRS = "REDUCED_REDUNDANCY"
	// Standard storage class
	STANDARD = "STANDARD"
)

// Standard constats for config info storage class
const (
	ClassStandard = "standard"
	ClassRRS      = "rrs"

	// Reduced redundancy storage class environment variable
	RRSEnv = "MINIO_STORAGE_CLASS_RRS"
	// Standard storage class environment variable
	StandardEnv = "MINIO_STORAGE_CLASS_STANDARD"

	// Supported storage class scheme is EC
	schemePrefix = "EC"

	// Min parity disks
	minParityDisks = 2

	// Default RRS parity is always minimum parity.
	defaultRRSParity = minParityDisks
)

// DefaultKVS - default storage class config
var (
	DefaultKVS = config.KVS{
		config.KV{
			Key:   ClassStandard,
			Value: "",
		},
		config.KV{
			Key:   ClassRRS,
			Value: "EC:2",
		},
	}
)

// StorageClass - holds storage class information
type StorageClass struct {
	Parity int
}

// Config storage class configuration
type Config struct {
	Standard StorageClass `json:"standard"`
	RRS      StorageClass `json:"rrs"`
}

// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
func (sCfg *Config) UnmarshalJSON(data []byte) error {
	type Alias Config
	aux := &struct {
		*Alias
	}{
		Alias: (*Alias)(sCfg),
	}
	return json.Unmarshal(data, &aux)
}

// IsValid - returns true if input string is a valid
// storage class kind supported.
func IsValid(sc string) bool {
	return sc == RRS || sc == STANDARD
}

// UnmarshalText unmarshals storage class from its textual form into
// storageClass structure.
func (sc *StorageClass) UnmarshalText(b []byte) error {
	scStr := string(b)
	if scStr == "" {
		return nil
	}
	s, err := parseStorageClass(scStr)
	if err != nil {
		return err
	}
	sc.Parity = s.Parity
	return nil
}

// MarshalText - marshals storage class string.
func (sc *StorageClass) MarshalText() ([]byte, error) {
	if sc.Parity != 0 {
		return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
	}
	return []byte(""), nil
}

func (sc *StorageClass) String() string {
	if sc.Parity != 0 {
		return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
	}
	return ""
}

// Parses given storageClassEnv and returns a storageClass structure.
// Supported Storage Class format is "Scheme:Number of parity disks".
// Currently only supported scheme is "EC".
func parseStorageClass(storageClassEnv string) (sc StorageClass, err error) {
	s := strings.Split(storageClassEnv, ":")

	// only two elements allowed in the string - "scheme" and "number of parity disks"
	if len(s) > 2 {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv)
	} else if len(s) < 2 {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too few sections in " + storageClassEnv)
	}

	// only allowed scheme is "EC"
	if s[0] != schemePrefix {
		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Unsupported scheme " + s[0] + ". Supported scheme is EC")
	}

	// Number of parity disks should be integer
	parityDisks, err := strconv.Atoi(s[1])
	if err != nil {
		return StorageClass{}, config.ErrStorageClassValue(err)
	}

	return StorageClass{
		Parity: parityDisks,
	}, nil
}

// Validates the parity disks.
func validateParity(ssParity, rrsParity, drivesPerSet int) (err error) {
	if ssParity == 0 && rrsParity == 0 {
		return nil
	}

	// SS parity disks should be greater than or equal to minParityDisks.
	// Parity below minParityDisks is not supported.
	if ssParity < minParityDisks {
		return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
			ssParity, minParityDisks)
	}

	// RRS parity disks should be greater than or equal to minParityDisks.
	// Parity below minParityDisks is not supported.
	if rrsParity < minParityDisks {
		return fmt.Errorf("Reduced redundancy storage class parity %d should be greater than or equal to %d", rrsParity, minParityDisks)
	}

	if ssParity > drivesPerSet/2 {
		return fmt.Errorf("Standard storage class parity %d should be less than or equal to %d", ssParity, drivesPerSet/2)
	}

	if rrsParity > drivesPerSet/2 {
		return fmt.Errorf("Reduced redundancy storage class parity %d should be less than  or equal to %d", rrsParity, drivesPerSet/2)
	}

	if ssParity > 0 && rrsParity > 0 {
		if ssParity < rrsParity {
			return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
		}
	}
	return nil
}

// GetParityForSC - Returns the data and parity drive count based on storage class
// If storage class is set using the env vars MINIO_STORAGE_CLASS_RRS and MINIO_STORAGE_CLASS_STANDARD
// or config.json fields
// -- corresponding values are returned
// If storage class is not set during startup, default values are returned
// -- Default for Reduced Redundancy Storage class is, parity = 2 and data = N-Parity
// -- Default for Standard Storage class is, parity = N/2, data = N/2
// If storage class is empty
// -- standard storage class is assumed and corresponding data and parity is returned
func (sCfg Config) GetParityForSC(sc string) (parity int) {
	switch strings.TrimSpace(sc) {
	case RRS:
		// set the rrs parity if available
		if sCfg.RRS.Parity == 0 {
			return defaultRRSParity
		}
		return sCfg.RRS.Parity
	default:
		return sCfg.Standard.Parity
	}
}

// Enabled returns if etcd is enabled.
func Enabled(kvs config.KVS) bool {
	ssc := kvs.Get(ClassStandard)
	rrsc := kvs.Get(ClassRRS)
	return ssc != "" || rrsc != ""
}

// LookupConfig - lookup storage class config and override with valid environment settings if any.
func LookupConfig(kvs config.KVS, drivesPerSet int) (cfg Config, err error) {
	cfg = Config{}
	cfg.Standard.Parity = drivesPerSet / 2
	cfg.RRS.Parity = defaultRRSParity

	if err = config.CheckValidKeys(config.StorageClassSubSys, kvs, DefaultKVS); err != nil {
		return Config{}, err
	}

	ssc := env.Get(StandardEnv, kvs.Get(ClassStandard))
	rrsc := env.Get(RRSEnv, kvs.Get(ClassRRS))
	// Check for environment variables and parse into storageClass struct
	if ssc != "" {
		cfg.Standard, err = parseStorageClass(ssc)
		if err != nil {
			return Config{}, err
		}
	}
	if cfg.Standard.Parity == 0 {
		cfg.Standard.Parity = drivesPerSet / 2
	}

	if rrsc != "" {
		cfg.RRS, err = parseStorageClass(rrsc)
		if err != nil {
			return Config{}, err
		}
	}
	if cfg.RRS.Parity == 0 {
		cfg.RRS.Parity = defaultRRSParity
	}

	// Validation is done after parsing both the storage classes. This is needed because we need one
	// storage class value to deduce the correct value of the other storage class.
	if err = validateParity(cfg.Standard.Parity, cfg.RRS.Parity, drivesPerSet); err != nil {
		return Config{}, err
	}

	return cfg, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Fission Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
	"github.com/fission/fission/pkg/crd"
	"github.com/fission/fission/pkg/types"
	"github.com/fission/fission/pkg/utils"
)

type (
	PreUpgradeTaskClient struct {
		logger        *zap.Logger
		fissionClient *crd.FissionClient
		k8sClient     *kubernetes.Clientset
		apiExtClient  *apiextensionsclient.Clientset
		fnPodNs       string
		envBuilderNs  string
	}
)

const (
	maxRetries  = 5
	FunctionCRD = "functions.fission.io"
)

func makePreUpgradeTaskClient(logger *zap.Logger, fnPodNs, envBuilderNs string) (*PreUpgradeTaskClient, error) {
	fissionClient, k8sClient, apiExtClient, err := crd.MakeFissionClient()
	if err != nil {
		return nil, errors.Wrap(err, "error making fission client")
	}

	return &PreUpgradeTaskClient{
		logger:        logger.Named("pre_upgrade_task_client"),
		fissionClient: fissionClient,
		k8sClient:     k8sClient,
		fnPodNs:       fnPodNs,
		envBuilderNs:  envBuilderNs,
		apiExtClient:  apiExtClient,
	}, nil
}

// IsFissionReInstall checks if there is atleast one fission CRD, i.e. function in this case, on this cluster.
// We need this to find out if fission had been previously installed on this cluster
func (client *PreUpgradeTaskClient) IsFissionReInstall() bool {
	for i := 0; i < maxRetries; i++ {
		_, err := client.apiExtClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(FunctionCRD, metav1.GetOptions{})
		if err != nil && k8serrors.IsNotFound(err) {
			return false
		}
		if err == nil {
			return true
		}
	}

	return false
}

// VerifyFunctionSpecReferences verifies that a function references secrets, configmaps, pkgs in its own namespace and
// outputs a list of functions that don't adhere to this requirement.
func (client *PreUpgradeTaskClient) VerifyFunctionSpecReferences() {
	client.logger.Info("verifying function spec references for all functions in the cluster")

	result := &multierror.Error{}
	var err error
	var fList *fv1.FunctionList

	for i := 0; i < maxRetries; i++ {
		fList, err = client.fissionClient.Functions(metav1.NamespaceAll).List(metav1.ListOptions{})
		if err == nil {
			break
		}
	}

	if err != nil {
		client.logger.Fatal("error listing functions after max retries",
			zap.Error(err),
			zap.Int("max_retries", maxRetries))
	}

	// check that all secrets, configmaps, packages are in the same namespace
	for _, fn := range fList.Items {
		secrets := fn.Spec.Secrets
		for _, secret := range secrets {
			if secret.Namespace != fn.Metadata.Namespace {
				result = multierror.Append(result, fmt.Errorf("function : %s.%s cannot reference a secret : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, secret.Name, secret.Namespace))
			}
		}

		configmaps := fn.Spec.ConfigMaps
		for _, configmap := range configmaps {
			if configmap.Namespace != fn.Metadata.Namespace {
				result = multierror.Append(result, fmt.Errorf("function : %s.%s cannot reference a configmap : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, configmap.Name, configmap.Namespace))
			}
		}

		if fn.Spec.Package.PackageRef.Namespace != fn.Metadata.Namespace {
			result = multierror.Append(result, fmt.Errorf("function : %s.%s cannot reference a package : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, fn.Spec.Package.PackageRef.Name, fn.Spec.Package.PackageRef.Namespace))
		}
	}

	if result != nil {
		client.logger.Fatal("installation failed",
			zap.Error(err),
			zap.String("summary", "a function cannot reference secrets, configmaps and packages outside it's own namespace"))
	}

	client.logger.Info("function spec references verified")
}

// deleteClusterRoleBinding deletes the clusterRoleBinding passed as an argument to it.
// If its not present, it just ignores and returns no errors
func (client *PreUpgradeTaskClient) deleteClusterRoleBinding(clusterRoleBinding string) (err error) {
	for i := 0; i < maxRetries; i++ {
		err = client.k8sClient.RbacV1beta1().ClusterRoleBindings().Delete(clusterRoleBinding, &metav1.DeleteOptions{})
		if err != nil && k8serrors.IsNotFound(err) || err == nil {
			return nil
		}
	}

	return err
}

// RemoveClusterAdminRolesForFissionSAs deletes the clusterRoleBindings previously created on this cluster
func (client *PreUpgradeTaskClient) RemoveClusterAdminRolesForFissionSAs() {
	clusterRoleBindings := []string{"fission-builder-crd", "fission-fetcher-crd"}
	for _, clusterRoleBinding := range clusterRoleBindings {
		err := client.deleteClusterRoleBinding(clusterRoleBinding)
		if err != nil {
			client.logger.Fatal("error deleting rolebinding",
				zap.Error(err),
				zap.String("role_binding", clusterRoleBinding))
		}
	}

	client.logger.Info("femoved cluster admin privileges for fission-builder and fission-fetcher service accounts")
}

// NeedRoleBindings checks if there is atleast one package or function in default namespace.
// It is needed to find out if package-getter-rb and secret-configmap-getter-rb needs to be created for fission-fetcher
// and fission-builder service accounts.
// This is because, we just deleted the ClusterRoleBindings for these service accounts in the previous function and
// for the existing functions to work, we need to give these SAs the right privileges
func (client *PreUpgradeTaskClient) NeedRoleBindings() bool {
	pkgList, err := client.fissionClient.Packages(metav1.NamespaceDefault).List(metav1.ListOptions{})
	if err == nil && len(pkgList.Items) > 0 {
		return true
	}

	fnList, err := client.fissionClient.Functions(metav1.NamespaceDefault).List(metav1.ListOptions{})
	if err == nil && len(fnList.Items) > 0 {
		return true
	}

	return false
}

// Setup appropriate role bindings for fission-fetcher and fission-builder SAs
func (client *PreUpgradeTaskClient) SetupRoleBindings() {
	if !client.NeedRoleBindings() {
		client.logger.Info("no fission objects found, so no role-bindings to create")
		return
	}

	// the fact that we're here implies that there had been a prior installation of fission and objects are present still
	// so, we go ahead and create the role-bindings necessary for the fission-fetcher and fission-builder Service Accounts.
	err := utils.SetupRoleBinding(client.logger, client.k8sClient, types.PackageGetterRB, metav1.NamespaceDefault, types.PackageGetterCR, types.ClusterRole, types.FissionFetcherSA, client.fnPodNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.PackageGetterRB),
			zap.String("service_account", types.FissionFetcherSA),
			zap.String("service_account_namespace", client.fnPodNs))
	}

	err = utils.SetupRoleBinding(client.logger, client.k8sClient, types.PackageGetterRB, metav1.NamespaceDefault, types.PackageGetterCR, types.ClusterRole, types.FissionBuilderSA, client.envBuilderNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.PackageGetterRB),
			zap.String("service_account", types.FissionBuilderSA),
			zap.String("service_account_namespace", client.envBuilderNs))
	}

	err = utils.SetupRoleBinding(client.logger, client.k8sClient, types.SecretConfigMapGetterRB, metav1.NamespaceDefault, types.SecretConfigMapGetterCR, types.ClusterRole, types.FissionFetcherSA, client.fnPodNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.SecretConfigMapGetterRB),
			zap.String("service_account", types.FissionFetcherSA),
			zap.String("service_account_namespace", client.fnPodNs))
	}

	client.logger.Info("created rolebindings in default namespace",
		zap.Strings("role_bindings", []string{types.PackageGetterRB, types.SecretConfigMapGetterRB}))
}
<commit_msg>Fix helm pre-upgrade check failure problem (#1397)<commit_after>/*
Copyright 2016 The Fission Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
	"github.com/fission/fission/pkg/crd"
	"github.com/fission/fission/pkg/types"
	"github.com/fission/fission/pkg/utils"
)

type (
	PreUpgradeTaskClient struct {
		logger        *zap.Logger
		fissionClient *crd.FissionClient
		k8sClient     *kubernetes.Clientset
		apiExtClient  *apiextensionsclient.Clientset
		fnPodNs       string
		envBuilderNs  string
	}
)

const (
	maxRetries  = 5
	FunctionCRD = "functions.fission.io"
)

func makePreUpgradeTaskClient(logger *zap.Logger, fnPodNs, envBuilderNs string) (*PreUpgradeTaskClient, error) {
	fissionClient, k8sClient, apiExtClient, err := crd.MakeFissionClient()
	if err != nil {
		return nil, errors.Wrap(err, "error making fission client")
	}

	return &PreUpgradeTaskClient{
		logger:        logger.Named("pre_upgrade_task_client"),
		fissionClient: fissionClient,
		k8sClient:     k8sClient,
		fnPodNs:       fnPodNs,
		envBuilderNs:  envBuilderNs,
		apiExtClient:  apiExtClient,
	}, nil
}

// IsFissionReInstall checks if there is atleast one fission CRD, i.e. function in this case, on this cluster.
// We need this to find out if fission had been previously installed on this cluster
func (client *PreUpgradeTaskClient) IsFissionReInstall() bool {
	for i := 0; i < maxRetries; i++ {
		_, err := client.apiExtClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(FunctionCRD, metav1.GetOptions{})
		if err != nil && k8serrors.IsNotFound(err) {
			return false
		}
		if err == nil {
			return true
		}
	}

	return false
}

// VerifyFunctionSpecReferences verifies that a function references secrets, configmaps, pkgs in its own namespace and
// outputs a list of functions that don't adhere to this requirement.
func (client *PreUpgradeTaskClient) VerifyFunctionSpecReferences() {
	client.logger.Info("verifying function spec references for all functions in the cluster")

	var err error
	var fList *fv1.FunctionList

	for i := 0; i < maxRetries; i++ {
		fList, err = client.fissionClient.Functions(metav1.NamespaceAll).List(metav1.ListOptions{})
		if err == nil {
			break
		}
	}

	if err != nil {
		client.logger.Fatal("error listing functions after max retries",
			zap.Error(err),
			zap.Int("max_retries", maxRetries))
	}

	errs := &multierror.Error{}

	// check that all secrets, configmaps, packages are in the same namespace
	for _, fn := range fList.Items {
		secrets := fn.Spec.Secrets
		for _, secret := range secrets {
			if secret.Namespace != fn.Metadata.Namespace {
				errs = multierror.Append(errs, fmt.Errorf("function : %s.%s cannot reference a secret : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, secret.Name, secret.Namespace))
			}
		}

		configmaps := fn.Spec.ConfigMaps
		for _, configmap := range configmaps {
			if configmap.Namespace != fn.Metadata.Namespace {
				errs = multierror.Append(errs, fmt.Errorf("function : %s.%s cannot reference a configmap : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, configmap.Name, configmap.Namespace))
			}
		}

		if fn.Spec.Package.PackageRef.Namespace != fn.Metadata.Namespace {
			errs = multierror.Append(errs, fmt.Errorf("function : %s.%s cannot reference a package : %s in namespace : %s", fn.Metadata.Name, fn.Metadata.Namespace, fn.Spec.Package.PackageRef.Name, fn.Spec.Package.PackageRef.Namespace))
		}
	}

	if errs.ErrorOrNil() != nil {
		client.logger.Fatal("installation failed",
			zap.Error(err),
			zap.String("summary", "a function cannot reference secrets, configmaps and packages outside it's own namespace"))
	}

	client.logger.Info("function spec references verified")
}

// deleteClusterRoleBinding deletes the clusterRoleBinding passed as an argument to it.
// If its not present, it just ignores and returns no errors
func (client *PreUpgradeTaskClient) deleteClusterRoleBinding(clusterRoleBinding string) (err error) {
	for i := 0; i < maxRetries; i++ {
		err = client.k8sClient.RbacV1beta1().ClusterRoleBindings().Delete(clusterRoleBinding, &metav1.DeleteOptions{})
		if err != nil && k8serrors.IsNotFound(err) || err == nil {
			return nil
		}
	}

	return err
}

// RemoveClusterAdminRolesForFissionSAs deletes the clusterRoleBindings previously created on this cluster
func (client *PreUpgradeTaskClient) RemoveClusterAdminRolesForFissionSAs() {
	clusterRoleBindings := []string{"fission-builder-crd", "fission-fetcher-crd"}
	for _, clusterRoleBinding := range clusterRoleBindings {
		err := client.deleteClusterRoleBinding(clusterRoleBinding)
		if err != nil {
			client.logger.Fatal("error deleting rolebinding",
				zap.Error(err),
				zap.String("role_binding", clusterRoleBinding))
		}
	}

	client.logger.Info("femoved cluster admin privileges for fission-builder and fission-fetcher service accounts")
}

// NeedRoleBindings checks if there is atleast one package or function in default namespace.
// It is needed to find out if package-getter-rb and secret-configmap-getter-rb needs to be created for fission-fetcher
// and fission-builder service accounts.
// This is because, we just deleted the ClusterRoleBindings for these service accounts in the previous function and
// for the existing functions to work, we need to give these SAs the right privileges
func (client *PreUpgradeTaskClient) NeedRoleBindings() bool {
	pkgList, err := client.fissionClient.Packages(metav1.NamespaceDefault).List(metav1.ListOptions{})
	if err == nil && len(pkgList.Items) > 0 {
		return true
	}

	fnList, err := client.fissionClient.Functions(metav1.NamespaceDefault).List(metav1.ListOptions{})
	if err == nil && len(fnList.Items) > 0 {
		return true
	}

	return false
}

// Setup appropriate role bindings for fission-fetcher and fission-builder SAs
func (client *PreUpgradeTaskClient) SetupRoleBindings() {
	if !client.NeedRoleBindings() {
		client.logger.Info("no fission objects found, so no role-bindings to create")
		return
	}

	// the fact that we're here implies that there had been a prior installation of fission and objects are present still
	// so, we go ahead and create the role-bindings necessary for the fission-fetcher and fission-builder Service Accounts.
	err := utils.SetupRoleBinding(client.logger, client.k8sClient, types.PackageGetterRB, metav1.NamespaceDefault, types.PackageGetterCR, types.ClusterRole, types.FissionFetcherSA, client.fnPodNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.PackageGetterRB),
			zap.String("service_account", types.FissionFetcherSA),
			zap.String("service_account_namespace", client.fnPodNs))
	}

	err = utils.SetupRoleBinding(client.logger, client.k8sClient, types.PackageGetterRB, metav1.NamespaceDefault, types.PackageGetterCR, types.ClusterRole, types.FissionBuilderSA, client.envBuilderNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.PackageGetterRB),
			zap.String("service_account", types.FissionBuilderSA),
			zap.String("service_account_namespace", client.envBuilderNs))
	}

	err = utils.SetupRoleBinding(client.logger, client.k8sClient, types.SecretConfigMapGetterRB, metav1.NamespaceDefault, types.SecretConfigMapGetterCR, types.ClusterRole, types.FissionFetcherSA, client.fnPodNs)
	if err != nil {
		client.logger.Fatal("error setting up rolebinding for service account",
			zap.Error(err),
			zap.String("role_binding", types.SecretConfigMapGetterRB),
			zap.String("service_account", types.FissionFetcherSA),
			zap.String("service_account_namespace", client.fnPodNs))
	}

	client.logger.Info("created rolebindings in default namespace",
		zap.Strings("role_bindings", []string{types.PackageGetterRB, types.SecretConfigMapGetterRB}))
}
<|endoftext|>


<commit_before>//    Copyright (C) 2016  mparaiso <mparaiso@online.fr>
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

package acl

// Type is a rule type
type Type byte

// Operation is a rule management operation
type Operation byte

const (
	// Allow is an allow rule type
	Allow Type = iota
	// Deny is a deny rule type
	Deny
)

const (
	// Add is is used when a rule is added to a rule set of an ACL
	Add Operation = iota
	// Remove is used when a rule is removed from an ACL rule set
	Remove
)

// Rule is an ACL rule
type Rule struct {
	Type
	Role
	Resource
	AllPrivileges bool
	Assertion
	Privilege string
}

// ResourceNode is a node in a tree of nodes
type ResourceNode struct {
	Instance Resource
	Children []Resource
	Parent   Resource
}

// RoleNode is a node in a tree of nodes
type RoleNode struct {
	Instance Role
	Children []Role
	Parent   Role
}

// ACL is an access control list
type ACL struct {
	RoleTree     map[string]*RoleNode
	ResourceTree map[string]*ResourceNode
	rules        []*Rule
}

// NewACL returns a new access control list
func NewACL() *ACL {
	return &ACL{RoleTree: map[string]*RoleNode{}, ResourceTree: map[string]*ResourceNode{}, rules: []*Rule{}}
}

// GetRole returns the Role or nil if not exists
func (acl *ACL) GetRole(role Role) Role {
	if role == nil {
		return nil
	}
	return acl.RoleTree[role.GetRoleID()].Instance
}

// HasRole returns true if ACL has role
func (acl *ACL) HasRole(role Role) bool {
	if acl.GetRole(role) != nil {
		return true
	}
	return false
}

// AddRole add role and its parents to the ACL
func (acl *ACL) AddRole(role Role, parent Role) *ACL {
	roleNode := &RoleNode{Instance: role}
	acl.RoleTree[role.GetRoleID()] = roleNode

	// prevents cyclic dependencies

	if parent != nil {
		if acl.InheritsRole(parent, role) {
			return acl
		}
		roleNode.Parent = parent
		if parentNode, ok := acl.RoleTree[parent.GetRoleID()]; ok {
			parentNode.Children = append(parentNode.Children, role)
		}
	}

	return acl
}

// RemoveRole removes a role from the role tree
func (acl *ACL) RemoveRole(role Role) *ACL {
	delete(acl.RoleTree, role.GetRoleID())
	return acl
}

// InheritsRole returns true if role inherits from parent
func (acl *ACL) InheritsRole(role, parent Role, direct ...bool) bool {
	if len(direct) == 0 {
		direct = []bool{false}
	}

	if roleNode, ok := acl.RoleTree[role.GetRoleID()]; ok && roleNode.Parent != nil && roleNode.Parent.GetRoleID() == parent.GetRoleID() {
		return true
	}
	if direct[0] == true {
		return false
	}

	if roleNode, ok := acl.RoleTree[role.GetRoleID()]; ok && roleNode.Parent != nil {
		return acl.InheritsRole(roleNode.Parent, parent)
	}

	return false
}

// AddResource add resource and its parent to the ACL
// A resource can only have 1 parent
func (acl *ACL) AddResource(resource Resource, parent ...Resource) *ACL {

	acl.ResourceTree[resource.GetResourceID()] = &ResourceNode{
		Instance: resource,
		Children: []Resource{},
	}
	if len(parent) > 0 {
		// check for potential cyclic dependency before appending a child
		if !acl.InheritsResource(parent[0], resource) {
			acl.ResourceTree[resource.GetResourceID()].Parent = parent[0]
			if parent, ok := acl.ResourceTree[parent[0].GetResourceID()]; ok {
				parent.Children = append(parent.Children, resource)
			}
		}
	}

	return acl
}

// GetResource returns a resource or nil if it doesn't exist
func (acl *ACL) GetResource(resource Resource) Resource {
	if resource == nil {
		return nil
	}
	if node, ok := acl.ResourceTree[resource.GetResourceID()]; ok {
		return node.Instance
	}
	return nil
}

// HasResource returns true if the resource exists
func (acl *ACL) HasResource(resource Resource) bool {
	if resource := acl.GetResource(resource); resource != nil {
		return true
	}
	return false
}
func (acl *ACL) setRule(operation Operation, Type Type, role Role, resource Resource, privileges ...string) *ACL {

	switch operation {
	case Add:
		if len(privileges) > 0 {
			for _, privilege := range privileges {
				acl.rules = append(
					[]*Rule{{Type: Type, Role: role, Resource: resource, Privilege: privilege}},
					acl.rules...)
			}
		} else {
			acl.rules = append([]*Rule{{Type: Type, Role: role, Resource: resource, AllPrivileges: true}}, acl.rules...)
		}
	case Remove:
		if len(privileges) > 0 {
			for _, privilege := range privileges {
				for i, rule := range acl.rules {
					if rule.Type == Type && role.GetRoleID() == rule.GetRoleID() && rule.GetResourceID() == resource.GetResourceID() && privilege == rule.Privilege && rule.Assertion == nil && rule.AllPrivileges == false {
						acl.rules = append(acl.rules[0:i], acl.rules[i+1:len(acl.rules)]...)
					}
				}
			}
		} else {
			for i, rule := range acl.rules {
				if rule.Type == Type && role.GetRoleID() == rule.GetRoleID() && rule.GetResourceID() == resource.GetResourceID() && rule.AllPrivileges == true {
					acl.rules = append(acl.rules[0:i], acl.rules[i+1:len(acl.rules)]...)
				}
			}
		}
	}
	return acl
}

// IsAllowed return true if role is allowed all privileges on resource
func (acl *ACL) IsAllowed(role Role, resource Resource, privileges ...string) bool {
	for _, privilege := range privileges {
		if !acl.isAllowed(role, resource, privilege) {
			return false
		}
	}
	return true
}

func (acl *ACL) isAllowed(role Role, resource Resource, privilege string) bool {
	// check for a direct rule
	for _, rule := range acl.rules {
		if (rule.Role != nil && role != nil && role.GetRoleID() == rule.GetRoleID()) || (rule.Role == nil) {
			if (rule.Resource != nil && resource != nil && rule.GetResourceID() == resource.GetResourceID()) || (rule.Resource == nil) {
				if rule.AllPrivileges || rule.Privilege == privilege {
					if rule.Type == Deny {
						return false
					}
					return true
				}
			}

		}
	}
	// check for a rule on the resource's parent
	if resource != nil && acl.HasResource(resource) {
		if node := acl.ResourceTree[resource.GetResourceID()]; node != nil {
			if node.Parent != nil {
				return acl.isAllowed(role, node.Parent, privilege)
			}
		}
	}
	// check for a rule on the role's parent
	if role != nil && acl.HasRole(role) {
		if node := acl.RoleTree[role.GetRoleID()]; node != nil {
			if node.Parent != nil {
				return acl.isAllowed(node.Parent, resource, privilege)
			}
		}
	}
	return false
}

// Allow adds an allow rule
func (acl *ACL) Allow(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Add, Allow, role, resource, privilege...)
}

// Deny adds a deny rule
func (acl *ACL) Deny(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Add, Deny, role, resource, privilege...)
}

// RemoveAllow removes a allow rule
func (acl *ACL) RemoveAllow(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Remove, Allow, role, resource, privilege...)
}

// RemoveDeny deny removes a deny rule
func (acl *ACL) RemoveDeny(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Remove, Deny, role, resource, privilege...)
}

// InheritsResource retruns true if resource is a child of parent
func (acl *ACL) InheritsResource(resource, parent Resource, direct ...bool) bool {
	if len(direct) == 0 {
		direct = []bool{false}
	}
	if !acl.HasResource(resource) {
		return false
	}
	if parentResource := acl.ResourceTree[resource.GetResourceID()].Parent; parentResource != nil && parentResource.GetResourceID() == parent.GetResourceID() {
		return true
	} else if direct[0] == true {
		return false
	} else if found := acl.GetResource(parentResource); found != nil {
		return acl.InheritsResource(found, parent)
	}
	return false
}

// Role is an ACL role
type Role interface {
	GetRoleID() string
}
type defaultRole struct {
	roleID string
}

// NewRole returns a Role
func NewRole(id string) Role {
	return defaultRole{id}
}
func (role defaultRole) GetRoleID() string {
	return role.roleID
}

// Resource is an ACL resource
type Resource interface {
	GetResourceID() string
}
type defaultResource struct {
	resourceID string
}

// NewResource returns a resource from a string
func NewResource(id string) Resource {
	return defaultResource{id}
}

func (resource defaultResource) GetResourceID() string {
	return resource.resourceID
}

type Assertion interface {
	Assert(acl ACL, role Role, resource Resource, permission string)
}
<commit_msg>rules => Rules<commit_after>//    Copyright (C) 2016  mparaiso <mparaiso@online.fr>
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

package acl

// Type is a rule type
type Type string

// Operation is a rule management operation
type Operation byte

const (
	// Allow is an allow rule type
	Allow Type = "Allow"
	// Deny is a deny rule type
	Deny Type = "Deny"
)

const (
	// Add is is used when a rule is added to a rule set of an ACL
	Add Operation = iota
	// Remove is used when a rule is removed from an ACL rule set
	Remove
)

// Rule is an ACL rule
type Rule struct {
	Type
	Role
	Resource
	AllPrivileges bool
	Assertion
	Privilege string
}

// ResourceNode is a node in a tree of nodes
type ResourceNode struct {
	Instance Resource
	Children []Resource
	Parent   Resource
}

// RoleNode is a node in a tree of nodes
type RoleNode struct {
	Instance Role
	Children []Role
	Parent   Role
}

// ACL is an access control list
type ACL struct {
	RoleTree     map[string]*RoleNode
	ResourceTree map[string]*ResourceNode
	Rules        []*Rule
}

// NewACL returns a new access control list
func NewACL() *ACL {
	return &ACL{RoleTree: map[string]*RoleNode{}, ResourceTree: map[string]*ResourceNode{}, Rules: []*Rule{}}
}

// GetRole returns the Role or nil if not exists
func (acl *ACL) GetRole(role Role) Role {
	if role == nil {
		return nil
	}
	return acl.RoleTree[role.GetRoleID()].Instance
}

// HasRole returns true if ACL has role
func (acl *ACL) HasRole(role Role) bool {
	if acl.GetRole(role) != nil {
		return true
	}
	return false
}

// AddRole add role and its parents to the ACL
func (acl *ACL) AddRole(role Role, parent Role) *ACL {
	roleNode := &RoleNode{Instance: role}
	acl.RoleTree[role.GetRoleID()] = roleNode

	// prevents cyclic dependencies

	if parent != nil {
		if acl.InheritsRole(parent, role) {
			return acl
		}
		roleNode.Parent = parent
		if parentNode, ok := acl.RoleTree[parent.GetRoleID()]; ok {
			parentNode.Children = append(parentNode.Children, role)
		} else {
			// creates an add the parent even if it doesnt exist
			parentNode = &RoleNode{Instance: parent, Children: []Role{role}}
			acl.RoleTree[parent.GetRoleID()] = parentNode
		}
	}

	return acl
}

// RemoveRole removes a role from the role tree
func (acl *ACL) RemoveRole(role Role) *ACL {
	delete(acl.RoleTree, role.GetRoleID())
	return acl
}

// InheritsRole returns true if role inherits from parent
func (acl *ACL) InheritsRole(role, parent Role, direct ...bool) bool {
	if len(direct) == 0 {
		direct = []bool{false}
	}

	if roleNode, ok := acl.RoleTree[role.GetRoleID()]; ok && roleNode.Parent != nil && roleNode.Parent.GetRoleID() == parent.GetRoleID() {
		return true
	}
	if direct[0] == true {
		return false
	}

	if roleNode, ok := acl.RoleTree[role.GetRoleID()]; ok && roleNode.Parent != nil {
		return acl.InheritsRole(roleNode.Parent, parent)
	}

	return false
}

// AddResource add resource and its parent to the ACL
// A resource can only have 1 parent
func (acl *ACL) AddResource(resource Resource, parent ...Resource) *ACL {

	acl.ResourceTree[resource.GetResourceID()] = &ResourceNode{
		Instance: resource,
		Children: []Resource{},
	}
	if len(parent) > 0 {
		// check for potential cyclic dependency before appending a child
		if !acl.InheritsResource(parent[0], resource) {
			acl.ResourceTree[resource.GetResourceID()].Parent = parent[0]
			if parent, ok := acl.ResourceTree[parent[0].GetResourceID()]; ok {
				parent.Children = append(parent.Children, resource)
			}
		}
	}

	return acl
}

// GetResource returns a resource or nil if it doesn't exist
func (acl *ACL) GetResource(resource Resource) Resource {
	if resource == nil {
		return nil
	}
	if node, ok := acl.ResourceTree[resource.GetResourceID()]; ok {
		return node.Instance
	}
	return nil
}

// HasResource returns true if the resource exists
func (acl *ACL) HasResource(resource Resource) bool {
	if resource := acl.GetResource(resource); resource != nil {
		return true
	}
	return false
}
func (acl *ACL) setRule(operation Operation, Type Type, role Role, resource Resource, privileges ...string) *ACL {

	switch operation {
	case Add:
		if len(privileges) > 0 {
			for _, privilege := range privileges {
				acl.Rules = append(
					[]*Rule{{Type: Type, Role: role, Resource: resource, Privilege: privilege}},
					acl.Rules...)
			}
		} else {
			acl.Rules = append([]*Rule{{Type: Type, Role: role, Resource: resource, AllPrivileges: true}}, acl.Rules...)
		}
	case Remove:
		if len(privileges) > 0 {
			for _, privilege := range privileges {
				for i, rule := range acl.Rules {
					if rule.Type == Type && role.GetRoleID() == rule.GetRoleID() && rule.GetResourceID() == resource.GetResourceID() && privilege == rule.Privilege && rule.Assertion == nil && rule.AllPrivileges == false {
						acl.Rules = append(acl.Rules[0:i], acl.Rules[i+1:len(acl.Rules)]...)
					}
				}
			}
		} else {
			for i, rule := range acl.Rules {
				if rule.Type == Type && role.GetRoleID() == rule.GetRoleID() && rule.GetResourceID() == resource.GetResourceID() && rule.AllPrivileges == true {
					acl.Rules = append(acl.Rules[0:i], acl.Rules[i+1:len(acl.Rules)]...)
				}
			}
		}
	}
	return acl
}

// IsAllowed return true if role is allowed all privileges on resource
func (acl *ACL) IsAllowed(role Role, resource Resource, privileges ...string) bool {
	for _, privilege := range privileges {
		if !acl.isAllowed(role, resource, privilege) {
			return false
		}
	}
	return true
}

func (acl *ACL) isAllowed(role Role, resource Resource, privilege string) bool {
	// check for a direct rule
	for _, rule := range acl.Rules {
		if (rule.Role != nil && role != nil && role.GetRoleID() == rule.GetRoleID()) || (rule.Role == nil) {
			if (rule.Resource != nil && resource != nil && rule.GetResourceID() == resource.GetResourceID()) || (rule.Resource == nil) {
				if rule.AllPrivileges || rule.Privilege == privilege {
					if rule.Type == Deny {
						return false
					}
					return true
				}
			}

		}
	}
	// check for a rule on the resource's parent
	if resource != nil && acl.HasResource(resource) {
		if node := acl.ResourceTree[resource.GetResourceID()]; node != nil {
			if node.Parent != nil {
				return acl.isAllowed(role, node.Parent, privilege)
			}
		}
	}
	// check for a rule on the role's parent
	if role != nil && acl.HasRole(role) {
		if node := acl.RoleTree[role.GetRoleID()]; node != nil {
			if node.Parent != nil {
				return acl.isAllowed(node.Parent, resource, privilege)
			}
		}
	}
	return false
}

// Allow adds an allow rule
func (acl *ACL) Allow(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Add, Allow, role, resource, privilege...)
}

// Deny adds a deny rule
func (acl *ACL) Deny(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Add, Deny, role, resource, privilege...)
}

// RemoveAllow removes a allow rule
func (acl *ACL) RemoveAllow(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Remove, Allow, role, resource, privilege...)
}

// RemoveDeny deny removes a deny rule
func (acl *ACL) RemoveDeny(role Role, resource Resource, privilege ...string) *ACL {
	return acl.setRule(Remove, Deny, role, resource, privilege...)
}

// InheritsResource retruns true if resource is a child of parent
func (acl *ACL) InheritsResource(resource, parent Resource, direct ...bool) bool {
	if len(direct) == 0 {
		direct = []bool{false}
	}
	if !acl.HasResource(resource) {
		return false
	}
	if parentResource := acl.ResourceTree[resource.GetResourceID()].Parent; parentResource != nil && parentResource.GetResourceID() == parent.GetResourceID() {
		return true
	} else if direct[0] == true {
		return false
	} else if found := acl.GetResource(parentResource); found != nil {
		return acl.InheritsResource(found, parent)
	}
	return false
}

// Role is an ACL role
type Role interface {
	GetRoleID() string
}
type defaultRole struct {
	roleID string
}

// NewRole returns a Role
func NewRole(id string) Role {
	return defaultRole{id}
}
func (role defaultRole) GetRoleID() string {
	return role.roleID
}

// Resource is an ACL resource
type Resource interface {
	GetResourceID() string
}
type defaultResource struct {
	resourceID string
}

// NewResource returns a resource from a string
func NewResource(id string) Resource {
	return defaultResource{id}
}

func (resource defaultResource) GetResourceID() string {
	return resource.resourceID
}

type Assertion interface {
	Assert(acl ACL, role Role, resource Resource, permission string)
}
<|endoftext|>


<commit_before>package packer

import (
	"fmt"
	"strings"
)

// MultiError is an error type to track multiple errors. This is used to
// accumulate errors in cases such as configuration parsing, and returning
// them as a single error.
type MultiError struct {
	Errors []error
}

func (e *MultiError) Error() string {
	points := make([]string, len(e.Errors))
	for i, err := range e.Errors {
		points[i] = fmt.Sprintf("* %s", err)
	}

	return fmt.Sprintf(
		"%d error(s) occurred:\n\n%s",
		len(e.Errors), strings.Join(points, "\n"))
}

// MultiErrorAppend is a helper function that will append more errors
// onto a MultiError in order to create a larger multi-error. If the
// original error is not a MultiError, it will be turned into one.
func MultiErrorAppend(err error, errs ...error) *MultiError {
	if err == nil {
		err = new(MultiError)
	}

	switch err := err.(type) {
	case *MultiError:
		if err == nil {
			err = new(MultiError)
		}

		if err.Errors == nil {
			err.Errors = make([]error, 0, len(errs))
		}

		err.Errors = append(err.Errors, errs...)
		return err
	default:
		newErrs := make([]error, len(errs)+1)
		newErrs[0] = err
		copy(newErrs[1:], errs)
		return &MultiError{
			Errors: newErrs,
		}
	}
}
<commit_msg>packer: no need to check if nil since we're appending to slice<commit_after>package packer

import (
	"fmt"
	"strings"
)

// MultiError is an error type to track multiple errors. This is used to
// accumulate errors in cases such as configuration parsing, and returning
// them as a single error.
type MultiError struct {
	Errors []error
}

func (e *MultiError) Error() string {
	points := make([]string, len(e.Errors))
	for i, err := range e.Errors {
		points[i] = fmt.Sprintf("* %s", err)
	}

	return fmt.Sprintf(
		"%d error(s) occurred:\n\n%s",
		len(e.Errors), strings.Join(points, "\n"))
}

// MultiErrorAppend is a helper function that will append more errors
// onto a MultiError in order to create a larger multi-error. If the
// original error is not a MultiError, it will be turned into one.
func MultiErrorAppend(err error, errs ...error) *MultiError {
	if err == nil {
		err = new(MultiError)
	}

	switch err := err.(type) {
	case *MultiError:
		if err == nil {
			err = new(MultiError)
		}

		err.Errors = append(err.Errors, errs...)
		return err
	default:
		newErrs := make([]error, len(errs)+1)
		newErrs[0] = err
		copy(newErrs[1:], errs)
		return &MultiError{
			Errors: newErrs,
		}
	}
}
<|endoftext|>


<commit_before>package consul

import (
	"fmt"
	"net"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/hashicorp/consul/consul/structs"
	"github.com/hashicorp/consul/testutil"
	"github.com/hashicorp/consul/vendor/github.com/hashicorp/net-rpc-msgpackrpc"
	"github.com/hashicorp/serf/serf"
)

func testClientConfig(t *testing.T, NodeName string) (string, *Config) {
	dir := tmpDir(t)
	config := DefaultConfig()
	config.Datacenter = "dc1"
	config.DataDir = dir
	config.NodeName = NodeName
	config.RPCAddr = &net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: getPort(),
	}
	config.SerfLANConfig.MemberlistConfig.BindAddr = "127.0.0.1"
	config.SerfLANConfig.MemberlistConfig.BindPort = getPort()
	config.SerfLANConfig.MemberlistConfig.ProbeTimeout = 200 * time.Millisecond
	config.SerfLANConfig.MemberlistConfig.ProbeInterval = time.Second
	config.SerfLANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond

	return dir, config
}

func testClient(t *testing.T) (string, *Client) {
	return testClientDC(t, "dc1")
}

func testClientDC(t *testing.T, dc string) (string, *Client) {
	dir, config := testClientConfig(t, "testco.internal")
	config.Datacenter = dc

	client, err := NewClient(config)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	return dir, client
}

func testClientWithConfig(t *testing.T, cb func(c *Config)) (string, *Client) {
	name := fmt.Sprintf("Client %d", getPort())
	dir, config := testClientConfig(t, name)
	cb(config)
	client, err := NewClient(config)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	return dir, client
}

func TestClient_StartStop(t *testing.T) {
	dir, client := testClient(t)
	defer os.RemoveAll(dir)

	if err := client.Shutdown(); err != nil {
		t.Fatalf("err: %v", err)
	}
}

func TestClient_JoinLAN(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}
	numServers := c1.serverMgr.GetNumServers()
	testutil.WaitForResult(func() (bool, error) {
		return numServers == 1, nil
	}, func(err error) {
		t.Fatalf("expected consul server: %d", numServers)
	})

	// Check the members
	testutil.WaitForResult(func() (bool, error) {
		server_check := len(s1.LANMembers()) == 2
		client_check := len(c1.LANMembers()) == 2
		return server_check && client_check, nil
	}, func(err error) {
		t.Fatalf("bad len")
	})

	numServers = c1.serverMgr.GetNumServers()
	// Check we have a new consul
	testutil.WaitForResult(func() (bool, error) {
		return numServers == 1, nil
	}, func(err error) {
		t.Fatalf("expected consul server")
	})
}

func TestClient_JoinLAN_Invalid(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClientDC(t, "other")
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err == nil {
		t.Fatalf("should error")
	}

	time.Sleep(50 * time.Millisecond)
	if len(s1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
	if len(c1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
}

func TestClient_JoinWAN_Invalid(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClientDC(t, "dc2")
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfWANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err == nil {
		t.Fatalf("should error")
	}

	time.Sleep(50 * time.Millisecond)
	if len(s1.WANMembers()) != 1 {
		t.Fatalf("should not join")
	}
	if len(c1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
}

func TestClient_RPC(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try an RPC
	var out struct{}
	if err := c1.RPC("Status.Ping", struct{}{}, &out); err != structs.ErrNoServers {
		t.Fatalf("err: %v", err)
	}

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check the members
	if len(s1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	if len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// RPC should succeed
	testutil.WaitForResult(func() (bool, error) {
		err := c1.RPC("Status.Ping", struct{}{}, &out)
		return err == nil, err
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})
}

func TestClient_RPC_Pool(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join.
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(s1.LANMembers()) != 2 || len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// Blast out a bunch of RPC requests at the same time to try to get
	// contention opening new connections.
	var wg sync.WaitGroup
	for i := 0; i < 150; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()
			var out struct{}
			testutil.WaitForResult(func() (bool, error) {
				err := c1.RPC("Status.Ping", struct{}{}, &out)
				return err == nil, err
			}, func(err error) {
				t.Fatalf("err: %v", err)
			})
		}()
	}

	wg.Wait()
}

func TestClient_RPC_TLS(t *testing.T) {
	dir1, conf1 := testServerConfig(t, "a.testco.internal")
	conf1.VerifyIncoming = true
	conf1.VerifyOutgoing = true
	configureTLS(conf1)
	s1, err := NewServer(conf1)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, conf2 := testClientConfig(t, "b.testco.internal")
	conf2.VerifyOutgoing = true
	configureTLS(conf2)
	c1, err := NewClient(conf2)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try an RPC
	var out struct{}
	if err := c1.RPC("Status.Ping", struct{}{}, &out); err != structs.ErrNoServers {
		t.Fatalf("err: %v", err)
	}

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check the members
	if len(s1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	if len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// RPC should succeed
	testutil.WaitForResult(func() (bool, error) {
		err := c1.RPC("Status.Ping", struct{}{}, &out)
		return err == nil, err
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})
}

func TestClientServer_UserEvent(t *testing.T) {
	clientOut := make(chan serf.UserEvent, 2)
	dir1, c1 := testClientWithConfig(t, func(conf *Config) {
		conf.UserEventHandler = func(e serf.UserEvent) {
			clientOut <- e
		}
	})
	defer os.RemoveAll(dir1)
	defer c1.Shutdown()

	serverOut := make(chan serf.UserEvent, 2)
	dir2, s1 := testServerWithConfig(t, func(conf *Config) {
		conf.UserEventHandler = func(e serf.UserEvent) {
			serverOut <- e
		}
	})
	defer os.RemoveAll(dir2)
	defer s1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Wait for the leader
	testutil.WaitForLeader(t, s1.RPC, "dc1")

	// Check the members
	testutil.WaitForResult(func() (bool, error) {
		return len(c1.LANMembers()) == 2 && len(s1.LANMembers()) == 2, nil
	}, func(err error) {
		t.Fatalf("bad len")
	})

	// Fire the user event
	codec := rpcClient(t, s1)
	event := structs.EventFireRequest{
		Name:       "foo",
		Datacenter: "dc1",
		Payload:    []byte("baz"),
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.EventFire", &event, nil); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Wait for all the events
	var clientReceived, serverReceived bool
	for i := 0; i < 2; i++ {
		select {
		case e := <-clientOut:
			switch e.Name {
			case "foo":
				clientReceived = true
			default:
				t.Fatalf("Bad: %#v", e)
			}

		case e := <-serverOut:
			switch e.Name {
			case "foo":
				serverReceived = true
			default:
				t.Fatalf("Bad: %#v", e)
			}

		case <-time.After(10 * time.Second):
			t.Fatalf("timeout")
		}
	}

	if !serverReceived || !clientReceived {
		t.Fatalf("missing events")
	}
}

func TestClient_Encrypted(t *testing.T) {
	dir1, c1 := testClient(t)
	defer os.RemoveAll(dir1)
	defer c1.Shutdown()

	key := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
	dir2, c2 := testClientWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = key
	})
	defer os.RemoveAll(dir2)
	defer c2.Shutdown()

	if c1.Encrypted() {
		t.Fatalf("should not be encrypted")
	}
	if !c2.Encrypted() {
		t.Fatalf("should be encrypted")
	}
}
<commit_msg>Unbreak client tests by reverting to original test<commit_after>package consul

import (
	"fmt"
	"net"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/hashicorp/consul/consul/structs"
	"github.com/hashicorp/consul/testutil"
	"github.com/hashicorp/consul/vendor/github.com/hashicorp/net-rpc-msgpackrpc"
	"github.com/hashicorp/serf/serf"
)

func testClientConfig(t *testing.T, NodeName string) (string, *Config) {
	dir := tmpDir(t)
	config := DefaultConfig()
	config.Datacenter = "dc1"
	config.DataDir = dir
	config.NodeName = NodeName
	config.RPCAddr = &net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: getPort(),
	}
	config.SerfLANConfig.MemberlistConfig.BindAddr = "127.0.0.1"
	config.SerfLANConfig.MemberlistConfig.BindPort = getPort()
	config.SerfLANConfig.MemberlistConfig.ProbeTimeout = 200 * time.Millisecond
	config.SerfLANConfig.MemberlistConfig.ProbeInterval = time.Second
	config.SerfLANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond

	return dir, config
}

func testClient(t *testing.T) (string, *Client) {
	return testClientDC(t, "dc1")
}

func testClientDC(t *testing.T, dc string) (string, *Client) {
	dir, config := testClientConfig(t, "testco.internal")
	config.Datacenter = dc

	client, err := NewClient(config)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	return dir, client
}

func testClientWithConfig(t *testing.T, cb func(c *Config)) (string, *Client) {
	name := fmt.Sprintf("Client %d", getPort())
	dir, config := testClientConfig(t, name)
	cb(config)
	client, err := NewClient(config)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	return dir, client
}

func TestClient_StartStop(t *testing.T) {
	dir, client := testClient(t)
	defer os.RemoveAll(dir)

	if err := client.Shutdown(); err != nil {
		t.Fatalf("err: %v", err)
	}
}

func TestClient_JoinLAN(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}
	testutil.WaitForResult(func() (bool, error) {
		return c1.serverMgr.GetNumServers() == 1, nil
	}, func(err error) {
		t.Fatalf("expected consul server")
	})

	// Check the members
	testutil.WaitForResult(func() (bool, error) {
		server_check := len(s1.LANMembers()) == 2
		client_check := len(c1.LANMembers()) == 2
		return server_check && client_check, nil
	}, func(err error) {
		t.Fatalf("bad len")
	})

	// Check we have a new consul
	testutil.WaitForResult(func() (bool, error) {
		return c1.serverMgr.GetNumServers() == 1, nil
	}, func(err error) {
		t.Fatalf("expected consul server")
	})
}

func TestClient_JoinLAN_Invalid(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClientDC(t, "other")
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err == nil {
		t.Fatalf("should error")
	}

	time.Sleep(50 * time.Millisecond)
	if len(s1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
	if len(c1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
}

func TestClient_JoinWAN_Invalid(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClientDC(t, "dc2")
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfWANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err == nil {
		t.Fatalf("should error")
	}

	time.Sleep(50 * time.Millisecond)
	if len(s1.WANMembers()) != 1 {
		t.Fatalf("should not join")
	}
	if len(c1.LANMembers()) != 1 {
		t.Fatalf("should not join")
	}
}

func TestClient_RPC(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try an RPC
	var out struct{}
	if err := c1.RPC("Status.Ping", struct{}{}, &out); err != structs.ErrNoServers {
		t.Fatalf("err: %v", err)
	}

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check the members
	if len(s1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	if len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// RPC should succeed
	testutil.WaitForResult(func() (bool, error) {
		err := c1.RPC("Status.Ping", struct{}{}, &out)
		return err == nil, err
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})
}

func TestClient_RPC_Pool(t *testing.T) {
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, c1 := testClient(t)
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try to join.
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(s1.LANMembers()) != 2 || len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// Blast out a bunch of RPC requests at the same time to try to get
	// contention opening new connections.
	var wg sync.WaitGroup
	for i := 0; i < 150; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()
			var out struct{}
			testutil.WaitForResult(func() (bool, error) {
				err := c1.RPC("Status.Ping", struct{}{}, &out)
				return err == nil, err
			}, func(err error) {
				t.Fatalf("err: %v", err)
			})
		}()
	}

	wg.Wait()
}

func TestClient_RPC_TLS(t *testing.T) {
	dir1, conf1 := testServerConfig(t, "a.testco.internal")
	conf1.VerifyIncoming = true
	conf1.VerifyOutgoing = true
	configureTLS(conf1)
	s1, err := NewServer(conf1)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	dir2, conf2 := testClientConfig(t, "b.testco.internal")
	conf2.VerifyOutgoing = true
	configureTLS(conf2)
	c1, err := NewClient(conf2)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	defer os.RemoveAll(dir2)
	defer c1.Shutdown()

	// Try an RPC
	var out struct{}
	if err := c1.RPC("Status.Ping", struct{}{}, &out); err != structs.ErrNoServers {
		t.Fatalf("err: %v", err)
	}

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check the members
	if len(s1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	if len(c1.LANMembers()) != 2 {
		t.Fatalf("bad len")
	}

	// RPC should succeed
	testutil.WaitForResult(func() (bool, error) {
		err := c1.RPC("Status.Ping", struct{}{}, &out)
		return err == nil, err
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})
}

func TestClientServer_UserEvent(t *testing.T) {
	clientOut := make(chan serf.UserEvent, 2)
	dir1, c1 := testClientWithConfig(t, func(conf *Config) {
		conf.UserEventHandler = func(e serf.UserEvent) {
			clientOut <- e
		}
	})
	defer os.RemoveAll(dir1)
	defer c1.Shutdown()

	serverOut := make(chan serf.UserEvent, 2)
	dir2, s1 := testServerWithConfig(t, func(conf *Config) {
		conf.UserEventHandler = func(e serf.UserEvent) {
			serverOut <- e
		}
	})
	defer os.RemoveAll(dir2)
	defer s1.Shutdown()

	// Try to join
	addr := fmt.Sprintf("127.0.0.1:%d",
		s1.config.SerfLANConfig.MemberlistConfig.BindPort)
	if _, err := c1.JoinLAN([]string{addr}); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Wait for the leader
	testutil.WaitForLeader(t, s1.RPC, "dc1")

	// Check the members
	testutil.WaitForResult(func() (bool, error) {
		return len(c1.LANMembers()) == 2 && len(s1.LANMembers()) == 2, nil
	}, func(err error) {
		t.Fatalf("bad len")
	})

	// Fire the user event
	codec := rpcClient(t, s1)
	event := structs.EventFireRequest{
		Name:       "foo",
		Datacenter: "dc1",
		Payload:    []byte("baz"),
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.EventFire", &event, nil); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Wait for all the events
	var clientReceived, serverReceived bool
	for i := 0; i < 2; i++ {
		select {
		case e := <-clientOut:
			switch e.Name {
			case "foo":
				clientReceived = true
			default:
				t.Fatalf("Bad: %#v", e)
			}

		case e := <-serverOut:
			switch e.Name {
			case "foo":
				serverReceived = true
			default:
				t.Fatalf("Bad: %#v", e)
			}

		case <-time.After(10 * time.Second):
			t.Fatalf("timeout")
		}
	}

	if !serverReceived || !clientReceived {
		t.Fatalf("missing events")
	}
}

func TestClient_Encrypted(t *testing.T) {
	dir1, c1 := testClient(t)
	defer os.RemoveAll(dir1)
	defer c1.Shutdown()

	key := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
	dir2, c2 := testClientWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = key
	})
	defer os.RemoveAll(dir2)
	defer c2.Shutdown()

	if c1.Encrypted() {
		t.Fatalf("should not be encrypted")
	}
	if !c2.Encrypted() {
		t.Fatalf("should be encrypted")
	}
}
<|endoftext|>


<commit_before>package checks

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func CheckMasterApis(urls string) error {
	log.Println("Checking master apis. At least one has to be up")

	urlArr := strings.Split(urls, ",")

	oneApiOk := false
	var msg string
	for _, u := range urlArr {
		if err := checkHttp(u); err == nil {
			oneApiOk = true
		} else {
			msg += u + " is not reachable. "
		}
	}

	if oneApiOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckOcGetNodes(buildNodes bool) error {
	log.Println("Checking oc get nodes output")

	var out string
	var err error
	for i := 0; i < 5; i++ {
		out, err = runOcGetNodes(buildNodes)
		if err != nil {
			return err
		}
		if strings.Contains(out, "NotReady") {
			// Wait a few seconds and see if still NotReady
			// to avoid wrong alerts
			time.Sleep(10 * time.Second)
			continue
		}
		return nil
	}
	var purpose string
	if buildNodes {
		purpose = "Buildnode "
	} else {
		purpose = "Workernode "
	}
	return errors.New(purpose + getNotReadyNodeNames(out) + " is not ready! 'oc get nodes' output contained NotReady. Output: " + out)
}

func getNotReadyNodeNames(out string) string {
	lines := strings.Split(out, "\n")
	var notReadyNodes []string
	for _, line := range lines {
		if strings.Contains(line, "NotReady") {
			s := strings.Fields(line)[0]
			notReadyNodes = append(notReadyNodes, s)
		}
	}
	return strings.Join(notReadyNodes, ", ")
}

func runOcGetNodes(buildNodes bool) (string, error) {
	buildNodes_grep_params := "-v"
	if buildNodes {
		buildNodes_grep_params = ""
	}
	out, err := exec.Command("bash", "-c", fmt.Sprintf("oc get nodes --show-labels | grep -v monitoring=false | grep %s purpose=buildnode | grep -v SchedulingDisabled", buildNodes_grep_params)).Output()
	if err != nil {
		msg := "Could not parse oc get nodes output: " + err.Error()
		log.Println(msg)
		return "", errors.New(msg)
	}
	return string(out), nil
}

func CheckDnsNslookupOnKubernetes() error {
	log.Println("Checking nslookup to kubernetes ip")

	cmd := exec.Command("nslookup", daemonDNSEndpoint+".", kubernetesIP)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		msg := "DNS resolution via nslookup & kubernetes failed." + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	stdOut := out.String()

	if strings.Contains(stdOut, "Server") && strings.Count(stdOut, "Address") >= 2 && strings.Contains(stdOut, "Name") {
		return nil
	} else {
		return errors.New("Problem with dns to kubernetes. nsLookup had wrong output")
	}
}

func CheckDnsServiceNode() error {
	log.Println("Checking dns to a openshift service")

	ips := getIpsForName(daemonDNSServiceA)

	if ips == nil {
		return errors.New("Failed to lookup ip on node (dnsmasq) for name " + daemonDNSServiceA)
	} else {
		return nil
	}
}

func CheckDnsInPod() error {
	log.Println("Checking dns to a openshift service inside a pod")

	ips := getIpsForName(daemonDNSPod)

	if ips == nil {
		return errors.New("Failed to lookup ip in pod for name " + daemonDNSPod)
	} else {
		return nil
	}
}

func CheckPodHttpAtoB() error {
	log.Println("Checking if http connection does not work if network not joined")

	// This should fail as we do not have access to this project
	if err := checkHttp("http://" + daemonDNSServiceB + ":8090/hello"); err == nil {
		errors.New("Pod A could access pod b. This should not be allowed!")
	}

	return nil
}

func CheckPodHttpAtoC(slow bool) error {
	log.Println("Checking if http connection does work with joined network")

	if err := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow)); err != nil {
		return errors.New("Pod A could access pod C. This should not work. Route/Router problem?")
	}

	return nil
}

func CheckHttpService(slow bool) error {
	errA := checkHttp("http://" + daemonDNSServiceA + ":8090/" + getEndpoint(slow))
	errB := checkHttp("http://" + daemonDNSServiceB + ":8090/" + getEndpoint(slow))
	errC := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow))

	if errA != nil || errB != nil || errC != nil {
		msg := "Could not reach one of the services (a/b/c)"
		log.Println(msg)
		return errors.New(msg)
	}

	return nil
}

func CheckHttpHaProxy(publicUrl string, slow bool) error {
	log.Println("Checking http via HA-Proxy")

	if err := checkHttp(publicUrl + ":80/" + getEndpoint(slow)); err != nil {
		return errors.New("Could not access pods via haproxy. Route/Router problem?")
	}

	return nil
}

func CheckRegistryHealth(ip string) error {
	log.Println("Checking registry health")

	if err := checkHttp("http://" + ip + ":5000/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Registry health check failed. %v", err2.Error())
		}
	}

	return nil
}

func CheckHawcularHealth(ip string) error {
	log.Println("Checking metrics health")

	if err := checkHttp("https://" + ip + ":443"); err != nil {
		return errors.New("Hawcular health check failed")
	}

	return nil
}

func CheckRouterHealth(ip string) error {
	log.Println("Checking router health", ip)

	if err := checkHttp("http://" + ip + ":1936/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Router health check failed for %v, %v", ip, err2.Error())
		}
	}

	return nil
}

func CheckLoggingRestartsCount() error {
	log.Println("Checking log-container restart count")

	out, err := exec.Command("bash", "-c", "oc get pods -n logging -o wide -l app=sematext-agent | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse logging container restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 2 {
				msg = "A logging-container has restart count bigger than 2 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func CheckRouterRestartCount() error {
	log.Println("Checking router restart count")

	out, err := exec.Command("bash", "-c", "oc get po -n default | grep router | grep -v deploy | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse router restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 5 {
				msg = "A Router has restart count bigger than 5 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if isOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckEtcdHealth(etcdIps string, etcdCertPath string) error {
	log.Println("Checking etcd health")

	var msg string
	isOk := true

	if len(etcdCertPath) > 0 {
		// Check etcd with custom certs path
		isOk = checkEtcdHealthWithCertPath(&msg, etcdCertPath, etcdIps)

		if !isOk {
			log.Println("etcd health check with custom cert path failed, trying with default")

			// Check etcd with default certs path
			isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
		}
	} else {
		// Check etcd with default certs path
		isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func checkEtcdHealthWithCertPath(msg *string, certPath string, etcdIps string) bool {
	cmd := exec.Command("etcdctl", "--peers", etcdIps, "--ca-file", certPath+"ca.crt",
		"--key-file", certPath+"peer.key", "--cert-file", certPath+"peer.crt", "cluster-health")

	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Println("error while running etcd health check", err)
		*msg = "etcd health check failed: " + err.Error()
		return false
	}

	stdOut := out.String()
	if strings.Contains(stdOut, "unhealthy") || strings.Contains(stdOut, "unreachable") {
		*msg += "Etcd health check was 'cluster unhealthy'"
		return false
	}

	return true
}

func CheckLimitsAndQuotas(allowedWithout int) error {
	log.Println("Checking limits & quotas")

	// Count projects
	projectCount, err := exec.Command("bash", "-c", "oc get projects  | wc -l").Output()
	if err != nil {
		msg := "Could not parse project count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count limits
	limitCount, err := exec.Command("bash", "-c", "oc get limits --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse limit count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count quotas
	quotaCount, err := exec.Command("bash", "-c", "oc get quota --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse quota count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Parse them
	pCount, err := strconv.Atoi(strings.TrimSpace(string(projectCount)))
	lCount, _ := strconv.Atoi(strings.TrimSpace(string(limitCount)))
	qCount, _ := strconv.Atoi(strings.TrimSpace(string(quotaCount)))

	log.Println("Parsed values (projects,limits,quotas)", pCount, lCount, qCount)

	if pCount-allowedWithout != lCount {
		return errors.New("There are some projects without limits")
	}
	if pCount-allowedWithout != qCount {
		return errors.New("There are some projects without quotas")
	}

	return nil
}
<commit_msg>Ignore exit 1 from grep when there are no buildnodes<commit_after>package checks

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func CheckMasterApis(urls string) error {
	log.Println("Checking master apis. At least one has to be up")

	urlArr := strings.Split(urls, ",")

	oneApiOk := false
	var msg string
	for _, u := range urlArr {
		if err := checkHttp(u); err == nil {
			oneApiOk = true
		} else {
			msg += u + " is not reachable. "
		}
	}

	if oneApiOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckOcGetNodes(buildNodes bool) error {
	log.Println("Checking oc get nodes output")

	var out string
	var err error
	for i := 0; i < 5; i++ {
		out, err = runOcGetNodes(buildNodes)
		if err != nil {
			return err
		}
		if strings.Contains(out, "NotReady") {
			// Wait a few seconds and see if still NotReady
			// to avoid wrong alerts
			time.Sleep(10 * time.Second)
			continue
		}
		return nil
	}
	var purpose string
	if buildNodes {
		purpose = "Buildnode "
	} else {
		purpose = "Workernode "
	}
	return errors.New(purpose + getNotReadyNodeNames(out) + " is not ready! 'oc get nodes' output contained NotReady. Output: " + out)
}

func getNotReadyNodeNames(out string) string {
	lines := strings.Split(out, "\n")
	var notReadyNodes []string
	for _, line := range lines {
		if strings.Contains(line, "NotReady") {
			s := strings.Fields(line)[0]
			notReadyNodes = append(notReadyNodes, s)
		}
	}
	return strings.Join(notReadyNodes, ", ")
}

func runOcGetNodes(buildNodes bool) (string, error) {
	buildNodes_grep_params := "-v"
	if buildNodes {
		buildNodes_grep_params = ""
	}
	out, err := exec.Command("bash", "-c", fmt.Sprintf("oc get nodes --show-labels | grep -v monitoring=false | grep -v SchedulingDisabled | grep %s purpose=buildnode || test $? -eq 1", buildNodes_grep_params)).Output()
	if err != nil {
		msg := "Could not parse oc get nodes output: " + err.Error()
		log.Println(msg)
		return "", errors.New(msg)
	}
	return string(out), nil
}

func CheckDnsNslookupOnKubernetes() error {
	log.Println("Checking nslookup to kubernetes ip")

	cmd := exec.Command("nslookup", daemonDNSEndpoint+".", kubernetesIP)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		msg := "DNS resolution via nslookup & kubernetes failed." + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	stdOut := out.String()

	if strings.Contains(stdOut, "Server") && strings.Count(stdOut, "Address") >= 2 && strings.Contains(stdOut, "Name") {
		return nil
	} else {
		return errors.New("Problem with dns to kubernetes. nsLookup had wrong output")
	}
}

func CheckDnsServiceNode() error {
	log.Println("Checking dns to a openshift service")

	ips := getIpsForName(daemonDNSServiceA)

	if ips == nil {
		return errors.New("Failed to lookup ip on node (dnsmasq) for name " + daemonDNSServiceA)
	} else {
		return nil
	}
}

func CheckDnsInPod() error {
	log.Println("Checking dns to a openshift service inside a pod")

	ips := getIpsForName(daemonDNSPod)

	if ips == nil {
		return errors.New("Failed to lookup ip in pod for name " + daemonDNSPod)
	} else {
		return nil
	}
}

func CheckPodHttpAtoB() error {
	log.Println("Checking if http connection does not work if network not joined")

	// This should fail as we do not have access to this project
	if err := checkHttp("http://" + daemonDNSServiceB + ":8090/hello"); err == nil {
		errors.New("Pod A could access pod b. This should not be allowed!")
	}

	return nil
}

func CheckPodHttpAtoC(slow bool) error {
	log.Println("Checking if http connection does work with joined network")

	if err := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow)); err != nil {
		return errors.New("Pod A could access pod C. This should not work. Route/Router problem?")
	}

	return nil
}

func CheckHttpService(slow bool) error {
	errA := checkHttp("http://" + daemonDNSServiceA + ":8090/" + getEndpoint(slow))
	errB := checkHttp("http://" + daemonDNSServiceB + ":8090/" + getEndpoint(slow))
	errC := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow))

	if errA != nil || errB != nil || errC != nil {
		msg := "Could not reach one of the services (a/b/c)"
		log.Println(msg)
		return errors.New(msg)
	}

	return nil
}

func CheckHttpHaProxy(publicUrl string, slow bool) error {
	log.Println("Checking http via HA-Proxy")

	if err := checkHttp(publicUrl + ":80/" + getEndpoint(slow)); err != nil {
		return errors.New("Could not access pods via haproxy. Route/Router problem?")
	}

	return nil
}

func CheckRegistryHealth(ip string) error {
	log.Println("Checking registry health")

	if err := checkHttp("http://" + ip + ":5000/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Registry health check failed. %v", err2.Error())
		}
	}

	return nil
}

func CheckHawcularHealth(ip string) error {
	log.Println("Checking metrics health")

	if err := checkHttp("https://" + ip + ":443"); err != nil {
		return errors.New("Hawcular health check failed")
	}

	return nil
}

func CheckRouterHealth(ip string) error {
	log.Println("Checking router health", ip)

	if err := checkHttp("http://" + ip + ":1936/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Router health check failed for %v, %v", ip, err2.Error())
		}
	}

	return nil
}

func CheckLoggingRestartsCount() error {
	log.Println("Checking log-container restart count")

	out, err := exec.Command("bash", "-c", "oc get pods -n logging -o wide -l app=sematext-agent | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse logging container restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 2 {
				msg = "A logging-container has restart count bigger than 2 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func CheckRouterRestartCount() error {
	log.Println("Checking router restart count")

	out, err := exec.Command("bash", "-c", "oc get po -n default | grep router | grep -v deploy | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse router restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 5 {
				msg = "A Router has restart count bigger than 5 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if isOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckEtcdHealth(etcdIps string, etcdCertPath string) error {
	log.Println("Checking etcd health")

	var msg string
	isOk := true

	if len(etcdCertPath) > 0 {
		// Check etcd with custom certs path
		isOk = checkEtcdHealthWithCertPath(&msg, etcdCertPath, etcdIps)

		if !isOk {
			log.Println("etcd health check with custom cert path failed, trying with default")

			// Check etcd with default certs path
			isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
		}
	} else {
		// Check etcd with default certs path
		isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func checkEtcdHealthWithCertPath(msg *string, certPath string, etcdIps string) bool {
	cmd := exec.Command("etcdctl", "--peers", etcdIps, "--ca-file", certPath+"ca.crt",
		"--key-file", certPath+"peer.key", "--cert-file", certPath+"peer.crt", "cluster-health")

	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Println("error while running etcd health check", err)
		*msg = "etcd health check failed: " + err.Error()
		return false
	}

	stdOut := out.String()
	if strings.Contains(stdOut, "unhealthy") || strings.Contains(stdOut, "unreachable") {
		*msg += "Etcd health check was 'cluster unhealthy'"
		return false
	}

	return true
}

func CheckLimitsAndQuotas(allowedWithout int) error {
	log.Println("Checking limits & quotas")

	// Count projects
	projectCount, err := exec.Command("bash", "-c", "oc get projects  | wc -l").Output()
	if err != nil {
		msg := "Could not parse project count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count limits
	limitCount, err := exec.Command("bash", "-c", "oc get limits --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse limit count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count quotas
	quotaCount, err := exec.Command("bash", "-c", "oc get quota --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse quota count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Parse them
	pCount, err := strconv.Atoi(strings.TrimSpace(string(projectCount)))
	lCount, _ := strconv.Atoi(strings.TrimSpace(string(limitCount)))
	qCount, _ := strconv.Atoi(strings.TrimSpace(string(quotaCount)))

	log.Println("Parsed values (projects,limits,quotas)", pCount, lCount, qCount)

	if pCount-allowedWithout != lCount {
		return errors.New("There are some projects without limits")
	}
	if pCount-allowedWithout != qCount {
		return errors.New("There are some projects without quotas")
	}

	return nil
}
<|endoftext|>