In a former life, I was a storage administrator for Thomas Jefferson University Hospital in Center City, Philadelphia. Aside from the wide array of lunch options we had to choose from, another thing I really enjoyed was working with IBM's SVC, or SAN Volume Controller. As a sysadmin, there are few software packages that you truly enjoy working with and you think are really, really good, but the SVC was one of those. One of the reasons was because the command-line interface was just a restricted Bash shell - something I knew very, very well. This allowed me to do pretty amazing things, as the shell is a wonderfully capable programming language. Back then, I had a Wiki that I used as a notepad, and I had several pages on the cool things you could do with SVC on the command line. Here is one of those pages, cut-and-pasted from my internal webserver. Be warned, though - none of this has been updated since about 2007!
Thanks to the awesome Aussie Storage Blog for the inspiration to resurrect these old pages.
SVC's command line interface (CLI) is a restricted bash shell. You might not be able to cd to other directories or run commands like "grep" or "awk", (see below) but you can still do some really useful stuff using the shell builtins available to you.
Thanks to the awesome Aussie Storage Blog for the inspiration to resurrect these old pages.
Basic info
- The "for var in blah blah blah blah; do; done" and "while read; do; done" loops are very powerful tools available to the shell script programmer, and are the backbone of virtually every miniscript I write.
- SVC's bash is restricted, but otherwise the same as the bash you would find on any linux box. Prior to SVC 4.2, the bash version is 2.05. At 4.2, the bash is updated to 3.1. If you've got a linux system available to play with, you can try out these miniscripts there before you try them on your production SVC. If you want to test these scripts on a different linux system, you might want to see the SVC miniscript testing page.
- Before you actually run commands, ESPECIALLY on an SVC where you can cause some serious damage to your SAN, prefix the command with an "echo" so you can see the command it would run without actually running the command!
So, before you run "svctask rmvdisk $x" from inside a loop; first run "echo svctask rmvdisk $x" and verify that it's going to do what you think it's going to do. Much better to see that you messed something up on the screen than let your SVC try to remove every vdisk you've got assigned. Clients will not be happy nor will they be too understanding when you tell them you were using some ultra-fancy "bash miniscripts" to make working on the SVC more efficient.
Possible gotchas
Beyond the obvious gotchas of really screwing up the configuration of your SVC if you get your miniscripts wrong, there are a couple little gotchas to be aware of.
* Variables set inside loops are not available outside of loops.
found=0 svcinfo lsvdisk -nohdr | while read line do [[ $line == *DS4800* ]] && (( ++found )) done echo "Found $found vdisks on the DS4800"
This will always report 0 vdisks, even if it does find vdisks, because the loop executes in a subshell, and thus any modification to the variable won't be available to the parent shell. A workaround is to use command substitution and catch the STDOUT of the loop:
found=$(svcinfo lsvdisk -nohdr | while read x; do [[ $x == *DS4800* ]] && echo -n X; done) echo "Found ${#found} vdisks on the DS4800"
For each disk I find, I echo an "X". At the end of the loop, I've got stored in the variable "found" something which looks like "XXXXX" for five vdisks. Echoing "${#found}" gives me the length of the variable, and thus, the number of vdisks. Painful workaround? Yep.
This is a bash thing -- don't blame SVC. Korn shell (ksh) works just fine either way, but we're not running on the ksh, are we?
Multi-line entry
To make things more readable, you can enter your mini scripts on multiple lines:
svcinfo lsmdisk -delim : | while read line do echo "$line" done
(this miniscript doesn't really accomplish anything... just echoes what it reads, as if we ran "svcinfo lsmdisk | cat")
Bash will convert your entry to a one-liner (separating lines with ";" as appropriate) when it enters it into the history file, so the above will look like this in the history file:
svcinfo lsmdisk -delim: | while read line; do echo "$line"; done
Because of this, when I'm writing mini-scripts, I just edit them in the one-line format. Your option.
Shortening command lines
if/then with only one command can be simplified:
if [[ $x = "yes" ]] then echo "yes" fi
[[ $x = "yes" ]] && echo "yes"
if/then/else with only one command can be simplified:
if [[ $x == "yes" ]] then echo "yes" else echo "no" fi
[[ $x == "yes" ]] && echo "yes" || echo "no"
Multiple commands can be run as well:
if [[ $x == "yes" ]] then echo "yes" runcommand fi
[[ $x == "yes" ]] && echo "yes" && runcommand
- caveat: The shortened version will not work exactly the same as the long version. In the short version, "runcommand" is only run if the first command,
echo "yes"
is successful. Odds are good that an echo isn't going to fail, but if "runcommand" was first and it failed, the next command doesn't run. In the "long" if/then format, both commands will be run, even if the first fails. - Basically, unless you really understand how the "&&" and "||" dividers work, stick to single commands in your shortened if/then commands. Anyhow, the if/then syntax for multiple commands (on one line) is still pretty easy:
if test; then command1; command2; fi
No "ls"? No problem!
SVC's restricted bash shell gives you very, VERY few commands. You can still get by without some of them using the bash builtins. Here's how we can simulate the "ls" command:
echo /dumps/*
This lists the contents of the /dumps/ directory, but in a pretty ugly format... all the files are listed separated by a space, and wrapped around lines. A little hard to read. Here's how we can have something more like "ls -F" format: ("/" appended to the name of directories)
for file in /dumps/* do [[ -d $file ]] && echo "$file/" || echo "$file" done
I don't know of any bash builtins that could give us the equivalent output of "ls -l", though. No way to get file size, permissions, or ownership. You can check if your own permissions as related to the file (can I read, write, execute) but can't see who owns it. That said, if the file is there, as admin you will have permissions to access it
No "grep"? No worries!
Wouldn't it be nice to "svcinfo lsvdisk | grep MyDisk"? Here's how: (basic form)
svcinfo lsvdisk -nohdr -delim : | while read line do [[ $line = *match* ]] && echo $line done
- The bash used in SVC is kinda old (version 2.05 on SVC v4.1) which is too bad. If it were a more modern version (v3.x) there are many REALLY powerful pattern matching capabilities available as builtins... including regular expressions!
- WUHU! SVC 4.2 has upgraded bash to version 3.x! This opens up some REAL possibilities for powerful pattern matching. See the bash doc for more info.
No "awk"? No concerns!
Since most SVC output is field specific, you can use modify the "grep" code above bash to split up the stuff that you're reading:
svcinfo lsmdiskgrp -nohdr -delim : | while IFS=: read id name status nummdisk numvdisk size extsize free do [[ $name == *4500* ]] && echo "MDiskGroup $name (id $id) has $free available of $size" done
Note: Using the ":" delimiter and setting IFS allows us to handle blank fields. F'rinstance, "lsvdisk" often has blanks for FC_id, FC_name, RC_id, and RC_name. (unless you've got Flash Copy on that vdisk) but the vdisk UID might be important to you. If you don't set IFS and leave "delim" unset, there will just be extra blank space, and the vdisk UID will be set to the "FC_id". Setting the IFS to the delimiter makes it set the blank fields to blank. IF you don't have to deal with blank fields (like say lsmdiskgrp) then you can leave the delim unset and just use a read: (you should still turn off the header so you don't process that!)
svcinfo lsmdiskgrp -nohdr | while read id name status nummdisk numvdisk size extsize free do ...
No "sleep"? Now that's a problem.
If you wanted to run a command repeatedly, separated by a certain time delay, I haven't figured out how to do that yet. The "sleep" command, oddly enough, isn't a bash builtin. (even though it seems like it could be, very easily... and not to harp on ksh93, but it's a builtin there as well, and takes fractional delays, to go with ksh93's capability for floating-point math... oh, and it leaves variables set in loops available to the original shell...)
One option I considered was checking the first field of /proc/uptime in a loop, and exiting the loop when the value equals (( initialvalue + sleeptime )), but the problem there is that bash will check the contents of the file as often as it can, many times a second, creating a possibly significant load on the system, which isn't a good idea.
- Interactive workaround for no "sleep" command:
Instead of having the system delay for a certain period of time, you can have the system wait for your input, using the "read" command to read a line of input. You can then CTRL-C or put in a specific exit string:
while read keyb do svc command goes here [[ $keyb == x ]] && break done
This will execute the command every time you hit ENTER, and if you type a single "x", it will return you to a command prompt. Don't use "exit" instead of "break" or you'll be logged out when you type "x".
Building associative arrays
One of the biggest pains in the butt with bash is the variable scoping. If you want to build an associative array of, say, mDisk Name mapping to its state, you can't just set up a while loop with the output of lsmdisk piped into it. The reason is that the pipe spawns a subshell and the array that you're merrily building belongs to the subshell and so ceases to exist once you exit the loop
I worked out the following method.
- Build an array of your keys, using command substitution
- Build an array of your values, using command substitution
- Iterate over your values and generate your associative array
eg
vdiskIds=(`svcinfo lsvdisk -nohdr | while read id rest; do echo -n "$id "; done`) vdiskNames=(`svcinfo lsvdisk -nohdr | while read id name rest; do echo -n "$name "; done`) vdiskNameMap=() for (( i = 0 ; i < ${#vdiskNames[} ; i++ ))do
vdiskNameMap[${vdiskIds[$i]}]=${vdiskNames[$i]}done @]
Because no subshell was spawned for the last loop, vdiskNameMap is avaiable for later. I use this in my extent summary script
Functions in bash
If you wanted to use the "grep" code above and actually call it "grep", you could do that by defining a function:
function grep { while read line; do [[ $line == *$1* ]] && echo "$line"; done }
- Since you can't edit your .bash_profile or .bashrc, every time you log into the SVC, you'd have to re-enter the function. This could be done via cut & paste, or if you wanted to get fancy, an expect script which logged in, defined your functions, and then gave you interactive control.
Since we don't have the capability to set aliases in restricted shell, you can use this if you want to get rid of the annoying need to prefix everything with "svcinfo" and "svctask":
function lsmdiskgrp { svcinfo lsmdiskgrp $*; }
"lsmdiskgrp" now works just like "svcinfo lsmdiskgrp", taking options (like "-nohdr") and arguments (like the name or id or an mdiskgrp) without having to type "svcinfo" first.
- bash can be finicky when you define a function and put commands in { } brackets. Usually bash isn't like perl in needing each line terminated with ";", but if you have a simple command like we have here, end the command with a ";" before the closing "}". Watch your whitespace, too.
A more powerful miniscript
An example. If you're running extents migrations, the progress information given by "svcinfo lsmigrate" is a little hard to read:
IBM_2145:TJUH_SVC:admin>svcinfo lsmigrate migrate_type MDisk_Extents_Migration progress 33 migrate_vdisk_index 13 migrate_source_mdisk_index 10 migrate_target_mdisk_index 1 number_extents 160 max_thread_count 1 migrate_type MDisk_Extents_Migration progress 90 migrate_vdisk_index 12 migrate_source_mdisk_index 10 migrate_target_mdisk_index 1 number_extents 60 max_thread_count 1
...and so forth. That's pretty ugly. Wouldn't it be nice if we could see output in a format like:
Vdisk 13 52% of 160 extents Vdisk 1 10% of 819 extents Vdisk 10 37% of 64 extents Vdisk 7 22% of 96 extents Vdisk 4 0% of 194 extents
Much nicer! It's actually pretty easy. Here's the command line:
svcinfo lsmigrate | while read x y; do [[ $x = progress ]] && p=$y [[ $x = migrate_vdisk_index ]] && vdisk=$y [[ $x = number_extents ]] && printf "Vdisk %3d %3d%% of %4d extents\n" "$vdisk" "$p" "$y" done
Normally I'll just create the whole command line with one line and semicolons separating commands, since it's easier to return to that line and edit.
In this instance, 0% means it hasn't started the migration... it can only run four threads at once, so a vdisk at 0% is waiting. Let's fancy it up some:
svcinfo lsmigrate | while read x y; do \ [[ $x = progress ]] && p=$y \ [[ $x = migrate_vdisk_index ]] && vdisk=$y \ if [[ $x = number_extents ]]; then \ if [[ $p = 0 ]] then echo "Vdisk $vdisk is waiting..." else printf "Vdisk %3d %3d%% of %4d extents\n" "$vdisk" "${p}" "$y" fi; fi done
That produces output like this:
Vdisk 1 35% of 819 extents Vdisk 4 85% of 194 extents Vdisk 5 82% of 160 extents Vdisk 0 6% of 16 extents Vdisk 14 is waiting... Vdisk 17 is waiting... Vdisk 32 is waiting...
As you can see, the command lines can get a little complicated, but you can do some REALLY powerful stuff.
More advanced stuff
- A progress bar could be simulated like so:
->bar="########################################" # That's 40 pound signs barlen=${#bar} # Sets "barlen" to 40 - the length of "bar" space=" " # 40 spaces blocks=$((p*barlen/100)) # How many blocks for that percentage. echo ">${bar:0:$blocks}${space:0:$((barlen-blocks))<"
For a value of p=70; output would look like this:
->>############################ <
pmwiki isn't showing all the spaces properly here... there would be 28 pound signs followed by 12 spaces, keeping the whole width between the > < characters at 40 characters.
Bash links
- http://www.gnu.org/software/bash/
GNU's page on bash. - http://www.gnu.org/software/bash/manual/bashref.html
GNU's bash reference manual
1 comment:
Hello,
Nice write up here.
So here's the thing, i'm faced with getting the list of vdisks, capacity and their respective disposition across various tiers of storage in a storwize V7000, tried a couple of things but no luck, i know the commands to do this individually but i have over 300 vdisks currently and going through each one and copying it to an excel spreadsheet is such a PITA, any chance you can help with a short script to achieve this?
just for reference, i have to run about two commands and copy some of the output
e.g lsvdisk - this lists all vdisks and i can also obtain the capacity from here
second one is lsvdisk vdiskName to get the disposition across tier0, tier1, enterprise and NL-SAS storage.
Post a Comment