root/honeyclient/branches/bug/42/lib/HoneyClient/Manager/VM.pm

Revision 96, 152.6 kB (checked in by kindlund, 2 years ago)

Completed registry parser documentation and unit tests; corrected minor mispellings; updated POD documentation to reflect public website.

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  Dec 29, 2005
3 # Package:     HoneyClient::Manager::VM
4 # File:        VM.pm
5 # Description: A SOAP server that provides programmatic access to all
6 #              VM clients.
7 #
8 # CVS: $Id$
9 #
10 # @author kindlund
11 #
12 # Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation, using version 2
17 # of the License.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301, USA.
28 #
29 #######################################################################
30
31 =pod
32
33 =head1 NAME
34
35 HoneyClient::Manager::VM - Perl extension to instantiate a SOAP server
36 that provides programmmatic access to all VM clients within the locally
37 running VMware Server / GSX server.
38
39 =head1 VERSION
40
41 This documentation refers to HoneyClient::Manager:VM version 1.0.
42
43 =head1 SYNOPSIS
44
45 =head2 CREATING THE SOAP SERVER
46
47   use HoneyClient::Manager::VM;
48
49   # Handle SOAP requests on the default address:port.
50   my $URL = HoneyClient::Manager::VM->init();
51
52   # Handle SOAP requests on TCP port localhost:9090
53   my $URL = HoneyClient::Manager::VM->init(address => "localhost",
54                                            port    => 9090);
55
56   print "Server URL: " . $URL . "\n";
57
58   # Create a cleanup function, to execute whenever
59   # the SOAP server needs to be destroyed.
60   sub cleanup {
61       HoneyClient::Manager::VM->destroy();
62       exit;
63   }
64
65   # Install the cleanup handler, in case parent process
66   # dies unexpectedly.
67   $SIG{HUP}       = \&cleanup;
68   $SIG{INT}       = \&cleanup;
69   $SIG{QUIT}      = \&cleanup;
70   $SIG{ABRT}      = \&cleanup;
71   $SIG{PIPE}      = \&cleanup;
72   $SIG{TERM}      = \&cleanup;
73
74   # Catch all parent code errors, in order to perform cleanup
75   # on all child processes before exiting.
76   eval {
77       # Do rest of the parent processing here...
78   };
79
80   # We assume you still want to still want to "die" on
81   # any errors found within the eval block.
82   if ($@) {
83       HoneyClient::Manager::VM->destroy();
84       die $@;
85   }
86
87   # Even if no errors occurred, initiate cleanup.
88   cleanup();
89
90 =head2 INTERACTING WITH THE SOAP SERVER
91
92   use HoneyClient::Util::SOAP qw(getClientHandle);
93
94   # Create a new SOAP client, to talk to the HoneyClient::Manager::VM
95   # module.
96   my $stub = getClientHandle(namespace => "HoneyClient::Manager::VM");
97   my $som;
98
99   # Enumerate all registered VMs.
100   $som = $stub->enumerate();
101   my @list = $som->paramsall;
102   print "\t$_\n" foreach (@list);
103   print "\n";
104
105   # Assume we have a particular VM.
106   my $vmConfig = "/path/to/vm.vmx";
107
108   # See if a particular VM is registered.
109   $som = $stub->isRegisteredVM(config => $vmConfig);
110   if ($som->result) {
111       print "Yes, the VM is registered.";
112   } else {
113       print "No, the VM is not registered.";
114   }
115
116   # Register a particular VM.
117   $som = $stub->registerVM(config => $vmConfig);
118   if ($som->result) {
119       print "Success!\n";
120   } else {
121       print "Failed!\n";
122   }
123
124   # Unregister a particular VM.
125   $som = $stub->unregisterVM(config => $vmConfig);
126   if ($som->result) {
127       print "Success!\n";
128   } else {
129       print "Failed!\n";
130   }
131
132   # Get the state of a particular VM.
133   use VMware::VmPerl qw(VM_EXECUTION_STATE_ON
134                         VM_EXECUTION_STATE_OFF
135                         VM_EXECUTION_STATE_STUCK
136                         VM_EXECUTION_STATE_SUSPENDED);
137   $som = $stub->getStateVM(config => $vmConfig);
138   if ($som->result == VM_EXECUTION_STATE_ON) {
139       print "ON\n";
140   } elsif ($som->result == VM_EXECUTION_STATE_OFF) {
141       print "OFF\n";
142   } elsif ($som->result == VM_EXECUTION_STATE_SUSPENDED) {
143       print "SUSPENDED\n";
144   } elsif ($som->result == VM_EXECUTION_STATE_STUCK) {
145       print "STUCK\n";
146   } else {
147       print "UNKNOWN\n";
148   }
149
150   # Start a particular VM.
151   $som = $stub->startVM(config => $vmConfig);
152   if ($som->result) {
153       print "Success!\n";
154   } else {
155       print "Failed!\n";
156   }
157  
158   # Stop a particular VM.
159   $som = $stub->stopVM(config => $vmConfig);
160   if ($som->result) {
161       print "Success!\n";
162   } else {
163       print "Failed!\n";
164   }
165  
166   # Reboot a particular VM.
167   $som = $stub->rebootVM(config => $vmConfig);
168   if ($som->result) {
169       print "Success!\n";
170   } else {
171       print "Failed!\n";
172   }
173  
174   # Suspend a particular VM.
175   $som = $stub->suspendVM(config => $vmConfig);
176   if ($som->result) {
177       print "Success!\n";
178   } else {
179       print "Failed!\n";
180   }
181
182   # After starting a particular VM, if the VM's
183   # state is STUCK, we can try automatically answering
184   # any pending questions that the VMware Server / GSX
185   # daemon is waiting for.
186   #
187   # Note: In most cases, this call doesn't need to
188   # be made, since startVM() will try this call
189   # automatically, if needed.
190   $som = $stub->answerVM(config => $vmConfig);
191   if ($som->result) {
192       print "Success!\n";
193   } else {
194       print "Failed!\n";
195   }
196
197   # Create a new full clone from a particular VM
198   # and put the clone in the "/vm/TEST" directory.
199   my $destDir = "/vm/TEST";
200   $som = $stub->fullCloneVM(src_config => $vmConfig, dest_dir => $destDir);
201   my $cloneConfig = $som->result;
202   if ($som->result) {
203       print "Successfully created clone VM at ($cloneConfig)!\n";
204   } else {
205       print "Failed to create clone!\n";
206   }
207  
208   # Create a new quick clone from a particular VM
209   # and put the clone in the "/vm/TEST" directory.
210   my $destDir = "/vm/TEST";
211   $som = $stub->quickCloneVM(src_config => $vmConfig, dest_dir => $destDir);
212   my $cloneConfig = $som->result;
213   if ($som->result) {
214       print "Successfully created clone VM at ($cloneConfig)!\n";
215   } else {
216       print "Failed to create clone!\n";
217   }
218  
219   # Set a particular VM to be a master image,
220   # allowing us to call quickCloneVM() without
221   # any arguments.
222   $som = $stub->setMasterVM(config => $vmConfig);
223   if ($som->result) {
224       print "Success!\n";
225   } else {
226       print "Failed!\n";
227   }
228
229   # Get the name of a particular VM.
230   $som = $stub->getNameVM(config => $vmConfig);
231   my $dispName = $som->result;
232   if ($som->result) {
233       print "VM Name: \"$dispName\"\n";
234   } else {
235       print "Failed to get VM name!\n";
236   }
237
238   # Set the name of a particular VM to "BLAH".
239   $som = $stub->setNameVM(config => $vmConfig, name => "BLAH");
240   my $dispName = $som->result;
241   if ($som->result) {
242       print "VM Renamed To: \"$dispName\"\n";
243   } else {
244       print "Failed to rename VM!\n";
245   }
246
247   # Get the MAC address of a particular VM's first NIC.
248   $som = $stub->getMACaddrVM(config => $vmConfig);
249   my $macAddress = $som->result;
250   if ($som->result) {
251       print "VM MAC Address: \"$macAddress\"\n";
252   } else {
253       print "Failed to get VM MAC address!\n";
254   }
255  
256   # Get the IP address of a particular VM's first NIC.
257   $som = $stub->getIPaddrVM(config => $vmConfig);
258   my $ipAddress = $som->result;
259   if ($som->result) {
260       print "VM IP Address: \"$ipAddress\"\n";
261   } else {
262       print "Failed to get VM IP address!\n";
263   }
264
265   # Destroy a particular VM.
266   $som = $stub->destroyVM(config => $vmConfig);
267   if ($som->result) {
268       print "Success!\n";
269   } else {
270       print "Failed!\n";
271   }
272
273   # Save a snapshot of a particular VM, saving the
274   # snapshot to "/path/to/snapshot.tar.gz".
275   $som = $stub->snapshotVM(config => $vmConfig, snapshot_file => "/path/to/snapshot.tar.gz");
276   my $destSnapshot = $som->result;
277   if ($som->result) {
278       print "Successfully snapshotted VM at ($destSnapshot)!\n";
279   } else {
280       print "Failed to snapshot VM!\n";
281   }
282
283   # Revert a particular VM back to a previous snapshot,
284   # where the snapshot file is located at
285   # "/path/to/snapshot.tar.gz".
286   $som = $stub->revertVM(config => $vmConfig, snapshot_file => "/path/to/snapshot.tar.gz");
287   my $revertConfig = $som->result;
288   if ($som->result) {
289       print "Successfully reverted VM at ($revertConfig)!\n";
290   } else {
291       print "Failed to revert VM!\n";
292   }
293
294 =head1 DESCRIPTION
295
296 Once created, the daemon acts as a stand-alone SOAP server,
297 processing individual requests and manipulating VMs on the
298 locally running VMware Server / GSX server.
299
300 =cut
301
302 package HoneyClient::Manager::VM;
303
304 use strict;
305 use warnings;
306 use Config;
307 use Carp ();
308
309 # Traps signals, allowing END: blocks to perform cleanup.
310 use sigtrap qw(die untrapped normal-signals error-signals);
311
312 #######################################################################
313 # Module Initialization                                               #
314 #######################################################################
315
316 BEGIN {
317     # Defines which functions can be called externally.
318     require Exporter;
319     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
320
321     # Set our package version.
322     $VERSION = 0.9;
323
324     @ISA = qw(Exporter);
325
326     # Symbols to export on request
327     @EXPORT = qw();
328
329     # Items to export into callers namespace by default. Note: do not export
330     # names by default without a very good reason. Use EXPORT_OK instead.
331     # Do not simply export all your public functions/methods/constants.
332
333     # This allows declaration use HoneyClient::Manager::VM ':all';
334     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
335     # will save memory.
336
337     %EXPORT_TAGS = (
338         'all' => [ qw() ],
339     );
340
341     # Symbols to autoexport (:DEFAULT tag)
342     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
343
344     # Check to see if ithreads are compiled into this version of Perl.
345     if (!$Config{useithreads}) {
346         Carp::croak "Error: Recompile Perl with ithread support, in order to use this module.\n";
347     }
348
349     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
350 }
351 our (@EXPORT_OK, $VERSION);
352
353 =pod
354
355 =begin testing
356
357 # Generate a notice, to clarify our assumptions.
358 diag("Note: These unit tests *expect* the VMware Server / GSX daemon to be operational on this system beforehand.");
359
360 # Make sure Log::Log4perl loads
361 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
362         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
363        
364         # Suppress all logging messages, since we need clean output for unit testing.
365         Log::Log4perl->init({
366             "log4perl.rootLogger"                               => "DEBUG, Buffer",
367             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
368             "log4perl.appender.Buffer.min_level"                => "fatal",
369             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
370             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
371         });
372 }
373 require_ok('Log::Log4perl');
374 use Log::Log4perl qw(:easy);
375
376 # Make sure HoneyClient::Util::Config loads.
377 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar))
378         or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path.");
379
380         # Suppress all logging messages, since we need clean output for unit testing.
381         Log::Log4perl->init({
382             "log4perl.rootLogger"                               => "DEBUG, Buffer",
383             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
384             "log4perl.appender.Buffer.min_level"                => "fatal",
385             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
386             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
387         });
388 }
389 require_ok('HoneyClient::Util::Config');
390 can_ok('HoneyClient::Util::Config', 'getVar');
391 use HoneyClient::Util::Config qw(getVar);
392
393 # Suppress all logging messages, since we need clean output for unit testing.
394 Log::Log4perl->init({
395     "log4perl.rootLogger"                               => "DEBUG, Buffer",
396     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
397     "log4perl.appender.Buffer.min_level"                => "fatal",
398     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
399     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
400 });
401
402 # Make sure the module loads properly, with the exportable
403 # functions shared.
404 BEGIN { use_ok('HoneyClient::Manager::VM') or diag("Can't load HoneyClient::Manager:VM package.  Check to make sure the package library is correctly listed within the path."); }
405 require_ok('HoneyClient::Manager::VM');
406 can_ok('HoneyClient::Manager::VM', 'init');
407 can_ok('HoneyClient::Manager::VM', 'destroy');
408 use HoneyClient::Manager::VM;
409
410 # Make sure HoneyClient::Util::SOAP loads.
411 BEGIN { use_ok('HoneyClient::Util::SOAP', qw(getServerHandle getClientHandle)) or diag("Can't load HoneyClient::Util::SOAP package.  Check to make sure the package library is correctly listed within the path."); }
412 require_ok('HoneyClient::Util::SOAP');
413 can_ok('HoneyClient::Util::SOAP', 'getServerHandle');
414 can_ok('HoneyClient::Util::SOAP', 'getClientHandle');
415 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
416
417 # Make sure File::Basename loads.
418 BEGIN { use_ok('File::Basename', qw(dirname basename)) or diag("Can't load File::Basename package.  Check to make sure the package library is correctly listed within the path."); }
419 require_ok('File::Basename');
420 can_ok('File::Basename', 'dirname');
421 can_ok('File::Basename', 'basename');
422 use File::Basename qw(dirname basename);
423
424 # Make sure File::Copy::Recursive loads.
425 BEGIN { use_ok('File::Copy::Recursive', qw(dircopy pathrmdir)) or diag("Can't load File::Copy::Recursive package.  Check to make sure the package library is correctly listed within the path."); }
426 require_ok('File::Copy::Recursive');
427 can_ok('File::Copy::Recursive', 'dircopy');
428 can_ok('File::Copy::Recursive', 'pathrmdir');
429 use File::Copy::Recursive qw(dircopy pathrmdir);
430
431 # Make sure Data::Dumper loads.
432 BEGIN { use_ok('Data::Dumper') or diag("Can't load Data::Dumper package.  Check to make sure the package library is correctly listed within the path."); }
433 require_ok('Data::Dumper');
434 use Data::Dumper;
435
436 # Make sure File::stat loads.
437 BEGIN { use_ok('File::stat') or diag("Can't load File::stat package.  Check to make sure the package library is correctly listed within the path."); }
438 require_ok('File::stat');
439 use File::stat;
440
441 # Make sure Digest::MD5 loads.
442 BEGIN { use_ok('Digest::MD5', qw(md5_hex)) or diag("Can't load Digest::MD5 package.  Check to make sure the package library is correctly listed within the path."); }
443 require_ok('Digest::MD5');
444 can_ok('Digest::MD5', 'md5_hex');
445 use Digest::MD5 qw(md5_hex);
446
447 # Make sure DateTime::HiRes loads.
448 BEGIN { use_ok('DateTime::HiRes') or diag("Can't load DateTime::HiRes package.  Check to make sure the package library is correctly listed within the path."); }
449 require_ok('DateTime::HiRes');
450 use DateTime::HiRes;
451
452 # Make sure Fcntl loads.
453 BEGIN { use_ok('Fcntl', qw(O_RDONLY)) or diag("Can't load Fcntl package.  Check to make sure the package library is correctly listed within the path."); }
454 require_ok('Fcntl');
455 use Fcntl qw(O_RDONLY);
456
457 # Make sure VMware::VmPerl loads.
458 BEGIN { use_ok('VMware::VmPerl', qw(VM_EXECUTION_STATE_ON VM_EXECUTION_STATE_OFF VM_EXECUTION_STATE_STUCK VM_EXECUTION_STATE_SUSPENDED)) or diag("Can't load VMware::VmPerl package.  Check to make sure the package library is correctly listed within the path."); }
459 require_ok('VMware::VmPerl');
460 use VMware::VmPerl qw(VM_EXECUTION_STATE_ON VM_EXECUTION_STATE_OFF VM_EXECUTION_STATE_STUCK VM_EXECUTION_STATE_SUSPENDED);
461
462 # Make sure VMware::VmPerl::Server loads.
463 BEGIN { use_ok('VMware::VmPerl::Server') or diag("Can't load VMware::VmPerl::Server package.  Check to make sure the package library is correctly listed within the path."); }
464 require_ok('VMware::VmPerl::Server');
465 use VMware::VmPerl::Server;
466
467 # Make sure VMware::VmPerl::ConnectParams loads.
468 BEGIN { use_ok('VMware::VmPerl::ConnectParams') or diag("Can't load VMware::VmPerl::ConnectParams package.  Check to make sure the package library is correctly listed within the path."); }
469 require_ok('VMware::VmPerl::ConnectParams');
470 use VMware::VmPerl::ConnectParams;
471
472 # Make sure VMware::VmPerl::VM loads.
473 BEGIN { use_ok('VMware::VmPerl::VM') or diag("Can't load VMware::VmPerl::VM package.  Check to make sure the package library is correctly listed within the path."); }
474 require_ok('VMware::VmPerl::VM');
475 use VMware::VmPerl::VM;
476
477 # Make sure VMware::VmPerl::VM loads.
478 BEGIN { use_ok('VMware::VmPerl::Question') or diag("Can't load VMware::VmPerl::Question package.  Check to make sure the package library is correctly listed within the path."); }
479 require_ok('VMware::VmPerl::Question');
480 use VMware::VmPerl::Question;
481
482 # Make sure threads loads.
483 BEGIN { use_ok('threads') or diag("Can't load threads package.  Check to make sure the package library is correctly listed within the path."); }
484 require_ok('threads');
485 use threads;
486
487 # Make sure threads::shared loads.
488 BEGIN { use_ok('threads::shared') or diag("Can't load threads::shared package.  Check to make sure the package library is correctly listed within the path."); }
489 require_ok('threads::shared');
490 use threads::shared;
491
492 # Make sure Thread::Queue loads.
493 BEGIN { use_ok('Thread::Queue') or diag("Can't load Thread::Queue package.  Check to make sure the package library is correctly listed within the path."); }
494 require_ok('Thread::Queue');
495 use Thread::Queue;
496
497 # Make sure Thread::Semaphore loads.
498 BEGIN { use_ok('Thread::Semaphore') or diag("Can't load Thread::Semaphore package.  Check to make sure the package library is correctly listed within the path."); }
499 require_ok('Thread::Semaphore');
500 use Thread::Semaphore;
501
502 # TODO: Remove this once unit testing should actually be used.
503 # Ideally, this should be handled programmatically, based upon user prompt.
504 exit;
505
506 # Generate a notice, to inform the tester that these tests are not
507 # exactly quick.
508 diag("Note: These unit tests will take *significant* time to complete (10-30 minutes).");
509
510 =end testing
511
512 =cut
513
514 #######################################################################
515 # Path Variables                                                      #
516 #######################################################################
517
518 # Include Global Configuration Processing Library
519 use HoneyClient::Util::Config qw(getVar);
520
521 # Include Data Dumper API
522 use Data::Dumper;
523
524 # Include Logging Library
525 use Log::Log4perl qw(:easy);
526
527 # The global logging object.
528 our $LOG = get_logger();
529
530 # Make Dumper format more terse.
531 $Data::Dumper::Terse = 1;
532 $Data::Dumper::Indent = 0;
533
534 # Default absolute path to use when cloning new VMs.
535 our $DATASTORE_PATH = getVar(name => "datastore_path");
536
537 # Default absolute path to use when storing snapshots.
538 our $SNAPSHOT_PATH = getVar(name => "snapshot_path");
539
540 # Make sure the $DATASTORE_PATH is a valid directory and exists.
541 if (!-d $DATASTORE_PATH) {
542     $LOG->fatal("Current datastore path ($DATASTORE_PATH) does not exist!");
543     Carp::croak "Error: Current datastore path ($DATASTORE_PATH) does not exist!\n";
544 }
545
546 # Make sure the $SNAPSHOT_PATH is a valid directory and exists.
547 if (!-d $SNAPSHOT_PATH) {
548     $LOG->fatal("Error: Current datastore path ($SNAPSHOT_PATH) does not exist!");
549     Carp::croak "Error: Current datastore path ($SNAPSHOT_PATH) does not exist!\n";
550 }
551 #######################################################################
552
553 # Include the SOAP Utility Library
554 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
555
556 # Include the VMware APIs
557 use VMware::VmPerl;
558 use VMware::VmPerl::Server;
559 use VMware::VmPerl::ConnectParams;
560 use VMware::VmPerl::VM;
561 use VMware::VmPerl::Question;
562
563 # Include POSIX Libraries
564 use POSIX qw(strftime);
565
566 # Include File/Directory Manipulation Libraries
567 use File::Copy;
568 use File::Copy::Recursive qw(dircopy pathrmdir);
569 use File::Basename qw(dirname basename);
570 use Tie::File;
571 use Fcntl qw(O_RDONLY);
572
573 # Include Thread Libraries
574 use threads;
575 use threads::shared;
576 use Thread::Queue;
577 use Thread::Semaphore;
578
579 # Include MD5 Libraries
580 use Digest::MD5 qw(md5_hex);
581
582 # Include ISO8601 Date/Time Library
583 use DateTime::HiRes;
584
585 # Global fault queue.
586 # Used to convey faults that have occurred within
587 # asynchronous threads back to synchronous, external
588 # function calls.
589 our $faultQueue = Thread::Queue->new();
590
591 # Global semaphore, designed to limit the maximum
592 # number of child threads that run.
593 #
594 # By default, we limit the number of children to 5.
595 # If more than 5 child threads are created, subsequent
596 # ones will block, until one of the running threads
597 # finishes.
598 our $maxThreadSemaphore = Thread::Semaphore->new(5);
599
600 # Hashtable used to contain VM-specific semaphores,
601 # used to guarantee only one operation per VM is performed
602 # at any given time.
603 our %vmSemaphoreHash;
604
605 # Global semaphore, designed to limit exclusive access
606 # to the %vmSemaphoreHash object. This lock is designed
607 # to prevent multiple threads from creating/deleting entries
608 # simultaneously, which would cause nasty race conditions.
609 our $hashSemaphore = Thread::Semaphore->new(1);
610
611 # Global semaphore, designed to guarantee only one thread
612 # may set the master VM configuration file at any given
613 # time.
614 our $masterVMSemaphore = Thread::Semaphore->new(1);
615
616 # Global semaphore, designed to allow only 1 thread
617 # at a time to perform chdir operations.
618 our $chdirSemaphore = Thread::Semaphore->new(1);
619
620 # Constants used to authenticate with the VMware Server /
621 # GSX server.
622 # If username and password are left undefined,
623 # the process owner's credentials will be used.
624 our $serverName     : shared = undef;
625 our $tcpPort        : shared = getVar(name => "vmware_port");
626 our $username       : shared = undef;
627 our $passwd         : shared = undef;
628
629 # VmPerl Objects used only by the parent thread.
630 our $server         = undef;
631 our $connectParams  = undef;
632 our $vm             = undef;
633
634 # Path to master config file, for eventual cloning.
635 our $vmMasterConfig : shared = undef;
636
637 # Complete URL of SOAP server, when initialized.
638 our $URL_BASE       : shared = undef;
639 our $URL            : shared = undef;
640
641 # If connectivity to the VMware Server / GSX server is
642 # ever lost, this indicates how may reconnection attempts
643 # will be made before failing completely.
644 our $MAX_RETRIES    : shared = 5;
645
646 # The process ID of the SOAP server daemon, once created.
647 our $DAEMON_PID     : shared = undef;
648
649 # The maximum length of any VMID generated.
650 our $VM_ID_LENGTH   : shared = getVar(name => "vm_id_length");
651
652 # The log file that contains DHCP lease log entries.
653 our $DHCP_LOGFILE   : shared = getVar(name => "dhcp_log");
654
655 #######################################################################
656 # Daemon Initialization / Destruction                                 #
657 #######################################################################
658
659 =pod
660
661 =head1 LOCAL FUNCTIONS
662
663 The following init() and destroy() functions are the only direct
664 calls required to startup and shutdown the SOAP server.
665
666 All other interactions with this daemon should be performed as
667 C<SOAP::Lite> function calls, in order to ensure consistency across
668 client sessions.  See the L<"EXTERNAL SOAP FUNCTIONS"> section, for
669 more details.
670
671 =head2 HoneyClient::Manager::VM->init(address => $localAddr, port => $localPort)
672
673 =over 4
674
675 Starts a new SOAP server, within a child process.
676
677 I<Inputs>:
678  B<$localAddr> is an optional argument, specifying the IP address for the SOAP server to listen on.
679  B<$localPort> is an optional argument, specifying the TCP port for the SOAP server to listen on.
680
681 I<Output>: The full URL of the web service provided by the SOAP server.
682
683 =back
684
685 =begin testing
686
687 # Shared test variables.
688 my $PORT = getVar(name      => "port",
689                   namespace => "HoneyClient::Manager::VM");
690 my ($stub, $som, $URL);
691 my $testVM = $ENV{PWD} . "/" . getVar(name      => "test_vm_config",
692                                       namespace => "HoneyClient::Manager::VM::Test");
693
694 # Test init() method.
695 $URL = HoneyClient::Manager::VM->init();
696 is($URL, "http://localhost:$PORT/HoneyClient/Manager/VM", "init()") or diag("Failed to start up the VM SOAP server.  Check to see if any other daemon is listening on TCP port $PORT.");
697
698 =end testing
699
700 =cut
701
702 sub init {
703     # Extract arguments.
704     my ($class, %args) = @_;
705
706     # Sanity check.  Make sure the daemon isn't already running.
707     if (defined($DAEMON_PID)) {
708         $LOG->fatal( __PACKAGE__ . " daemon is already running (PID = $DAEMON_PID)!");
709         Carp::croak "Error: " . __PACKAGE__ . " daemon is already running (PID = $DAEMON_PID)!\n";
710     }
711
712     my $argsExist = scalar(%args);
713
714     if (!($argsExist &&
715           exists($args{'address'}) &&
716           defined($args{'address'}))) {
717         $args{'address'} = getVar(name => "address");
718     }
719
720     if (!($argsExist &&
721           exists($args{'port'}) &&
722           defined($args{'port'}))) {
723         $args{'port'} = getVar(name => "port");
724     }
725
726
727     $URL_BASE = "http://" . $args{'address'} . ":" . $args{'port'};
728     $URL = $URL_BASE . "/" . join('/', split(/::/, __PACKAGE__));
729
730     my $pid = undef;
731     if ($pid = fork()) {
732
733         # Wait at least a second, in order to initialize the daemon.
734         sleep (1);
735         $DAEMON_PID = $pid;
736         return ($URL);
737
738     } else {
739
740         # Make sure the fork was successful.
741         if (!defined($pid)) {
742             $LOG->fatal("Error: Unable to fork child process. $!");
743             Carp::croak "Error: Unable to fork child process.\n$!";
744         }
745
746         # Do not attempt to rejoin parent process tree,
747         # if any type of termination signal is received.
748         local $SIG{HUP} = sub { exit; };
749         local $SIG{INT} = sub { exit; };
750         local $SIG{QUIT} = sub { exit; };
751         local $SIG{ABRT} = sub { exit; };
752         local $SIG{PIPE} = sub { exit; };
753         local $SIG{TERM} = sub { exit; };
754
755         my $daemon = getServerHandle(address => $args{'address'},
756                                      port    => $args{'port'});
757
758         for (;;) {
759             $daemon->handle();
760         }
761     }
762 }
763
764 =pod
765
766 =head2 HoneyClient::Manager::VM->destroy()
767
768 =over 4
769
770 Terminates the SOAP server within the child process.
771
772 I<Output>: True if successful, false otherwise.
773
774 =back
775
776 =begin testing
777
778 # Shared test variables.
779 my $PORT = getVar(name      => "port",
780                   namespace => "HoneyClient::Manager::VM");
781 my ($stub, $som, $URL);
782 my $testVM = $ENV{PWD} . "/" . getVar(name      => "test_vm_config",
783                                       namespace => "HoneyClient::Manager::VM::Test");
784
785 # Test destroy() method.
786 is(HoneyClient::Manager::VM->destroy(), 1, "destroy()") or diag("Unable to terminate VM SOAP server.  Be sure to check for any stale or lingering processes.");
787
788 =end testing
789
790 =cut
791
792 sub destroy {
793     my $ret = undef;
794     # Make sure the PID is defined and not
795     # the parent process...
796     if (defined($DAEMON_PID) && $DAEMON_PID) {
797         $ret = kill("QUIT", $DAEMON_PID);
798     }
799     if ($ret) {
800         $DAEMON_PID = undef;
801     }
802     return ($ret);
803 }
804
805 #######################################################################
806 # Private Methods Implemented                                         #
807 #######################################################################
808
809 # Helper function designed to connect to a specified VM.
810 # Requires specifying the full, absolute
811 # path to the VM's local configuration file.
812 #
813 # Inputs: config
814 # Outputs: None
815 sub _connectVM {
816     # Extract arguments.
817     my ($class, $config) = @_;
818
819     # Sanity check. Make sure we're connected.
820     if (!defined($server) || !defined($server->is_connected())) {
821         _connect();
822     }
823
824     # If possible, reuse the preexisting VM connection.
825     if (defined($vm) &&
826         $vm->is_connected() &&
827         ($vm->get_config_file_name() eq $config)) {
828         return;
829     }
830
831     # If we're trying to connect up to an unregistered VM, go ahead
832     # and register it...
833     if (!isRegisteredVM($class, (config => $config))) {
834         registerVM($class, (config => $config));
835     }
836
837     $vm = VMware::VmPerl::VM::new();
838
839     # Connect to the VM, using the same ConnectParams object.
840     # Throttle repeat connections to the VMware Server / GSX server.
841     my $count    = 0;
842     my $status = undef;
843     do {
844         sleep (2);
845         $status = $vm->connect($connectParams, $config);
846         $count++;
847     } while (!$status && $count < $MAX_RETRIES);
848     if ($count >= $MAX_RETRIES) {
849         my ($errorNumber, $errorString) = $server->get_last_error();
850         $LOG->warn("Could not connect to VM (" . $config . "). (" .
851                    $errorNumber . ": " . $errorString . ")");
852         die SOAP::Fault->faultcode(__PACKAGE__ . "->_connectVM()")
853                        ->faultstring("Could not connect to VM ($config).")
854                        ->faultdetail(bless { errNo  => $errorNumber,
855                                              errStr => $errorString },
856                                      'err');
857     }
858 }
859
860 # Helper function designed to disconnect from a previously specified VM.
861 #
862 # Inputs: None
863 # Outputs: None
864 sub _disconnectVM {
865     undef $vm;
866 }
867
868 # Helper function designed to emit the first queued fault.
869 # If any exist, automatically die with the earliest queued fault.
870 #
871 # Inputs: None
872 # Outputs: None
873 sub _emitQueuedFault {
874
875     my $fault = $faultQueue->dequeue_nb();
876     if (defined($fault)) {
877         my $deserializer = SOAP::Deserializer->new();
878         my $som = $deserializer->deserialize($fault);
879         if (defined($som->faultdetail)) {
880             die SOAP::Fault->faultcode($som->faultcode)
881                            ->faultstring($som->faultstring)
882                            ->faultdetail(bless { errNo  => $som->faultdetail->{"err"}->{"errNo"},
883                                                  errStr => $som->faultdetail->{"err"}->{"errStr"} },
884                                          'err');
885         } else {
886             die SOAP::Fault->faultcode($som->faultcode)
887                            ->faultstring($som->faultstring);
888         }
889     }
890 }
891
892 # Helper function designed to store faults in a globally shared queue.A
893 # Faults are serialized into XML form, then stored in the queue.
894 #
895 # Inputs: SOAP::Fault
896 # Outputs: None
897 sub _queueFault {
898
899     my $fault = shift;
900     my $serializer = SOAP::Serializer->new();
901     my $xml = $serializer->fault($fault->faultcode,
902                                  $fault->faultstring,
903                                  $fault->faultdetail,
904                                  $fault->faultactor);
905     $faultQueue->enqueue($xml);
906 }
907
908 # Helper function designed to return true if the "$serverName"
909 # is local; useful for functions that are designed to perform
910 # filesystem operations that can only be performed on a local
911 # server.
912 #
913 # Inputs: None
914 # Outputs: True if server is local, false otherwise.
915 sub _isServerLocal {
916    
917     return (!defined($serverName) ||
918             $serverName eq "localhost" ||
919             $serverName =~ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
920 }
921
922 # Helper function used by child threads to handle faults that
923 # occur during callbacks made to the parent SOAP server.
924 #
925 # When a fault is handled, it is converted back into a SOAP::Fault
926 # object and subsequently queued for final emission by the
927 # parent upon subsequent remote calls.
928 #
929 # Inputs: SOAP::SOM
930 # Outputs: None
931 sub _callbackFaultHandler {
932
933     # Extract arguments.
934     my ($class, $res) = @_;
935
936     # Reconstruct the SOAP::Fault.
937     # Figure out if the error occurred in transport or
938     # over on the other side.
939     my $errMsg = $class->transport->status; # Assume transport error.
940     if (ref $res) {
941        
942         if (defined($res->faultdetail)) {
943             # Detailed fault occurred.
944             die SOAP::Fault->faultcode($res->faultcode)
945                            ->faultstring($res->faultstring)
946                            ->faultdetail(bless { errNo  => $res->faultdetail->{"err"}->{"errNo"},
947                                                  errStr => $res->faultdetail->{"err"}->{"errStr"} },
948                                          'err');
949         } else {
950             # Basic fault occurred.
951             die SOAP::Fault->faultcode($res->faultcode)
952                            ->faultstring($res->faultstring);
953         }