github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibporderer/ibporderer_controller.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 ibporderer 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "reflect" 26 "strings" 27 "sync" 28 "time" 29 30 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 31 commoncontroller "github.com/IBM-Blockchain/fabric-operator/controllers/common" 32 config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" 33 "github.com/IBM-Blockchain/fabric-operator/pkg/global" 34 orderer "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/orderer/config/v1" 35 k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 36 "github.com/IBM-Blockchain/fabric-operator/pkg/offering" 37 baseorderer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/orderer" 38 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 39 k8sorderer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/k8s/orderer" 40 openshiftorderer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/openshift/orderer" 41 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 42 "github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts" 43 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 44 "github.com/IBM-Blockchain/fabric-operator/version" 45 "github.com/pkg/errors" 46 yaml "sigs.k8s.io/yaml" 47 48 appsv1 "k8s.io/api/apps/v1" 49 corev1 "k8s.io/api/core/v1" 50 k8serrors "k8s.io/apimachinery/pkg/api/errors" 51 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 52 "k8s.io/apimachinery/pkg/labels" 53 "k8s.io/apimachinery/pkg/runtime" 54 "k8s.io/apimachinery/pkg/types" 55 ctrl "sigs.k8s.io/controller-runtime" 56 "sigs.k8s.io/controller-runtime/pkg/client" 57 "sigs.k8s.io/controller-runtime/pkg/controller" 58 "sigs.k8s.io/controller-runtime/pkg/event" 59 "sigs.k8s.io/controller-runtime/pkg/handler" 60 logf "sigs.k8s.io/controller-runtime/pkg/log" 61 "sigs.k8s.io/controller-runtime/pkg/manager" 62 "sigs.k8s.io/controller-runtime/pkg/predicate" 63 "sigs.k8s.io/controller-runtime/pkg/reconcile" 64 "sigs.k8s.io/controller-runtime/pkg/source" 65 ) 66 67 const ( 68 KIND = "IBPOrderer" 69 ) 70 71 var log = logf.Log.WithName("controller_ibporderer") 72 73 // Add creates a new IBPOrderer Controller and adds it to the Manager. The Manager will set fields on the Controller 74 // and Start it when the Manager is Started. 75 func Add(mgr manager.Manager, config *config.Config) error { 76 r, err := newReconciler(mgr, config) 77 if err != nil { 78 return err 79 } 80 return add(mgr, r) 81 } 82 83 // newReconciler returns a new reconcile.Reconciler 84 func newReconciler(mgr manager.Manager, cfg *config.Config) (*ReconcileIBPOrderer, error) { 85 client := k8sclient.New(mgr.GetClient(), &global.ConfigSetter{Config: cfg.Operator.Globals}) 86 scheme := mgr.GetScheme() 87 88 ibporderer := &ReconcileIBPOrderer{ 89 client: client, 90 scheme: scheme, 91 Config: cfg, 92 update: map[string][]Update{}, 93 mutex: &sync.Mutex{}, 94 RestartService: staggerrestarts.New(client, cfg.Operator.Restart.Timeout.Get()), 95 } 96 97 switch cfg.Offering { 98 case offering.K8S: 99 ibporderer.Offering = k8sorderer.New(client, scheme, cfg) 100 case offering.OPENSHIFT: 101 ibporderer.Offering = openshiftorderer.New(client, scheme, cfg) 102 } 103 104 return ibporderer, nil 105 } 106 107 // add adds a new Controller to mgr with r as the reconcile.Reconciler 108 func add(mgr manager.Manager, r *ReconcileIBPOrderer) error { 109 // Create a new controller 110 predicateFuncs := predicate.Funcs{ 111 CreateFunc: r.CreateFunc, 112 UpdateFunc: r.UpdateFunc, 113 DeleteFunc: r.DeleteFunc, 114 } 115 116 c, err := controller.New("ibporderer-controller", mgr, controller.Options{Reconciler: r}) 117 if err != nil { 118 return err 119 } 120 121 // Watch for changes to primary resource IBPOrderer 122 err = c.Watch(&source.Kind{Type: ¤t.IBPOrderer{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 123 if err != nil { 124 return err 125 } 126 127 // Watch for changes to config maps (Create and Update funcs handle only watching for restart config map) 128 err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 129 if err != nil { 130 return err 131 } 132 133 // TODO(user): Modify this to be the types you create that are owned by the primary resource 134 // Watch for changes to secondary resource Pods and requeue the owner IBPOrderer 135 err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ 136 IsController: true, 137 OwnerType: ¤t.IBPOrderer{}, 138 }) 139 if err != nil { 140 return err 141 } 142 143 // Watch for changes to tertiary resource Secrets and requeue the owner IBPOrderer 144 err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ 145 IsController: true, 146 OwnerType: ¤t.IBPOrderer{}, 147 }, predicateFuncs) 148 if err != nil { 149 return err 150 } 151 152 return nil 153 } 154 155 var _ reconcile.Reconciler = &ReconcileIBPOrderer{} 156 157 //go:generate counterfeiter -o mocks/ordererreconcile.go -fake-name OrdererReconcile . ordererReconcile 158 159 type ordererReconcile interface { 160 Reconcile(*current.IBPOrderer, baseorderer.Update) (common.Result, error) 161 } 162 163 // ReconcileIBPOrderer reconciles a IBPOrderer object 164 type ReconcileIBPOrderer struct { 165 // This client, initialized using mgr.Client() above, is a split client 166 // that reads objects from the cache and writes to the apiserver 167 client k8sclient.Client 168 scheme *runtime.Scheme 169 170 Offering ordererReconcile 171 Config *config.Config 172 RestartService *staggerrestarts.StaggerRestartsService 173 174 update map[string][]Update 175 mutex *sync.Mutex 176 } 177 178 // Reconcile reads that state of the cluster for a IBPOrderer object and makes changes based on the state read 179 // and what is in the IBPOrderer.Spec 180 // Note: 181 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 182 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 183 func (r *ReconcileIBPOrderer) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 184 var err error 185 186 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 187 188 // If orderer-restart-config configmap is the object being reconciled, reconcile the 189 // restart configmap. 190 if request.Name == "orderer-restart-config" { 191 requeue, err := r.ReconcileRestart(request.Namespace) 192 // Error reconciling restart - requeue the request. 193 if err != nil { 194 return reconcile.Result{}, err 195 } 196 // Restart reconciled, requeue request if required. 197 return reconcile.Result{ 198 Requeue: requeue, 199 }, nil 200 } 201 202 reqLogger.Info("Reconciling IBPOrderer") 203 204 // Fetch the IBPOrderer instance 205 instance := ¤t.IBPOrderer{} 206 err = r.client.Get(context.TODO(), request.NamespacedName, instance) 207 if err != nil { 208 if k8serrors.IsNotFound(err) { 209 // Request object not found, could have been deleted after reconcile request. 210 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 211 // Return and don't requeue 212 return reconcile.Result{}, nil 213 } 214 // Error reading the object - requeue the request. 215 return reconcile.Result{}, operatorerrors.IsBreakingError(err, "failed to reconcile restart", log) 216 } 217 218 var maxNameLength *int 219 if instance.Spec.ConfigOverride != nil { 220 override := &orderer.OrdererOverrides{} 221 err := json.Unmarshal(instance.Spec.ConfigOverride.Raw, override) 222 if err != nil { 223 return reconcile.Result{}, err 224 } 225 maxNameLength = override.MaxNameLength 226 } 227 228 err = util.ValidationChecks(instance.TypeMeta, instance.ObjectMeta, "IBPOrderer", maxNameLength) 229 if err != nil { 230 return reconcile.Result{}, err 231 } 232 233 if instance.Spec.NodeNumber == nil { 234 // If version is nil, then this is a v210 instance and the reconcile 235 // loop needs to be triggered to allow for instance migration 236 if instance.Status.Version != "" { 237 if instance.Status.Type == current.Deployed || instance.Status.Type == current.Warning { 238 // This is cluster's update, we don't want to reconcile. 239 // It should only be status update 240 log.Info(fmt.Sprintf("Update detected on %s cluster spec '%s', not supported", instance.Status.Type, instance.GetName())) 241 return reconcile.Result{}, nil 242 } 243 } 244 } 245 246 reqLogger.Info(fmt.Sprintf("Current update stack to process: %+v", GetUpdateStack(r.update))) 247 248 update := r.GetUpdateStatus(instance) 249 reqLogger.Info(fmt.Sprintf("Reconciling IBPOrderer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 250 251 result, err := r.Offering.Reconcile(instance, r.PopUpdate(instance.Name)) 252 setStatusErr := r.SetStatus(instance, &result, err) 253 if setStatusErr != nil { 254 return reconcile.Result{}, operatorerrors.IsBreakingError(setStatusErr, "failed to update status", log) 255 } 256 257 if err != nil { 258 return reconcile.Result{}, operatorerrors.IsBreakingError(errors.Wrapf(err, "Orderer instance '%s' encountered error", instance.GetName()), "stopping reconcile loop", log) 259 } 260 261 if result.Requeue { 262 r.PushUpdate(instance.Name, *update) 263 } 264 265 reqLogger.Info(fmt.Sprintf("Finished reconciling IBPOrderer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 266 267 // If the stack still has items that require processing, keep reconciling 268 // until the stack has been cleared 269 _, found := r.update[instance.GetName()] 270 if found { 271 if len(r.update[instance.GetName()]) > 0 { 272 return reconcile.Result{ 273 Requeue: true, 274 }, nil 275 } 276 } 277 278 return result.Result, nil 279 } 280 281 func (r *ReconcileIBPOrderer) SetStatus(instance *current.IBPOrderer, result *common.Result, reconcileErr error) error { 282 err := r.SaveSpecState(instance) 283 if err != nil { 284 return errors.Wrap(err, "failed to save spec state") 285 } 286 287 // Hierachy of setting status on orderer node instance 288 // 1. If error has occurred update status and return 289 // 2. If error has not occurred, get list of pods and determine 290 // if pods are all running or still waiting to start. If all pods 291 // are running mark status as Deployed otherwise mark status as 292 // Deploying, but dont update status yet 293 // 3. Check to see if a custom status has been passed. If so, 294 // set that status but don't update. However, if OverrideUpdateStatus 295 // flag is set to true update the status and return 296 // 4. If OverrideUpdateStatus was not set in step 3, determine if genesis 297 // secret exists for the instance. If genesis secret does not exit update 298 // the status to precreate and return 299 300 // Need to get to ensure we are working with the latest state of the instance 301 err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, instance) 302 if err != nil { 303 return err 304 } 305 306 status := instance.Status.CRStatus 307 308 if reconcileErr != nil { 309 status.Type = current.Error 310 status.Status = current.True 311 status.Reason = "errorOccurredDuringReconcile" 312 status.Message = reconcileErr.Error() 313 status.LastHeartbeatTime = time.Now().String() 314 status.ErrorCode = operatorerrors.GetErrorCode(reconcileErr) 315 316 instance.Status = current.IBPOrdererStatus{ 317 CRStatus: status, 318 } 319 320 log.Info(fmt.Sprintf("Updating status of IBPOrderer custom resource (%s) to %s phase", instance.GetName(), instance.Status.Type)) 321 err := r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 322 Resilient: &k8sclient.ResilientPatch{ 323 Retry: 2, 324 Into: ¤t.IBPOrderer{}, 325 Strategy: client.MergeFrom, 326 }, 327 }) 328 if err != nil { 329 return err 330 } 331 332 return nil 333 } 334 335 status.Versions.Reconciled = instance.Spec.FabricVersion 336 337 // If this is a parent (cluster spec), then ignore setting status. Status should 338 // be set by the child nodes only, and child nodes should update the status of parent 339 // according to the statuses of the child nodes. This needs to stay after the check for 340 // reconcile error otherwise the CR won't get updated with error on parent CR if there 341 // are validation errors on the spec. 342 if instance.Spec.NodeNumber == nil { 343 return nil 344 } 345 346 podStatus, err := r.GetPodStatus(instance) 347 if err != nil { 348 return err 349 } 350 351 numberOfPodsRunning := 0 352 for _, status := range podStatus { 353 if status.Phase == corev1.PodRunning { 354 numberOfPodsRunning++ 355 } 356 } 357 358 // if numberOfPodsRunning == len(podStatus) && len(podStatus) > 0 { 359 if len(podStatus) > 0 { 360 if len(podStatus) == numberOfPodsRunning { 361 status.Type = current.Deployed 362 status.Status = current.True 363 status.Reason = "allPodsRunning" 364 status.Message = "allPodsRunning" 365 } else { 366 status.Type = current.Deploying 367 status.Status = current.True 368 status.Reason = "waitingForPods" 369 status.Message = "waitingForPods" 370 } 371 } 372 373 // Check if reconcile loop returned an updated status that differs from exisiting status. 374 // If so, set status to the reconcile status. 375 if result != nil { 376 reconcileStatus := result.Status 377 if reconcileStatus != nil { 378 if instance.Status.Type != reconcileStatus.Type || instance.Status.Reason != reconcileStatus.Reason || instance.Status.Message != reconcileStatus.Message { 379 status.Type = reconcileStatus.Type 380 status.Status = current.True 381 status.Reason = reconcileStatus.Reason 382 status.Message = reconcileStatus.Message 383 status.LastHeartbeatTime = time.Now().String() 384 385 if result.OverrideUpdateStatus { 386 instance.Status = current.IBPOrdererStatus{ 387 CRStatus: status, 388 } 389 390 log.Info(fmt.Sprintf("Updating status returned by reconcile loop of IBPOrderer custom resource (%s) to %s phase", instance.GetName(), instance.Status.Type)) 391 err := r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 392 Resilient: &k8sclient.ResilientPatch{ 393 Retry: 2, 394 Into: ¤t.IBPOrderer{}, 395 Strategy: client.MergeFrom, 396 }, 397 }) 398 if err != nil { 399 return err 400 } 401 402 return nil 403 } 404 } else { 405 // If the reconcile loop returned an updated status that is the same as the current instance status, then no status update required. 406 // NOTE: This will only occur once the instance has hit Deployed state for the first time and would only switch between Deployed and 407 // Warning states. 408 log.Info(fmt.Sprintf("Reconcile loop returned a status that is the same as %s's current status (%s), not updating status", reconcileStatus.Type, instance.Name)) 409 return nil 410 } 411 } 412 413 // There are cases we want to return before checking for genesis secrets, such as updating the spec with default values 414 // during prereconcile checks 415 if result.OverrideUpdateStatus { 416 return nil 417 } 418 } 419 420 precreated := false 421 if instance.Spec.IsUsingChannelLess() { 422 log.Info(fmt.Sprintf("IBPOrderer custom resource (%s) is using channel less mode", instance.GetName())) 423 precreated = false 424 } else { 425 err = r.GetGenesisSecret(instance) 426 if err != nil { 427 log.Info(fmt.Sprintf("IBPOrderer custom resource (%s) pods are waiting for genesis block, setting status to precreate", instance.GetName())) 428 precreated = true 429 } 430 } 431 432 if precreated { 433 status.Type = current.Precreated 434 status.Status = current.True 435 status.Reason = "waiting for genesis block" 436 status.Message = "waiting for genesis block" 437 } 438 439 // Only update status if status is different from current status 440 if status.Type != "" && (instance.Status.Type != status.Type || instance.Status.Reason != status.Reason || instance.Status.Message != status.Message) { 441 status.LastHeartbeatTime = time.Now().String() 442 log.Info(fmt.Sprintf("Updating status of IBPOrderer custom resource (%s) from %s to %s phase", instance.GetName(), instance.Status.Type, status.Type)) 443 444 instance.Status = current.IBPOrdererStatus{ 445 CRStatus: status, 446 } 447 448 err = r.client.PatchStatus(context.TODO(), instance, nil, k8sclient.PatchOption{ 449 Resilient: &k8sclient.ResilientPatch{ 450 Retry: 2, 451 Into: ¤t.IBPOrderer{}, 452 Strategy: client.MergeFrom, 453 }, 454 }) 455 if err != nil { 456 return err 457 } 458 } 459 460 return nil 461 } 462 463 func (r *ReconcileIBPOrderer) SaveSpecState(instance *current.IBPOrderer) error { 464 data, err := yaml.Marshal(instance.Spec) 465 if err != nil { 466 return err 467 } 468 469 cm := &corev1.ConfigMap{ 470 ObjectMeta: metav1.ObjectMeta{ 471 Name: fmt.Sprintf("%s-spec", instance.GetName()), 472 Namespace: instance.GetNamespace(), 473 Labels: instance.GetLabels(), 474 }, 475 BinaryData: map[string][]byte{ 476 "spec": data, 477 }, 478 } 479 480 err = r.client.CreateOrUpdate(context.TODO(), cm, k8sclient.CreateOrUpdateOption{Owner: instance, Scheme: r.scheme}) 481 if err != nil { 482 return err 483 } 484 485 return nil 486 } 487 488 func (r *ReconcileIBPOrderer) GetSpecState(instance *current.IBPOrderer) (*corev1.ConfigMap, error) { 489 cm := &corev1.ConfigMap{} 490 nn := types.NamespacedName{ 491 Name: fmt.Sprintf("%s-spec", instance.GetName()), 492 Namespace: instance.GetNamespace(), 493 } 494 495 err := r.client.Get(context.TODO(), nn, cm) 496 if err != nil { 497 return nil, err 498 } 499 500 return cm, nil 501 } 502 503 func (r *ReconcileIBPOrderer) GetPodStatus(instance *current.IBPOrderer) (map[string]corev1.PodStatus, error) { 504 statuses := map[string]corev1.PodStatus{} 505 506 labelSelector, err := labels.Parse(fmt.Sprintf("app=%s", instance.GetName())) 507 if err != nil { 508 return statuses, errors.Wrap(err, "failed to parse label selector for app name") 509 } 510 511 listOptions := &client.ListOptions{ 512 LabelSelector: labelSelector, 513 Namespace: instance.GetNamespace(), 514 } 515 516 podList := &corev1.PodList{} 517 err = r.client.List(context.TODO(), podList, listOptions) 518 if err != nil { 519 return statuses, err 520 } 521 522 for _, pod := range podList.Items { 523 statuses[pod.Name] = pod.Status 524 } 525 526 return statuses, nil 527 } 528 529 func (r *ReconcileIBPOrderer) GetGenesisSecret(instance *current.IBPOrderer) error { 530 nn := types.NamespacedName{ 531 Name: fmt.Sprintf("%s-genesis", instance.GetName()), 532 Namespace: instance.GetNamespace(), 533 } 534 err := r.client.Get(context.TODO(), nn, &corev1.Secret{}) 535 if err != nil { 536 return err 537 } 538 539 return nil 540 } 541 542 func (r *ReconcileIBPOrderer) CreateFunc(e event.CreateEvent) bool { 543 update := Update{} 544 545 switch e.Object.(type) { 546 case *current.IBPOrderer: 547 orderer := e.Object.(*current.IBPOrderer) 548 log.Info(fmt.Sprintf("Create event detected for orderer '%s'", orderer.GetName())) 549 550 if orderer.Status.HasType() { 551 log.Info(fmt.Sprintf("Operator restart detected, performing update checks on exisitng orderer '%s'", orderer.GetName())) 552 553 cm, err := r.GetSpecState(orderer) 554 if err != nil { 555 log.Info(fmt.Sprintf("Failed getting saved orderer spec '%s', can't perform update checks, triggering reconcile: %s", orderer.GetName(), err.Error())) 556 return true 557 } 558 559 specBytes := cm.BinaryData["spec"] 560 savedOrderer := ¤t.IBPOrderer{} 561 562 err = yaml.Unmarshal(specBytes, &savedOrderer.Spec) 563 if err != nil { 564 log.Info(fmt.Sprintf("Unmarshal failed for saved orderer spec '%s', can't perform update checks, triggering reconcile: %s", orderer.GetName(), err.Error())) 565 return true 566 } 567 568 if !reflect.DeepEqual(orderer.Spec, savedOrderer.Spec) { 569 log.Info(fmt.Sprintf("IBPOrderer '%s' spec was updated while operator was down", orderer.GetName())) 570 update.specUpdated = true 571 } 572 573 if !reflect.DeepEqual(orderer.Spec.ConfigOverride, savedOrderer.Spec.ConfigOverride) { 574 log.Info(fmt.Sprintf("IBPOrderer '%s' overrides were updated while operator was down", orderer.GetName())) 575 update.overridesUpdated = true 576 } 577 578 update.imagesUpdated = imagesUpdated(savedOrderer, orderer) 579 update.fabricVersionUpdated = fabricVersionUpdated(savedOrderer, orderer) 580 if fabricVersionUpdatedTo149plusOr221plus(savedOrderer, orderer) { 581 log.Info(fmt.Sprintf("Fabric version update detected from '%s' to '%s' setting tls cert created flag '%s'", savedOrderer.Spec.FabricVersion, orderer.Spec.FabricVersion, orderer.GetName())) 582 update.tlsCertCreated = true 583 } 584 585 log.Info(fmt.Sprintf("Create event triggering reconcile for updating orderer '%s'", orderer.GetName())) 586 r.PushUpdate(orderer.GetName(), update) 587 } 588 589 // If creating resource for the first time, check that a unique name is provided 590 err := commoncontroller.ValidateCRName(r.client, orderer.Name, orderer.Namespace, commoncontroller.IBPORDERER) 591 if err != nil { 592 log.Error(err, "failed to validate orderer name") 593 operror := operatorerrors.Wrap(err, operatorerrors.InvalidCustomResourceCreateRequest, "failed to validate custom resource name") 594 err = r.SetStatus(orderer, nil, operror) 595 if err != nil { 596 log.Error(err, "failed to set status to error", "orderer.name", orderer.Name, "error", "InvalidCustomResourceCreateRequest") 597 } 598 return false 599 } 600 601 log.Info(fmt.Sprintf("Create event triggering reconcile for creating orderer '%s'", orderer.GetName())) 602 603 case *corev1.Secret: 604 secret := e.Object.(*corev1.Secret) 605 606 if secret.OwnerReferences == nil || len(secret.OwnerReferences) == 0 { 607 isOrdererSecret, err := r.AddOwnerReferenceToSecret(secret) 608 if err != nil || !isOrdererSecret { 609 return false 610 } 611 } 612 613 if secret.OwnerReferences[0].Kind == KIND { 614 log.Info(fmt.Sprintf("Create event detected for secret '%s'", secret.GetName())) 615 instanceName := secret.OwnerReferences[0].Name 616 if util.IsSecretTLSCert(secret.Name) { 617 update.tlsCertCreated = true 618 } else if util.IsSecretEcert(secret.Name) { 619 update.ecertCreated = true 620 } else { 621 return false 622 } 623 624 log.Info(fmt.Sprintf("Orderer crypto create triggering reconcile on IBPOrderer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 625 r.PushUpdate(instanceName, update) 626 } 627 628 case *appsv1.Deployment: 629 dep := e.Object.(*appsv1.Deployment) 630 log.Info(fmt.Sprintf("Create event detected by IBPOrderer controller for deployment '%s', triggering reconcile", dep.GetName())) 631 632 case *corev1.ConfigMap: 633 cm := e.Object.(*corev1.ConfigMap) 634 if cm.Name == "orderer-restart-config" { 635 log.Info(fmt.Sprintf("Create event detected by IBPOrderer contoller for config map '%s', triggering restart reconcile", cm.GetName())) 636 } else { 637 return false 638 } 639 640 } 641 642 return true 643 } 644 645 func (r *ReconcileIBPOrderer) UpdateFunc(e event.UpdateEvent) bool { 646 update := Update{} 647 648 switch e.ObjectOld.(type) { 649 case *current.IBPOrderer: 650 oldOrderer := e.ObjectOld.(*current.IBPOrderer) 651 newOrderer := e.ObjectNew.(*current.IBPOrderer) 652 log.Info(fmt.Sprintf("Update event detected for orderer '%s'", oldOrderer.GetName())) 653 654 if oldOrderer.Spec.NodeNumber == nil { 655 if oldOrderer.Status.Type != newOrderer.Status.Type { 656 log.Info(fmt.Sprintf("Parent orderer %s status updated from %s to %s", oldOrderer.Name, oldOrderer.Status.Type, newOrderer.Status.Type)) 657 } 658 659 if oldOrderer.Status.Type == current.Deployed || oldOrderer.Status.Type == current.Error || oldOrderer.Status.Type == current.Warning { 660 // Parent orderer has been fully deployed by this point 661 log.Info(fmt.Sprintf("Ignoring the IBPOrderer cluster (parent) update after %s", oldOrderer.Status.Type)) 662 return false 663 } 664 665 } 666 667 if util.CheckIfZoneOrRegionUpdated(oldOrderer.Spec.Zone, newOrderer.Spec.Zone) { 668 log.Error(errors.New("Zone update is not allowed"), "invalid spec update") 669 return false 670 } 671 672 if util.CheckIfZoneOrRegionUpdated(oldOrderer.Spec.Region, newOrderer.Spec.Region) { 673 log.Error(errors.New("Region update is not allowed"), "invalid spec update") 674 return false 675 } 676 677 // Need to trigger update when status has changed is to allow us update the 678 // status of the parent, since the status of the parent depends on the status 679 // of its children .Only flag status update when there is a meaninful change 680 // and not everytime the heartbeat is updated 681 if oldOrderer.Status != newOrderer.Status { 682 if oldOrderer.Status.Type != newOrderer.Status.Type || 683 oldOrderer.Status.Reason != newOrderer.Status.Reason || 684 oldOrderer.Status.Message != newOrderer.Status.Message { 685 686 log.Info(fmt.Sprintf("%s status changed to '%+v' from '%+v'", oldOrderer.GetName(), newOrderer.Status, oldOrderer.Status)) 687 update.statusUpdated = true 688 } 689 } 690 691 if !reflect.DeepEqual(oldOrderer.Spec.ConfigOverride, newOrderer.Spec.ConfigOverride) { 692 log.Info(fmt.Sprintf("%s config override updated", oldOrderer.GetName())) 693 update.overridesUpdated = true 694 } 695 696 if !reflect.DeepEqual(oldOrderer.Spec, newOrderer.Spec) { 697 log.Info(fmt.Sprintf("%s spec updated", oldOrderer.GetName())) 698 update.specUpdated = true 699 } 700 701 // Check for changes to orderer tag to determine if any migration logic needs to be executed 702 // from old orderer version to new orderer version 703 if oldOrderer.Spec.Images != nil && newOrderer.Spec.Images != nil { 704 if oldOrderer.Spec.Images.OrdererTag != newOrderer.Spec.Images.OrdererTag { 705 log.Info(fmt.Sprintf("%s orderer tag updated from %s to %s", oldOrderer.GetName(), oldOrderer.Spec.Images.OrdererTag, newOrderer.Spec.Images.OrdererTag)) 706 update.ordererTagUpdated = true 707 } 708 } 709 710 if fabricVersionUpdatedTo149plusOr221plus(oldOrderer, newOrderer) { 711 log.Info(fmt.Sprintf("Fabric version update detected from '%s' to '%s' setting tls cert created flag '%s'", oldOrderer.Spec.FabricVersion, newOrderer.Spec.FabricVersion, newOrderer.GetName())) 712 update.tlsCertCreated = true 713 } 714 715 update.mspUpdated = commoncontroller.MSPInfoUpdateDetected(oldOrderer.Spec.Secret, newOrderer.Spec.Secret) 716 717 if newOrderer.Spec.Action.Restart { 718 update.restartNeeded = true 719 } 720 721 if oldOrderer.Spec.Action.Reenroll.Ecert != newOrderer.Spec.Action.Reenroll.Ecert { 722 update.ecertReenrollNeeded = newOrderer.Spec.Action.Reenroll.Ecert 723 } 724 725 if oldOrderer.Spec.Action.Reenroll.TLSCert != newOrderer.Spec.Action.Reenroll.TLSCert { 726 update.tlscertReenrollNeeded = newOrderer.Spec.Action.Reenroll.TLSCert 727 } 728 729 if oldOrderer.Spec.Action.Reenroll.EcertNewKey != newOrderer.Spec.Action.Reenroll.EcertNewKey { 730 update.ecertNewKeyReenroll = newOrderer.Spec.Action.Reenroll.EcertNewKey 731 } 732 733 if oldOrderer.Spec.Action.Reenroll.TLSCertNewKey != newOrderer.Spec.Action.Reenroll.TLSCertNewKey { 734 update.tlscertNewKeyReenroll = newOrderer.Spec.Action.Reenroll.TLSCertNewKey 735 } 736 737 if newOrderer.Spec.Action.Enroll.Ecert { 738 update.ecertEnroll = true 739 } 740 741 if newOrderer.Spec.Action.Enroll.TLSCert { 742 update.tlscertEnroll = true 743 } 744 745 update.deploymentUpdated = deploymentUpdated(oldOrderer, newOrderer) 746 oldVer := version.String(oldOrderer.Spec.FabricVersion) 747 newVer := version.String(newOrderer.Spec.FabricVersion) 748 749 // check if this V1 -> V2.2.x/V2.4.x orderer migration 750 if (oldOrderer.Spec.FabricVersion == "" || 751 version.GetMajorReleaseVersion(oldOrderer.Spec.FabricVersion) == version.V1) && 752 version.GetMajorReleaseVersion(newOrderer.Spec.FabricVersion) == version.V2 { 753 update.migrateToV2 = true 754 if newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1) { 755 update.migrateToV24 = true 756 // Re-enrolling tls cert to include admin hostname in SAN (for orderers >=2.4.1) 757 update.tlscertReenrollNeeded = true 758 } 759 } 760 761 // check if this V2.2.x -> V2.4.x orderer migration 762 if (version.GetMajorReleaseVersion(oldOrderer.Spec.FabricVersion) == version.V2) && 763 oldVer.LessThan(version.V2_4_1) && 764 (newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1)) { 765 update.migrateToV24 = true 766 // Re-enrolling tls cert to include admin hostname in SAN (for orderers >=2.4.1) 767 update.tlscertReenrollNeeded = true 768 } 769 770 if oldOrderer.Spec.NodeOUDisabled() != newOrderer.Spec.NodeOUDisabled() { 771 update.nodeOUUpdated = true 772 } 773 774 // if use updates NumSecondsWarningPeriod field once we have already run the reconcile 775 // we need to retrigger the timer logic 776 if oldOrderer.Spec.NumSecondsWarningPeriod != newOrderer.Spec.NumSecondsWarningPeriod { 777 update.ecertUpdated = true 778 update.tlsCertUpdated = true 779 log.Info(fmt.Sprintf("%s NumSecondsWarningPeriod updated", oldOrderer.GetName())) 780 } 781 782 if update.Detected() { 783 log.Info(fmt.Sprintf("Spec update triggering reconcile on IBPOrderer custom resource %s: update [ %+v ]", oldOrderer.GetName(), update.GetUpdateStackWithTrues())) 784 r.PushUpdate(oldOrderer.GetName(), update) 785 return true 786 } 787 788 case *appsv1.Deployment: 789 oldDeployment := e.ObjectOld.(*appsv1.Deployment) 790 log.Info(fmt.Sprintf("Spec update detected by IBPOrderer controller on deployment '%s'", oldDeployment.GetName())) 791 792 case *corev1.Secret: 793 oldSecret := e.ObjectOld.(*corev1.Secret) 794 newSecret := e.ObjectNew.(*corev1.Secret) 795 796 if oldSecret.OwnerReferences == nil || len(oldSecret.OwnerReferences) == 0 { 797 isOrdererSecret, err := r.AddOwnerReferenceToSecret(oldSecret) 798 if err != nil || !isOrdererSecret { 799 return false 800 } 801 } 802 803 if oldSecret.OwnerReferences[0].Kind == KIND { 804 if reflect.DeepEqual(oldSecret.Data, newSecret.Data) { 805 return false 806 } 807 808 log.Info(fmt.Sprintf("Update event detected on secret '%s'", oldSecret.GetName())) 809 instanceName := oldSecret.OwnerReferences[0].Name 810 if util.IsSecretTLSCert(oldSecret.Name) { 811 update.tlsCertUpdated = true 812 log.Info(fmt.Sprintf("TLS cert updated for %s", instanceName)) 813 } 814 if util.IsSecretEcert(oldSecret.Name) { 815 update.ecertUpdated = true 816 log.Info(fmt.Sprintf("ecert updated for %s", instanceName)) 817 } 818 819 if update.CertificateUpdated() { 820 log.Info(fmt.Sprintf("Orderer crypto update triggering reconcile on IBPOrderer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 821 r.PushUpdate(instanceName, update) 822 return true 823 } 824 } 825 826 case *corev1.ConfigMap: 827 cm := e.ObjectOld.(*corev1.ConfigMap) 828 if cm.Name == "orderer-restart-config" { 829 log.Info("Update event detected for orderer-restart-config, triggering restart reconcile") 830 return true 831 } 832 } 833 834 return false 835 } 836 837 func (r *ReconcileIBPOrderer) DeleteFunc(e event.DeleteEvent) bool { 838 switch e.Object.(type) { 839 case *current.IBPOrderer: 840 oldOrderer := e.Object.(*current.IBPOrderer) 841 842 if oldOrderer.Spec.NodeNumber != nil { 843 log.Info(fmt.Sprintf("Orderer node %d (%s) deleted", *oldOrderer.Spec.NodeNumber, oldOrderer.GetName())) 844 845 // Deleting this config map manually, in 2.5.1 release of operator this config map was created 846 // without proper controller references set and was not cleaned up on orderer resource deletion. 847 log.Info(fmt.Sprintf("Deleting %s-init-config config map, if found", oldOrderer.GetName())) 848 if err := r.client.Delete(context.TODO(), &corev1.ConfigMap{ 849 ObjectMeta: metav1.ObjectMeta{ 850 Name: fmt.Sprintf("%s-init-config", oldOrderer.GetName()), 851 Namespace: oldOrderer.GetNamespace(), 852 }, 853 }); client.IgnoreNotFound(err) != nil { 854 log.Info(fmt.Sprintf("failed to delete config map: %s", err)) 855 } 856 857 parentName := oldOrderer.ObjectMeta.Labels["parent"] 858 labelSelector, err := labels.Parse(fmt.Sprintf("parent=%s", parentName)) 859 if err != nil { 860 log.Info(fmt.Sprintf("failed to parse selector for parent name: %s", err.Error())) 861 return false 862 } 863 864 listOptions := &client.ListOptions{ 865 LabelSelector: labelSelector, 866 Namespace: oldOrderer.GetNamespace(), 867 } 868 869 ordererList := ¤t.IBPOrdererList{} 870 err = r.client.List(context.TODO(), ordererList, listOptions) 871 if err != nil { 872 log.Info(fmt.Sprintf("Ignoring Deletion of Orderer node %d (%s) due to error in getting list of other nodes: %s", *oldOrderer.Spec.NodeNumber, oldOrderer.GetName(), err.Error())) 873 return false 874 } 875 876 log.Info(fmt.Sprintf("There are %d child nodes for the orderer parent %s.", len(ordererList.Items), parentName)) 877 878 if len(ordererList.Items) == 0 { 879 log.Info(fmt.Sprintf("Deleting Parent (%s) of Orderer node %d (%s) as all nodes are deleted.", parentName, *oldOrderer.Spec.NodeNumber, oldOrderer.GetName())) 880 parent := ¤t.IBPOrderer{} 881 parent.SetName(parentName) 882 parent.SetNamespace(oldOrderer.GetNamespace()) 883 884 err := r.client.Delete(context.TODO(), parent) 885 if err != nil { 886 log.Error(err, fmt.Sprintf("Error deleting parent (%s) of Orderer node %d (%s).", parentName, *oldOrderer.Spec.NodeNumber, oldOrderer.GetName())) 887 } 888 return false 889 } 890 891 log.Info(fmt.Sprintf("Ignoring Deletion of Orderer node %d (%s) as there are %d nodes of parent still around", *oldOrderer.Spec.NodeNumber, oldOrderer.GetName(), len(ordererList.Items))) 892 return false 893 } 894 895 log.Info(fmt.Sprintf("Orderer parent %s deleted", oldOrderer.GetName())) 896 parentName := oldOrderer.GetName() 897 labelSelector, err := labels.Parse(fmt.Sprintf("parent=%s", parentName)) 898 if err != nil { 899 log.Info(fmt.Sprintf("failed to parse selector for parent name: %s", err.Error())) 900 } 901 902 listOptions := &client.ListOptions{ 903 LabelSelector: labelSelector, 904 Namespace: oldOrderer.GetNamespace(), 905 } 906 907 ordererList := ¤t.IBPOrdererList{} 908 err = r.client.List(context.TODO(), ordererList, listOptions) 909 if err != nil { 910 log.Info(fmt.Sprintf("Ignoring Deletion of Orderer parent %s due to error in getting list of child nodes: %s", oldOrderer.GetName(), err.Error())) 911 return false 912 } 913 914 log.Info(fmt.Sprintf("There are %d child nodes for the orderer parent %s.", len(ordererList.Items), parentName)) 915 916 for _, item := range ordererList.Items { 917 log.Info(fmt.Sprintf("Deleting child node %s", item.GetName())) 918 919 child := ¤t.IBPOrderer{} 920 child.SetName(item.GetName()) 921 child.SetNamespace(item.GetNamespace()) 922 923 err := r.client.Delete(context.TODO(), child) 924 if err != nil { 925 log.Error(err, fmt.Sprintf("Error child node (%s) of Orderer (%s).", child.GetName(), parentName)) 926 } 927 } 928 929 return false 930 931 case *appsv1.Deployment: 932 dep := e.Object.(*appsv1.Deployment) 933 log.Info(fmt.Sprintf("Delete detected by IBPOrderer controller on deployment '%s'", dep.GetName())) 934 case *corev1.Secret: 935 secret := e.Object.(*corev1.Secret) 936 log.Info(fmt.Sprintf("Delete detected by IBPOrderer controller on secret '%s'", secret.GetName())) 937 case *corev1.ConfigMap: 938 cm := e.Object.(*corev1.ConfigMap) 939 log.Info(fmt.Sprintf("Delete detected by IBPOrderer controller on configmap '%s'", cm.GetName())) 940 } 941 942 return true 943 } 944 945 func (r *ReconcileIBPOrderer) GetUpdateStatusAtElement(instance *current.IBPOrderer, index int) *Update { 946 r.mutex.Lock() 947 defer r.mutex.Unlock() 948 949 update := Update{} 950 _, ok := r.update[instance.GetName()] 951 if !ok { 952 return &update 953 } 954 955 if len(r.update[instance.GetName()]) >= 1 { 956 update = r.update[instance.GetName()][index] 957 } 958 959 return &update 960 } 961 962 func (r *ReconcileIBPOrderer) GetUpdateStatus(instance *current.IBPOrderer) *Update { 963 return r.GetUpdateStatusAtElement(instance, 0) 964 } 965 966 func (r *ReconcileIBPOrderer) PushUpdate(instanceName string, update Update) { 967 r.mutex.Lock() 968 defer r.mutex.Unlock() 969 970 r.update[instanceName] = r.AppendUpdateIfMissing(r.update[instanceName], update) 971 } 972 973 func (r *ReconcileIBPOrderer) PopUpdate(instanceName string) *Update { 974 r.mutex.Lock() 975 defer r.mutex.Unlock() 976 977 update := Update{} 978 if len(r.update[instanceName]) >= 1 { 979 update = r.update[instanceName][0] 980 if len(r.update[instanceName]) == 1 { 981 r.update[instanceName] = []Update{} 982 } else { 983 r.update[instanceName] = r.update[instanceName][1:] 984 } 985 } 986 987 return &update 988 } 989 990 func (r *ReconcileIBPOrderer) AppendUpdateIfMissing(updates []Update, update Update) []Update { 991 for _, u := range updates { 992 if u == update { 993 return updates 994 } 995 } 996 return append(updates, update) 997 } 998 999 func deploymentUpdated(oldOrderer, newOrderer *current.IBPOrderer) bool { 1000 if !reflect.DeepEqual(oldOrderer.Spec.Images, newOrderer.Spec.Images) { 1001 log.Info(fmt.Sprintf("Images updated for '%s', deployment will be updated", newOrderer.Name)) 1002 return true 1003 } 1004 1005 if !reflect.DeepEqual(oldOrderer.Spec.Replicas, newOrderer.Spec.Replicas) { 1006 log.Info(fmt.Sprintf("Replica size updated for '%s', deployment will be updated", newOrderer.Name)) 1007 return true 1008 } 1009 1010 if !reflect.DeepEqual(oldOrderer.Spec.Resources, newOrderer.Spec.Resources) { 1011 log.Info(fmt.Sprintf("Resources updated for '%s', deployment will be updated", newOrderer.Name)) 1012 return true 1013 } 1014 1015 if !reflect.DeepEqual(oldOrderer.Spec.Storage, newOrderer.Spec.Storage) { 1016 log.Info(fmt.Sprintf("Storage updated for '%s', deployment will be updated", newOrderer.Name)) 1017 return true 1018 } 1019 1020 if len(oldOrderer.Spec.ImagePullSecrets) != len(newOrderer.Spec.ImagePullSecrets) { 1021 log.Info(fmt.Sprintf("ImagePullSecret updated for '%s', deployment will be updated", newOrderer.Name)) 1022 return true 1023 } 1024 for i, v := range newOrderer.Spec.ImagePullSecrets { 1025 if v != oldOrderer.Spec.ImagePullSecrets[i] { 1026 log.Info(fmt.Sprintf("ImagePullSecret updated for '%s', deployment will be updated", newOrderer.Name)) 1027 return true 1028 } 1029 } 1030 1031 return false 1032 } 1033 1034 func (r *ReconcileIBPOrderer) AddOwnerReferenceToSecret(secret *corev1.Secret) (bool, error) { 1035 // Orderer secrets we are looking to add owner references to are named: 1036 // <prefix>-<instance name>-<type> 1037 // <instance name>-init-rootcert 1038 1039 // The following secrets are created by operator, and will have owner references: 1040 // <instance name>-genesis 1041 // <instance name>-crypto-backup 1042 // <instance name>-secret 1043 1044 items := strings.Split(secret.Name, "-") 1045 if len(items) < 3 { 1046 // Secret names we are looking for will be split into at least 3 strings: 1047 // [prefix, instance name, type] OR [instance name, "init", "rootcert"] 1048 return false, nil 1049 } 1050 1051 // Account for the case where the instance's name is hyphenated 1052 var instanceName string 1053 if strings.Contains(secret.Name, "-init-rootcert") { 1054 instanceName = strings.Join(items[:len(items)-2], "-") // instance name contains all but last 2 items 1055 } else { 1056 instanceName = strings.Join(items[1:len(items)-1], "-") // instance name contains all but first and last item 1057 } 1058 1059 listOptions := &client.ListOptions{ 1060 Namespace: secret.Namespace, 1061 } 1062 1063 ordererList := ¤t.IBPOrdererList{} 1064 err := r.client.List(context.TODO(), ordererList, listOptions) 1065 if err != nil { 1066 return false, errors.Wrap(err, "failed to get list of orderers") 1067 } 1068 1069 for _, o := range ordererList.Items { 1070 orderer := o 1071 if orderer.Name == instanceName { 1072 // Instance 'i' found in list of orderers 1073 err := r.client.Update(context.TODO(), secret, k8sclient.UpdateOption{ 1074 Owner: &orderer, 1075 Scheme: r.scheme, 1076 }) 1077 if err != nil { 1078 return false, err 1079 } 1080 return true, nil 1081 } 1082 } 1083 1084 return false, nil 1085 } 1086 1087 func (r *ReconcileIBPOrderer) SetupWithManager(mgr ctrl.Manager) error { 1088 return ctrl.NewControllerManagedBy(mgr). 1089 For(¤t.IBPOrderer{}). 1090 Complete(r) 1091 } 1092 1093 func GetUpdateStack(allUpdates map[string][]Update) string { 1094 stack := "" 1095 1096 for orderer, updates := range allUpdates { 1097 currentStack := "" 1098 for index, update := range updates { 1099 currentStack += fmt.Sprintf("{ %s}", update.GetUpdateStackWithTrues()) 1100 if index != len(updates)-1 { 1101 currentStack += " , " 1102 } 1103 } 1104 stack += fmt.Sprintf("%s: [ %s ] ", orderer, currentStack) 1105 } 1106 1107 return stack 1108 } 1109 1110 func (r *ReconcileIBPOrderer) ReconcileRestart(namespace string) (bool, error) { 1111 requeue, err := r.RestartService.Reconcile("orderer", namespace) 1112 if err != nil { 1113 log.Error(err, "failed to reconcile restart queues in orderer-restart-config") 1114 return false, err 1115 } 1116 1117 return requeue, nil 1118 }