forked from rook/rook
Merge pull request #7726 from sp98/pdb-controller-changes
ceph: update podDisruptionBudget controller for OSD
This commit is contained in:
@@ -18,7 +18,7 @@ Even if an upgrade agent were only to drain one node at a time, Ceph would have
|
||||
The failure domain will be determined by the smallest failure domain of all the Ceph Pools in that cluster.
|
||||
We begin with creating a single PodDisruptionBudget for all the OSD with maxUnavailable=1. This will allow one OSD to go down anytime. Once the user drains a node and an OSD goes down, we determine the failure domain for the draining OSD (using the OSD deployment labels). Then we create blocking PodDisruptionBudgets (maxUnavailable=0) for all other failure domains and delete the main PodDisruptionBudget. This blocks OSDs from going down in multiple failure domains simultaneously.
|
||||
|
||||
Once the drained OSDs are back and all the pgs are active+clean, that is, the cluster is healed, the main PodDisruptionBudget is added back and the blocking ones are deleted. User can also add a timeout for the pgs to become healthy. If the timeout exceeds, the operator will ignore the pg health, add the main PodDisruptionBudget and delete the blocking ones.
|
||||
Once the drained OSDs are back and all the pgs are active+clean, that is, the cluster is healed, the default PodDisruptionBudget (with maxUnavailable=1) is added back and the blocking ones are deleted. User can also add a timeout for the pgs to become healthy. If the timeout exceeds, the operator will ignore the pg health, add the main PodDisruptionBudget and delete the blocking ones.
|
||||
|
||||
Detecting drains is not easy as they are a client side operation. The client cordons the node and continuously attempts to evict all pods from the node until it succeeds. Whenever an OSD goes into pending state, that is, `ReadyReplicas` count is 0, we assume that some drain operation is happening.
|
||||
|
||||
@@ -52,6 +52,8 @@ a common way of deploying kubernetes. This will also work to mitigate manual dra
|
||||
|
||||
When an node is drained, we will also delay it's DOWN/OUT process by placing a noout on that node. We will remove that noout after a timeout.
|
||||
|
||||
An OSD can be down due to reasons other than node drain, say, disk failure. In such a situation, if the pgs are unhealthy then rook will create a blocking PodDisruptionBudget on other failure domains to prevent further node drains on them. `noout` flag won't be set on node this is case. If the OSD is down but all the pgs are `active+clean`, the cluster will be treated as fully healthy. The default PodDisruptionBudget (with maxUnavailable=1) will be added back and the blocking ones will be deleted.
|
||||
|
||||
### Mon, Mgr, MDS, RGW, RBDMirror
|
||||
|
||||
Since there is no strict failure domain requirement for each of these, and they are not logically grouped, a stactic PDB will suffice.
|
||||
|
||||
@@ -46,6 +46,7 @@ const (
|
||||
osdPDBAppName = "rook-ceph-osd"
|
||||
drainingFailureDomainKey = "draining-failure-domain"
|
||||
drainingFailureDomainDurationKey = "draining-failure-domain-duration"
|
||||
setNoOut = "set-no-out"
|
||||
pgHealthCheckDurationKey = "pg-health-check-duration"
|
||||
// DefaultMaintenanceTimeout is the period for which a drained failure domain will remain in noout
|
||||
DefaultMaintenanceTimeout = 30 * time.Minute
|
||||
@@ -68,9 +69,9 @@ func (r *ReconcileClusterDisruption) deletePDB(pdb *policyv1beta1.PodDisruptionB
|
||||
return nil
|
||||
}
|
||||
|
||||
// createOverallPDBforOSD creates a single PDB for all OSDs with maxUnavailable=1
|
||||
// createDefaultPDBforOSD creates a single PDB for all OSDs with maxUnavailable=1
|
||||
// This allows all OSDs in a single failure domain to go down.
|
||||
func (r *ReconcileClusterDisruption) createOverallPDBforOSD(namespace string) error {
|
||||
func (r *ReconcileClusterDisruption) createDefaultPDBforOSD(namespace string) error {
|
||||
cephCluster, ok := r.clusterMap.GetCluster(namespace)
|
||||
if !ok {
|
||||
return errors.Errorf("failed to find the namespace %q in the clustermap", namespace)
|
||||
@@ -97,8 +98,8 @@ func (r *ReconcileClusterDisruption) createOverallPDBforOSD(namespace string) er
|
||||
err = r.client.Get(context.TODO(), pdbRequest, &policyv1beta1.PodDisruptionBudget{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
logger.Info("all osds are up. pg health is active+clean")
|
||||
logger.Infof("creating the main pdb %q with maxUnavailable=1 for all osd", osdPDBAppName)
|
||||
logger.Info("all PGs are active+clean. Restoring default OSD pdb settings")
|
||||
logger.Infof("creating the default pdb %q with maxUnavailable=1 for all osd", osdPDBAppName)
|
||||
return r.createPDB(pdb)
|
||||
}
|
||||
return errors.Wrapf(err, "failed to get pdb %q", pdb.Name)
|
||||
@@ -106,7 +107,7 @@ func (r *ReconcileClusterDisruption) createOverallPDBforOSD(namespace string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterDisruption) deleteOverallPDBforOSD(namespace string) error {
|
||||
func (r *ReconcileClusterDisruption) deleteDefaultPDBforOSD(namespace string) error {
|
||||
pdbRequest := types.NamespacedName{Name: osdPDBAppName, Namespace: namespace}
|
||||
pdb := &policyv1beta1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -121,7 +122,7 @@ func (r *ReconcileClusterDisruption) deleteOverallPDBforOSD(namespace string) er
|
||||
}
|
||||
return errors.Wrapf(err, "failed to get pdb %q", pdb.Name)
|
||||
}
|
||||
logger.Infof("deleting the main pdb %q with maxUnavailable=1 for all osd", osdPDBAppName)
|
||||
logger.Infof("deleting the default pdb %q with maxUnavailable=1 for all osd", osdPDBAppName)
|
||||
return r.deletePDB(pdb)
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ func (r *ReconcileClusterDisruption) initializePDBState(request reconcile.Reques
|
||||
|
||||
if apierrors.IsNotFound(err) {
|
||||
// create configmap to track the draining failure domain
|
||||
pdbStateMap.Data = map[string]string{drainingFailureDomainKey: ""}
|
||||
pdbStateMap.Data = map[string]string{drainingFailureDomainKey: "", setNoOut: ""}
|
||||
err := r.client.Create(context.TODO(), pdbStateMap)
|
||||
if err != nil {
|
||||
return pdbStateMap, errors.Wrapf(err, "failed to create the PDB state map %q", pdbStateMapRequest)
|
||||
@@ -215,74 +216,100 @@ func (r *ReconcileClusterDisruption) reconcilePDBsForOSDs(
|
||||
pdbStateMap *corev1.ConfigMap,
|
||||
failureDomainType string,
|
||||
allFailureDomains,
|
||||
drainingFailureDomains []string,
|
||||
) error {
|
||||
osdDownFailureDomains []string,
|
||||
activeNodeDrains bool,
|
||||
) (reconcile.Result, error) {
|
||||
var osdDown bool
|
||||
var drainingFailureDomain string
|
||||
if len(osdDownFailureDomains) > 0 {
|
||||
osdDown = true
|
||||
drainingFailureDomain = osdDownFailureDomains[0]
|
||||
}
|
||||
|
||||
pgHealthMsg, clean, err := cephclient.IsClusterClean(r.context.ClusterdContext, clusterInfo)
|
||||
pgHealthMsg, pgClean, err := cephclient.IsClusterClean(r.context.ClusterdContext, clusterInfo)
|
||||
if err != nil {
|
||||
// If the error contains that message, this means the cluster is not up and running
|
||||
// No monitors are present and thus no ceph configuration has been created
|
||||
if strings.Contains(err.Error(), opcontroller.UninitializedCephConfigError) {
|
||||
logger.Debugf("Ceph %q cluster not ready, cannot check Ceph status yet.", request.Namespace)
|
||||
return nil
|
||||
logger.Infof("Ceph %q cluster not ready, cannot check Ceph status yet.", request.Namespace)
|
||||
return opcontroller.WaitForRequeueIfOperatorNotInitialized, nil
|
||||
}
|
||||
return errors.Wrapf(err, "failed to check cluster health")
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to check cluster health")
|
||||
}
|
||||
|
||||
if pdbStateMap.Data == nil {
|
||||
pdbStateMap.Data = make(map[string]string)
|
||||
}
|
||||
_, ok := pdbStateMap.Data[drainingFailureDomainKey]
|
||||
if !ok {
|
||||
pdbStateMap.Data[drainingFailureDomainKey] = ""
|
||||
}
|
||||
|
||||
var drainingFailureDomain string
|
||||
activeDrains := len(drainingFailureDomains) > 0
|
||||
|
||||
// create blocking PDBs for all the OSDs that are not being drained right now
|
||||
if activeDrains {
|
||||
drainingFailureDomain = drainingFailureDomains[0]
|
||||
logger.Infof("all %q failure domains: %v. currently draining: %q. pg health: %q", failureDomainType,
|
||||
allFailureDomains, drainingFailureDomain, pgHealthMsg)
|
||||
err := r.handleActiveDrains(allFailureDomains, drainingFailureDomain, failureDomainType, clusterInfo.Namespace, clean)
|
||||
switch {
|
||||
// osd is down but pgs are active+clean
|
||||
case osdDown && pgClean:
|
||||
lastDrainTimeStamp, err := getLastDrainTimeStamp(pdbStateMap, drainingFailureDomainDurationKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to handle active drains")
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to get last drain timestamp from the configmap %q", pdbStateMap.Name)
|
||||
}
|
||||
timeSinceOSDDown := time.Since(lastDrainTimeStamp)
|
||||
if timeSinceOSDDown > 30*time.Second {
|
||||
logger.Infof("osd is down in failure domain %q is down for the last %.2f minutes, but pgs are active+clean", drainingFailureDomain, timeSinceOSDDown.Minutes())
|
||||
resetPDBConfig(pdbStateMap)
|
||||
} else {
|
||||
logger.Infof("osd is down in the failure domain %q, but pgs are active+clean. Requeuing in case pg status is not updated yet...", drainingFailureDomain)
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: 15 * time.Second}, nil
|
||||
}
|
||||
|
||||
// osd is down and pgs are not healthy
|
||||
case osdDown && !pgClean:
|
||||
logger.Infof("osd is down in failure domain %q and pgs are not active+clean. pg health: %q", drainingFailureDomain, pgHealthMsg)
|
||||
currentlyDrainingFD, ok := pdbStateMap.Data[drainingFailureDomainKey]
|
||||
if !ok || drainingFailureDomain != currentlyDrainingFD {
|
||||
pdbStateMap.Data[drainingFailureDomainKey] = drainingFailureDomain
|
||||
pdbStateMap.Data[drainingFailureDomainDurationKey] = time.Now().Format(time.RFC3339)
|
||||
}
|
||||
} else if clean || r.hasPGHealthCheckTimedout(pdbStateMap) {
|
||||
if activeNodeDrains {
|
||||
pdbStateMap.Data[setNoOut] = "true"
|
||||
}
|
||||
|
||||
// osd is back up and either pgs have become healthy or pg healthy check timeout has elapsed
|
||||
case !osdDown && (pgClean || r.hasPGHealthCheckTimedout(pdbStateMap)):
|
||||
// reset the configMap if cluster is clean or if the timeout for PGs to become active+clean has exceeded
|
||||
logger.Debugf("no drains detected in %q failure domains: %v. pg health: %q", failureDomainType, allFailureDomains, pgHealthMsg)
|
||||
pdbStateMap.Data[drainingFailureDomainKey] = ""
|
||||
delete(pdbStateMap.Data, drainingFailureDomainDurationKey)
|
||||
delete(pdbStateMap.Data, pgHealthCheckDurationKey)
|
||||
} else {
|
||||
logger.Infof("all osds are up. last drained failure domain: %q. pg health: %q. waiting for PGs to be active+clean or PG health check to timeout",
|
||||
pdbStateMap.Data[drainingFailureDomainKey], pgHealthMsg)
|
||||
logger.Debugf("no OSD is down in the %q failure domains: %v. pg health: %q", failureDomainType, allFailureDomains, pgHealthMsg)
|
||||
resetPDBConfig(pdbStateMap)
|
||||
|
||||
default:
|
||||
logger.Infof("all %q failure domains: %v. osd is down in failure domain: %q. active node drains: %t. pg health: %q", failureDomainType,
|
||||
allFailureDomains, drainingFailureDomain, activeNodeDrains, pgHealthMsg)
|
||||
}
|
||||
|
||||
err = r.updateNoout(clusterInfo, pdbStateMap, allFailureDomains)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to update maintenance noout in cluster %q. %v", request, err)
|
||||
if pdbStateMap.Data[setNoOut] == "true" {
|
||||
err = r.updateNoout(clusterInfo, pdbStateMap, allFailureDomains)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to update maintenance noout in cluster %q. %v", request, err)
|
||||
}
|
||||
}
|
||||
|
||||
if pdbStateMap.Data[drainingFailureDomainKey] != "" && !pgClean {
|
||||
// delete default OSD pdb and create blocking OSD pdbs
|
||||
err := r.handleActiveDrains(allFailureDomains, pdbStateMap.Data[drainingFailureDomainKey], failureDomainType, clusterInfo.Namespace, pgClean)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, errors.Wrap(err, "failed to handle active drains")
|
||||
}
|
||||
} else if pdbStateMap.Data[drainingFailureDomainKey] == "" {
|
||||
// delete all blocking OSD pdb and restore the default OSD pdb
|
||||
err := r.handleInactiveDrains(allFailureDomains, failureDomainType, clusterInfo.Namespace)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, errors.Wrap(err, "failed to handle inactive drains")
|
||||
}
|
||||
// reset `set-no-out` flag on the configMap
|
||||
pdbStateMap.Data[setNoOut] = ""
|
||||
}
|
||||
|
||||
err = r.client.Update(context.TODO(), pdbStateMap)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update configMap %q in cluster %q", pdbStateMapName, request)
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to update configMap %q in cluster %q", pdbStateMapName, request)
|
||||
}
|
||||
|
||||
// handle inactive drains by deleting all blocking pdb and restoring the main pdb
|
||||
if pdbStateMap.Data[drainingFailureDomainKey] == "" {
|
||||
err := r.handleInactiveDrains(allFailureDomains, clusterInfo.Namespace, failureDomainType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to handle inactive drains")
|
||||
}
|
||||
// requeue if drain is still in progress
|
||||
if len(pdbStateMap.Data[drainingFailureDomainKey]) > 0 {
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterDisruption) handleActiveDrains(allFailureDomains []string, drainingFailureDomain,
|
||||
@@ -305,20 +332,20 @@ func (r *ReconcileClusterDisruption) handleActiveDrains(allFailureDomains []stri
|
||||
}
|
||||
}
|
||||
|
||||
// delete the main PDB for OSD
|
||||
// delete the default PDB for OSD
|
||||
// This will allow all OSDs in the currently drained failure domain to be removed.
|
||||
logger.Debug("deleting main pdb with maxUnavailable=1 for all osd")
|
||||
err := r.deleteOverallPDBforOSD(namespace)
|
||||
logger.Debug("deleting default pdb with maxUnavailable=1 for all osd")
|
||||
err := r.deleteDefaultPDBforOSD(namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to delete the main osd pdb")
|
||||
return errors.Wrap(err, "failed to delete the default osd pdb")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterDisruption) handleInactiveDrains(allFailureDomains []string, namespace, failureDomainType string) error {
|
||||
err := r.createOverallPDBforOSD(namespace)
|
||||
func (r *ReconcileClusterDisruption) handleInactiveDrains(allFailureDomains []string, failureDomainType, namespace string) error {
|
||||
err := r.createDefaultPDBforOSD(namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create main pdb")
|
||||
return errors.Wrap(err, "failed to create default pdb")
|
||||
}
|
||||
for _, failureDomainName := range allFailureDomains {
|
||||
err := r.deleteBlockingPDBForOSD(namespace, failureDomainType, failureDomainName)
|
||||
@@ -375,37 +402,51 @@ func (r *ReconcileClusterDisruption) updateNoout(clusterInfo *cephclient.Cluster
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterDisruption) getOSDFailureDomains(clusterInfo *cephclient.ClusterInfo, request reconcile.Request, poolFailureDomain string) ([]string, []string, error) {
|
||||
func (r *ReconcileClusterDisruption) getOSDFailureDomains(clusterInfo *cephclient.ClusterInfo, request reconcile.Request, poolFailureDomain string) ([]string, []string, []string, error) {
|
||||
osdDeploymentList := &appsv1.DeploymentList{}
|
||||
namespaceListOpts := client.InNamespace(request.Namespace)
|
||||
topologyLocationLabel := fmt.Sprintf(osd.TopologyLocationLabel, poolFailureDomain)
|
||||
err := r.client.List(context.TODO(), osdDeploymentList, client.MatchingLabels{k8sutil.AppAttr: osd.AppName}, namespaceListOpts)
|
||||
if err != nil {
|
||||
return []string{}, []string{}, errors.Wrap(err, "failed to list osd deployments")
|
||||
return nil, nil, nil, errors.Wrap(err, "failed to list osd deployments")
|
||||
}
|
||||
|
||||
allFailureDomains := sets.NewString()
|
||||
drainingFailureDomains := sets.NewString()
|
||||
nodeDrainFailureDomains := sets.NewString()
|
||||
osdDownFailureDomains := sets.NewString()
|
||||
|
||||
for _, deployment := range osdDeploymentList.Items {
|
||||
labels := deployment.Spec.Template.ObjectMeta.GetLabels()
|
||||
failureDomainName := labels[topologyLocationLabel]
|
||||
if failureDomainName == "" {
|
||||
return []string{}, []string{}, errors.Errorf("failed to get the topology location label %q in OSD deployment %q",
|
||||
return nil, nil, nil, errors.Errorf("failed to get the topology location label %q in OSD deployment %q",
|
||||
topologyLocationLabel, deployment.Name)
|
||||
}
|
||||
|
||||
// Assume node drain if osd deployment ReadyReplicas count is 0 and OSD pod is not scheduled on a node
|
||||
if deployment.Status.ReadyReplicas < 1 {
|
||||
if !drainingFailureDomains.Has(failureDomainName) {
|
||||
drainingFailureDomains.Insert(failureDomainName)
|
||||
if !osdDownFailureDomains.Has(failureDomainName) {
|
||||
osdDownFailureDomains.Insert(failureDomainName)
|
||||
}
|
||||
} else {
|
||||
if !allFailureDomains.Has(failureDomainName) {
|
||||
allFailureDomains.Insert(failureDomainName)
|
||||
isDrained, err := hasOSDNodeDrained(r.client, request.Namespace, labels[osd.OsdIdLabelKey])
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrapf(err, "failed to check if osd %q node is drained", deployment.Name)
|
||||
}
|
||||
if isDrained {
|
||||
logger.Infof("osd %q is down and a possible node drain is detected", deployment.Name)
|
||||
if !nodeDrainFailureDomains.Has(failureDomainName) {
|
||||
nodeDrainFailureDomains.Insert(failureDomainName)
|
||||
}
|
||||
} else {
|
||||
logger.Infof("osd %q is down but no node drain is detected", deployment.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if !allFailureDomains.Has(failureDomainName) {
|
||||
allFailureDomains.Insert(failureDomainName)
|
||||
}
|
||||
}
|
||||
return allFailureDomains.List(), drainingFailureDomains.List(), nil
|
||||
return allFailureDomains.List(), nodeDrainFailureDomains.List(), osdDownFailureDomains.List(), nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterDisruption) hasPGHealthCheckTimedout(pdbStateMap *corev1.ConfigMap) bool {
|
||||
@@ -435,6 +476,74 @@ func (r *ReconcileClusterDisruption) hasPGHealthCheckTimedout(pdbStateMap *corev
|
||||
return false
|
||||
}
|
||||
|
||||
// hasNodeDrained returns true if OSD pod is not assigned to any node or if the OSD node is not schedulable
|
||||
func hasOSDNodeDrained(c client.Client, namespace, osdID string) (bool, error) {
|
||||
osdNodeName, err := getOSDNodeName(c, namespace, osdID)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to get node name assigned to OSD %q POD", osdID)
|
||||
}
|
||||
|
||||
if osdNodeName == "" {
|
||||
logger.Debugf("osd %q POD is not assigned to any node. assuming node drain", osdID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
node, err := getNode(c, osdNodeName)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to get node assigned to OSD %q POD", osdID)
|
||||
}
|
||||
return node.Spec.Unschedulable, nil
|
||||
}
|
||||
|
||||
func getOSDNodeName(c client.Client, namespace, osdID string) (string, error) {
|
||||
pods := &corev1.PodList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{osd.OsdIdLabelKey: osdID},
|
||||
}
|
||||
|
||||
err := c.List(context.TODO(), pods, listOpts...)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to list pods for osd %q", osdID)
|
||||
}
|
||||
|
||||
if len(pods.Items) > 0 {
|
||||
return pods.Items[0].Spec.NodeName, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func getNode(c client.Client, nodeName string) (*corev1.Node, error) {
|
||||
node := &corev1.Node{}
|
||||
err := c.Get(context.TODO(), types.NamespacedName{Name: nodeName}, node)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get node %q", nodeName)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func getPDBName(failureDomainType, failureDomainName string) string {
|
||||
return k8sutil.TruncateNodeName(fmt.Sprintf("%s-%s-%s", osdPDBAppName, failureDomainType, "%s"), failureDomainName)
|
||||
}
|
||||
|
||||
func getLastDrainTimeStamp(pdbStateMap *corev1.ConfigMap, key string) (time.Time, error) {
|
||||
var err error
|
||||
var lastDrainTimeStamp time.Time
|
||||
lastDrainTimeStampString, ok := pdbStateMap.Data[key]
|
||||
if !ok || len(lastDrainTimeStampString) == 0 {
|
||||
return time.Now(), nil
|
||||
} else {
|
||||
lastDrainTimeStamp, err = time.Parse(time.RFC3339, pdbStateMap.Data[key])
|
||||
if err != nil {
|
||||
return time.Time{}, errors.Wrapf(err, "failed to parse timestamp %q", pdbStateMap.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
return lastDrainTimeStamp, nil
|
||||
}
|
||||
|
||||
func resetPDBConfig(pdbStateMap *corev1.ConfigMap) {
|
||||
pdbStateMap.Data[drainingFailureDomainKey] = ""
|
||||
delete(pdbStateMap.Data, drainingFailureDomainDurationKey)
|
||||
delete(pdbStateMap.Data, pgHealthCheckDurationKey)
|
||||
}
|
||||
|
||||
@@ -45,13 +45,26 @@ const (
|
||||
unHealthyCephStatus = `{"fsid":"613975f3-3025-4802-9de1-a2280b950e75","health":{"checks":{"OSD_DOWN":{"severity":"HEALTH_WARN","summary":{"message":"1 osds down"}},"OSD_HOST_DOWN":{"severity":"HEALTH_WARN","summary":{"message":"1 host (1 osds) down"}},"PG_AVAILABILITY":{"severity":"HEALTH_WARN","summary":{"message":"Reduced data availability: 101 pgs stale"}},"POOL_APP_NOT_ENABLED":{"severity":"HEALTH_WARN","summary":{"message":"application not enabled on 1 pool(s)"}}},"status":"HEALTH_WARN","overall_status":"HEALTH_WARN"},"election_epoch":12,"quorum":[0,1,2],"quorum_names":["rook-ceph-mon0","rook-ceph-mon2","rook-ceph-mon1"],"monmap":{"epoch":3,"fsid":"613975f3-3025-4802-9de1-a2280b950e75","modified":"2017-08-11 20:13:02.075679","created":"2017-08-11 20:12:35.314510","features":{"persistent":["kraken","luminous"],"optional":[]},"mons":[{"rank":0,"name":"rook-ceph-mon0","addr":"10.3.0.45:6789/0","public_addr":"10.3.0.45:6789/0"},{"rank":1,"name":"rook-ceph-mon2","addr":"10.3.0.249:6789/0","public_addr":"10.3.0.249:6789/0"},{"rank":2,"name":"rook-ceph-mon1","addr":"10.3.0.252:6789/0","public_addr":"10.3.0.252:6789/0"}]},"osdmap":{"osdmap":{"epoch":17,"num_osds":2,"num_up_osds":1,"num_in_osds":2,"full":false,"nearfull":true,"num_remapped_pgs":0}},"pgmap":{"pgs_by_state":[{"state_name":"stale+active+clean","count":101},{"state_name":"active+clean","count":99}],"num_pgs":200,"num_pools":2,"num_objects":243,"data_bytes":976793635,"bytes_used":13611479040,"bytes_avail":19825307648,"bytes_total":33436786688},"fsmap":{"epoch":1,"by_rank":[]},"mgrmap":{"epoch":3,"active_gid":14111,"active_name":"rook-ceph-mgr0","active_addr":"10.2.73.6:6800/9","available":true,"standbys":[],"modules":["restful","status"],"available_modules":["dashboard","prometheus","restful","status","zabbix"]},"servicemap":{"epoch":1,"modified":"0.000000","services":{}}}`
|
||||
)
|
||||
|
||||
var nodeName = "node01"
|
||||
var namespace = "rook-ceph"
|
||||
|
||||
var cephCluster = &cephv1.CephCluster{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ceph-cluster"}}
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ceph-cluster"},
|
||||
}
|
||||
|
||||
var pdbConfigMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: pdbStateMapName, Namespace: namespace}}
|
||||
var nodeObj = &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: nodeName},
|
||||
Spec: corev1.NodeSpec{
|
||||
Unschedulable: false,
|
||||
},
|
||||
}
|
||||
|
||||
var unschedulableNodeObj = &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: nodeName},
|
||||
Spec: corev1.NodeSpec{
|
||||
Unschedulable: true,
|
||||
},
|
||||
}
|
||||
|
||||
func fakeOSDDeployment(id, readyReplicas int) appsv1.Deployment {
|
||||
osd := appsv1.Deployment{
|
||||
@@ -67,6 +80,7 @@ func fakeOSDDeployment(id, readyReplicas int) appsv1.Deployment {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"topology-location-zone": fmt.Sprintf("zone-%d", id),
|
||||
"ceph-osd-id": fmt.Sprintf("%d", id),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -78,6 +92,30 @@ func fakeOSDDeployment(id, readyReplicas int) appsv1.Deployment {
|
||||
return osd
|
||||
}
|
||||
|
||||
func fakeOSDPod(id int, nodeName string) corev1.Pod {
|
||||
osdPod := corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("osd-%d", id),
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "rook-ceph-osd",
|
||||
"ceph-osd-id": fmt.Sprintf("%d", id),
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
},
|
||||
}
|
||||
return osdPod
|
||||
}
|
||||
|
||||
func fakePDBConfigMap(drainingFailureDomain string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: pdbStateMapName, Namespace: namespace},
|
||||
Data: map[string]string{drainingFailureDomainKey: drainingFailureDomain, setNoOut: ""},
|
||||
}
|
||||
}
|
||||
|
||||
func getFakeReconciler(t *testing.T, obj ...runtime.Object) *ReconcileClusterDisruption {
|
||||
scheme := scheme.Scheme
|
||||
err := policyv1beta1.AddToScheme(scheme)
|
||||
@@ -110,128 +148,193 @@ func getFakeClusterInfo() *client.ClusterInfo {
|
||||
|
||||
func TestGetOSDFailureDomains(t *testing.T) {
|
||||
testcases := []struct {
|
||||
label string
|
||||
name string
|
||||
osds []appsv1.Deployment
|
||||
osdPods []corev1.Pod
|
||||
node *corev1.Node
|
||||
expectedAllFailureDomains []string
|
||||
expectedDrainingFailureDomains []string
|
||||
expectedOsdDownFailureDomains []string
|
||||
}{
|
||||
{
|
||||
label: "case 1: all osds are running",
|
||||
name: "case 1: all osds are running",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 1), fakeOSDDeployment(2, 1),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
osdPods: []corev1.Pod{fakeOSDPod(1, nodeName), fakeOSDPod(2, nodeName),
|
||||
fakeOSDPod(3, nodeName)},
|
||||
node: nodeObj,
|
||||
expectedAllFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
expectedOsdDownFailureDomains: []string{},
|
||||
expectedDrainingFailureDomains: []string{},
|
||||
},
|
||||
{
|
||||
label: "case 1: osd in zone-1 is pending",
|
||||
name: "case 2: osd in zone-1 is pending and node is unscheduable",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 0), fakeOSDDeployment(2, 1),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
expectedAllFailureDomains: []string{"zone-2", "zone-3"},
|
||||
osdPods: []corev1.Pod{fakeOSDPod(1, ""), fakeOSDPod(2, nodeName),
|
||||
fakeOSDPod(3, nodeName)},
|
||||
node: nodeObj,
|
||||
expectedAllFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
expectedOsdDownFailureDomains: []string{"zone-1"},
|
||||
expectedDrainingFailureDomains: []string{"zone-1"},
|
||||
},
|
||||
{
|
||||
label: "case 1: osds in zone-1 and zone-2 are pending",
|
||||
name: "case 3: osd in zone-1 and zone-2 are pending and node is unscheduable",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 0), fakeOSDDeployment(2, 0),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
expectedAllFailureDomains: []string{"zone-3"},
|
||||
osdPods: []corev1.Pod{fakeOSDPod(1, ""), fakeOSDPod(2, ""),
|
||||
fakeOSDPod(3, nodeName)},
|
||||
node: nodeObj,
|
||||
expectedAllFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
expectedOsdDownFailureDomains: []string{"zone-1", "zone-2"},
|
||||
expectedDrainingFailureDomains: []string{"zone-1", "zone-2"},
|
||||
},
|
||||
{
|
||||
name: "case 4: osd in zone-1 is pending but osd node is scheduable",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 0), fakeOSDDeployment(2, 1),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
osdPods: []corev1.Pod{fakeOSDPod(1, nodeName), fakeOSDPod(2, nodeName),
|
||||
fakeOSDPod(3, nodeName)},
|
||||
node: nodeObj,
|
||||
expectedAllFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
expectedOsdDownFailureDomains: []string{"zone-1"},
|
||||
expectedDrainingFailureDomains: []string{},
|
||||
},
|
||||
{
|
||||
name: "case 5: osd in zone-1 is pending but osd node is not scheduable",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 0), fakeOSDDeployment(2, 1),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
osdPods: []corev1.Pod{fakeOSDPod(1, nodeName), fakeOSDPod(2, nodeName),
|
||||
fakeOSDPod(3, nodeName)},
|
||||
node: unschedulableNodeObj,
|
||||
expectedAllFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
expectedOsdDownFailureDomains: []string{"zone-1"},
|
||||
expectedDrainingFailureDomains: []string{"zone-1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
r := getFakeReconciler(t, cephCluster, &corev1.ConfigMap{}, tc.osds[0].DeepCopy(),
|
||||
tc.osds[1].DeepCopy(), tc.osds[2].DeepCopy())
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
allfailureDomains, drainingFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, "zone")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedAllFailureDomains, allfailureDomains)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomains, drainingFailureDomains)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
objs := []runtime.Object{
|
||||
cephCluster,
|
||||
&corev1.ConfigMap{},
|
||||
tc.node,
|
||||
}
|
||||
for _, osdDeployment := range tc.osds {
|
||||
objs = append(objs, osdDeployment.DeepCopy())
|
||||
}
|
||||
for _, osdPod := range tc.osdPods {
|
||||
objs = append(objs, osdPod.DeepCopy())
|
||||
}
|
||||
r := getFakeReconciler(t, objs...)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
allfailureDomains, nodeDrainFailureDomains, osdDownFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, "zone")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedAllFailureDomains, allfailureDomains)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomains, nodeDrainFailureDomains)
|
||||
assert.Equal(t, tc.expectedOsdDownFailureDomains, osdDownFailureDomains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOSDFailureDomainsError(t *testing.T) {
|
||||
testcases := []struct {
|
||||
label string
|
||||
name string
|
||||
osds []appsv1.Deployment
|
||||
expectedAllFailureDomains []string
|
||||
expectedDrainingFailureDomains []string
|
||||
expectedOsdDownFailureDomains []string
|
||||
}{
|
||||
{
|
||||
label: "case 1: one or more OSD deployment is missing crush location label",
|
||||
name: "case 1: one or more OSD deployment is missing crush location label",
|
||||
osds: []appsv1.Deployment{fakeOSDDeployment(1, 1), fakeOSDDeployment(2, 1),
|
||||
fakeOSDDeployment(3, 1)},
|
||||
expectedAllFailureDomains: []string{},
|
||||
expectedDrainingFailureDomains: []string{},
|
||||
expectedAllFailureDomains: nil,
|
||||
expectedDrainingFailureDomains: nil,
|
||||
expectedOsdDownFailureDomains: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
osd := tc.osds[0].DeepCopy()
|
||||
osd.Spec.Template.ObjectMeta.Labels["topology-location-zone"] = ""
|
||||
r := getFakeReconciler(t, cephCluster, &corev1.ConfigMap{},
|
||||
tc.osds[1].DeepCopy(), tc.osds[2].DeepCopy(), osd)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
allfailureDomains, drainingFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, "zone")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedAllFailureDomains, allfailureDomains)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomains, drainingFailureDomains)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
osd := tc.osds[0].DeepCopy()
|
||||
osd.Spec.Template.ObjectMeta.Labels["topology-location-zone"] = ""
|
||||
r := getFakeReconciler(t, cephCluster, &corev1.ConfigMap{},
|
||||
tc.osds[1].DeepCopy(), tc.osds[2].DeepCopy(), osd)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
allfailureDomains, nodeDrainFailureDomains, osdDownFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, "zone")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedAllFailureDomains, allfailureDomains)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomains, nodeDrainFailureDomains)
|
||||
assert.Equal(t, tc.expectedOsdDownFailureDomains, osdDownFailureDomains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilePDBForOSD(t *testing.T) {
|
||||
pdbConfig := pdbConfigMap.DeepCopy()
|
||||
r := getFakeReconciler(t, cephCluster, pdbConfig)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
|
||||
testcases := []struct {
|
||||
label string
|
||||
name string
|
||||
fakeCephStatus string
|
||||
fakeOSDDump string
|
||||
configMap *corev1.ConfigMap
|
||||
allFailureDomains []string
|
||||
drainingFailureDomains []string
|
||||
osdDownFailureDomains []string
|
||||
activeNodeDrains bool
|
||||
expectedSetNoOutValue string
|
||||
expectedOSDPDBCount int
|
||||
expectedMaxUnavailableCount int
|
||||
expectedDrainingFailureDomainName string
|
||||
}{
|
||||
{
|
||||
label: "case 1: no draining failure domain and all pgs are healthy",
|
||||
name: "case 1: no draining failure domain and all pgs are healthy",
|
||||
fakeCephStatus: healthyCephStatus,
|
||||
fakeOSDDump: `{"OSDs": [{"OSD": 3, "Up": 3, "In": 3}]}`,
|
||||
allFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
drainingFailureDomains: []string{},
|
||||
osdDownFailureDomains: []string{},
|
||||
configMap: fakePDBConfigMap(""),
|
||||
activeNodeDrains: false,
|
||||
expectedSetNoOutValue: "",
|
||||
expectedOSDPDBCount: 1,
|
||||
expectedMaxUnavailableCount: 1,
|
||||
expectedDrainingFailureDomainName: "",
|
||||
},
|
||||
{
|
||||
label: "case 2: zone-1 failure domain is draining and pgs are unhealthy",
|
||||
name: "case 2: zone-1 failure domain is draining and pgs are unhealthy",
|
||||
fakeCephStatus: unHealthyCephStatus,
|
||||
fakeOSDDump: `{"OSDs": [{"OSD": 3, "Up": 3, "In": 2}]}`,
|
||||
allFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
drainingFailureDomains: []string{"zone-1"},
|
||||
osdDownFailureDomains: []string{"zone-1"},
|
||||
configMap: fakePDBConfigMap(""),
|
||||
activeNodeDrains: true,
|
||||
expectedSetNoOutValue: "true",
|
||||
expectedOSDPDBCount: 2,
|
||||
expectedMaxUnavailableCount: 0,
|
||||
expectedDrainingFailureDomainName: "zone-1",
|
||||
},
|
||||
{
|
||||
label: "case 3: zone-1 is back online. But pgs are still unhealthy from zone-1 drain",
|
||||
name: "case 3: zone-1 is back online. But pgs are still unhealthy from zone-1 drain",
|
||||
fakeCephStatus: unHealthyCephStatus,
|
||||
fakeOSDDump: `{"OSDs": [{"OSD": 3, "Up": 3, "In": 3}]}`,
|
||||
allFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
drainingFailureDomains: []string{},
|
||||
osdDownFailureDomains: []string{},
|
||||
configMap: fakePDBConfigMap("zone-1"),
|
||||
activeNodeDrains: true,
|
||||
expectedSetNoOutValue: "",
|
||||
expectedOSDPDBCount: 2,
|
||||
expectedMaxUnavailableCount: 0,
|
||||
expectedDrainingFailureDomainName: "zone-1",
|
||||
},
|
||||
{
|
||||
label: "case 4: zone-1 is back online and pgs are also healthy",
|
||||
name: "case 4: zone-1 is back online and pgs are also healthy",
|
||||
fakeCephStatus: healthyCephStatus,
|
||||
fakeOSDDump: `{"OSDs": [{"OSD": 3, "Up": 3, "In": 3}]}`,
|
||||
allFailureDomains: []string{"zone-1", "zone-2", "zone-3"},
|
||||
drainingFailureDomains: []string{},
|
||||
osdDownFailureDomains: []string{},
|
||||
configMap: fakePDBConfigMap(""),
|
||||
activeNodeDrains: true,
|
||||
expectedSetNoOutValue: "",
|
||||
expectedOSDPDBCount: 1,
|
||||
expectedMaxUnavailableCount: 1,
|
||||
expectedDrainingFailureDomainName: "",
|
||||
@@ -239,40 +342,47 @@ func TestReconcilePDBForOSD(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
executor := &exectest.MockExecutor{}
|
||||
executor.MockExecuteCommandWithOutputFile = func(command, outputFile string, args ...string) (string, error) {
|
||||
logger.Infof("Command: %s %v", command, args)
|
||||
if args[0] == "status" {
|
||||
return tc.fakeCephStatus, nil
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getFakeReconciler(t, cephCluster, tc.configMap)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
executor := &exectest.MockExecutor{}
|
||||
executor.MockExecuteCommandWithOutputFile = func(command, outputFile string, args ...string) (string, error) {
|
||||
logger.Infof("Command: %s %v", command, args)
|
||||
if args[0] == "status" {
|
||||
return tc.fakeCephStatus, nil
|
||||
}
|
||||
if args[0] == "osd" && args[1] == "dump" {
|
||||
return tc.fakeOSDDump, nil
|
||||
}
|
||||
return "", errors.Errorf("unexpected ceph command '%v'", args)
|
||||
}
|
||||
if args[0] == "osd" && args[1] == "dump" {
|
||||
return tc.fakeOSDDump, nil
|
||||
r.context = &controllerconfig.Context{ClusterdContext: &clusterd.Context{Executor: executor}}
|
||||
_, err := r.reconcilePDBsForOSDs(clusterInfo, request, tc.configMap, "zone", tc.allFailureDomains, tc.osdDownFailureDomains, tc.activeNodeDrains)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// assert that pdb for osd are created correctly
|
||||
existingPDBs := &policyv1beta1.PodDisruptionBudgetList{}
|
||||
err = r.client.List(context.TODO(), existingPDBs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedOSDPDBCount, len(existingPDBs.Items))
|
||||
for _, pdb := range existingPDBs.Items {
|
||||
assert.Equal(t, tc.expectedMaxUnavailableCount, pdb.Spec.MaxUnavailable.IntValue())
|
||||
}
|
||||
return "", errors.Errorf("unexpected ceph command '%v'", args)
|
||||
}
|
||||
r.context = &controllerconfig.Context{ClusterdContext: &clusterd.Context{Executor: executor}}
|
||||
err := r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", tc.allFailureDomains, tc.drainingFailureDomains)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// assert that pdb for osd are created correctly
|
||||
existingPDBs := &policyv1beta1.PodDisruptionBudgetList{}
|
||||
err = r.client.List(context.TODO(), existingPDBs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedOSDPDBCount, len(existingPDBs.Items))
|
||||
for _, pdb := range existingPDBs.Items {
|
||||
assert.Equal(t, tc.expectedMaxUnavailableCount, pdb.Spec.MaxUnavailable.IntValue())
|
||||
}
|
||||
// assert that config map is updated with correct failure domain
|
||||
existingConfigMaps := &corev1.ConfigMapList{}
|
||||
err = r.client.List(context.TODO(), existingConfigMaps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomainName, existingConfigMaps.Items[0].Data[drainingFailureDomainKey])
|
||||
assert.Equal(t, tc.expectedSetNoOutValue, existingConfigMaps.Items[0].Data[setNoOut])
|
||||
})
|
||||
|
||||
// assert that config map is updated with correct failure domain
|
||||
existingConfigMaps := &corev1.ConfigMapList{}
|
||||
err = r.client.List(context.TODO(), existingConfigMaps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedDrainingFailureDomainName, existingConfigMaps.Items[0].Data[drainingFailureDomainKey])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPGHealthcheckTimeout(t *testing.T) {
|
||||
pdbConfig := pdbConfigMap.DeepCopy()
|
||||
pdbConfig := fakePDBConfigMap("")
|
||||
r := getFakeReconciler(t, cephCluster, pdbConfig)
|
||||
clusterInfo := getFakeClusterInfo()
|
||||
request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: namespace}}
|
||||
@@ -292,23 +402,49 @@ func TestPGHealthcheckTimeout(t *testing.T) {
|
||||
r.pgHealthCheckTimeout = time.Duration(time.Minute * 10)
|
||||
|
||||
// reconcile OSD PDB with active drains (on zone-1) and unhealthy PGs
|
||||
err := r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{"zone-1"})
|
||||
_, err := r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{"zone-1"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "zone-1", pdbConfig.Data[drainingFailureDomainKey])
|
||||
assert.Equal(t, "true", pdbConfig.Data[setNoOut])
|
||||
|
||||
// update the pgHealthCheckDuration time by -9 minutes
|
||||
pdbConfig.Data[pgHealthCheckDurationKey] = time.Now().Add(time.Duration(-7) * time.Minute).Format(time.RFC3339)
|
||||
// reconcile OSD PDB with no active drains and unhealthy PGs
|
||||
err = r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{})
|
||||
_, err = r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{}, true)
|
||||
assert.NoError(t, err)
|
||||
// assert that pdb config map was not reset as the PG health check was not timed out
|
||||
assert.Equal(t, "zone-1", pdbConfig.Data[drainingFailureDomainKey])
|
||||
assert.Equal(t, "true", pdbConfig.Data[setNoOut])
|
||||
|
||||
// update the drainingFailureDomain time by -9 minutes
|
||||
pdbConfig.Data[pgHealthCheckDurationKey] = time.Now().Add(time.Duration(-11) * time.Minute).Format(time.RFC3339)
|
||||
// reconcile OSD PDB with no active drains and unhealthy PGs
|
||||
err = r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{})
|
||||
_, err = r.reconcilePDBsForOSDs(clusterInfo, request, pdbConfig, "zone", []string{"zone-1", "zone-2"}, []string{}, false)
|
||||
assert.NoError(t, err)
|
||||
// assert that pdb config map was reset as the PG health check was timed out
|
||||
assert.Equal(t, "", pdbConfig.Data[drainingFailureDomainKey])
|
||||
assert.Equal(t, "", pdbConfig.Data[setNoOut])
|
||||
}
|
||||
|
||||
func TestHasNodeDrained(t *testing.T) {
|
||||
osdPOD := fakeOSDPod(0, nodeName)
|
||||
// Not expecting node drain because OSD pod is assigned to a schedulable node
|
||||
r := getFakeReconciler(t, nodeObj, osdPOD.DeepCopy(), &corev1.ConfigMap{})
|
||||
expected, err := hasOSDNodeDrained(r.client, namespace, "0")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, expected)
|
||||
|
||||
// Expecting node drain because OSD pod is assigned to an unschedulable node
|
||||
r = getFakeReconciler(t, unschedulableNodeObj, osdPOD.DeepCopy(), &corev1.ConfigMap{})
|
||||
expected, err = hasOSDNodeDrained(r.client, namespace, "0")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, expected)
|
||||
|
||||
// Expecting node drain because OSD pod is not assigned to any node
|
||||
osdPodObj := osdPOD.DeepCopy()
|
||||
osdPodObj.Spec.NodeName = ""
|
||||
r = getFakeReconciler(t, nodeObj, osdPodObj, &corev1.ConfigMap{})
|
||||
expected, err = hasOSDNodeDrained(r.client, namespace, "0")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, expected)
|
||||
}
|
||||
|
||||
@@ -169,8 +169,8 @@ func (r *ReconcileClusterDisruption) reconcile(request reconcile.Request) (recon
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
// get all failure domains and the draining failure domains list
|
||||
allFailureDomains, drainingFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, poolFailureDomain)
|
||||
// get a list of all the failure domains, failure domains with failed OSDs and failure domains with drained nodes
|
||||
allFailureDomains, nodeDrainFailureDomains, osdDownFailureDomains, err := r.getOSDFailureDomains(clusterInfo, request, poolFailureDomain)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
@@ -181,17 +181,8 @@ func (r *ReconcileClusterDisruption) reconcile(request reconcile.Request) (recon
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
err = r.reconcilePDBsForOSDs(clusterInfo, request, pdbStateMap, poolFailureDomain, allFailureDomains, drainingFailureDomains)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
disabledPDB, ok := pdbStateMap.Data[drainingFailureDomainKey]
|
||||
if ok && len(disabledPDB) > 0 {
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil
|
||||
}
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
activeNodeDrains := len(nodeDrainFailureDomains) > 0
|
||||
return r.reconcilePDBsForOSDs(clusterInfo, request, pdbStateMap, poolFailureDomain, allFailureDomains, osdDownFailureDomains, activeNodeDrains)
|
||||
}
|
||||
|
||||
// ClusterMap maintains the association between namespace and clusername
|
||||
|
||||
Reference in New Issue
Block a user