On Sun, Apr 03, 2011 at 10:15:50AM +0200, Nhan Ngo Dinh wrote:
> Hello all,
> 
> Please find the attached plugin.
> 
> It requires vSphere SDK for Perl:
> http://www.vmware.com/support/developer/viperltoolkit/
> 
> Parameters:
> 
> hostlist (e.g. hostname1=VMNAME;hostname2=VMNAME2), ip (vCenter IP),
> username, password
> 
> Tested with 4.1

I don't know too much about vCenter, I'll assume that the interactions
with it are "correct", whatever that means.

But I know my share of perl and pacemaker.
So here are a few comments.


> #!/usr/bin/perl
> #
> # External STONITH module for VMWare vCenter
> #
> # Author:  Nhan Ngo Dinh
> # License:      GNU General Public License (GPL)
> #
> 
> use strict;
> use warnings;
> use Switch;

I don't like that Switch.pm thingy ;-)
But never mind...

> use VMware::VIRuntime;
> 
> my $command = $ARGV[0];
> my $targetHost;
> if ( defined $ARGV[1] ) {
>     $targetHost = $ARGV[1];
> }

This end result of this is still the same as just writing
  my $targetHost = $ARGV[1];
 

> if (defined($ENV{'ip'})) {

If you mean to test for existence of hash elements, please always use
"exists". The above will create an empty but from now on exported 'ip'
variable into to environment.
You likely want to say

if (exists $ENV{ip}) {

(and yes, you can give it more line noise,
if you feel more comfortable that way)

If you actually want to say "if defined",
then you could also just do away with the if,
and assign directly.

>   $ENV{'VI_PORTNUMBER'} = 443;
>   $ENV{'VI_PROTOCOL'} = "https";
>   $ENV{'VI_SERVER'} = $ENV{'ip'};


Why would you only override VI_PORTNUMBER and VI_PROTOCOL
if the ip is defined?

Why would you want to have your own "ip" environment,
and not expose VI_SERVER directly?


> }
> 
> if (defined($ENV{'username'})) {
>   $ENV{'VI_USERNAME'} = $ENV{'username'};
>   $ENV{'VI_PASSWORD'} = $ENV{'password'};
>   Opts::parse();
>   Opts::validate();
> } else {
>   Opts::parse();

No validate here? Why not?

> }

I suppose the Opts::validate() is supposed to throw an error (aka die()),
if there is insufficient information to contact the management entity?
If so, please document that.

Probably you want to catch that via eval {},
and log the error message with some context information
about where it comes from, before propagating the error.

> 
> my $ret = 255;
> my $hosts = {};
> my $realTarget;
> 
> if ( defined $ENV{'hostlist'} ) {
>       my @lines;
>       my $line;
>       @lines = split(/;/, $ENV{'hostlist'});
>         foreach $line (@lines) {
>             my @config = split(/=/, $line);
>             $hosts->{$config[0]} = $config[1];
>         }
>       if (defined $targetHost) { $realTarget = $hosts->{$targetHost}; }
> }

Please add a comment what this is supposed to do, and why.
Not what it does, that's clear ;-)

Also: clearly document that no whitespace is allowed, nowhere,
and hostname=VMNAME; is a hard requirement, even if hostname and VMNAME
should match.
Or change the code to allow that special case to be written as just
"hostname;" in the hostlist.

Since you later do the reverse mapping in nested loops,
how about constructing both forward and backward mapping hashes here?
%host_to_vm = ();
%vm_to_host = ();
...

> sub getvms {
>       Util::connect();
>       my $regex = "";
>       for (keys %$hosts) {
>               if (length($regex) > 0) { $regex .= "|"; }
>               $regex .= $hosts->{$_};
>       }
>         my $vms = Vim::find_entity_views(view_type => "VirtualMachine", 
> filter => { 'name' => qr/^($regex)/ });

I'm not sure if you really mean what you wrote there.

My guess would be that you actually meant to say
my $regex = join "|" map { qr/\Q$_\E/ } values %$hosts;
my $vms = Vim::find_entity_views(view_type => "VirtualMachine", filter => { 
'name' => qr/^($regex)$/i });

What's the difference?
So it is written differently.
So what. You can also write that as a for (keys ...) {} no problem.

Then, it does a case insensitive match (I'm not sure, but I assume
VMNAME is supposed to be matched case insensitive?)

It does not omit the trailing $,
so VMNAM would not match both VMNAM and VMNAME ;-)

Most importantly, it \Q-ot-\E-s the VMNAMEs, so VM.NA.ME won't match
VMxNAyME, but only VM.NA.ME.

>       Util::disconnect();
>       return $vms;
> }
> 
> switch ($command) {
>       case "reset" {

Bah. Did I mention that I dislike switch statements in Perl?
Ah, never you mind ;)

>               Util::connect();
>               my $vm = Vim::find_entity_view(view_type => "VirtualMachine", 
> filter => { name => $realTarget });

Unless this filter thing has a special mode where it internally does a
"$x eq $y" for scalars and "$x =~ $y" for explicitly designated qr//
Regexp objects, I'd suggest to here also do
        filter => { name => qr/^\Q$realTarget\E$/i }

>               my $hostname = Vim::get_view(mo_ref => 
> $vm->{"runtime"}->{"host"})->name;

What is the hostname used for, now?

>               $vm->ResetVM();

Hm. Apparently the filter thingy did something different,
the return value seems to be a reference to some "manageable" VM object,
previously it was supposed to be a reference to an array of such objects?

>               Util::disconnect();

Do the Util::connect(), disconnect() once, not within each case.

I suggest doing
Util::connect();
eval {
        ...
}
if ($@) {
        my $error = $@;

        log the error in a suitable way

        ....
        $ret = 1; # or 255, or 42 or whatever feels "right".
} else {
        $ret = 0;
}
Util::disconnect();


>               $ret = 0;
>       }
> 
>       case "off" {
>               Util::connect();
>               my $vm = Vim::find_entity_view(view_type => "VirtualMachine", 
> filter => { name => $realTarget });
>               my $hostname = Vim::get_view(mo_ref => 
> $vm->{"runtime"}->{"host"})->name;

Again, useless (?) use of $hostname.

>               $vm->PowerOffVM();
>               Util::disconnect();
>               $ret = 0;
>       }
> 
>       case "on" {
>               Util::connect();
>               my $vm = Vim::find_entity_view(view_type => "VirtualMachine", 
> filter => { name => $realTarget });
>               my $hostname = Vim::get_view(mo_ref => 
> $vm->{"runtime"}->{"host"})->name;
>               $vm->PowerOnVM();
>               Util::disconnect();
>               $ret = 0;
>       }
> 
>       case "gethosts" {
>               my $vms = getvms();
>               foreach my $vm (@$vms) {
>                       while (my ($k, $v) = each %$hosts) {
>                               if ($vm->name eq $v) {
>                                       print $k . "\n";
>                               }
>                       }
>               }

        I'd always write "$k\n" instead of $k . "\n";
        I'd use the previously created vm_to_host hash.

        So that would become
        foreach my $vm (@$vms) { print "$vm_to_host{$vm->name}\n" };


>               $ret = 0;
>       }
> 
>       case "status" {
>               $ret = system("ping -c1 $ENV{'ip'}");
>               #Util::connect();
>               #Util::disconnect();
>               #$ret = 0;

Hm.
You I'd like some sort of "no-op" that actually excercises the
connection to the "VM manager" level, instead of pinging some ip,
which may even be misspelled accidentally (10.0.0.23 instead of
10.0.0.32 ;-)

So rather actually do an Util::connect(), so something with the Vim::
blahfoo (e.g. chose a random hostname from the hostlist, and query it's
vms "power" status)

>       }
> 
>       case "getconfignames" {
>               print "hostlist\nip\nusername\npassword\n";
>               $ret = 0;
>       }
> 
>       case "getinfo-devid" {
>               print "VMware vCenter STONITH device\n";
>               $ret = 0;
>       }
> 
>       case "getinfo-devname" {
>               print "VMware vCenter STONITH device\n";
>               $ret = 0;
>       }
> 
>       case "getinfo-devdescr" {
>               print "VMWare vCenter STONITH device\n";
>               $ret = 0;
>       }
> 
>       case "getinfo-devurl" {
>               print "http://www.vmware.com/\n";;
>               $ret = 0;
>       }
> 
>       case "getinfo-xml" {
>               print "<parameters>
> <parameter name=\"hostlist\" unique=\"1\">

Did you know:
print q{blafoo <lala/> x="unquoted double quotes" possible here.};
(mind the difference between q{} and qq{};
also, there are "HERE" documents in perl as well...)

If there are two ways to express something,
I tend to use the one with less line noise,
so I'd try to avoid all those \"\"

(btw, I did not validate the xml).

                print q{<parameters>
<parameter name="hostlist" unique="1">

        Is it unique?  Why?
        I think it would be legit to be able to stonith the same hosts
        from more than one instance of stonith plugins.

<content type="string"/>
<shortdesc lang="en">hostlist</shortdesc>
<longdesc lang="en">
The list of hosts that the VMware vCenter STONITH device controls.
Format is "hostname1=VirtualMachineName1;hostname2=VirtualMachineName2"

        No whitespace allowed.
        hostname is supposed to be "uname -n", which is what is passed
        in as STONITH victim on the command line.

        [if I understood correctly]

</longdesc>
</parameter>
<parameter name="ip" unique="1">

Again, why would that be unique?
Same for parameters below.

<content type="string"/>
<shortdesc lang="en">ip</shortdesc>
<longdesc lang="en">
The VMware vCenter IP address
</longdesc>
</parameter>
<parameter name="username" unique="1">
<content type="string"/>
<shortdesc lang="en">username</shortdesc>
<longdesc lang="en">
The username to access VMware vCenter
</longdesc>
</parameter>
<parameter name="password" unique="1">
<content type="string"/>
<shortdesc lang="en">password</shortdesc>
<longdesc lang="en">
The password to access VMware vCenter

        You probably want to mention that it would be a good idea to
        secure all pacemaker communication paths against the potential
        of sniffing, and lock down the permissions on any location that
        could end up containing copies of the cib in either xml or plain
        text, or risk funny DoS attacks ;-)
        Then again, may that is obvious.

        That's a problem for all STONITH devices, though, unless they
        somehow stored their credentials somewhere else.

        I think there is some bugzilla on that issue (protecting
        sensitive information in the cib from appearing in logs,
        or rather keep them out of the cib completely).

        Dejan?

</longdesc>
</parameter>
</parameters>
};

>               $ret = 0;
>       }
> 
>       else {
>               $ret = 1;
>       }
> 
> }
> 
> exit($ret);

Enough for today.

Thank you for this contribution.

If we now can have a few other "Reviewed-by:" or "Tested-by:" signatures,
that would be great.


-- 
: Lars Ellenberg
: LINBIT | Your Way to High Availability
: DRBD/HA support and consulting http://www.linbit.com
_______________________________________________________
Linux-HA-Dev: Linux-HA-Dev@lists.linux-ha.org
http://lists.linux-ha.org/mailman/listinfo/linux-ha-dev
Home Page: http://linux-ha.org/

Reply via email to