github.com/IBM-Blockchain/fabric-operator@v1.0.4/integration/integration.go (about)

     1  /*
     2   * Copyright contributors to the Hyperledger Fabric Operator project
     3   *
     4   * SPDX-License-Identifier: Apache-2.0
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at:
     9   *
    10   * 	  http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package integration
    20  
    21  import (
    22  	"context"
    23  	"encoding/base64"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"path/filepath"
    30  	"time"
    31  
    32  	config "github.com/IBM-Blockchain/fabric-operator/operatorconfig"
    33  	ibpclient "github.com/IBM-Blockchain/fabric-operator/pkg/client"
    34  	"github.com/IBM-Blockchain/fabric-operator/pkg/command"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    36  
    37  	corev1 "k8s.io/api/core/v1"
    38  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	"k8s.io/apimachinery/pkg/types"
    41  	"k8s.io/apimachinery/pkg/watch"
    42  	"k8s.io/client-go/kubernetes"
    43  	"k8s.io/client-go/rest"
    44  	"k8s.io/client-go/tools/clientcmd"
    45  
    46  	"sigs.k8s.io/controller-runtime/pkg/client"
    47  	"sigs.k8s.io/yaml"
    48  )
    49  
    50  const (
    51  	TestAutomation1IngressDomain = "vcap.me"
    52  )
    53  
    54  var (
    55  	defaultConfigs = "../../defaultconfig"
    56  	defaultDef     = "../../definitions"
    57  
    58  	operatorCfg        *config.Config
    59  	operatorContext    context.Context
    60  	operatorCancelFunc context.CancelFunc
    61  )
    62  
    63  type Config struct {
    64  	OperatorServiceAccount string
    65  	OperatorRole           string
    66  	OperatorRoleBinding    string
    67  	OperatorDeployment     string
    68  	OrdererSecret          string
    69  	PeerSecret             string
    70  	ConsoleTLSSecret       string
    71  }
    72  
    73  func SetupSignalHandler() context.Context {
    74  	operatorContext, operatorCancelFunc = context.WithCancel(context.Background())
    75  	return operatorContext
    76  }
    77  
    78  func Setup(ginkgoWriter io.Writer, cfg *Config, suffix, pathToDefaultDir string) (string, *kubernetes.Clientset, *ibpclient.IBPClient, error) {
    79  	// Set up a signal handler Context to allow a graceful shutdown of the operator.
    80  	SetupSignalHandler()
    81  
    82  	var err error
    83  
    84  	if pathToDefaultDir != "" {
    85  		defaultConfigs = filepath.Join(pathToDefaultDir, "defaultconfig")
    86  		defaultDef = filepath.Join(pathToDefaultDir, "definitions")
    87  	}
    88  	operatorCfg = getOperatorCfg()
    89  
    90  	wd, err := os.Getwd()
    91  	if err != nil {
    92  		return "", nil, nil, err
    93  	}
    94  	fmt.Fprintf(ginkgoWriter, "Working directory: %s\n", wd)
    95  
    96  	namespace := os.Getenv("OPERATOR_NAMESPACE")
    97  	if namespace == "" {
    98  		namespace = "operatortest"
    99  	}
   100  	if suffix != "" {
   101  		namespace = fmt.Sprintf("%s%s", namespace, suffix)
   102  	}
   103  
   104  	fmt.Fprintf(ginkgoWriter, "Namespace set to '%s'\n", namespace)
   105  
   106  	setupConfig, err := GetConfig()
   107  	if err != nil {
   108  		return "", nil, nil, err
   109  	}
   110  
   111  	fmt.Fprintf(ginkgoWriter, "Setup config %+v\n", setupConfig)
   112  
   113  	kclient, ibpCRClient, err := InitClients(setupConfig)
   114  	if err != nil {
   115  		return "", nil, nil, err
   116  	}
   117  
   118  	err = os.Setenv("CLUSTERTYPE", "K8S")
   119  	if err != nil {
   120  		return "", nil, nil, err
   121  	}
   122  	err = os.Setenv("WATCH_NAMESPACE", namespace)
   123  	if err != nil {
   124  		return "", nil, nil, err
   125  	}
   126  
   127  	err = CleanupNamespace(ginkgoWriter, kclient, namespace)
   128  	if err != nil {
   129  		return "", nil, nil, err
   130  	}
   131  
   132  	ns := &corev1.Namespace{
   133  		ObjectMeta: metav1.ObjectMeta{
   134  			Name: namespace,
   135  		},
   136  	}
   137  
   138  	_, err = kclient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
   139  	if err != nil {
   140  		return "", nil, nil, err
   141  	}
   142  	fmt.Fprintf(ginkgoWriter, "Namespace '%s' created\n", namespace)
   143  
   144  	// Set up an image pull secret if a docker config json has been specified
   145  	if setupConfig.DockerConfigJson != "" {
   146  		fmt.Fprintf(ginkgoWriter, "Creating 'regcred' image pull secret for DOCKERCONFIGJSON")
   147  
   148  		err = CreatePullSecret(kclient, "regcred", namespace, setupConfig.DockerConfigJson)
   149  		if err != nil {
   150  			return "", nil, nil, err
   151  		}
   152  	}
   153  
   154  	err = DeployOperator(ginkgoWriter, operatorContext, cfg, kclient, namespace)
   155  	if err != nil {
   156  		return "", nil, nil, err
   157  	}
   158  
   159  	return namespace, kclient, ibpCRClient, nil
   160  }
   161  
   162  func deleteNamespace(ginkgoWriter io.Writer, kclient *kubernetes.Clientset, namespace string) error {
   163  	var zero int64 = 0
   164  	policy := metav1.DeletePropagationForeground
   165  	deleteOptions := metav1.DeleteOptions{
   166  		GracePeriodSeconds: &zero,
   167  		PropagationPolicy:  &policy,
   168  	}
   169  	fmt.Fprintf(ginkgoWriter, "Deleting namespace '%s' with options %s\n", namespace, &deleteOptions)
   170  	return kclient.CoreV1().Namespaces().Delete(context.TODO(), namespace, deleteOptions)
   171  }
   172  
   173  type SetupConfig struct {
   174  	DockerConfigJson string
   175  	KubeConfig       string
   176  }
   177  
   178  func GetConfig() (*SetupConfig, error) {
   179  	return &SetupConfig{
   180  		DockerConfigJson: os.Getenv("DOCKERCONFIGJSON"),
   181  		KubeConfig:       os.Getenv("KUBECONFIG_PATH"),
   182  	}, nil
   183  }
   184  
   185  func InitClients(setupConfig *SetupConfig) (*kubernetes.Clientset, *ibpclient.IBPClient, error) {
   186  	config, err := rest.InClusterConfig()
   187  	if err != nil {
   188  		// Not running in a cluster, get kube config from KUBECONFIG env var
   189  		kubeConfigPath := setupConfig.KubeConfig
   190  		config, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath)
   191  		if err != nil {
   192  			fmt.Println("error:", err)
   193  			return nil, nil, err
   194  		}
   195  	}
   196  
   197  	kclient, err := kubernetes.NewForConfig(config)
   198  	if err != nil {
   199  		return nil, nil, err
   200  	}
   201  
   202  	client, err := ibpclient.New(config)
   203  	if err != nil {
   204  		return nil, nil, err
   205  	}
   206  
   207  	return kclient, client, nil
   208  }
   209  
   210  func DeployOperator(ginkgoWriter io.Writer, signal context.Context, cfg *Config, kclient *kubernetes.Clientset, namespace string) error {
   211  	fmt.Fprintf(ginkgoWriter, "Deploying operator in namespace '%s'\n", namespace)
   212  	// Create service account for operator
   213  	sa, err := util.GetServiceAccountFromFile(cfg.OperatorServiceAccount)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	_, err = kclient.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), sa, metav1.CreateOptions{})
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	// Create cluster role with permissions required by operator
   223  	role, err := util.GetClusterRoleFromFile(cfg.OperatorRole)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	_, err = kclient.RbacV1().ClusterRoles().Create(context.TODO(), role, metav1.CreateOptions{})
   228  	if err != nil {
   229  		if !k8serrors.IsAlreadyExists(err) {
   230  			return err
   231  		}
   232  	}
   233  
   234  	// Create role binding for operator's cluster role
   235  	roleBinding, err := util.GetClusterRoleBindingFromFile(cfg.OperatorRoleBinding)
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	roleBinding.Name = fmt.Sprintf("operator-%s", namespace)
   241  	roleBinding.Subjects[0].Namespace = namespace
   242  
   243  	_, err = kclient.RbacV1().ClusterRoleBindings().Create(context.TODO(), roleBinding, metav1.CreateOptions{})
   244  	if err != nil {
   245  		if !k8serrors.IsAlreadyExists(err) {
   246  			return err
   247  		}
   248  	}
   249  
   250  	// Create resource secrets
   251  	ordererSecret, err := util.GetSecretFromFile(cfg.OrdererSecret)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	_, err = kclient.CoreV1().Secrets(namespace).Create(context.TODO(), ordererSecret, metav1.CreateOptions{})
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	// Peer 1 secret
   261  	peerSecret, err := util.GetSecretFromFile(cfg.PeerSecret)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	_, err = kclient.CoreV1().Secrets(namespace).Create(context.TODO(), peerSecret, metav1.CreateOptions{})
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	// Peer 2 secret
   271  	peerSecret.Name = "ibppeer2-secret"
   272  	_, err = kclient.CoreV1().Secrets(namespace).Create(context.TODO(), peerSecret, metav1.CreateOptions{})
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	consoleTLSSecret, err := util.GetSecretFromFile(cfg.ConsoleTLSSecret)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	_, err = kclient.CoreV1().Secrets(namespace).Create(context.TODO(), consoleTLSSecret, metav1.CreateOptions{})
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	err = command.OperatorWithSignal(operatorCfg, signal, false, true)
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	fmt.Fprintf(ginkgoWriter, "Done deploying operator in namespace '%s'\n", namespace)
   292  
   293  	return nil
   294  }
   295  
   296  func Cleanup(ginkgoWriter io.Writer, kclient *kubernetes.Clientset, namespace string) error {
   297  
   298  	// The operator must halt before the namespace can be deleted in the foreground.
   299  	ShutdownOperator(ginkgoWriter)
   300  
   301  	err := CleanupNamespace(ginkgoWriter, kclient, namespace)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  func ShutdownOperator(ginkgoWriter io.Writer) {
   310  	if operatorContext != nil {
   311  		fmt.Fprintf(ginkgoWriter, "Stopping operator\n")
   312  		operatorContext.Done()
   313  		operatorCancelFunc()
   314  	}
   315  }
   316  
   317  func CleanupNamespace(ginkgoWriter io.Writer, kclient *kubernetes.Clientset, namespace string) error {
   318  	err := deleteNamespace(ginkgoWriter, kclient, namespace)
   319  	if err != nil {
   320  		if k8serrors.IsNotFound(err) {
   321  			return nil // Namespace does not exist, don't need to wait for deletion to complete
   322  		}
   323  	}
   324  
   325  	opts := metav1.ListOptions{}
   326  	watchNamespace, err := kclient.CoreV1().Namespaces().Watch(context.TODO(), opts)
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	fmt.Fprintf(ginkgoWriter, "Waiting for namespace deletion\n")
   332  	for {
   333  		resultChan := <-watchNamespace.ResultChan()
   334  		if resultChan.Type == watch.Deleted {
   335  			ns := resultChan.Object.(*corev1.Namespace)
   336  			if ns.Name == namespace {
   337  				break
   338  			}
   339  		}
   340  	}
   341  	fmt.Fprintf(ginkgoWriter, "Done deleting namespace '%s'\n", namespace)
   342  	return nil
   343  }
   344  
   345  func DeleteNamespace(ginkgoWriter io.Writer, kclient *kubernetes.Clientset, namespace string) error {
   346  	err := deleteNamespace(ginkgoWriter, kclient, namespace)
   347  	if err != nil {
   348  		if k8serrors.IsNotFound(err) {
   349  			return nil // Namespace does not exist, don't need to wait for deletion to complete
   350  		}
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  func CreatePullSecret(kclient *kubernetes.Clientset, name string, namespace string, dockerConfigJson string) error {
   357  	b, err := base64.StdEncoding.DecodeString(dockerConfigJson)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	pullSecret := &corev1.Secret{
   363  		ObjectMeta: metav1.ObjectMeta{
   364  			Name: name,
   365  		},
   366  		Data: map[string][]byte{
   367  			".dockerconfigjson": b,
   368  		},
   369  		Type: corev1.SecretTypeDockerConfigJson,
   370  	}
   371  
   372  	_, err = kclient.CoreV1().Secrets(namespace).Create(context.TODO(), pullSecret, metav1.CreateOptions{})
   373  	if err != nil {
   374  		return err
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  func ClearOperatorConfig(kclient *kubernetes.Clientset, namespace string) error {
   381  	err := kclient.CoreV1().ConfigMaps(namespace).Delete(context.TODO(), "operator-config", *metav1.NewDeleteOptions(0))
   382  	if !k8serrors.IsNotFound(err) {
   383  		return err
   384  	}
   385  	return nil
   386  }
   387  
   388  func ResilientPatch(kclient *ibpclient.IBPClient, name, namespace, kind string, retry int, into client.Object, patch func(i client.Object)) error {
   389  
   390  	for i := 0; i < retry; i++ {
   391  		err := resilientPatch(kclient, name, namespace, kind, into, patch)
   392  		if err != nil {
   393  			if i == retry {
   394  				return err
   395  			}
   396  			if k8serrors.IsConflict(err) {
   397  				time.Sleep(2 * time.Second)
   398  				continue
   399  			}
   400  			return err
   401  		}
   402  	}
   403  
   404  	return nil
   405  }
   406  
   407  func resilientPatch(kclient *ibpclient.IBPClient, name, namespace, kind string, into client.Object, patch func(i client.Object)) error {
   408  	result := kclient.Get().Namespace(namespace).Resource(kind).Name(name).Do(context.TODO())
   409  	if result.Error() != nil {
   410  		return result.Error()
   411  	}
   412  
   413  	err := result.Into(into)
   414  	if err != nil {
   415  		return err
   416  	}
   417  
   418  	patch(into)
   419  	bytes, err := json.Marshal(into)
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	result = kclient.Patch(types.MergePatchType).Namespace(namespace).Resource(kind).Name(name).Body(bytes).Do(context.TODO())
   425  	if result.Error() != nil {
   426  		return result.Error()
   427  	}
   428  
   429  	return nil
   430  }
   431  
   432  func CreateOperatorConfigMapFromFile(namespace string, kclient *kubernetes.Clientset, file string) error {
   433  	configData, err := ioutil.ReadFile(filepath.Clean(file))
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	cm := &corev1.ConfigMap{
   439  		ObjectMeta: metav1.ObjectMeta{
   440  			Name:      "operator",
   441  			Namespace: namespace,
   442  		},
   443  		Data: map[string]string{
   444  			"config.yaml": string(configData),
   445  		},
   446  	}
   447  
   448  	_, err = kclient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{})
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	return nil
   454  }
   455  
   456  // CreateConfigMap creates config map
   457  func CreateConfigMap(kclient *kubernetes.Clientset, config interface{}, key, name, namespace string) error {
   458  	configBytes, err := yaml.Marshal(config)
   459  	if err != nil {
   460  		return err
   461  	}
   462  
   463  	cm := &corev1.ConfigMap{
   464  		ObjectMeta: metav1.ObjectMeta{
   465  			Name:      name,
   466  			Namespace: namespace,
   467  		},
   468  		Data: map[string]string{
   469  			key: string(configBytes),
   470  		},
   471  	}
   472  
   473  	_, err = kclient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{})
   474  	if err != nil {
   475  		return err
   476  	}
   477  
   478  	return nil
   479  }
   480  
   481  func OperatorCfg() *config.Config {
   482  	return getOperatorCfg()
   483  }
   484  
   485  func getOperatorCfg() *config.Config {
   486  	defaultPeerDef := filepath.Join(defaultDef, "peer")
   487  	defaultCADef := filepath.Join(defaultDef, "ca")
   488  	defaultOrdererDef := filepath.Join(defaultDef, "orderer")
   489  	defaultConsoleDef := filepath.Join(defaultDef, "console")
   490  	return GetOperatorConfig(defaultConfigs, defaultCADef, defaultPeerDef, defaultOrdererDef, defaultConsoleDef)
   491  }