Adding helm chart & documentation for redis-cluster related study ticket
[ric-plt/ric-dep.git] / helm / redis-cluster / templates / configMap.yaml
diff --git a/helm/redis-cluster/templates/configMap.yaml b/helm/redis-cluster/templates/configMap.yaml
new file mode 100644 (file)
index 0000000..d7da426
--- /dev/null
@@ -0,0 +1,387 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  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.
+# ============LICENSE_END=========================================================
+###################################################################################
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Values.rediscluster.name }}-cm
+data:
+  update-node.sh: |
+    #!/bin/sh
+    REDIS_NODES="/data/nodes.conf"
+    sed -i -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${REDIS_NODES}
+    exec "$@"
+
+  redis.conf: |+
+    cluster-enabled yes
+    cluster-require-full-coverage no
+    cluster-node-timeout {{ .Values.rediscluster.nodetimeout }}
+    cluster-config-file /data/nodes.conf
+    cluster-migration-barrier 1
+    appendonly yes
+    protected-mode no
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Values.assigner.name }}-cm
+data:
+  placenode.pl: |
+    #!/usr/bin/env perl
+    =head
+    ============LICENSE_START=======================================================
+    
+    ================================================================================
+    Copyright (C) 2020 Hcl Technologies Limited.
+    ================================================================================
+    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.
+    ============LICENSE_END=========================================================
+  
+    
+    About:
+  
+    This script has been developed as part of https://jira.o-ran-sc.org/browse/RIC-360
+    This script identifies the missing anti-affinity(as per above ticket) of redis instances 
+    required in a redis-cluster. If there is an  undesired  anti-affinity this script can  be 
+    executed to communicate to redis nodes  to switch roles (e.g. master/slave) such that the 
+    end-state meets the desired anti-affinity.
+           
+  
+    Pre-requisites: 
+  
+      1) A redis cluster with 3 masters (2 replicas each) deployed on kubernetes 1.18 (or later) 
+      2) Three available worker nodes for serving redis workloads
+      3) kubectl (with access to the k8 cluster)
+   
+    =cut
+  
+  
+    my $podRow = { 
+    "podIP"      => "",
+    "podName"    => "",
+    "k8Node"     => "",
+    
+    "rdNodeRole" => "",
+    "rdNodeID"   => "",
+    
+    "rdMasterNodeID"   => "",
+    "slaveIPs"    => [] 
+    };
+    
+    # Pod label for redis nodes
+    my $podLabel = $ENV{'POD_LABEL'};
+    
+    my $podTable =   [];
+    my $k8NodeInfo = [];
+    
+    setk8NodesInfo();
+    validate();
+    
+    # Master
+    spreadMastersIfRequired();
+    # Slave
+    my $disparity = getSlaveDisparity();
+    spreadSlavesIfRequired();
+    
+    sub validate() {
+        my @masters = map { $_->{'rdNodeRole'} eq 'master' ? $_ : () } @{$podTable};
+       if ( @masters > @{$k8NodeInfo->{allk8Nodes}} ) {
+               print "Info: Skipping any action as num of master > number of k8 nodes..\n";
+           exit;
+       }
+    }
+    
+    
+    sub spreadSlavesIfRequired() {
+        
+    
+       # Get node with maximum disparity first
+        my @disparityMatrix = reverse sort { @{$a} <=> @{$b} } @${disparity}; 
+        #@disparityMatrix = grep defined, @disparityMatrix;
+        #@disparityMatrix = map { defined $_ ? $_ : () } @disparityMatrix;
+    
+        # Get list of slaves to be swapped roles.
+        my @slaveSwapList = ();
+        my $maxDisparityPerNode = @{$disparityMatrix[0]};
+    
+        for (my $disparityPass=0; $disparityPass < $maxDisparityPerNode; $disparityPass++) {
+            for (my $k8NodeIndex=0; $k8NodeIndex <= $#{disparityMatrix}; $k8NodeIndex++) {
+                  #print "$disparityMatrix[$disparityPass] && $disparityMatrix[$k8NodeIndex][$disparityPass]";
+               if ( $disparityMatrix[$disparityPass] && $disparityMatrix[$k8NodeIndex][$disparityPass] ) {
+                           push(@slaveSwapList,$disparityMatrix[$k8NodeIndex][$disparityPass]);
+                  }
+            }
+        }
+        if ( ! @slaveSwapList ) {
+               print "Info: No disparity found with slaves.\n" if ( @slaveSwapList < 2);
+               exit;
+       } elsif ( @slaveSwapList == 1 ) {
+               print "Info: single host scenario (with no swap candidate in other k8 nodes) found.\n";
+               exit;
+       } else {
+               print "Info: slave disparity found.\n";
+        }
+    
+       # Swap slaves 
+       for (my $swapIndex=0; $swapIndex < @slaveSwapList; $swapIndex++) {
+               $pod1 = $slaveSwapList[$swapIndex];
+               $pod2 = $slaveSwapList[++$swapIndex];
+               #print "Info: Swapping Slaves: " . join($pod1->{podName}, $pod2->{podName}) . "\n";
+               
+               my $cmd1 = qq[kubectl exec -it ].
+                                  qq[$pod1->{podName}  -- redis-cli -p 6379 cluster replicate $pod2->{rdMasterNodeID} ];
+               
+               my $cmd2 = qq[kubectl exec -it ].
+                                  qq[$pod2->{podName}  -- redis-cli -p 6379 cluster replicate $pod1->{rdMasterNodeID} ];
+    
+           runRediClusterCmd($cmd1);
+           runRediClusterCmd($cmd2);
+               #print "\n$cmd1";
+               #print "\n$cmd2\n";
+        }
+    
+    }
+    
+    
+    sub getSlaveDisparity() {
+    
+        # Get Slave Disparity Metrix
+        my $disparity = ();
+        my $nodeIndex = 0;
+        foreach my $k8NodeName ( @{$k8NodeInfo->{allk8Nodes}} ) {
+            my @redisNodesOnk8Node = map { $_->{'k8Node'} eq $k8NodeName ? $_ : () } @{$podTable};
+            @redisNodesOnk8Node    = sort { $a->{"rdNodeRole"} cmp $b->{"rdNodeRole"} } @redisNodesOnk8Node;
+    
+            my $master = shift @redisNodesOnk8Node;
+            
+            for (my $index=0; $index <= $#{redisNodesOnk8Node}; $index++ ) {
+                my $slave = $redisNodesOnk8Node[$index];
+                #print "chekcing for pod:  $slave->{podName}\n";
+                my $disparityFound = 0;
+                if ( $slave->{rdMasterNodeID} eq $master->{rdNodeID} ) {
+                   $disparityFound = 1;
+                } else {
+                   #check is other slaves are its sibling
+                   for (my $nextIndex=$index + 1; $nextIndex <= $#{redisNodesOnk8Node}; $nextIndex++ ) {
+                       if ( $slave->{rdMasterNodeID} eq $redisNodesOnk8Node[$nextIndex]->{rdMasterNodeID} ) {
+                              $disparityFound = 1;
+                           break;
+                       }
+                   }
+                }
+                       if ($disparityFound) {
+                       #$disparity[$nodeIndex][$index] = { 'podName' => $slave->{"podName"}, 'rdMasterNodeID' => $slave->{"rdMasterNodeID"} } ;
+                       push(@{$disparity[$nodeIndex]},{ 'podName' => $slave->{"podName"}, 'rdMasterNodeID' => $slave->{"rdMasterNodeID"} } ) ;
+                       }
+            }
+            $nodeIndex++;
+        }
+            return \@disparity;
+    }
+    
+    sub spreadMastersIfRequired() {
+    
+       NODE_WITH_NO_MASTER: foreach my $nodeWithoutMaster (@{$k8NodeInfo->{k8NodesWithoutMaster}}) {
+          # For each k8Node without any master 
+          #    Check for each extra master on its hostNode
+          #        Find its slave on the this hostNode (i.e. without any master) 
+          # Such slave must be Found for 3x3 set-up:
+          # Then Promote as master # Re-Evaluate
+    
+          # Get All Redis Slaves on This k8 node
+          print "Info: K8 node without any master : $nodeWithoutMaster\n";
+          my @rdSlaveNodes =  map { ($_->{'k8Node'} eq $nodeWithoutMaster ) && ($_->{'rdNodeRole'} eq 'slave') ? $_ : () } @{$podTable};
+    
+               foreach my $nodeWithExtraMaster (@{$k8NodeInfo->{k8NodesWithExtraMaster}} ) {
+                  print "Info: k8 Node with extra master : $nodeWithExtraMaster\n";
+                  #my @rdSlaveNodes =  map { ($_->{'k8Node'} eq $nodeWithoutMaster ) && ($_->{'rdNodeRole'} eq 'slave') ? $_ : () } @{$podTable};
+    
+                  my @masterInstances = map { ($_->{'k8Node'} eq $nodeWithExtraMaster ) && ($_->{'rdNodeRole'} eq 'master') ? $_ : () } @{$podTable};        
+                  foreach my $master (@masterInstances) {
+                      my @slave = map { $_->{"rdMasterNodeID"} eq $master->{rdNodeID} ? $_ : () } @rdSlaveNodes;
+                      if ( @slave ) {
+                          promoteSlaveAsMaster($slave[0]);
+                                         my $isPromoted = 0;
+                                     my $slaveNodeID= $slave[0]->{rdNodeID};
+                                         while( ! $isPromoted ) {
+                                                sleep(8);
+                                            setk8NodesInfo();
+                                                my ($promotedNode) = map { $slaveNodeID eq $_->{rdNodeID} ? $_ : () } @{$podTable};
+    
+                                                if ( $promotedNode->{'rdNodeRole'} ne 'master' ) {
+                                                       print ("Info: Waiting for node promotion confirmation..\n");
+                                                } else {
+                                                       $isPromoted = 1;
+                                                       print ("Info: Node promotion confirmed.\n");
+                                                }
+                                         }
+                          next NODE_WITH_NO_MASTER;
+                      }
+                  }
+               }
+       }
+       print "Info: All redis masters are on separate k8 Nodes. \n"    if ( ! @{$k8NodeInfo->{k8NodesWithoutMaster}}) ;
+    }
+    
+    sub promoteSlaveAsMaster() {
+        my $slavePod = shift;    
+        #print "Info: Promoting Slave $slavePod->{'podName'} On $slavePod->{'k8Node'} as master";
+        my $cmd = qq[kubectl exec -it $slavePod->{'podName'} -- redis-cli -p 6379 cluster failover takeover];
+        runRediClusterCmd($cmd);
+        
+    }
+    sub runRediClusterCmd() {
+      my $cmd = shift;    
+      print "Info: Running Cmd:$cmd \n";
+      `$cmd;`;
+      sleep(8);
+    }
+    
+    
+    #foreach my $item (@{$podTable}) {
+    #}
+    
+    # find_nodes_without-a-single_master
+    sub setk8NodesInfo() {
+    
+       $podTable   = [];
+       $k8NodeInfo = [];
+    
+       getCurrentStatus();
+       # All k8 nodes
+       my @k8NodeList = uniq(map { $_->{'k8Node'} } @$podTable);
+    
+       # Find Nodes with At least One master
+       my @k8NodesWithMaster;
+       foreach my $nodeName (@k8NodeList) {
+          push(@k8NodesWithMaster, map { ($_->{'k8Node'} eq $nodeName) && ($_->{'rdNodeRole'} eq 'master')   ? $nodeName : ()  } @{$podTable} );
+       }
+    
+       # Find Nodes without any master = All nodes - Nodes with at least one Master
+       my %k8NodesMap = ();
+       foreach (@k8NodesWithMaster) { 
+               if ( exists $k8NodesMap{$_} ) {
+                       $k8NodesMap{$_}++;
+               } else {
+                       $k8NodesMap{$_} = 1;
+               }
+       }
+       my @k8NodesWithoutMaster = map { exists $k8NodesMap{$_} ? () : $_ } @k8NodeList;
+       my @k8NodesWithExtraMaster = uniq(map { $k8NodesMap{$_} > 1 ? $_ : () } @k8NodesWithMaster);
+    
+       $k8NodeInfo = { 'allk8Nodes' => \@k8NodeList, 'k8NodesWithExtraMaster' => \@k8NodesWithExtraMaster, 'k8NodesWithoutMaster' => \@k8NodesWithoutMaster };
+    }
+    
+    
+    
+    
+    
+    # Validate if number of masters ,= number of rea
+    
+    #
+    #sub filter
+    
+    =head
+    get 
+    podName where k8Node eq "x"
+        get position of k8node eq x 
+    where 
+    =cut
+    
+    exit;
+    
+    sub uniq {
+        my %seen;
+        grep !$seen{$_}++, @_;
+    }
+    
+    sub getCurrentStatus() {
+    
+        # Run pod list command    
+        my @getPods = `kubectl get po --no-headers  -o wide -l $podLabel |grep Running`;    chomp @getPods;
+        #my @getPods = `kubectl get po --no-headers  -o wide -l managed-by=redis-cluster-operator|grep Running`;    chomp @getPods;
+    
+        foreach my $podLine (@getPods) {
+            my @podData = split(/\s+/,$podLine);
+            my ($podName,$status,$age,$podIP,$podNode) = ($podData[0], $podData[2], $podData[4], $podData[5],$podData[6]);
+    
+            #print "$podName,$status,$age,$podIP,$podNode" ."\n"; 
+            my $podRow = { 'podIP' => $podIP, 'podName' => $podName, 'k8Node' => $podNode, 'podAge' => $age, 'podStatus' => $status };    
+            push (@{$podTable},$podRow)
+        }
+    
+        my $podName = $podTable->[0]{'podName'};
+        #print "Info:kubectl exec $podName  -- cat nodes.conf|sort -k3\n";
+        my @rdNodeData = `kubectl exec $podName  -- cat nodes.conf|sort -k3`;    chomp @rdNodeData;
+        foreach my $rdNodeLine (@rdNodeData) {
+            next if ($rdNodeLine !~ /master|slave/);
+                my @rdNodeData = split(/\s+/,$rdNodeLine);
+                my ($rdNodeID,$rdRole,$rdMasterNodeID,$epoch) = ($rdNodeData[0], $rdNodeData[2], $rdNodeData[3],$rdNodeData[5]);
+                my ($podIP) = split(/:/,$rdNodeData[1]);
+                $rdRole =~ s/myself,//;
+    
+                #print "$rdNodeID,$rdRole,$rdMasterNodeID,$podIP" ."\n";
+                my $rdElem = { 'podIP'    => $podIP, 
+                               'rdNodeID' => $rdNodeID,
+                               'rdRole'   => $rdRole,
+                               'rdMasterNodeID' => $rdMasterNodeID,
+                               'epoch'          => $epoch
+                };
+    
+            for(my $index=0; $index <= $#{$podTable}; $index++) {
+                if ( $podTable->[$index]{'podIP'} eq $podIP ) {
+                    #print "Matched\n";
+                    $podTable->[$index]{'rdNodeID'}       = $rdNodeID;
+                    $podTable->[$index]{'rdNodeRole'}        = $rdRole;
+                    $podTable->[$index]{'rdMasterNodeID'} = $rdMasterNodeID;
+                    $podTable->[$index]{'epoch'}          = $epoch;
+                }
+            }
+            #exit;
+    
+        }
+    }
+
+  relatenode.sh: |
+    #!/bin/sh
+    podLabel=${POD_LABEL}
+    firstPod=$(kubectl  get   po -o wide -l app.kubernetes.io/name=redis-cluster --no-headers=true|head -1|cut -d" " -f1)
+    
+    kubectl get po -o wide -l $podLabel |tail +2|awk '{printf("%s:%s:%s:%s\n",$6,$1,$7,$10)}'|sort  > /tmp/1.txt
+    kubectl exec  $firstPod  -- cat nodes.conf|sed 's/myself,//'|awk '/master|slave/ {print $2,$1,$3,$4}'|sort > /tmp/2.txt
+    join -t ":"  /tmp/1.txt /tmp/2.txt |sort -k3,4 | sed 's/ /:/g'|awk -F":" '{print $2,$7,$3,$1,$4,$6,$8}' > /tmp/3.txt
+    
+    echo "\n   POD_NAME      ROLE      k8NODE        POD_IP                   REDIS_NODE_ID                       REDIS_MASTER_NODE_ID"
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '1p') /tmp/3.txt
+    echo ""
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '2p') /tmp/3.txt
+    echo ""
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '3p') /tmp/3.txt
+