#!/bin/bash # Fred Denis -- May 2019 -- fred.denis3@gmail.com -- http://unknowndba.blogspot.com # cell-status.sh - an overview of your Exadata cell and grid disks (http://bit.ly/2VxJIUH) # Copyright (C) 2021 Fred Denis # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # # More info and git repo: http://bit.ly/2VxJIUH -- https://github.com/freddenis/oracle-scripts # # The current script version is 20211111 # # History : # # 20211111 - Fred Denis - GPLv3 licence # 20211019 - Fred Denis - Code cleaning, set a default cell_group which is $HOME/cell_group as it is mainly # what everybody uses so it makes sense not to have to specify it at each execution # 20200914 - Fred Denis - Passing a cell_group file now works correctly # 20190510 - Fred Denis - Initial release # # Variables # NB_PER_LINE=$(bc <<< "`tput cols`/30") # Number of DG to show per line, can be changed with -n option TMP=$(mktemp) # A tempfile TMP2=$(mktemp) # A tempfile SHOW_BAD_DISKS="NO" # Shows the details of the bad disks (-v option) DBMACHINE="/opt/oracle.SupportTools/onecommand/databasemachine.xml" # databasemachine.xml file CELL_GROUP="${HOME}/cell_group" # default cell_group file as it is usually what everybody uses # # User used to connect to the cells `basename $0` -h for more information on this # USER="root" # User to connect to the cells (-u option) NONROOTUSER="cellmonitor" # User to connect to the cells if a non root user runs cell-status.sh (-u option) # If root is not used to run the script it is then less likely than root SSH keys will be deployed for this user # We then use ${NONROOTUSER} to connect if [[ $(id -u) -ne 0 ]]; then USER="${NONROOTUSER}" fi # # An usage function # usage() { printf "\n\033[1;37m%-8s\033[m\n" "NAME" ; cat << END cell-status.sh - an overview of your Exadata cell and grid disks (http://bit.ly/2VxJIUH) END printf "\n\033[1;37m%-8s\033[m\n" "SYNOPSIS" ; cat << END $0 [-v] [-u] [-c] [-o] [-f] [-n] [-h] END printf "\n\033[1;37m%-8s\033[m\n" "DESCRIPTION" ; cat << END $(basename $0) shows a status of the Cell disks and the Grid disks of all the cells of an Exadata It has be be executed by a user with SSH equivalence on the cell servers - If $(basename $0) is executed as root, then $USER is used to connect to the cells - If $(basename $0) is executed as a non root user, then $NONROOTUSER is used to connect to the cells - You can change this behavior by forcing the use of a specific user with the -u option About the cells $(basename $0) reports about: - If $(basename $0) is executed as root: The default cell_group file "${CELL_GROUP}" file is used If "${CELL_GROUP}" does not exist: - on < X8M it uses ibhosts to build the list of cells to connect to - on >= X8M, it fails as there is no ibhosts so a hardcoded cell_group file has to be specified (-c option) - If $(basename $0) is executed as a non root user, it uses the databasemachine.xml file to build the list of cells to connect to END printf "\n\033[1;37m%-8s\033[m\n" "OPTIONS" ; cat << END -v Shows the details of the bad disks (with error or bad status) -u User to connect to the cells (if the default does not suit you) -c -C Specify a file which contains the cell list to connect to (aka cell_group), default is: "${CELL_GROUP}" -o Save the output of the dcli commands in a file ($(basename $0) -o outputfile.log) -f Use a file generated by the -o option as input ($(basename $0) -f outputfile.log) -n Number of diskgroups to show per line (if not specified, $(basename $0) adapts it to the terminal size) -h Shows this help END exit 123 } # # Options # while getopts "ho:f:n:vu:c:C:" OPT; do case ${OPT} in o) OUT="${OPTARG}" ;; f) IN="${OPTARG}" ;; n) NB_PER_LINE="${OPTARG}" ;; v)SHOW_BAD_DISKS="YES" ;; u) USER="${OPTARG}" ;; c) CELL_GROUP="${OPTARG}" ;; C) CELL_GROUP="${OPTARG}" ;; h) usage ;; \?) echo "Invalid option: -$OPTARG" >&2; usage ;; esac done if [[ -z "${IN}" ]]; then # No input file specified, we dynamically find the info from the cells if [[ -z "${CELL_GROUP}" ]]; then if [[ $(id -u) -eq 0 ]]; then # root is executing the script ibhosts | sed s'/"//' | grep cel | awk '{print $6}' | sort > ${TMP2} # list of cells else # When no root if [[ -f "${DBMACHINE}" ]]; then cat "${DBMACHINE}" | awk 'BEGIN {FS="<|>"} {if ($3 == "cellnode") {while(getline) {if ($2 == "ADMINNAME") {print $3; break; } } }}' > "${TMP2}" else cat << END Cannot access ${DBMACHINE}, cannot continue. END exit 255 fi fi fi if [[ -n "${CELL_GROUP}" && -f "${CELL_GROUP}" ]]; then # If a cell_group file is specified we use it cp "${CELL_GROUP}" "${TMP2}" fi dcli -g "${TMP2}" -l "${USER}" "echo celldisk; cellcli -e list celldisk attributes name,status,size,errorcount,disktype; echo BREAK; echo griddisk; cellcli -e list griddisk attributes asmDiskGroupName,name,asmmodestatus,asmdeactivationoutcome,size,errorcount,disktype; echo BREAK_CELL" > "${TMP}" IN="${TMP}" fi if [[ -n "${OUT}" ]]; then # Output file specified, we save the cell infos in and we exit cp "${TMP}" "${OUT}" rm "${TMP}" cat << END Output file ${OUT} has been successfully generated. END exit 456 fi if [[ ! -f "${IN}" ]]; then cat << ! Cannot find the file ${IN}; cannot continue. ! exit 123 fi # # Show the Exadata model if possible # printf "\n" if [ -f "${DBMACHINE}" ] && [ -r "${DBMACHINE}" ]; then cat << ! Cluster is a $(grep -i MACHINETYPES ${DBMACHINE} | sed s'/\t*//' | sed -e s':::g' -e s'/^ *//' -e s'/ *$//') ! else printf "\n" fi # # Read the information from the cells and make nice tables # awk -v nb_per_line="$NB_PER_LINE" -v show_bad_disks="$SHOW_BAD_DISKS" 'BEGIN\ { # Some colors COLOR_BEGIN = "\033[1;" ; COLOR_END = "\033[m" ; RED = "31m" ; GREEN = "32m" ; YELLOW = "33m" ; BLUE = "34m" ; TEAL = "36m" ; WHITE = "37m" ; NORMAL = "0m" ; BACK_LIGHTBLUE = "104m" ; RED_BACKGROUND = "41m" ; # Column size COL_CELL = 20 ; COL_DISKTYPE = 26 ; COL_NB = COL_DISKTYPE/3 ; } # # A function to center the outputs with colors # function center( str, n, color, sep) { right = int((n - length(str)) / 2) ; left = n - length(str) - right ; return sprintf(COLOR_BEGIN color "%" left "s%s%" right "s" COLOR_END sep, "", str, "" ) ; } # # A function that just print a "---" white line # function print_a_line(size) { if ( ! size) { size = COL_DB+COL_VER+(COL_NODE*n)+COL_TYPE+n+3 ; } printf("%s", COLOR_BEGIN WHITE) ; for (k=1; k<=size; k++) {printf("%s", "-");} ; printf("%s", COLOR_END"\n") ; } { sub (":", "", $1) ; if ($2 == "celldisk") { cell = $1 ; tab_cell[cell] = cell ; while (getline) { sub (":", "", $1) ; if ($2 == "BREAK") { break ; } if ($3 == "normal") { tab_status[cell,$NF,$3]++ ; # With status = normal } else { bad_cell_disks[$0] = $0 ; # Bad disks with status != normal } tab_err[cell,$NF]+=$(NF-1) ; # Disks with errors if ($(NF-1) > 0) { bad_cell_disks[$0] = $0 ; # Details to show with -v option } tab_nbdisks[cell,$NF]++ ; # NB disks per distype tab_disktype[$NF]=$NF ; # Disktypes } } # End if ($2 == "celldisk") if ($2 == "griddisk") { cell = $1 ; while(getline) { sub (":", "", $1) ; if ($2 == "BREAK_CELL") { break ; } if ($3 != "UNUSED") # Unused disks have no DG { tab2_err[cell,$2]+=$7 ; # Grid disks with errors if ($7 > 0) { bad_grid_disks[$0] = $0 ; # Details to show with -v option } tab2_nbdisks[cell,$2]++ ; # Nb disks per diskgroup tab2_dgs[$2]=$2 ; # Diskgroups if (tolower($5) != "yes") # asmDeactivationOutcome { tab2_deact[cell,$2]="no" ; bad_grid_disks[$0] = $0 ; # Details to show with -v option } if ($4 == "ONLINE") { tab2_status[cell,$2]++ ; # cell,DG } else { tab2_bad[cell,$2]++ ; # bad status disks bad_grid_disks[$0] = $0 ; # Details to show with -v option } } } } # End if ($2 == "griddisk") } function print_blue_hyphen(size, sep) { printf ("%s", center("--", size, BLUE, sep)) ; # Just print a blue "--" } function print_red_cross(size, sep) { printf ("%s", center("xx", size, COLOR_STATUS, sep)) ; # Just print a red "xx" } function print_legend() { # A legend behind the tables printf(COLOR_BEGIN BLUE " %-"3"s" COLOR_END, "--") ; printf(COLOR_BEGIN WHITE " %-"12"s |" COLOR_END, ": Unused disks") ; printf(COLOR_BEGIN RED " %-"3"s" COLOR_END, "xx") ; printf(COLOR_BEGIN WHITE " %-"20"s |" COLOR_END, ": Not ONLINE disks") ; printf(COLOR_BEGIN RED_BACKGROUND " %-"3"s" COLOR_END, " ") ; printf(COLOR_BEGIN WHITE " %-"20"s" COLOR_END, ": asmDeactivationOutcome is NOT yes"); } function print_table(in_array, in_title, in_header) { # Print a table from in_array adapting every column to the largest colum in the table # including the header from in_header hich is a string collectyion separated by blank like "col1 col2 col3" # Only the first column always have a COL_CELL size to match with the other tables to keep nice output # It then always make a nice table and it was fun to code :) a=asort(in_array, sorted) ; sorted[a+1]= in_header ; # Table header print sorted[0] ; printf("%s", center(in_title, COL_CELL, TEAL)) ; printf("\n") ; for (i=1; i<=a+1; i++) # For each line { split(sorted[i], bad) ; for (j=1; j<=length(bad); j++) # For each column { if (j == 1) # To have the cell column same on all tables { size[j] = COL_CELL ; } else { if (length(bad[j])>size[j]) { size[j] = length(bad[j])+2} ; } } } line_size=0 ; for (k=1; k<=length(size); k++) { line_size+=size[k] ;} for (i=1; i<=a; i++) # For each line { split(sorted[i], bad) ; if (i == 1) { for (j=1; j<=length(bad); j++) # For each column { split(sorted[a+1], title) ; printf("%s", center(title[j], size[j], NORMAL, "|")) ; } printf("\n") ; print_a_line(line_size+length(size)) ; } for (j=1; j<=length(bad); j++) # Each column # For each column { printf("%s", center(bad[j], size[j], NORMAL, "|")) ; } printf("\n") ; } print_a_line(line_size+length(size)) ; } function print_griddisk_header(i) { printed=0 ; printf("\n\n", "") ; printf ("%s", center("Grid Disks", COL_CELL, TEAL, "|")) ; for (j=i; j nb_dgs) # Everything is printed so we stop even if line is not full { break ; } printf ("%s", center(dg, COL_DISKTYPE, WHITE, "|")) ; } printf("\n") ; printf ("%s", center(" ", COL_CELL, WHITE, "|")) ; for (j=i; j nb_dgs) # Everything is printed so we stop even if line is not full { break ; } printf ("%s", center("Nb", COL_NB, WHITE, "|")) ; printf ("%s", center("Online", COL_NB, WHITE, "|")) ; printf ("%s", center("Errors", COL_NB, WHITE, "|")) ; printed++ ; } printf("\n") ; print_a_line(COL_CELL+COL_DISKTYPE*printed+printed+1) ; } END\ { # Sort the arrays nb_cells=asort(tab_cell, tab_cell_sorted) ; # # CELL DISKS # # Disk Types printf("\n", "") ; printf ("%s", center("Cell Disks", COL_CELL, TEAL, "|")) ; for (disktype in tab_disktype) { printf ("%s", center(disktype, COL_DISKTYPE, WHITE, "|")) ; } printf("\n") ; printf ("%s", center(" ", COL_CELL, WHITE, "|")) ; for (disktype in tab_disktype) { printf ("%s", center("Nb", COL_NB, WHITE, "|")) ; printf ("%s", center("Normal", COL_NB, WHITE, "|")) ; printf ("%s", center("Errors", COL_NB, WHITE, "|")) ; } printf("\n") ; print_a_line(COL_CELL+COL_DISKTYPE*length(tab_disktype)+length(tab_disktype)+1) ; for (x=1; x<=nb_cells; x++) { cell=tab_cell_sorted[x] ; printf ("%s", center(cell, COL_CELL, WHITE, "|")) ; for (y in tab_status) { split(y,sep,SUBSEP) ; if (sep[1] == cell) { for (disktype in tab_disktype) { COLOR_ERROR=GREEN ; COLOR_STATUS=GREEN ; # Nb disks printf ("%s", center(tab_nbdisks[cell,disktype], COL_NB, WHITE, "|")) ; # Disks status if (tab_status[cell,disktype,sep[3]]0) { COLOR_ERROR=RED; } printf ("%s", center(tab_err[cell,disktype], COL_NB, COLOR_ERROR, "|")) ; } break ; } } printf("\n") ; } print_a_line(COL_CELL+COL_DISKTYPE*length(tab_disktype)+length(tab_disktype)+1) ; # # Print the failed cell disks details contained in the array bad_cell_disks # if (tolower(show_bad_disks) == "yes") { if (length(bad_cell_disks) > 0) { print_table(bad_cell_disks, "Failed Cell Disks details", "Cell Name Status Size Nb_Error Disktype") ; } } # # GRID DISKS # nb_dgs=asort(tab2_dgs, dgs_sorted) ; for (i=1; i<=nb_dgs; i+=nb_per_line) { print_griddisk_header(i) ; for (x=1; x<=nb_cells; x++) { cell=tab_cell_sorted[x] ; # To ease the naming below nb_printed=0 ; printf ("%s", center(cell, COL_CELL, WHITE, "|")) ; for (k=i; k nb_dgs) # Everything is printed so we stop even if line is not full { break ; } dg=dgs_sorted[k] ; # To ease the naming below if (tab2_deact[cell,dg]) # asmdeactivationoutcome is NOT yes { COLOR_ERROR=RED_BACKGROUND ; COLOR_STATUS=RED_BACKGROUND ; COLOR_STATUS_BAD=RED_BACKGROUND ; COLOR_NB_DISKS=RED_BACKGROUND ; } else { COLOR_ERROR=GREEN ; COLOR_STATUS=GREEN ; COLOR_STATUS_BAD=RED ; COLOR_NB_DISKS=WHITE ; } if (tab2_nbdisks[cell,dg]) { printf ("%s", center(tab2_nbdisks[cell,dg], COL_NB, COLOR_NB_DISKS, "|")) ; # NB disks } else { print_blue_hyphen(COL_NB, "|") ; } if (tab2_status[cell,dg] 0) { print_red_cross(COL_NB, "|") ; } else { if (tab2_status[cell,dg]) { printf ("%s", center(tab2_status[cell,dg], COL_NB, COLOR_STATUS, "|")) ; # Nb disks with ONLINE status } else { print_blue_hyphen(COL_NB, "|") ; } } if (tab2_err[cell,dg]>0) { COLOR_ERROR=COLOR_STATUS_BAD; } if (tab2_err[cell,dg] != "") { printf ("%s", center(tab2_err[cell,dg], COL_NB, COLOR_ERROR, "|")) ; # NB errors } else { print_blue_hyphen(COL_NB, "|") ; } nb_printed++ ; } printf("\n") ; } print_a_line(COL_CELL+COL_DISKTYPE*nb_printed+nb_printed+1) ; print_legend() ; } # End for (i=1; i<=nb_dgs; i++) # Show bad grid disks if (tolower(show_bad_disks) == "yes") { printf("\n\n") ; printf("%s", center("Failed Grid Disks details", COL_CELL, TEAL)) ; printf("\n") ; # print_table(bad_grid_disks, "Failed Grid Disks details", "Cell asmDGName Name Status Deact Size NBError Disktype") ; if (length(bad_grid_disks) > 0) { a=asort(bad_grid_disks, bad_grid_disks_sorted) ; printf("%-14s%-24s%12s%16s%6s%8s%6s%16s\n", "cell", "asmDGName", "name","status", "deactoutcome", "size", "error" ,"disktype" ) ; for (i=1; i<=a; i++) { printf ("%s\n", bad_grid_disks_sorted[i]) ; } } printf("\n") ; } printf("\n") ; printf("\n") ; }' "${IN}" # # Delete tempfiles # for F in "${TMP}" "${TMP2}"; do rm -f "${F}" done #****************************************************************# # E N D O F S O U R C E *# #****************************************************************#