github.com/spotahome/redis-operator@v1.2.4/test/integration/redisfailover/creation_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package redisfailover_test 5 6 import ( 7 "context" 8 "fmt" 9 "net" 10 "path/filepath" 11 "testing" 12 "time" 13 14 rediscli "github.com/go-redis/redis/v8" 15 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 corev1 "k8s.io/api/core/v1" 19 v1 "k8s.io/api/core/v1" 20 apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 "k8s.io/apimachinery/pkg/labels" 23 "k8s.io/client-go/kubernetes" 24 25 _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" 26 "k8s.io/client-go/util/homedir" 27 28 redisfailoverv1 "github.com/spotahome/redis-operator/api/redisfailover/v1" 29 redisfailoverclientset "github.com/spotahome/redis-operator/client/k8s/clientset/versioned" 30 "github.com/spotahome/redis-operator/cmd/utils" 31 "github.com/spotahome/redis-operator/log" 32 "github.com/spotahome/redis-operator/metrics" 33 "github.com/spotahome/redis-operator/operator/redisfailover" 34 "github.com/spotahome/redis-operator/service/k8s" 35 "github.com/spotahome/redis-operator/service/redis" 36 ) 37 38 const ( 39 name = "testing" 40 namespace = "rf-integration-tests" 41 redisSize = int32(3) 42 sentinelSize = int32(3) 43 authSecretPath = "redis-auth" 44 testPass = "test-pass" 45 redisAddr = "redis://127.0.0.1:6379" 46 ) 47 48 type clients struct { 49 k8sClient kubernetes.Interface 50 rfClient redisfailoverclientset.Interface 51 aeClient apiextensionsclientset.Interface 52 redisClient redis.Client 53 } 54 55 func (c *clients) prepareNS() error { 56 ns := &corev1.Namespace{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: namespace, 59 }, 60 } 61 _, err := c.k8sClient.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) 62 return err 63 } 64 65 func (c *clients) cleanup(stopC chan struct{}) { 66 c.k8sClient.CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) 67 close(stopC) 68 } 69 70 func TestRedisFailover(t *testing.T) { 71 require := require.New(t) 72 73 // Create signal channels. 74 stopC := make(chan struct{}) 75 errC := make(chan error) 76 77 flags := &utils.CMDFlags{ 78 KubeConfig: filepath.Join(homedir.HomeDir(), ".kube", "config"), 79 Development: true, 80 } 81 82 // Kubernetes clients. 83 k8sClient, customClient, aeClientset, err := utils.CreateKubernetesClients(flags) 84 require.NoError(err) 85 86 // Create the redis clients 87 redisClient := redis.New(metrics.Dummy) 88 89 clients := clients{ 90 k8sClient: k8sClient, 91 rfClient: customClient, 92 aeClient: aeClientset, 93 redisClient: redisClient, 94 } 95 96 // Create kubernetes service. 97 k8sservice := k8s.New(k8sClient, customClient, aeClientset, log.Dummy, metrics.Dummy) 98 99 // Prepare namespace 100 prepErr := clients.prepareNS() 101 require.NoError(prepErr) 102 103 // Give time to the namespace to be ready 104 time.Sleep(15 * time.Second) 105 106 // Create operator and run. 107 redisfailoverOperator, err := redisfailover.New(redisfailover.Config{}, k8sservice, k8sClient, namespace, redisClient, metrics.Dummy, log.Dummy) 108 require.NoError(err) 109 110 go func() { 111 errC <- redisfailoverOperator.Run(context.Background()) 112 }() 113 114 // Prepare cleanup for when the test ends 115 defer clients.cleanup(stopC) 116 117 // Give time to the operator to start 118 time.Sleep(15 * time.Second) 119 120 // Create secret 121 secret := &v1.Secret{ 122 ObjectMeta: metav1.ObjectMeta{ 123 Name: authSecretPath, 124 Namespace: namespace, 125 }, 126 Data: map[string][]byte{ 127 "password": []byte(testPass), 128 }, 129 } 130 _, err = k8sClient.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{}) 131 require.NoError(err) 132 133 // Check that if we create a RedisFailover, it is certainly created and we can get it 134 ok := t.Run("Check Custom Resource Creation", clients.testCRCreation) 135 require.True(ok, "the custom resource has to be created to continue") 136 137 // Giving time to the operator to create the resources 138 time.Sleep(3 * time.Minute) 139 140 // Verify that auth is set and actually working 141 t.Run("Check that auth is set in sentinel and redis configs", clients.testAuth) 142 143 // Check custom config is set 144 t.Run("Check that custom config is behave expected", clients.testCustomConfig) 145 146 // Check that a Redis Statefulset is created and the size of it is the one defined by the 147 // Redis Failover definition created before. 148 t.Run("Check Redis Statefulset existing and size", clients.testRedisStatefulSet) 149 150 // Check that a Sentinel Deployment is created and the size of it is the one defined by the 151 // Redis Failover definition created before. 152 t.Run("Check Sentinel Deployment existing and size", clients.testSentinelDeployment) 153 154 // Connect to all the Redis pods and, asking to the Redis running inside them, check 155 // that only one of them is the master of the failover. 156 t.Run("Check Only One Redis Master", clients.testRedisMaster) 157 158 // Connect to all the Sentinel pods and, asking to the Sentinel running inside them, 159 // check that all of them are connected to the same Redis node, and also that that node 160 // is the master. 161 t.Run("Check Sentinels Checking the Redis Master", clients.testSentinelMonitoring) 162 } 163 164 func (c *clients) testCRCreation(t *testing.T) { 165 assert := assert.New(t) 166 toCreate := &redisfailoverv1.RedisFailover{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: name, 169 Namespace: namespace, 170 }, 171 Spec: redisfailoverv1.RedisFailoverSpec{ 172 Redis: redisfailoverv1.RedisSettings{ 173 Replicas: redisSize, 174 Exporter: redisfailoverv1.Exporter{ 175 Enabled: true, 176 }, 177 CustomConfig: []string{`save ""`}, 178 }, 179 Sentinel: redisfailoverv1.SentinelSettings{ 180 Replicas: sentinelSize, 181 }, 182 Auth: redisfailoverv1.AuthSettings{ 183 SecretPath: authSecretPath, 184 }, 185 }, 186 } 187 188 c.rfClient.DatabasesV1().RedisFailovers(namespace).Create(context.Background(), toCreate, metav1.CreateOptions{}) 189 gotRF, err := c.rfClient.DatabasesV1().RedisFailovers(namespace).Get(context.Background(), name, metav1.GetOptions{}) 190 191 assert.NoError(err) 192 assert.Equal(toCreate.Spec, gotRF.Spec) 193 } 194 195 func (c *clients) testRedisStatefulSet(t *testing.T) { 196 assert := assert.New(t) 197 redisSS, err := c.k8sClient.AppsV1().StatefulSets(namespace).Get(context.Background(), fmt.Sprintf("rfr-%s", name), metav1.GetOptions{}) 198 assert.NoError(err) 199 assert.Equal(redisSize, int32(redisSS.Status.Replicas)) 200 } 201 202 func (c *clients) testSentinelDeployment(t *testing.T) { 203 assert := assert.New(t) 204 sentinelD, err := c.k8sClient.AppsV1().Deployments(namespace).Get(context.Background(), fmt.Sprintf("rfs-%s", name), metav1.GetOptions{}) 205 assert.NoError(err) 206 assert.Equal(3, int(sentinelD.Status.Replicas)) 207 } 208 209 func (c *clients) testRedisMaster(t *testing.T) { 210 assert := assert.New(t) 211 masters := []string{} 212 213 redisSS, err := c.k8sClient.AppsV1().StatefulSets(namespace).Get(context.Background(), fmt.Sprintf("rfr-%s", name), metav1.GetOptions{}) 214 assert.NoError(err) 215 216 listOptions := metav1.ListOptions{ 217 LabelSelector: labels.FormatLabels(redisSS.Spec.Selector.MatchLabels), 218 } 219 220 redisPodList, err := c.k8sClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) 221 222 assert.NoError(err) 223 224 for _, pod := range redisPodList.Items { 225 ip := pod.Status.PodIP 226 if ok, _ := c.redisClient.IsMaster(ip, "6379", testPass); ok { 227 masters = append(masters, ip) 228 } 229 } 230 231 assert.Equal(1, len(masters), "only one master expected") 232 } 233 234 func (c *clients) testSentinelMonitoring(t *testing.T) { 235 assert := assert.New(t) 236 masters := []string{} 237 238 sentinelD, err := c.k8sClient.AppsV1().Deployments(namespace).Get(context.Background(), fmt.Sprintf("rfs-%s", name), metav1.GetOptions{}) 239 assert.NoError(err) 240 241 listOptions := metav1.ListOptions{ 242 LabelSelector: labels.FormatLabels(sentinelD.Spec.Selector.MatchLabels), 243 } 244 sentinelPodList, err := c.k8sClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) 245 assert.NoError(err) 246 247 for _, pod := range sentinelPodList.Items { 248 ip := pod.Status.PodIP 249 master, _, _ := c.redisClient.GetSentinelMonitor(ip) 250 masters = append(masters, master) 251 } 252 253 for _, masterIP := range masters { 254 assert.Equal(masters[0], masterIP, "all master ip monitoring should equal") 255 } 256 257 isMaster, err := c.redisClient.IsMaster(masters[0], "6379", testPass) 258 assert.NoError(err) 259 assert.True(isMaster, "Sentinel should monitor the Redis master") 260 } 261 262 func (c *clients) testAuth(t *testing.T) { 263 assert := assert.New(t) 264 265 redisCfg, err := c.k8sClient.CoreV1().ConfigMaps(namespace).Get(context.Background(), fmt.Sprintf("rfr-%s", name), metav1.GetOptions{}) 266 assert.NoError(err) 267 assert.Contains(redisCfg.Data["redis.conf"], "requirepass "+testPass) 268 assert.Contains(redisCfg.Data["redis.conf"], "masterauth "+testPass) 269 270 redisSS, err := c.k8sClient.AppsV1().StatefulSets(namespace).Get(context.Background(), fmt.Sprintf("rfr-%s", name), metav1.GetOptions{}) 271 assert.NoError(err) 272 273 assert.Len(redisSS.Spec.Template.Spec.Containers, 2) 274 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[1].Name, "REDIS_ADDR") 275 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[1].Value, redisAddr) 276 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[2].Name, "REDIS_PORT") 277 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[2].Value, "6379") 278 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[3].Name, "REDIS_USER") 279 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[3].Value, "default") 280 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[4].Name, "REDIS_PASSWORD") 281 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[4].ValueFrom.SecretKeyRef.Key, "password") 282 assert.Equal(redisSS.Spec.Template.Spec.Containers[1].Env[4].ValueFrom.SecretKeyRef.LocalObjectReference.Name, authSecretPath) 283 } 284 285 func (c *clients) testCustomConfig(t *testing.T) { 286 assert := assert.New(t) 287 288 redisSS, err := c.k8sClient.AppsV1().StatefulSets(namespace).Get(context.Background(), fmt.Sprintf("rfr-%s", name), metav1.GetOptions{}) 289 assert.NoError(err) 290 291 listOptions := metav1.ListOptions{ 292 LabelSelector: labels.FormatLabels(redisSS.Spec.Selector.MatchLabels), 293 } 294 redisPodList, err := c.k8sClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) 295 assert.NoError(err) 296 297 rClient := rediscli.NewClient(&rediscli.Options{ 298 Addr: net.JoinHostPort(redisPodList.Items[0].Status.PodIP, "6379"), 299 Password: testPass, 300 DB: 0, 301 }) 302 defer rClient.Close() 303 304 result := rClient.ConfigGet(context.TODO(), "save") 305 assert.NoError(result.Err()) 306 307 values, err := result.Result() 308 assert.NoError(err) 309 310 assert.Len(values, 2) 311 assert.Empty(values[1]) 312 }