github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibppeer/ibppeer_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 ibppeer 20 21 import ( 22 "context" 23 "fmt" 24 "os" 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 controllerclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/offering" 36 basepeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/peer" 37 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 38 k8speer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/k8s/peer" 39 openshiftpeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/openshift/peer" 40 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 41 "github.com/IBM-Blockchain/fabric-operator/pkg/restart/staggerrestarts" 42 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 43 "github.com/IBM-Blockchain/fabric-operator/version" 44 "github.com/pkg/errors" 45 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 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 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 53 "k8s.io/apimachinery/pkg/labels" 54 "k8s.io/apimachinery/pkg/runtime" 55 "k8s.io/apimachinery/pkg/types" 56 ctrl "sigs.k8s.io/controller-runtime" 57 "sigs.k8s.io/controller-runtime/pkg/client" 58 k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 59 "sigs.k8s.io/controller-runtime/pkg/controller" 60 "sigs.k8s.io/controller-runtime/pkg/event" 61 "sigs.k8s.io/controller-runtime/pkg/handler" 62 logf "sigs.k8s.io/controller-runtime/pkg/log" 63 "sigs.k8s.io/controller-runtime/pkg/manager" 64 "sigs.k8s.io/controller-runtime/pkg/predicate" 65 "sigs.k8s.io/controller-runtime/pkg/reconcile" 66 "sigs.k8s.io/controller-runtime/pkg/source" 67 ) 68 69 const ( 70 KIND = "IBPPeer" 71 ) 72 73 var log = logf.Log.WithName("controller_ibppeer") 74 75 type CoreConfig interface { 76 GetMaxNameLength() *int 77 } 78 79 // Add creates a new IBPPeer Controller and adds it to the Manager. The Manager will set fields on the Controller 80 // and Start it when the Manager is Started. 81 func Add(mgr manager.Manager, config *config.Config) error { 82 r, err := newReconciler(mgr, config) 83 if err != nil { 84 return err 85 } 86 return add(mgr, r) 87 } 88 89 // newReconciler returns a new reconcile.Reconciler 90 func newReconciler(mgr manager.Manager, cfg *config.Config) (*ReconcileIBPPeer, error) { 91 client := controllerclient.New(mgr.GetClient(), &global.ConfigSetter{Config: cfg.Operator.Globals}) 92 scheme := mgr.GetScheme() 93 94 ibppeer := &ReconcileIBPPeer{ 95 client: client, 96 scheme: scheme, 97 Config: cfg, 98 update: map[string][]Update{}, 99 mutex: &sync.Mutex{}, 100 RestartService: staggerrestarts.New(client, cfg.Operator.Restart.Timeout.Get()), 101 } 102 103 restClient, err := clientset.NewForConfig(mgr.GetConfig()) 104 if err != nil { 105 return nil, err 106 } 107 108 switch cfg.Offering { 109 case offering.K8S: 110 ibppeer.Offering = k8speer.New(client, scheme, cfg) 111 case offering.OPENSHIFT: 112 ibppeer.Offering = openshiftpeer.New(client, scheme, cfg, restClient) 113 } 114 115 return ibppeer, nil 116 } 117 118 // add adds a new Controller to mgr with r as the reconcile.Reconciler 119 func add(mgr manager.Manager, r *ReconcileIBPPeer) error { 120 // Create a new controller 121 predicateFuncs := predicate.Funcs{ 122 CreateFunc: r.CreateFunc, 123 UpdateFunc: r.UpdateFunc, 124 DeleteFunc: r.DeleteFunc, 125 } 126 127 c, err := controller.New("ibppeer-controller", mgr, controller.Options{Reconciler: r}) 128 if err != nil { 129 return err 130 } 131 132 // Watch for changes to primary resource IBPPeer 133 err = c.Watch(&source.Kind{Type: ¤t.IBPPeer{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 134 if err != nil { 135 return err 136 } 137 138 // Watch for changes to config maps (Create and Update funcs handle only watching for restart config map) 139 err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, predicateFuncs) 140 if err != nil { 141 return err 142 } 143 144 // Watch for changes to secondary resource Pods and requeue the owner IBPPeer 145 err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ 146 IsController: true, 147 OwnerType: ¤t.IBPPeer{}, 148 }) 149 if err != nil { 150 return err 151 } 152 153 // Watch for changes to tertiary resource Secrets and requeue the owner IBPPeer 154 err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{ 155 IsController: true, 156 OwnerType: ¤t.IBPPeer{}, 157 }) 158 if err != nil { 159 return err 160 } 161 162 // Watch for changes to tertiary resource Secrets and requeue the owner IBPPeer 163 err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ 164 IsController: true, 165 OwnerType: ¤t.IBPPeer{}, 166 }, predicateFuncs) 167 if err != nil { 168 return err 169 } 170 171 return nil 172 } 173 174 var _ reconcile.Reconciler = &ReconcileIBPPeer{} 175 176 //go:generate counterfeiter -o mocks/peerreconcile.go -fake-name PeerReconcile . peerReconcile 177 178 type peerReconcile interface { 179 Reconcile(*current.IBPPeer, basepeer.Update) (common.Result, error) 180 } 181 182 // ReconcileIBPPeer reconciles a IBPPeer object 183 type ReconcileIBPPeer struct { 184 // This client, initialized using mgr.Client() above, is a split client 185 // that reads objects from the cache and writes to the apiserver 186 client controllerclient.Client 187 scheme *runtime.Scheme 188 189 k8sSecret *corev1.Secret 190 191 Offering peerReconcile 192 Config *config.Config 193 RestartService *staggerrestarts.StaggerRestartsService 194 195 update map[string][]Update 196 mutex *sync.Mutex 197 } 198 199 // Reconcile reads that state of the cluster for a IBPPeer object and makes changes based on the state read 200 // and what is in the IBPPeer.Spec 201 // Note: 202 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 203 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 204 func (r *ReconcileIBPPeer) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 205 var err error 206 207 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 208 209 // If peer-restart-config configmap is the object being reconciled, reconcile the 210 // restart configmap. 211 if request.Name == "peer-restart-config" { 212 requeue, err := r.ReconcileRestart(request.Namespace) 213 // Error reconciling restart - requeue the request. 214 if err != nil { 215 return reconcile.Result{}, err 216 } 217 // Restart reconciled, requeue request if required. 218 return reconcile.Result{ 219 Requeue: requeue, 220 }, nil 221 } 222 223 reqLogger.Info("Reconciling IBPPeer") 224 225 // Fetch the IBPPeer instance 226 instance := ¤t.IBPPeer{} 227 err = r.client.Get(context.TODO(), request.NamespacedName, instance) 228 if err != nil { 229 if k8serrors.IsNotFound(err) { 230 // Request object not found, could have been deleted after reconcile request. 231 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 232 // Return and don't requeue 233 return reconcile.Result{}, nil 234 } 235 // Error reading the object - requeue the request. 236 return reconcile.Result{}, err 237 } 238 239 var maxNameLength *int 240 241 co, err := instance.GetConfigOverride() 242 if err != nil { 243 return reconcile.Result{}, err 244 } 245 246 configOverride := co.(CoreConfig) 247 maxNameLength = configOverride.GetMaxNameLength() 248 249 err = util.ValidationChecks(instance.TypeMeta, instance.ObjectMeta, "IBPPeer", maxNameLength) 250 if err != nil { 251 return reconcile.Result{}, err 252 } 253 254 reqLogger.Info(fmt.Sprintf("Current update stack to process: %+v", GetUpdateStack(r.update))) 255 256 update := r.GetUpdateStatus(instance) 257 reqLogger.Info(fmt.Sprintf("Reconciling IBPPeer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 258 259 result, err := r.Offering.Reconcile(instance, r.PopUpdate(instance.GetName())) 260 setStatusErr := r.SetStatus(instance, result.Status, err) 261 if setStatusErr != nil { 262 return reconcile.Result{}, operatorerrors.IsBreakingError(setStatusErr, "failed to update status", log) 263 } 264 265 if err != nil { 266 return reconcile.Result{}, operatorerrors.IsBreakingError(errors.Wrapf(err, "Peer instance '%s' encountered error", instance.GetName()), "stopping reconcile loop", log) 267 } 268 269 if result.Requeue { 270 r.PushUpdate(instance.GetName(), *update) 271 } 272 273 reqLogger.Info(fmt.Sprintf("Finished reconciling IBPPeer '%s' with update values of [ %+v ]", instance.GetName(), update.GetUpdateStackWithTrues())) 274 275 // If the stack still has items that require processing, keep reconciling 276 // until the stack has been cleared 277 _, found := r.update[instance.GetName()] 278 if found { 279 if len(r.update[instance.GetName()]) > 0 { 280 return reconcile.Result{ 281 Requeue: true, 282 }, nil 283 } 284 } 285 286 return result.Result, nil 287 } 288 289 func (r *ReconcileIBPPeer) SetStatus(instance *current.IBPPeer, reconcileStatus *current.CRStatus, reconcileErr error) error { 290 err := r.SaveSpecState(instance) 291 if err != nil { 292 return errors.Wrap(err, "failed to save spec state") 293 } 294 295 // This is get is required but should not be, the reason we need to get the latest instance is because 296 // there is code between the reconcile start and SetStatus that ends up updating the instance. Since 297 // instance gets updated, but we are still working with original (outdated) version of instance, trying 298 // to update it fails with "object as been modified". 299 // 300 // TODO: Instance should only be updated at the start of reconcile (e.g. PreReconcileChecks), and if is updated 301 // the request should be requeued and not processed. The only only time the intance should be updated is in 302 // SetStatus 303 err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, instance) 304 if err != nil { 305 return err 306 } 307 308 status := instance.Status.CRStatus 309 310 if reconcileErr != nil { 311 status.Type = current.Error 312 status.Status = current.True 313 status.Reason = "errorOccurredDuringReconcile" 314 status.Message = reconcileErr.Error() 315 status.LastHeartbeatTime = time.Now().String() 316 status.ErrorCode = operatorerrors.GetErrorCode(reconcileErr) 317 318 instance.Status = current.IBPPeerStatus{ 319 CRStatus: status, 320 } 321 322 log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type)) 323 err = r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{ 324 Resilient: &controllerclient.ResilientPatch{ 325 Retry: 2, 326 Into: ¤t.IBPPeer{}, 327 Strategy: k8sclient.MergeFrom, 328 }, 329 }) 330 if err != nil { 331 return err 332 } 333 334 return nil 335 } 336 337 status.Versions.Reconciled = instance.Spec.FabricVersion 338 339 // Check if reconcile loop returned an updated status that differs from exisiting status. 340 // If so, set status to the reconcile status. 341 if reconcileStatus != nil { 342 if instance.Status.Type != reconcileStatus.Type || instance.Status.Reason != reconcileStatus.Reason || instance.Status.Message != reconcileStatus.Message { 343 status.Type = reconcileStatus.Type 344 status.Status = current.True 345 status.Reason = reconcileStatus.Reason 346 status.Message = reconcileStatus.Message 347 status.LastHeartbeatTime = time.Now().String() 348 349 instance.Status = current.IBPPeerStatus{ 350 CRStatus: status, 351 } 352 353 log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type)) 354 err := r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{ 355 Resilient: &controllerclient.ResilientPatch{ 356 Retry: 2, 357 Into: ¤t.IBPPeer{}, 358 Strategy: k8sclient.MergeFrom, 359 }, 360 }) 361 if err != nil { 362 return err 363 } 364 365 return nil 366 } 367 } 368 369 running, err := r.GetPodStatus(instance) 370 if err != nil { 371 return err 372 } 373 374 if running { 375 if instance.Status.Type == current.Deployed || instance.Status.Type == current.Warning { 376 return nil 377 } 378 status.Type = current.Deployed 379 status.Status = current.True 380 status.Reason = "allPodsRunning" 381 } else { 382 if instance.Status.Type == current.Deploying { 383 return nil 384 } 385 status.Type = current.Deploying 386 status.Status = current.True 387 status.Reason = "waitingForPods" 388 } 389 390 instance.Status = current.IBPPeerStatus{ 391 CRStatus: status, 392 } 393 instance.Status.LastHeartbeatTime = time.Now().String() 394 log.Info(fmt.Sprintf("Updating status of IBPPeer custom resource to %s phase", instance.Status.Type)) 395 err = r.client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{ 396 Resilient: &controllerclient.ResilientPatch{ 397 Retry: 2, 398 Into: ¤t.IBPPeer{}, 399 Strategy: k8sclient.MergeFrom, 400 }, 401 }) 402 if err != nil { 403 return err 404 } 405 406 return nil 407 } 408 409 func (r *ReconcileIBPPeer) SaveSpecState(instance *current.IBPPeer) error { 410 data, err := yaml.Marshal(instance.Spec) 411 if err != nil { 412 return err 413 } 414 415 cm := &corev1.ConfigMap{ 416 ObjectMeta: v1.ObjectMeta{ 417 Name: fmt.Sprintf("%s-spec", instance.GetName()), 418 Namespace: instance.GetNamespace(), 419 Labels: instance.GetLabels(), 420 }, 421 BinaryData: map[string][]byte{ 422 "spec": data, 423 }, 424 } 425 426 err = r.client.CreateOrUpdate(context.TODO(), cm, controllerclient.CreateOrUpdateOption{Owner: instance, Scheme: r.scheme}) 427 if err != nil { 428 return err 429 } 430 431 return nil 432 } 433 434 func (r *ReconcileIBPPeer) GetSpecState(instance *current.IBPPeer) (*corev1.ConfigMap, error) { 435 cm := &corev1.ConfigMap{} 436 nn := types.NamespacedName{ 437 Name: fmt.Sprintf("%s-spec", instance.GetName()), 438 Namespace: instance.GetNamespace(), 439 } 440 441 err := r.client.Get(context.TODO(), nn, cm) 442 if err != nil { 443 return nil, err 444 } 445 446 return cm, nil 447 } 448 449 func (r *ReconcileIBPPeer) GetPodStatus(instance *current.IBPPeer) (bool, error) { 450 labelSelector, err := labels.Parse(fmt.Sprintf("app=%s", instance.GetName())) 451 if err != nil { 452 return false, errors.Wrap(err, "failed to parse label selector for app name") 453 } 454 455 listOptions := &client.ListOptions{ 456 LabelSelector: labelSelector, 457 Namespace: instance.GetNamespace(), 458 } 459 460 podList := &corev1.PodList{} 461 err = r.client.List(context.TODO(), podList, listOptions) 462 if err != nil { 463 return false, err 464 } 465 466 if len(podList.Items) == 0 { 467 return false, nil 468 } 469 470 for _, pod := range podList.Items { 471 if pod.Status.Phase != corev1.PodRunning { 472 return false, nil 473 } 474 } 475 476 return true, nil 477 } 478 479 func (r *ReconcileIBPPeer) getIgnoreDiffs() []string { 480 return []string{ 481 `Template\.Spec\.Containers\.slice\[\d\]\.Resources\.Requests\.map\[memory\].s`, 482 `Template\.Spec\.InitContainers\.slice\[\d\]\.Resources\.Requests\.map\[memory\].s`, 483 `Ports\.slice\[\d\]\.Protocol`, 484 } 485 } 486 487 func (r *ReconcileIBPPeer) getSelectorLabels(instance *current.IBPPeer) map[string]string { 488 label := os.Getenv("OPERATOR_LABEL_PREFIX") 489 if label == "" { 490 label = "fabric" 491 } 492 493 return map[string]string{ 494 "app": instance.Name, 495 "creator": label, 496 "orgname": instance.Spec.MSPID, 497 "release": "operator", 498 "helm.sh/chart": "ibm-" + label, 499 "app.kubernetes.io/name": label, 500 "app.kubernetes.io/instance": label + "peer", 501 "app.kubernetes.io/managed-by": label + "-operator", 502 } 503 } 504 505 func (r *ReconcileIBPPeer) CreateFunc(e event.CreateEvent) bool { 506 update := Update{} 507 508 switch e.Object.(type) { 509 case *current.IBPPeer: 510 peer := e.Object.(*current.IBPPeer) 511 log.Info(fmt.Sprintf("Create event detected for peer '%s'", peer.GetName())) 512 513 if peer.Status.HasType() { 514 cm, err := r.GetSpecState(peer) 515 if err != nil { 516 log.Info(fmt.Sprintf("Failed getting saved peer spec '%s', can't perform update checks, triggering reconcile: %s", peer.GetName(), err.Error())) 517 return true 518 } 519 520 specBytes := cm.BinaryData["spec"] 521 savedPeer := ¤t.IBPPeer{} 522 523 err = yaml.Unmarshal(specBytes, &savedPeer.Spec) 524 if err != nil { 525 log.Info(fmt.Sprintf("Unmarshal failed for saved peer spec '%s', can't perform update checks, triggering reconcile: %s", peer.GetName(), err.Error())) 526 return true 527 } 528 529 if !reflect.DeepEqual(peer.Spec, savedPeer.Spec) { 530 log.Info(fmt.Sprintf("IBPPeer '%s' spec was updated while operator was down", peer.GetName())) 531 update.specUpdated = true 532 } 533 534 if !reflect.DeepEqual(peer.Spec.ConfigOverride, savedPeer.Spec.ConfigOverride) { 535 log.Info(fmt.Sprintf("IBPPeer '%s' overrides were updated while operator was down", peer.GetName())) 536 update.overridesUpdated = true 537 } 538 539 update.imagesUpdated = imagesUpdated(savedPeer, peer) 540 update.fabricVersionUpdated = fabricVersionUpdated(savedPeer, peer) 541 542 log.Info(fmt.Sprintf("Create event triggering reconcile for updating peer '%s'", peer.GetName())) 543 r.PushUpdate(peer.GetName(), update) 544 return true 545 } 546 547 // If creating resource for the first time, check that a unique name is provided 548 err := commoncontroller.ValidateCRName(r.client, peer.Name, peer.Namespace, commoncontroller.IBPPEER) 549 if err != nil { 550 log.Error(err, "failed to validate peer name") 551 operror := operatorerrors.Wrap(err, operatorerrors.InvalidCustomResourceCreateRequest, "failed to validate custom resource name") 552 err = r.SetStatus(peer, nil, operror) 553 if err != nil { 554 log.Error(err, "failed to set status to error", "peer.name", peer.Name, "error", "InvalidCustomResourceCreateRequest") 555 } 556 return false 557 } 558 559 log.Info(fmt.Sprintf("Create event triggering reconcile for creating peer '%s'", peer.GetName())) 560 561 case *corev1.Secret: 562 secret := e.Object.(*corev1.Secret) 563 564 if secret.OwnerReferences == nil || len(secret.OwnerReferences) == 0 { 565 isPeerSecret, err := r.AddOwnerReferenceToSecret(secret) 566 if err != nil || !isPeerSecret { 567 return false 568 } 569 } 570 571 if secret.OwnerReferences[0].Kind == KIND { 572 log.Info(fmt.Sprintf("Create event detected for secret '%s'", secret.GetName())) 573 instanceName := secret.OwnerReferences[0].Name 574 575 if util.IsSecretTLSCert(secret.Name) { 576 update.tlsCertCreated = true 577 log.Info(fmt.Sprintf("TLS cert create detected on IBPPeer custom resource %s", instanceName)) 578 } else if util.IsSecretEcert(secret.Name) { 579 update.ecertCreated = true 580 log.Info(fmt.Sprintf("Ecert create detected on IBPPeer custom resource %s", instanceName)) 581 } else { 582 return false 583 } 584 585 log.Info(fmt.Sprintf("Peer crypto create triggering reconcile on IBPPeer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 586 r.PushUpdate(instanceName, update) 587 } 588 589 case *appsv1.Deployment: 590 dep := e.Object.(*appsv1.Deployment) 591 log.Info(fmt.Sprintf("Create event detected by IBPPeer controller for deployment '%s', triggering reconcile", dep.GetName())) 592 case *corev1.ConfigMap: 593 cm := e.Object.(*corev1.ConfigMap) 594 if cm.Name == "peer-restart-config" { 595 log.Info(fmt.Sprintf("Create event detected by IBPPeer contoller for config map '%s', triggering restart reconcile", cm.GetName())) 596 } else { 597 return false 598 } 599 600 } 601 602 return true 603 } 604 605 func (r *ReconcileIBPPeer) UpdateFunc(e event.UpdateEvent) bool { 606 update := Update{} 607 608 switch e.ObjectOld.(type) { 609 case *current.IBPPeer: 610 oldPeer := e.ObjectOld.(*current.IBPPeer) 611 newPeer := e.ObjectNew.(*current.IBPPeer) 612 log.Info(fmt.Sprintf("Update event detected for peer '%s'", oldPeer.GetName())) 613 614 if util.CheckIfZoneOrRegionUpdated(oldPeer.Spec.Zone, newPeer.Spec.Zone) { 615 log.Error(errors.New("Zone update is not allowed"), "invalid spec update") 616 return false 617 } 618 619 if util.CheckIfZoneOrRegionUpdated(oldPeer.Spec.Region, newPeer.Spec.Region) { 620 log.Error(errors.New("Region update is not allowed"), "invalid spec update") 621 return false 622 } 623 624 if reflect.DeepEqual(oldPeer.Spec, newPeer.Spec) { 625 return false 626 } 627 log.Info(fmt.Sprintf("%s spec updated", oldPeer.GetName())) 628 update.specUpdated = true 629 630 // Check for changes to peer tag to determine if any migration logic needs to be executed 631 // from old peer version to new peer version 632 if oldPeer.Spec.Images != nil && newPeer.Spec.Images != nil { 633 if oldPeer.Spec.Images.PeerTag != newPeer.Spec.Images.PeerTag { 634 log.Info(fmt.Sprintf("Peer tag update from %s to %s", oldPeer.Spec.Images.PeerTag, newPeer.Spec.Images.PeerTag)) 635 update.peerTagUpdated = true 636 } 637 } 638 639 if !reflect.DeepEqual(oldPeer.Spec.ConfigOverride, newPeer.Spec.ConfigOverride) { 640 log.Info(fmt.Sprintf("%s config override updated", oldPeer.GetName())) 641 update.overridesUpdated = true 642 } 643 644 update.mspUpdated = commoncontroller.MSPInfoUpdateDetected(oldPeer.Spec.Secret, newPeer.Spec.Secret) 645 646 if newPeer.Spec.Action.Restart == true { 647 update.restartNeeded = true 648 } 649 650 if oldPeer.Spec.Action.Reenroll.Ecert != newPeer.Spec.Action.Reenroll.Ecert { 651 update.ecertReenrollNeeded = newPeer.Spec.Action.Reenroll.Ecert 652 } 653 654 if oldPeer.Spec.Action.Reenroll.TLSCert != newPeer.Spec.Action.Reenroll.TLSCert { 655 update.tlsReenrollNeeded = newPeer.Spec.Action.Reenroll.TLSCert 656 } 657 658 if oldPeer.Spec.Action.Reenroll.EcertNewKey != newPeer.Spec.Action.Reenroll.EcertNewKey { 659 update.ecertNewKeyReenroll = newPeer.Spec.Action.Reenroll.EcertNewKey 660 } 661 662 if oldPeer.Spec.Action.Reenroll.TLSCertNewKey != newPeer.Spec.Action.Reenroll.TLSCertNewKey { 663 update.tlscertNewKeyReenroll = newPeer.Spec.Action.Reenroll.TLSCertNewKey 664 } 665 666 oldVer := version.String(oldPeer.Spec.FabricVersion) 667 newVer := version.String(newPeer.Spec.FabricVersion) 668 669 // check if this V1 -> V2.2.x / V2.4.x peer migration 670 if (oldPeer.Spec.FabricVersion == "" || 671 version.GetMajorReleaseVersion(oldPeer.Spec.FabricVersion) == version.V1) && 672 version.GetMajorReleaseVersion(newPeer.Spec.FabricVersion) == version.V2 { 673 update.migrateToV2 = true 674 if newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1) { 675 update.migrateToV24 = true 676 } 677 } 678 679 // check if this V2.2.x -> V2.4.x peer migration 680 if (version.GetMajorReleaseVersion(oldPeer.Spec.FabricVersion) == version.V2) && 681 oldVer.LessThan(version.V2_4_1) && 682 (newVer.EqualWithoutTag(version.V2_4_1) || newVer.GreaterThan(version.V2_4_1)) { 683 update.migrateToV24 = true 684 } 685 686 if newPeer.Spec.Action.UpgradeDBs == true { 687 update.upgradedbs = true 688 } 689 690 if newPeer.Spec.Action.Enroll.Ecert == true { 691 update.ecertEnroll = true 692 } 693 694 if newPeer.Spec.Action.Enroll.TLSCert == true { 695 update.tlscertEnroll = true 696 } 697 698 if oldPeer.Spec.NodeOUDisabled() != newPeer.Spec.NodeOUDisabled() { 699 update.nodeOUUpdated = true 700 } 701 702 // if use updates NumSecondsWarningPeriod field once we have already run the reconcile 703 // we need to retrigger the timer logic 704 if oldPeer.Spec.NumSecondsWarningPeriod != newPeer.Spec.NumSecondsWarningPeriod { 705 update.ecertUpdated = true 706 update.tlsCertUpdated = true 707 log.Info(fmt.Sprintf("%s NumSecondsWarningPeriod updated", oldPeer.Name)) 708 } 709 710 update.imagesUpdated = imagesUpdated(oldPeer, newPeer) 711 update.fabricVersionUpdated = fabricVersionUpdated(oldPeer, newPeer) 712 713 log.Info(fmt.Sprintf("Spec update triggering reconcile on IBPPeer custom resource %s, update [ %+v ]", oldPeer.Name, update.GetUpdateStackWithTrues())) 714 r.PushUpdate(oldPeer.Name, update) 715 return true 716 717 case *corev1.Secret: 718 oldSecret := e.ObjectOld.(*corev1.Secret) 719 newSecret := e.ObjectNew.(*corev1.Secret) 720 721 if oldSecret.OwnerReferences == nil || len(oldSecret.OwnerReferences) == 0 { 722 isPeerSecret, err := r.AddOwnerReferenceToSecret(oldSecret) 723 if err != nil || !isPeerSecret { 724 return false 725 } 726 } 727 728 if oldSecret.OwnerReferences[0].Kind == KIND { 729 if reflect.DeepEqual(oldSecret.Data, newSecret.Data) { 730 return false 731 } 732 733 log.Info(fmt.Sprintf("Update event detected on secret '%s'", oldSecret.GetName())) 734 instanceName := oldSecret.OwnerReferences[0].Name 735 if util.IsSecretTLSCert(oldSecret.Name) { 736 update.tlsCertUpdated = true 737 log.Info(fmt.Sprintf("TLS cert update detected on IBPPeer custom resource %s", instanceName)) 738 } else if util.IsSecretEcert(oldSecret.Name) { 739 update.ecertUpdated = true 740 log.Info(fmt.Sprintf("ecert update detected on IBPPeer custom resource %s", instanceName)) 741 } else { 742 return false 743 } 744 745 log.Info(fmt.Sprintf("Peer crypto update triggering reconcile on IBPPeer custom resource %s: update [ %+v ]", instanceName, update.GetUpdateStackWithTrues())) 746 r.PushUpdate(instanceName, update) 747 return true 748 } 749 750 case *appsv1.Deployment: 751 oldDeployment := e.ObjectOld.(*appsv1.Deployment) 752 log.Info(fmt.Sprintf("Spec update detected by IBPPeer controller on deployment '%s'", oldDeployment.GetName())) 753 754 case *corev1.ConfigMap: 755 cm := e.ObjectOld.(*corev1.ConfigMap) 756 if cm.Name == "peer-restart-config" { 757 log.Info("Update event detected for peer-restart-config, triggering restart reconcile") 758 return true 759 } 760 761 } 762 763 return false 764 } 765 766 // DeleteFunc will perform any necessary clean up, such as removing artificates that were 767 // left dangling after the deletion of the peer resource 768 func (r *ReconcileIBPPeer) DeleteFunc(e event.DeleteEvent) bool { 769 switch e.Object.(type) { 770 case *current.IBPPeer: 771 peer := e.Object.(*current.IBPPeer) 772 log.Info(fmt.Sprintf("Peer (%s) deleted", peer.GetName())) 773 774 // Deleting this config map manually, in 2.5.1 release of operator this config map was created 775 // without proper controller references set and was not cleaned up on peer resource deletion. 776 log.Info(fmt.Sprintf("Deleting %s-init-config config map, if found", peer.GetName())) 777 if err := r.client.Delete(context.TODO(), &corev1.ConfigMap{ 778 ObjectMeta: metav1.ObjectMeta{ 779 Name: fmt.Sprintf("%s-init-config", peer.GetName()), 780 Namespace: peer.GetNamespace(), 781 }, 782 }); client.IgnoreNotFound(err) != nil { 783 log.Info(fmt.Sprintf("failed to delete config map: %s", err)) 784 } 785 786 case *appsv1.Deployment: 787 dep := e.Object.(*appsv1.Deployment) 788 log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on deployment '%s'", dep.GetName())) 789 case *corev1.Secret: 790 secret := e.Object.(*corev1.Secret) 791 log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on secret '%s'", secret.GetName())) 792 case *corev1.ConfigMap: 793 cm := e.Object.(*corev1.ConfigMap) 794 log.Info(fmt.Sprintf("Delete detected by IBPPeer controller on configmap '%s'", cm.GetName())) 795 796 } 797 798 return true 799 } 800 801 func (r *ReconcileIBPPeer) GetUpdateStatusAtElement(instance *current.IBPPeer, index int) *Update { 802 r.mutex.Lock() 803 defer r.mutex.Unlock() 804 805 update := Update{} 806 _, ok := r.update[instance.GetName()] 807 if !ok { 808 return &update 809 } 810 811 if len(r.update[instance.GetName()]) >= 1 { 812 update = r.update[instance.GetName()][index] 813 } 814 815 return &update 816 } 817 818 func (r *ReconcileIBPPeer) GetUpdateStatus(instance *current.IBPPeer) *Update { 819 return r.GetUpdateStatusAtElement(instance, 0) 820 } 821 822 func (r *ReconcileIBPPeer) PushUpdate(instanceName string, update Update) { 823 r.mutex.Lock() 824 defer r.mutex.Unlock() 825 826 r.update[instanceName] = r.AppendUpdateIfMissing(r.update[instanceName], update) 827 } 828 829 func (r *ReconcileIBPPeer) PopUpdate(instanceName string) *Update { 830 r.mutex.Lock() 831 defer r.mutex.Unlock() 832 833 update := Update{} 834 if len(r.update[instanceName]) >= 1 { 835 update = r.update[instanceName][0] 836 if len(r.update[instanceName]) == 1 { 837 r.update[instanceName] = []Update{} 838 } else { 839 r.update[instanceName] = r.update[instanceName][1:] 840 } 841 } 842 843 return &update 844 } 845 846 func (r *ReconcileIBPPeer) AppendUpdateIfMissing(updates []Update, update Update) []Update { 847 for _, u := range updates { 848 if u == update { 849 return updates 850 } 851 } 852 return append(updates, update) 853 } 854 855 func (r *ReconcileIBPPeer) AddOwnerReferenceToSecret(secret *corev1.Secret) (bool, error) { 856 // Peer secrets we are looking to add owner references to are named: 857 // <prefix>-<instance name>-<type> 858 // <instance name>-init-rootcert 859 860 // The following secrets are created by operator, and will have owner references: 861 // <instance name>-genesis 862 // <instance name>-crypto-backup 863 // <instance name>-secret 864 865 items := strings.Split(secret.Name, "-") 866 if len(items) < 3 { 867 // Secret names we are looking for will be split into at least 3 strings: 868 // [prefix, instance name, type] OR [instance name, "init", "rootcert"] 869 return false, nil 870 } 871 872 // Account for the case where the instance's name is hyphenated 873 var instanceName string 874 if strings.Contains(secret.Name, "-init-rootcert") { 875 instanceName = strings.Join(items[:len(items)-2], "-") // instance name contains all but last 2 items 876 } else { 877 instanceName = strings.Join(items[1:len(items)-1], "-") // instance name contains all but first and last item 878 } 879 880 listOptions := &client.ListOptions{ 881 Namespace: secret.Namespace, 882 } 883 884 peerList := ¤t.IBPPeerList{} 885 err := r.client.List(context.TODO(), peerList, listOptions) 886 if err != nil { 887 return false, errors.Wrap(err, "failed to get list of peers") 888 } 889 890 for _, o := range peerList.Items { 891 peer := o 892 if peer.Name == instanceName { 893 // Instance 'i' found in list of orderers 894 err := r.client.Update(context.TODO(), secret, controllerclient.UpdateOption{ 895 Owner: &peer, 896 Scheme: r.scheme, 897 }) 898 if err != nil { 899 return false, err 900 } 901 return true, nil 902 } 903 } 904 905 return false, nil 906 } 907 908 func (r *ReconcileIBPPeer) SetupWithManager(mgr ctrl.Manager) error { 909 return ctrl.NewControllerManagedBy(mgr). 910 For(¤t.IBPPeer{}). 911 Complete(r) 912 } 913 914 func GetUpdateStack(allUpdates map[string][]Update) string { 915 stack := "" 916 917 for orderer, updates := range allUpdates { 918 currentStack := "" 919 for index, update := range updates { 920 currentStack += fmt.Sprintf("{ %s}", update.GetUpdateStackWithTrues()) 921 if index != len(updates)-1 { 922 currentStack += " , " 923 } 924 } 925 stack += fmt.Sprintf("%s: [ %s ] ", orderer, currentStack) 926 } 927 928 return stack 929 } 930 931 func (r *ReconcileIBPPeer) ReconcileRestart(namespace string) (bool, error) { 932 requeue, err := r.RestartService.Reconcile("peer", namespace) 933 if err != nil { 934 log.Error(err, "failed to reconcile restart queues in peer-restart-config") 935 return false, err 936 } 937 938 return requeue, nil 939 }