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 }