forked from rook/rook
Rook has been setting the application automatically on all pools to rbd for CephBlockPools, rook-ceph-rgw for CephObjectStores, mgr on the built-in .mgr pool, and nfs on the built-in .nfs pool. The legacy pool device_health_metrics is long gone from Pacific which is no longer supported, so we can remove special handling for that pool in the upgrade guide and in the code. The application setting is now available on the pool spec although it is not expected to commonly need to override the default applications set by Rook. The application for CephFilesystem pools is now being set to cephfs, where it was previously blank. Signed-off-by: travisn <tnielsen@redhat.com>
653 lines
22 KiB
Go
653 lines
22 KiB
Go
/*
|
|
Copyright 2016 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 pool
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coreos/pkg/capnslog"
|
|
"github.com/pkg/errors"
|
|
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
|
|
rookclient "github.com/rook/rook/pkg/client/clientset/versioned/fake"
|
|
"github.com/rook/rook/pkg/clusterd"
|
|
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
|
|
"github.com/rook/rook/pkg/operator/ceph/config"
|
|
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
|
|
"github.com/rook/rook/pkg/operator/k8sutil"
|
|
testop "github.com/rook/rook/pkg/operator/test"
|
|
exectest "github.com/rook/rook/pkg/util/exec/test"
|
|
"github.com/stretchr/testify/assert"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/kubernetes/scheme"
|
|
"k8s.io/client-go/tools/record"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
)
|
|
|
|
func TestCreatePool(t *testing.T) {
|
|
p := &cephv1.NamedPoolSpec{}
|
|
enabledMetricsApp := false
|
|
enabledMgrApp := false
|
|
clusterInfo := cephclient.AdminTestClusterInfo("mycluster")
|
|
executor := &exectest.MockExecutor{
|
|
MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
|
|
logger.Infof("Command: %s %v", command, args)
|
|
if command == "ceph" {
|
|
if args[1] == "erasure-code-profile" {
|
|
return `{"k":"2","m":"1","plugin":"jerasure","technique":"reed_sol_van"}`, nil
|
|
}
|
|
if args[0] == "osd" && args[1] == "pool" && args[2] == "application" {
|
|
if args[3] == "get" {
|
|
return ``, nil
|
|
}
|
|
assert.Equal(t, "enable", args[3])
|
|
if args[5] != "rbd" {
|
|
if args[4] == ".mgr" {
|
|
enabledMgrApp = true
|
|
assert.Equal(t, ".mgr", args[4])
|
|
assert.Equal(t, "mgr", args[5])
|
|
} else {
|
|
assert.Fail(t, fmt.Sprintf("invalid pool %q", args[4]))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if command == "rbd" {
|
|
assert.Equal(t, []string{"pool", "init", p.Name}, args[0:3])
|
|
}
|
|
return "", nil
|
|
},
|
|
}
|
|
context := &clusterd.Context{Executor: executor}
|
|
|
|
clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{Config: map[string]string{cephclient.CrushRootConfigKey: "cluster-crush-root"}}}
|
|
|
|
t.Run("replicated pool", func(t *testing.T) {
|
|
p.Name = "replicapool"
|
|
p.Replicated.Size = 1
|
|
p.Replicated.RequireSafeReplicaSize = false
|
|
err := createPool(context, clusterInfo, clusterSpec, p)
|
|
assert.Nil(t, err)
|
|
assert.False(t, enabledMetricsApp)
|
|
})
|
|
|
|
t.Run("built-in mgr pool", func(t *testing.T) {
|
|
p.Name = ".mgr"
|
|
err := createPool(context, clusterInfo, clusterSpec, p)
|
|
assert.Nil(t, err)
|
|
assert.True(t, enabledMgrApp)
|
|
})
|
|
|
|
t.Run("ec pool", func(t *testing.T) {
|
|
p.Name = "ecpool"
|
|
p.Replicated.Size = 0
|
|
p.ErasureCoded.CodingChunks = 1
|
|
p.ErasureCoded.DataChunks = 2
|
|
err := createPool(context, clusterInfo, clusterSpec, p)
|
|
assert.Nil(t, err)
|
|
})
|
|
}
|
|
|
|
func TestCephPoolName(t *testing.T) {
|
|
t.Run("spec not set", func(t *testing.T) {
|
|
p := cephv1.CephBlockPool{ObjectMeta: metav1.ObjectMeta{Name: "metapool"}}
|
|
name := p.ToNamedPoolSpec().Name
|
|
assert.Equal(t, "metapool", name)
|
|
})
|
|
t.Run("same name already set", func(t *testing.T) {
|
|
p := cephv1.CephBlockPool{ObjectMeta: metav1.ObjectMeta{Name: "metapool"}, Spec: cephv1.NamedBlockPoolSpec{Name: "metapool"}}
|
|
name := p.ToNamedPoolSpec().Name
|
|
assert.Equal(t, "metapool", name)
|
|
})
|
|
t.Run("override mgr", func(t *testing.T) {
|
|
p := cephv1.CephBlockPool{ObjectMeta: metav1.ObjectMeta{Name: "default-mgr"}, Spec: cephv1.NamedBlockPoolSpec{Name: ".mgr"}}
|
|
name := p.ToNamedPoolSpec().Name
|
|
assert.Equal(t, ".mgr", name)
|
|
})
|
|
t.Run("override nfs", func(t *testing.T) {
|
|
p := cephv1.CephBlockPool{ObjectMeta: metav1.ObjectMeta{Name: "default-nfs"}, Spec: cephv1.NamedBlockPoolSpec{Name: ".nfs"}}
|
|
name := p.ToNamedPoolSpec().Name
|
|
assert.Equal(t, ".nfs", name)
|
|
})
|
|
}
|
|
|
|
func TestDeletePool(t *testing.T) {
|
|
failOnDelete := false
|
|
clusterInfo := cephclient.AdminTestClusterInfo("mycluster")
|
|
executor := &exectest.MockExecutor{
|
|
MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
|
|
emptyPool := "{\"images\":{\"count\":0,\"provisioned_bytes\":0,\"snap_count\":0},\"trash\":{\"count\":1,\"provisioned_bytes\":2048,\"snap_count\":0}}"
|
|
p := "{\"images\":{\"count\":1,\"provisioned_bytes\":1024,\"snap_count\":0},\"trash\":{\"count\":1,\"provisioned_bytes\":2048,\"snap_count\":0}}"
|
|
logger.Infof("Command: %s %v", command, args)
|
|
if command == "ceph" && args[1] == "lspools" {
|
|
return `[{"poolnum":1,"poolname":"mypool"}]`, nil
|
|
} else if command == "ceph" && args[1] == "pool" && args[2] == "get" {
|
|
return `{"pool": "mypool","pool_id": 1,"size":1}`, nil
|
|
} else if command == "ceph" && args[1] == "pool" && args[2] == "delete" {
|
|
return "", nil
|
|
} else if args[0] == "pool" {
|
|
if args[1] == "stats" {
|
|
if !failOnDelete {
|
|
return emptyPool, nil
|
|
}
|
|
return p, nil
|
|
}
|
|
return "", errors.Errorf("rbd: error opening pool %q: (2) No such file or directory", args[3])
|
|
}
|
|
return "", errors.Errorf("unexpected rbd command %q", args)
|
|
},
|
|
}
|
|
context := &clusterd.Context{Executor: executor}
|
|
|
|
// delete a pool that exists
|
|
p := &cephv1.NamedPoolSpec{Name: "mypool"}
|
|
err := deletePool(context, clusterInfo, p)
|
|
assert.Nil(t, err)
|
|
|
|
// succeed even if the pool doesn't exist
|
|
p = &cephv1.NamedPoolSpec{Name: "otherpool"}
|
|
err = deletePool(context, clusterInfo, p)
|
|
assert.Nil(t, err)
|
|
|
|
// fail if images/snapshosts exist in the pool
|
|
failOnDelete = true
|
|
p = &cephv1.NamedPoolSpec{Name: "mypool"}
|
|
err = deletePool(context, clusterInfo, p)
|
|
assert.NotNil(t, err)
|
|
}
|
|
|
|
// TestCephBlockPoolController runs ReconcileCephBlockPool.Reconcile() against a
|
|
// fake client that tracks a CephBlockPool object.
|
|
func TestCephBlockPoolController(t *testing.T) {
|
|
ctx := context.TODO()
|
|
// Set DEBUG logging
|
|
capnslog.SetGlobalLogLevel(capnslog.DEBUG)
|
|
os.Setenv("ROOK_LOG_LEVEL", "DEBUG")
|
|
var (
|
|
name = "my-pool"
|
|
namespace = "rook-ceph"
|
|
replicas uint = 3
|
|
)
|
|
|
|
// A Pool resource with metadata and spec.
|
|
pool := &cephv1.CephBlockPool{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
UID: types.UID("c47cac40-9bee-4d52-823b-ccd803ba5bfe"),
|
|
},
|
|
Spec: cephv1.NamedBlockPoolSpec{
|
|
PoolSpec: cephv1.PoolSpec{
|
|
Replicated: cephv1.ReplicatedSpec{
|
|
Size: replicas,
|
|
},
|
|
Mirroring: cephv1.MirroringSpec{
|
|
Peers: &cephv1.MirroringPeerSpec{},
|
|
},
|
|
StatusCheck: cephv1.MirrorHealthCheckSpec{
|
|
Mirror: cephv1.HealthCheckSpec{
|
|
Disabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Status: &cephv1.CephBlockPoolStatus{
|
|
Phase: "",
|
|
},
|
|
}
|
|
|
|
// Objects to track in the fake client.
|
|
object := []runtime.Object{
|
|
pool,
|
|
}
|
|
|
|
executor := &exectest.MockExecutor{
|
|
MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
|
|
if args[0] == "status" {
|
|
return `{"fsid":"c47cac40-9bee-4d52-823b-ccd803ba5bfe","health":{"checks":{},"status":"HEALTH_ERR"},"pgmap":{"num_pgs":100,"pgs_by_state":[{"state_name":"active+clean","count":100}]}}`, nil
|
|
}
|
|
|
|
return "", nil
|
|
},
|
|
}
|
|
c := &clusterd.Context{
|
|
Executor: executor,
|
|
Clientset: testop.New(t, 1),
|
|
RookClientset: rookclient.NewSimpleClientset(),
|
|
}
|
|
|
|
// Register operator types with the runtime scheme.
|
|
s := scheme.Scheme
|
|
s.AddKnownTypes(cephv1.SchemeGroupVersion, pool, &cephv1.CephClusterList{})
|
|
|
|
// Create a fake client to mock API calls.
|
|
cl := fake.NewClientBuilder().WithRuntimeObjects(object...).Build()
|
|
|
|
// Create a ReconcileCephBlockPool object with the scheme and fake client.
|
|
r := &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
|
|
// Mock request to simulate Reconcile() being called on an event for a
|
|
// watched resource .
|
|
req := reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
}
|
|
|
|
cephCluster := &cephv1.CephCluster{}
|
|
|
|
t.Run("failure no CephCluster", func(t *testing.T) {
|
|
// Create pool for updateCephBlockPoolStatus()
|
|
_, err := c.RookClientset.CephV1().CephBlockPools(namespace).Create(ctx, pool, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.True(t, res.Requeue)
|
|
})
|
|
|
|
t.Run("failure CephCluster not ready", func(t *testing.T) {
|
|
cephCluster = &cephv1.CephCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: namespace,
|
|
Namespace: namespace,
|
|
},
|
|
Status: cephv1.ClusterStatus{
|
|
Phase: "",
|
|
CephVersion: &cephv1.ClusterVersion{
|
|
Version: "14.2.9-0",
|
|
},
|
|
CephStatus: &cephv1.CephStatus{
|
|
Health: "",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.AddKnownTypes(cephv1.SchemeGroupVersion, &cephv1.CephCluster{}, &cephv1.CephClusterList{})
|
|
|
|
// Create CephCluster for updateCephBlockPoolStatus()
|
|
_, err := c.RookClientset.CephV1().CephClusters(namespace).Create(ctx, cephCluster, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
object = append(object, cephCluster)
|
|
// Create a fake client to mock API calls.
|
|
cl = fake.NewClientBuilder().WithRuntimeObjects(object...).Build()
|
|
// Create a ReconcileCephBlockPool object with the scheme and fake client.
|
|
r = &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.True(t, res.Requeue)
|
|
})
|
|
|
|
t.Run("failure CephCluster is ready", func(t *testing.T) {
|
|
cephCluster.Status.Phase = cephv1.ConditionReady
|
|
cephCluster.Status.CephStatus.Health = "HEALTH_OK"
|
|
objects := []runtime.Object{
|
|
pool,
|
|
cephCluster,
|
|
}
|
|
// Create a fake client to mock API calls.
|
|
cl = fake.NewClientBuilder().WithRuntimeObjects(objects...).Build()
|
|
c.Client = cl
|
|
|
|
executor = &exectest.MockExecutor{
|
|
MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
|
|
if args[0] == "status" {
|
|
return `{"fsid":"c47cac40-9bee-4d52-823b-ccd803ba5bfe","health":{"checks":{},"status":"HEALTH_OK"},"pgmap":{"num_pgs":100,"pgs_by_state":[{"state_name":"active+clean","count":100}]}}`, nil
|
|
}
|
|
if args[0] == "config" && args[2] == "mgr" && args[3] == "mgr/prometheus/rbd_stats_pools" {
|
|
return "", nil
|
|
}
|
|
|
|
return "", nil
|
|
},
|
|
}
|
|
c.Executor = executor
|
|
|
|
// Mock clusterInfo
|
|
secrets := map[string][]byte{
|
|
"fsid": []byte(name),
|
|
"mon-secret": []byte("monsecret"),
|
|
"admin-secret": []byte("adminsecret"),
|
|
}
|
|
secret := &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "rook-ceph-mon",
|
|
Namespace: namespace,
|
|
},
|
|
Data: secrets,
|
|
Type: k8sutil.RookType,
|
|
}
|
|
_, err := c.Clientset.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
s.AddKnownTypes(cephv1.SchemeGroupVersion, &cephv1.CephBlockPoolList{})
|
|
// Create a ReconcileCephBlockPool object with the scheme and fake client.
|
|
r = &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.False(t, res.Requeue)
|
|
|
|
err = r.client.Get(context.TODO(), req.NamespacedName, pool)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, cephv1.ConditionReady, pool.Status.Phase)
|
|
})
|
|
|
|
t.Run("failure no mirror mode", func(t *testing.T) {
|
|
pool.Spec.Mirroring.Enabled = true
|
|
err := r.client.Update(context.TODO(), pool)
|
|
assert.NoError(t, err)
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.True(t, res.Requeue)
|
|
})
|
|
|
|
t.Setenv("POD_NAME", "test")
|
|
t.Setenv("POD_NAMESPACE", namespace)
|
|
p := &v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
Namespace: namespace,
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
Kind: "ReplicaSet",
|
|
Name: "testReplicaSet",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Create fake pod
|
|
_, err := r.context.Clientset.CoreV1().Pods(namespace).Create(context.TODO(), p, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
replicaSet := &appsv1.ReplicaSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "testReplicaSet",
|
|
Namespace: namespace,
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
Kind: "Deployment",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create fake replicaset
|
|
_, err = r.context.Clientset.AppsV1().ReplicaSets(namespace).Create(context.TODO(), replicaSet, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
t.Run("success - mirroring set", func(t *testing.T) {
|
|
executor = &exectest.MockExecutor{
|
|
MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
|
|
if args[0] == "mirror" && args[1] == "pool" && args[2] == "peer" && args[3] == "bootstrap" && args[4] == "create" {
|
|
return `eyJmc2lkIjoiYzZiMDg3ZjItNzgyOS00ZGJiLWJjZmMtNTNkYzM0ZTBiMzVkIiwiY2xpZW50X2lkIjoicmJkLW1pcnJvci1wZWVyIiwia2V5IjoiQVFBV1lsWmZVQ1Q2RGhBQVBtVnAwbGtubDA5YVZWS3lyRVV1NEE9PSIsIm1vbl9ob3N0IjoiW3YyOjE5Mi4xNjguMTExLjEwOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTA6Njc4OV0sW3YyOjE5Mi4xNjguMTExLjEyOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTI6Njc4OV0sW3YyOjE5Mi4xNjguMTExLjExOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTE6Njc4OV0ifQ==`, nil
|
|
}
|
|
return "", nil
|
|
},
|
|
}
|
|
c.Executor = executor
|
|
r = &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
|
|
pool.Spec.Mirroring.Mode = "image"
|
|
pool.Spec.Mirroring.Peers.SecretNames = []string{}
|
|
err = r.client.Update(context.TODO(), pool)
|
|
assert.NoError(t, err)
|
|
for i := 0; i < 5; i++ {
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.False(t, res.Requeue)
|
|
err = r.client.Get(context.TODO(), req.NamespacedName, pool)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, cephv1.ConditionReady, pool.Status.Phase)
|
|
if _, ok := pool.Status.Info[opcontroller.RBDMirrorBootstrapPeerSecretName]; ok {
|
|
break
|
|
}
|
|
logger.Infof("FIX: trying again to update the mirroring status")
|
|
}
|
|
assert.NotEmpty(t, pool.Status.Info[opcontroller.RBDMirrorBootstrapPeerSecretName], pool.Status.Info)
|
|
|
|
// fetch the secret
|
|
myPeerSecret, err := c.Clientset.CoreV1().Secrets(namespace).Get(ctx, pool.Status.Info[opcontroller.RBDMirrorBootstrapPeerSecretName], metav1.GetOptions{})
|
|
assert.NoError(t, err)
|
|
if myPeerSecret != nil {
|
|
assert.NotEmpty(t, myPeerSecret.Data["token"], myPeerSecret.Data)
|
|
assert.NotEmpty(t, myPeerSecret.Data["pool"])
|
|
}
|
|
})
|
|
|
|
peerSecretName := "peer-secret"
|
|
t.Run("failure - import peer token but was not created", func(t *testing.T) {
|
|
// Create a fake client to mock API calls.
|
|
cl = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(object...).Build()
|
|
|
|
// Create a ReconcileCephBlockPool object with the scheme and fake client.
|
|
r = &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
|
|
pool.Spec.Mirroring.Peers.SecretNames = []string{peerSecretName}
|
|
err := r.client.Update(context.TODO(), pool)
|
|
assert.NoError(t, err)
|
|
res, err := r.Reconcile(ctx, req)
|
|
// assert reconcile failure because peer token secert was not created
|
|
assert.NoError(t, err)
|
|
assert.True(t, res.Requeue)
|
|
})
|
|
|
|
t.Run("success - import peer token but was created", func(t *testing.T) {
|
|
bootstrapPeerToken := `eyJmc2lkIjoiYzZiMDg3ZjItNzgyOS00ZGJiLWJjZmMtNTNkYzM0ZTBiMzVkIiwiY2xpZW50X2lkIjoicmJkLW1pcnJvci1wZWVyIiwia2V5IjoiQVFBV1lsWmZVQ1Q2RGhBQVBtVnAwbGtubDA5YVZWS3lyRVV1NEE9PSIsIm1vbl9ob3N0IjoiW3YyOjE5Mi4xNjguMTExLjEwOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTA6Njc4OV0sW3YyOjE5Mi4xNjguMTExLjEyOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTI6Njc4OV0sW3YyOjE5Mi4xNjguMTExLjExOjMzMDAsdjE6MTkyLjE2OC4xMTEuMTE6Njc4OV0ifQ==` //nolint:gosec // This is just a var name, not a real token
|
|
peerSecret := &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: peerSecretName,
|
|
Namespace: namespace,
|
|
},
|
|
Data: map[string][]byte{"token": []byte(bootstrapPeerToken), "pool": []byte("goo")},
|
|
Type: k8sutil.RookType,
|
|
}
|
|
_, err := c.Clientset.CoreV1().Secrets(namespace).Create(ctx, peerSecret, metav1.CreateOptions{})
|
|
assert.NoError(t, err)
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.False(t, res.Requeue)
|
|
err = r.client.Get(context.TODO(), req.NamespacedName, pool)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("failure - mirroring disabled", func(t *testing.T) {
|
|
r = &ReconcileCephBlockPool{
|
|
client: cl,
|
|
scheme: s,
|
|
context: c,
|
|
blockPoolContexts: make(map[string]*blockPoolHealth),
|
|
opManagerContext: context.TODO(),
|
|
recorder: record.NewFakeRecorder(5),
|
|
}
|
|
pool.Spec.Mirroring.Enabled = false
|
|
pool.Spec.Mirroring.Mode = "image"
|
|
err := r.client.Update(context.TODO(), pool)
|
|
assert.NoError(t, err)
|
|
res, err := r.Reconcile(ctx, req)
|
|
assert.NoError(t, err)
|
|
assert.False(t, res.Requeue)
|
|
err = r.client.Get(context.TODO(), req.NamespacedName, pool)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, cephv1.ConditionReady, pool.Status.Phase)
|
|
assert.Nil(t, pool.Status.MirroringStatus)
|
|
})
|
|
}
|
|
|
|
func TestConfigureRBDStats(t *testing.T) {
|
|
var (
|
|
s = runtime.NewScheme()
|
|
context = &clusterd.Context{}
|
|
namespace = "rook-ceph"
|
|
)
|
|
|
|
mockedPools := ""
|
|
executor := &exectest.MockExecutor{
|
|
MockExecuteCommandWithTimeout: func(timeout time.Duration, command string, args ...string) (string, error) {
|
|
logger.Infof("Command: %s %v", command, args)
|
|
if args[0] == "config" && args[2] == "mgr" && args[3] == "mgr/prometheus/rbd_stats_pools" {
|
|
if args[1] == "set" {
|
|
return "", nil
|
|
}
|
|
if args[1] == "get" {
|
|
return mockedPools, nil
|
|
}
|
|
if args[1] == "rm" {
|
|
return "", nil
|
|
}
|
|
}
|
|
return "", errors.Errorf("unexpected arguments %q", args)
|
|
},
|
|
}
|
|
|
|
context.Executor = executor
|
|
context.Client = fake.NewClientBuilder().WithScheme(s).Build()
|
|
|
|
clusterInfo := cephclient.AdminTestClusterInfo(namespace)
|
|
|
|
// Case 1: CephBlockPoolList is not registered in scheme.
|
|
// So, an error is expected as List() operation would fail.
|
|
err := configureRBDStats(context, clusterInfo, "")
|
|
assert.NotNil(t, err)
|
|
|
|
s.AddKnownTypes(cephv1.SchemeGroupVersion, &cephv1.CephBlockPoolList{})
|
|
// Case 2: CephBlockPoolList is registered in schema.
|
|
// So, no error is expected.
|
|
err = configureRBDStats(context, clusterInfo, "")
|
|
assert.Nil(t, err)
|
|
|
|
s.AddKnownTypes(cephv1.SchemeGroupVersion, &cephv1.CephBlockPool{})
|
|
// A Pool resource with metadata and spec.
|
|
poolWithRBDStatsDisabled := &cephv1.CephBlockPool{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "my-pool-without-rbd-stats",
|
|
Namespace: namespace,
|
|
},
|
|
Spec: cephv1.NamedBlockPoolSpec{
|
|
PoolSpec: cephv1.PoolSpec{
|
|
Replicated: cephv1.ReplicatedSpec{
|
|
Size: 3,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Case 3: One CephBlockPool with EnableRBDStats:false (default).
|
|
objects := []runtime.Object{
|
|
poolWithRBDStatsDisabled,
|
|
}
|
|
context.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objects...).Build()
|
|
err = configureRBDStats(context, clusterInfo, "")
|
|
assert.Nil(t, err)
|
|
|
|
// Case 4: Two CephBlockPools with EnableRBDStats:false & EnableRBDStats:true.
|
|
poolWithRBDStatsEnabled := poolWithRBDStatsDisabled.DeepCopy()
|
|
poolWithRBDStatsEnabled.Name = "my-pool-with-rbd-stats"
|
|
poolWithRBDStatsEnabled.Spec.EnableRBDStats = true
|
|
objects = append(objects, poolWithRBDStatsEnabled)
|
|
context.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objects...).Build()
|
|
err = configureRBDStats(context, clusterInfo, "")
|
|
assert.Nil(t, err)
|
|
|
|
// Case 5: Two external pools(non CephBlockPools) with rbd_stats_pools config already set
|
|
monStore := config.GetMonStore(context, clusterInfo)
|
|
e := monStore.Set("mgr", "mgr/prometheus/rbd_stats_pools", "pool1,pool2")
|
|
assert.Nil(t, e)
|
|
mockedPools = "my-pool-with-rbd-stats,pool1,pool2"
|
|
e = configureRBDStats(context, clusterInfo, "")
|
|
assert.Nil(t, e)
|
|
|
|
rbdStatsPools, err := monStore.Get("mgr", "mgr/prometheus/rbd_stats_pools")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "my-pool-with-rbd-stats,pool1,pool2", rbdStatsPools)
|
|
|
|
// Case 6: Deleted CephBlockPool should be excluded from config
|
|
mockedPools = "pool1,pool2"
|
|
err = configureRBDStats(context, clusterInfo, "my-pool-with-rbd-stats")
|
|
assert.Nil(t, err)
|
|
|
|
rbdStatsPools, err = monStore.Get("mgr", "mgr/prometheus/rbd_stats_pools")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "pool1,pool2", rbdStatsPools)
|
|
|
|
//Case 7: Duplicate entries should be removed from config
|
|
e = monStore.Set("mgr", "mgr/prometheus/rbd_stats_pools", "pool1,pool2,pool1")
|
|
assert.Nil(t, e)
|
|
err = configureRBDStats(context, clusterInfo, "")
|
|
assert.Nil(t, err)
|
|
|
|
rbdStatsPools, err = monStore.Get("mgr", "mgr/prometheus/rbd_stats_pools")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "pool1,pool2", rbdStatsPools)
|
|
|
|
// Case 8: Two CephBlockPools with EnableRBDStats:false & EnableRBDStats:true.
|
|
// SetConfig returns an error
|
|
context.Executor = &exectest.MockExecutor{
|
|
MockExecuteCommandWithTimeout: func(timeout time.Duration, command string, args ...string) (string, error) {
|
|
logger.Infof("Command: %s %v", command, args)
|
|
return "", errors.New("mock error to simulate failure of mon store Set() function")
|
|
},
|
|
}
|
|
err = configureRBDStats(context, clusterInfo, "")
|
|
assert.NotNil(t, err)
|
|
|
|
}
|