Files
my-rook-config/pkg/operator/ceph/cluster/watcher.go
T
2025-01-24 15:59:07 +01:00

707 lines
24 KiB
Go

/*
Copyright 2020 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cluster to manage a Ceph cluster.
package cluster
import (
"context"
"encoding/json"
stderrors "errors"
"fmt"
"strings"
"time"
addonsv1alpha1 "github.com/csi-addons/kubernetes-csi-addons/api/csiaddons/v1alpha1"
pkgerror "github.com/pkg/errors"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
"github.com/rook/rook/pkg/clusterd"
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
discoverDaemon "github.com/rook/rook/pkg/daemon/discover"
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
"github.com/rook/rook/pkg/operator/k8sutil"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// clientCluster struct contains a client to interact with Kubernetes object
// as well as the NamespacedName (used in requests)
type clientCluster struct {
client client.Client
namespace string
context *clusterd.Context
}
var (
nodesCheckedForReconcile = sets.New[string]()
networkFenceLabel = "cephClusterUID"
errActiveClientNotFound = stderrors.New("active client not found")
)
// drivers that supports fencing, used in naming networkFence object
const (
rbdDriver = "rbd"
cephfsDriver = "cephfs"
)
func newClientCluster(client client.Client, namespace string, context *clusterd.Context) *clientCluster {
return &clientCluster{
client: client,
namespace: namespace,
context: context,
}
}
func checkStorageForNode(cluster *cephv1.CephCluster) bool {
if !cluster.Spec.Storage.UseAllNodes && len(cluster.Spec.Storage.Nodes) == 0 && len(cluster.Spec.Storage.StorageClassDeviceSets) == 0 {
logger.Debugf("node watcher: useAllNodes is set to false and no nodes storageClassDevicesets or volumeSources are specified in cluster %q, skipping", cluster.Namespace)
return false
}
return true
}
// onK8sNode is triggered when a node is added in the Kubernetes cluster
func (c *clientCluster) onK8sNode(ctx context.Context, object runtime.Object, opNamespace string) bool {
node, ok := object.(*corev1.Node)
if !ok {
return false
}
// Get CephCluster
cluster := c.getCephCluster()
// Continue reconcile in case of failure too since we don't want to block other node reconcile
// if err := c.handleNodeFailure(ctx, cluster, node, opNamespace); err != nil {
// logger.Errorf("failed to handle node failure. %v", err)
// }
// skip reconcile if node is already checked in a previous reconcile
if nodesCheckedForReconcile.Has(node.Name) {
return false
}
if !k8sutil.GetNodeSchedulable(*node, false) {
logger.Debugf("node watcher: skipping cluster update. added node %q is unschedulable", node.Labels[k8sutil.LabelHostname()])
return false
}
if !k8sutil.NodeIsTolerable(*node, cephv1.GetOSDPlacement(cluster.Spec.Placement).Tolerations, false) {
logger.Debugf("node watcher: node %q is not tolerable for cluster %q, skipping", node.Name, cluster.Namespace)
return false
}
if !checkStorageForNode(cluster) {
nodesCheckedForReconcile.Insert(node.Name)
return false
}
// Too strict? this replaces clusterInfo == nil
if cluster.Status.Phase != cephv1.ConditionReady {
logger.Debugf("node watcher: cluster %q is not ready. skipping orchestration", cluster.Namespace)
return false
}
logger.Debugf("node %q is ready, checking if it can run OSDs", node.Name)
nodesCheckedForReconcile.Insert(node.Name)
err := k8sutil.ValidNode(*node, cephv1.GetOSDPlacement(cluster.Spec.Placement), cluster.Spec.Storage.ScheduleAlways)
if err == nil {
nodeName := node.Name
hostname, ok := node.Labels[k8sutil.LabelHostname()]
if ok && hostname != "" {
nodeName = hostname
}
// Make sure we can call Ceph properly
// Is the node in the CRUSH map already?
// If so we don't need to reconcile, this is done to avoid double reconcile on operator restart
// Assume the admin key since we are watching for node status to create OSDs
clusterInfo := cephclient.AdminClusterInfo(ctx, cluster.Namespace, cluster.Name)
osds, err := cephclient.GetOSDOnHost(c.context, clusterInfo, nodeName)
if err != nil {
if strings.Contains(err.Error(), opcontroller.UninitializedCephConfigError) {
logger.Debug(opcontroller.OperatorNotInitializedMessage)
return false
}
// If it fails, this might be due to the operator just starting and catching an add event for that node
logger.Debugf("failed to get osds on node %q, assume reconcile is necessary", nodeName)
return true
}
// Reconcile if there are no OSDs in the CRUSH map and if the host does not exist in the CRUSH map.
if osds == "" {
logger.Infof("node watcher: adding node %q to cluster %q", node.Labels[k8sutil.LabelHostname()], cluster.Namespace)
return true
}
// This is Debug level because the node receives frequent updates and this will pollute the logs
logger.Debugf("node watcher: node %q is already an OSD node with %q", nodeName, osds)
}
return false
}
func (c *clientCluster) handleNodeFailure(ctx context.Context, cluster *cephv1.CephCluster, node *corev1.Node, opNamespace string) error {
watchForNodeLoss, err := k8sutil.GetOperatorSetting(ctx, c.context.Clientset, opcontroller.OperatorSettingConfigMapName, "ROOK_WATCH_FOR_NODE_FAILURE", "true")
if err != nil {
return pkgerror.Wrapf(err, "failed to get configmap value `ROOK_WATCH_FOR_NODE_FAILURE`.")
}
if strings.ToLower(watchForNodeLoss) != "true" {
logger.Debugf("not watching for node failures since `ROOK_WATCH_FOR_NODE_FAILURE` is set to %q", watchForNodeLoss)
return nil
}
disabledCSI, err := k8sutil.GetOperatorSetting(ctx, c.context.Clientset, opcontroller.OperatorSettingConfigMapName, "ROOK_CSI_DISABLE_DRIVER", "false")
if err != nil {
return pkgerror.Wrapf(err, "failed to get configmap value `ROOK_CSI_DISABLE_DRIVER`.")
}
if strings.ToLower(disabledCSI) != "false" {
logger.Debugf("not watching for node failures since `ROOK_CSI_DISABLE_DRIVER` is set to %q, skip creating networkFence", disabledCSI)
return nil
}
_, err = c.context.ApiExtensionsClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, "networkfences.csiaddons.openshift.io", metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
logger.Debug("networkfences.csiaddons.openshift.io CRD not found, skip creating networkFence")
return nil
}
return pkgerror.Wrapf(err, "failed to get networkfences.csiaddons.openshift.io CRD, skip creating networkFence")
}
nodeHasOutOfServiceTaint := false
for _, taint := range node.Spec.Taints {
if taint.Key == corev1.TaintNodeOutOfService {
nodeHasOutOfServiceTaint = true
logger.Infof("Found taint: Key=%v, Value=%v on node %s\n", taint.Key, taint.Value, node.Name)
break
}
}
if nodeHasOutOfServiceTaint {
err := c.fenceNode(ctx, node, cluster, opNamespace)
if err != nil {
return pkgerror.Wrapf(err, "failed to create network fence for node %q.", node.Name)
}
return nil
}
err = c.unfenceAndDeleteNetworkFence(ctx, *node, cluster, rbdDriver)
if err != nil {
return pkgerror.Wrapf(err, "failed to delete rbd network fence for node %q.", node.Name)
}
err = c.unfenceAndDeleteNetworkFence(ctx, *node, cluster, cephfsDriver)
if err != nil {
return pkgerror.Wrapf(err, "failed to delete cephFS network fence for node %q.", node.Name)
}
return nil
}
func (c *clientCluster) fenceNode(ctx context.Context, node *corev1.Node, cluster *cephv1.CephCluster, opNamespace string) error {
volumesInuse := node.Status.VolumesInUse
if len(volumesInuse) == 0 {
logger.Debugf("no volumes in use for node %q", node.Name)
return nil
}
logger.Debugf("volumesInuse %s", volumesInuse)
rbdVolumesInUse, cephFSVolumeInUse := getCephVolumesInUse(cluster, volumesInuse, opNamespace)
if len(rbdVolumesInUse) == 0 && len(cephFSVolumeInUse) == 0 {
logger.Debugf("no rbd or cephfs subvolumes in use for out of service node %q", node.Name)
return nil
}
listPVs, err := c.context.Clientset.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{})
if err != nil {
return pkgerror.Wrapf(err, "failed to list PV")
}
if len(rbdVolumesInUse) != 0 {
rbdPVList := listRBDPV(listPVs, cluster, rbdVolumesInUse, opNamespace)
if len(rbdPVList) == 0 {
logger.Debug("No rbd PVs found on the node")
} else {
logger.Infof("node %q require fencing, found rbd volumes in use", node.Name)
clusterInfo, _, _, err := opcontroller.LoadClusterInfo(c.context, ctx, cluster.Namespace, &cluster.Spec)
if err != nil {
return pkgerror.Wrapf(err, "Failed to load cluster info.")
}
for i := range rbdPVList {
err = c.fenceRbdImage(ctx, node, cluster, clusterInfo, rbdPVList[i])
// We only need to create the network fence for any one of rbd pv.
if err == nil {
break
}
// continue to fence next rbd volume if active client not found
if stderrors.Is(err, errActiveClientNotFound) {
continue
}
if i == len(rbdPVList)-1 {
return pkgerror.Wrapf(err, "failed to fence rbd volumes")
}
logger.Errorf("failed to fence rbd volumes %q, trying next rbd volume", rbdPVList[i].Name)
}
}
}
if len(cephFSVolumeInUse) != 0 {
cephFSVolumeInUseMap := make(map[string]struct{})
for _, vol := range cephFSVolumeInUse {
cephFSVolumeInUseMap[vol] = struct{}{}
}
cephFSPVList := listRWOCephFSPV(listPVs, cluster, cephFSVolumeInUseMap, opNamespace)
if len(cephFSPVList) == 0 {
logger.Debug("No cephfs PVs found on the node %s", node.Name)
return nil
}
logger.Infof("node %q require fencing, found cephfs subvolumes in use", node.Name)
clusterInfo, _, _, err := opcontroller.LoadClusterInfo(c.context, ctx, cluster.Namespace, &cluster.Spec)
if err != nil {
return pkgerror.Wrap(err, "Failed to load cluster info.")
}
for i := range cephFSPVList {
err = c.fenceCephFSSubvolume(ctx, node, cluster, clusterInfo, cephFSPVList[i])
// We only need to create the network fence for any one of cephFS pv.
if err == nil {
break
}
// continue to fence next cephfs subvolume if active client not found
if stderrors.Is(err, errActiveClientNotFound) {
continue
}
if i == len(cephFSPVList)-1 {
return pkgerror.Wrap(err, "failed to fence cephfs subvolumes")
}
logger.Errorf("failed to fence cephfs subvolume %q, trying next cephfs subvolume", cephFSPVList[i].Name)
}
}
return nil
}
func getCephVolumesInUse(cluster *cephv1.CephCluster, volumesInUse []corev1.UniqueVolumeName, opNamespace string) ([]string, []string) {
var rbdVolumesInUse, cephFSVolumeInUse []string
for _, volume := range volumesInUse {
splitVolumeInUseBased := trimeVolumeInUse(volume)
logger.Infof("volumeInUse after split based on '^' %v", splitVolumeInUseBased)
if len(splitVolumeInUseBased) == 2 && splitVolumeInUseBased[0] == fmt.Sprintf("%s.rbd.csi.ceph.com", opNamespace) {
rbdVolumesInUse = append(rbdVolumesInUse, splitVolumeInUseBased[1])
}
if len(splitVolumeInUseBased) == 2 && splitVolumeInUseBased[0] == fmt.Sprintf("%s.cephfs.csi.ceph.com", opNamespace) {
cephFSVolumeInUse = append(cephFSVolumeInUse, splitVolumeInUseBased[1])
}
}
return rbdVolumesInUse, cephFSVolumeInUse
}
func trimeVolumeInUse(volume corev1.UniqueVolumeName) []string {
volumesInuseRemoveK8sPrefix := strings.TrimPrefix(string(volume), "kubernetes.io/csi/")
splitVolumeInUseBased := strings.Split(volumesInuseRemoveK8sPrefix, "^")
return splitVolumeInUseBased
}
func listRBDPV(listPVs *corev1.PersistentVolumeList, cluster *cephv1.CephCluster, rbdVolumesInUse []string, opNamespace string) []corev1.PersistentVolume {
var listRbdPV []corev1.PersistentVolume
for _, pv := range listPVs.Items {
// Skip if pv is not provisioned by CSI
if pv.Spec.CSI == nil {
logger.Debugf("pv %q is not provisioned by CSI", pv.Name)
continue
}
if pv.Spec.CSI.Driver == fmt.Sprintf("%s.rbd.csi.ceph.com", opNamespace) {
// Ignore PVs that support multinode access (RWX, ROX), since they can be mounted on multiple nodes.
if pvSupportsMultiNodeAccess(pv.Spec.AccessModes) {
continue
}
if pv.Spec.CSI.VolumeAttributes["staticVolume"] == "true" || pv.Spec.CSI.VolumeAttributes["pool"] == "" || pv.Spec.CSI.VolumeAttributes["imageName"] == "" {
logger.Debugf("skipping, static pv %q", pv.Name)
continue
}
for _, rbdVolume := range rbdVolumesInUse {
if pv.Spec.CSI.VolumeHandle == rbdVolume {
listRbdPV = append(listRbdPV, pv)
}
}
}
}
return listRbdPV
}
func listRWOCephFSPV(listPVs *corev1.PersistentVolumeList, cluster *cephv1.CephCluster, cephFSVolumesInUse map[string]struct{}, opNamespace string) []corev1.PersistentVolume {
var listCephFSPV []corev1.PersistentVolume
for _, pv := range listPVs.Items {
// Skip if pv is not provisioned by CSI
if pv.Spec.CSI == nil {
logger.Debugf("PV %q is not provisioned by CSI", pv.Name)
continue
}
if pv.Spec.CSI.Driver == fmt.Sprintf("%s.cephfs.csi.ceph.com", opNamespace) {
// Ignore PVs that support multinode access (RWX, ROX), since they can be mounted on multiple nodes.
if pvSupportsMultiNodeAccess(pv.Spec.AccessModes) {
continue
}
if pv.Spec.CSI.VolumeAttributes["staticVolume"] == "true" {
logger.Debugf("skipping, static PV %q", pv.Name)
continue
}
// Check if the volume is in use
if _, exists := cephFSVolumesInUse[pv.Spec.CSI.VolumeHandle]; exists {
listCephFSPV = append(listCephFSPV, pv)
}
}
}
return listCephFSPV
}
// pvSupportsMultiNodeAccess returns true if the PV access modes contain ReadWriteMany or ReadOnlyMany.
func pvSupportsMultiNodeAccess(accessModes []corev1.PersistentVolumeAccessMode) bool {
for _, accessMode := range accessModes {
switch accessMode {
case corev1.ReadOnlyMany, corev1.ReadWriteMany:
return true
}
}
return false
}
func (c *clientCluster) fenceRbdImage(
ctx context.Context, node *corev1.Node, cluster *cephv1.CephCluster,
clusterInfo *cephclient.ClusterInfo, rbdPV corev1.PersistentVolume) error {
logger.Debugf("rbd PV NAME %v", rbdPV.Spec.CSI.VolumeAttributes)
args := []string{"status", fmt.Sprintf("%s/%s", rbdPV.Spec.CSI.VolumeAttributes["pool"], rbdPV.Spec.CSI.VolumeAttributes["imageName"])}
cmd := cephclient.NewRBDCommand(c.context, clusterInfo, args)
cmd.JsonOutput = true
buf, err := cmd.Run()
if err != nil {
return pkgerror.Wrapf(err, "failed to list watchers for pool/imageName %s/%s.", rbdPV.Spec.CSI.VolumeAttributes["pool"], rbdPV.Spec.CSI.VolumeAttributes["imageName"])
}
ips, err := rbdStatusUnMarshal(buf)
if err != nil {
return pkgerror.Wrapf(err, "failed to unmarshal rbd status output")
}
if len(ips) == 0 {
logger.Infof("no active rbd clients found for rbd volume %q", rbdPV.Name)
return errActiveClientNotFound
}
err = c.createNetworkFence(ctx, rbdPV, node, cluster, ips, rbdDriver)
if err != nil {
return pkgerror.Wrapf(err, "failed to create network fence for node %q", node.Name)
}
return nil
}
func (c *clientCluster) fenceCephFSSubvolume(
ctx context.Context, node *corev1.Node, cluster *cephv1.CephCluster,
clusterInfo *cephclient.ClusterInfo, cephFSPV corev1.PersistentVolume) error {
logger.Infof("fencing cephfs subvolume %q on node %q", cephFSPV.Name, node.Name)
status, err := cephclient.StatusWithUser(c.context, clusterInfo)
if err != nil {
return pkgerror.Wrap(err, "failed to get ceph status for check active mds")
}
var activeMDS string
for _, fsRank := range status.Fsmap.ByRank {
if fsRank.Status == "up:active" {
activeMDS = fsRank.Name
}
}
args := []string{"tell", fmt.Sprintf("mds.%s", activeMDS), "client", "ls", "--format", "json"}
cmd := cephclient.NewCephCommand(c.context, clusterInfo, args)
cmd.JsonOutput = true
buf, err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to list watchers for cephfs subvolumeName %s. %v", cephFSPV.Spec.CSI.VolumeAttributes["subvolumeName"], err)
}
ips, err := cephFSMDSClientMarshal(buf, node.Name, cephFSPV)
if err != nil {
return pkgerror.Wrap(err, "failed to unmarshal cephfs mds output")
}
if len(ips) == 0 {
logger.Infof("no active mds clients found for cephfs subvolume %q", cephFSPV.Name)
return errActiveClientNotFound
}
err = c.createNetworkFence(ctx, cephFSPV, node, cluster, ips, cephfsDriver)
if err != nil {
return pkgerror.Wrapf(err, "failed to create network fence for node %q", node.Name)
}
return nil
}
func cephFSMDSClientMarshal(output []byte, nodeName string, cephFSPV corev1.PersistentVolume) ([]string, error) {
type entity struct {
Addr struct {
Addr string `json:"addr"`
Nonce int `json:"nonce"`
} `json:"addr"`
}
type clientMetadata struct {
Root string `json:"root"`
Hostname string `json:"hostname"`
}
type cephFSData struct {
Entity entity `json:"entity"`
ClientMetadata clientMetadata `json:"client_metadata"`
}
var data []cephFSData
err := json.Unmarshal([]byte(output), &data)
if err != nil {
return []string{}, pkgerror.Wrapf(err, "failed to unmarshal cephFS data output")
}
watcherIPlist := []string{}
for _, d := range data {
if d.ClientMetadata.Hostname != "" {
if strings.Contains(nodeName, d.ClientMetadata.Hostname) && cephFSPV.Spec.CSI.VolumeAttributes["subvolumePath"] == d.ClientMetadata.Root {
logger.Infof("cephfs mds client ips to fence %v", d.Entity.Addr)
watcherIP := concatenateWatcherIp(d.Entity.Addr.Addr)
watcherIPlist = append(watcherIPlist, watcherIP)
break
}
}
}
return watcherIPlist, nil
}
func rbdStatusUnMarshal(output []byte) ([]string, error) {
type rbdStatus struct {
Watchers []struct {
Address string `json:"address"`
} `json:"watchers"`
}
var rbdStatusObj rbdStatus
err := json.Unmarshal([]byte(output), &rbdStatusObj)
if err != nil {
return []string{}, pkgerror.Wrapf(err, "failed to unmarshal rbd status output")
}
watcherIPlist := []string{}
for _, watcher := range rbdStatusObj.Watchers {
watcherIP := concatenateWatcherIp(watcher.Address)
watcherIPlist = append(watcherIPlist, watcherIP)
}
return watcherIPlist, nil
}
func concatenateWatcherIp(address string) string {
// address is in format `10.63.0.5:0/1254753579` for rbd and
// in the format '10.244.0.12:0' for cephfs
// split with separation ':0' to remove nounce and concatenating `/32` to define a network with only one IP address
watcherIP := strings.Split(address, ":0")[0] + "/32"
return watcherIP
}
func fenceResourceName(nodeName, driver, namespace string) string {
return fmt.Sprintf("%s-%s-%s", nodeName, driver, namespace)
}
func (c *clientCluster) createNetworkFence(ctx context.Context, pv corev1.PersistentVolume, node *corev1.Node, cluster *cephv1.CephCluster, cidr []string, driver string) error {
logger.Warningf("Blocking node IP %s", cidr)
secretName := pv.Annotations["volume.kubernetes.io/provisioner-deletion-secret-name"]
secretNameSpace := pv.Annotations["volume.kubernetes.io/provisioner-deletion-secret-namespace"]
if secretName == "" || secretNameSpace == "" {
storageClass, err := c.context.Clientset.StorageV1().StorageClasses().Get(ctx, pv.Spec.StorageClassName, metav1.GetOptions{})
if err != nil {
return pkgerror.Wrap(err, "failed to get storage class to fence volume")
}
secretName = storageClass.Parameters["csi.storage.k8s.io/provisioner-secret-name"]
secretNameSpace = storageClass.Parameters["csi.storage.k8s.io/provisioner-secret-namespace"]
}
networkFence := &addonsv1alpha1.NetworkFence{
ObjectMeta: metav1.ObjectMeta{
Name: fenceResourceName(node.Name, driver, cluster.Namespace),
Labels: map[string]string{
networkFenceLabel: string(cluster.GetUID()),
},
},
Spec: addonsv1alpha1.NetworkFenceSpec{
Driver: pv.Spec.CSI.Driver,
FenceState: addonsv1alpha1.Fenced,
Secret: addonsv1alpha1.SecretSpec{
Name: secretName,
Namespace: secretNameSpace,
},
Cidrs: cidr,
Parameters: map[string]string{
"clusterID": pv.Spec.CSI.VolumeAttributes["clusterID"],
},
},
}
err := c.client.Create(ctx, networkFence)
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
logger.Infof("successfully created network fence CR for node %q", node.Name)
return nil
}
func (c *clientCluster) unfenceAndDeleteNetworkFence(ctx context.Context, node corev1.Node, cluster *cephv1.CephCluster, driver string) error {
networkFence := &addonsv1alpha1.NetworkFence{}
err := c.client.Get(ctx, types.NamespacedName{Name: fenceResourceName(node.Name, driver, cluster.Namespace)}, networkFence)
if err != nil && !errors.IsNotFound(err) {
return err
} else if errors.IsNotFound(err) {
return nil
}
logger.Infof("node %s does not have taint %s, unfencing networkFence CR", node.Name, corev1.TaintNodeOutOfService)
// Unfencing is required to unblock the node and then delete the network fence CR
networkFence.Spec.FenceState = addonsv1alpha1.Unfenced
err = c.client.Update(ctx, networkFence)
if err != nil {
logger.Errorf("failed to unFence network fence CR. %v", err)
return err
}
err = wait.PollUntilContextTimeout(ctx, 2*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
err = c.client.Get(ctx, types.NamespacedName{Name: fenceResourceName(node.Name, driver, cluster.Namespace)}, networkFence)
if err != nil && !errors.IsNotFound(err) {
return false, err
}
if networkFence.Status.Message != addonsv1alpha1.UnFenceOperationSuccessfulMessage {
logger.Infof("waiting for network fence CR %q status to get result %q", networkFence.Name, addonsv1alpha1.UnFenceOperationSuccessfulMessage)
return false, err
}
logger.Infof("successfully unfenced %q network fence cr %q, proceeding with deletion", driver, networkFence.Name)
err = c.client.Delete(ctx, networkFence)
if err == nil || errors.IsNotFound(err) {
logger.Infof("successfully deleted network fence CR %s", networkFence.Name)
return true, nil
}
return false, nil
})
if err != nil {
return pkgerror.Wrapf(err, "timeout out deleting the %s network fence CR %s", driver, networkFence.Name)
}
return nil
}
// onDeviceCMUpdate is trigger when the hot plug config map is updated
func (c *clientCluster) onDeviceCMUpdate(oldObj, newObj runtime.Object) bool {
oldCm, ok := oldObj.(*corev1.ConfigMap)
if !ok {
return false
}
logger.Debugf("hot-plug cm watcher: onDeviceCMUpdate old device cm: %+v", oldCm)
newCm, ok := newObj.(*corev1.ConfigMap)
if !ok {
return false
}
logger.Debugf("hot-plug cm watcher: onDeviceCMUpdate new device cm: %+v", newCm)
oldDevStr, ok := oldCm.Data[discoverDaemon.LocalDiskCMData]
if !ok {
logger.Warning("hot-plug cm watcher: unexpected old configmap data")
return false
}
newDevStr, ok := newCm.Data[discoverDaemon.LocalDiskCMData]
if !ok {
logger.Warning("hot-plug cm watcher: unexpected new configmap data")
return false
}
devicesEqual, err := discoverDaemon.DeviceListsEqual(oldDevStr, newDevStr)
if err != nil {
logger.Warningf("hot-plug cm watcher: failed to compare device lists. %v", err)
return false
}
if devicesEqual {
logger.Debug("hot-plug cm watcher: device lists are equal. skipping orchestration")
return false
}
// Get CephCluster
cluster := c.getCephCluster()
if cluster.Status.Phase != cephv1.ConditionReady {
logger.Debugf("hot-plug cm watcher: cluster %q is not ready. skipping orchestration.", cluster.Namespace)
return false
}
if len(cluster.Spec.Storage.StorageClassDeviceSets) > 0 {
logger.Info("hot-plug cm watcher: skip orchestration on device config map update for OSDs on PVC")
return false
}
logger.Infof("hot-plug cm watcher: running orchestration for namespace %q after device change", cluster.Namespace)
return true
}
func (c *clientCluster) getCephCluster() *cephv1.CephCluster {
clusterList := &cephv1.CephClusterList{}
err := c.client.List(context.TODO(), clusterList, client.InNamespace(c.namespace))
if err != nil {
logger.Debugf("%q: failed to fetch CephCluster %v", controllerName, err)
return &cephv1.CephCluster{}
}
if len(clusterList.Items) == 0 {
logger.Debugf("%q: no CephCluster resource found in namespace %q", controllerName, c.namespace)
return &cephv1.CephCluster{}
}
return &clusterList.Items[0]
}