root/honeyclient/branches/rel/1.0/lib/HoneyClient/Manager/VM.pm

Revision 994, 153.9 kB (checked in by kindlund, 1 year ago)

Bumped version to v1.00.

  • 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) 2007 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.00.
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 = 1.00;
323
324     @ISA = qw(Exporter);
325
326     # Symbols to export automatically
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 (when qw(:all) tag is used)
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 # Make sure ExtUtils::MakeMaker loads.
358 BEGIN { use_ok('ExtUtils::MakeMaker', qw(prompt)) or diag("Can't load ExtUtils::MakeMaker package.  Check to make sure the package library is correctly listed within the path."); }
359 require_ok('ExtUtils::MakeMaker');
360 can_ok('ExtUtils::MakeMaker', 'prompt');
361 use ExtUtils::MakeMaker qw(prompt);
362
363 # Generate a notice, to clarify our assumptions.
364 diag("About to run basic unit tests.");
365 diag("Note: These tests *expect* VMware Server or VMware GSX to be installed and running on this system beforehand.");
366
367 my $question;
368 $question = prompt("# Do you want to run basic tests?", "yes");
369 if ($question !~ /^y.*/i) {
370     exit;
371 }
372
373 # Make sure Log::Log4perl loads
374 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
375         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
376        
377         # Suppress all logging messages, since we need clean output for unit testing.
378         Log::Log4perl->init({
379             "log4perl.rootLogger"                               => "DEBUG, Buffer",
380             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
381             "log4perl.appender.Buffer.min_level"                => "fatal",
382             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
383             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
384         });
385 }
386 require_ok('Log::Log4perl');
387 use Log::Log4perl qw(:easy);
388
389 # Make sure HoneyClient::Util::Config loads.
390 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar))
391         or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path.");
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 require_ok('HoneyClient::Util::Config');
403 can_ok('HoneyClient::Util::Config', 'getVar');
404 use HoneyClient::Util::Config qw(getVar);
405
406 # Suppress all logging messages, since we need clean output for unit testing.
407 Log::Log4perl->init({
408     "log4perl.rootLogger"                               => "DEBUG, Buffer",
409     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
410     "log4perl.appender.Buffer.min_level"                => "fatal",
411     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
412     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
413 });
414
415 # Make sure the module loads properly, with the exportable
416 # functions shared.
417 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."); }
418 require_ok('HoneyClient::Manager::VM');
419 can_ok('HoneyClient::Manager::VM', 'init');
420 can_ok('HoneyClient::Manager::VM', 'destroy');
421 use HoneyClient::Manager::VM;
422
423 # Make sure HoneyClient::Util::SOAP loads.
424 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."); }
425 require_ok('HoneyClient::Util::SOAP');
426 can_ok('HoneyClient::Util::SOAP', 'getServerHandle');
427 can_ok('HoneyClient::Util::SOAP', 'getClientHandle');
428 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
429
430 # Make sure File::Basename loads.
431 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."); }
432 require_ok('File::Basename');
433 can_ok('File::Basename', 'dirname');
434 can_ok('File::Basename', 'basename');
435 use File::Basename qw(dirname basename);
436
437 # Make sure File::Copy::Recursive loads.
438 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."); }
439 require_ok('File::Copy::Recursive');
440 can_ok('File::Copy::Recursive', 'dircopy');
441 can_ok('File::Copy::Recursive', 'pathrmdir');
442 use File::Copy::Recursive qw(dircopy pathrmdir);
443
444 # Make sure Data::Dumper loads.
445 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."); }
446 require_ok('Data::Dumper');
447 use Data::Dumper;
448
449 # Make sure File::stat loads.
450 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."); }
451 require_ok('File::stat');
452 use File::stat;
453
454 # Make sure Digest::MD5 loads.
455 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."); }
456 require_ok('Digest::MD5');
457 can_ok('Digest::MD5', 'md5_hex');
458 use Digest::MD5 qw(md5_hex);
459
460 # Make sure DateTime::HiRes loads.
461 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."); }
462 require_ok('DateTime::HiRes');
463 use DateTime::HiRes;
464
465 # Make sure Fcntl loads.
466 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."); }
467 require_ok('Fcntl');
468 use Fcntl qw(O_RDONLY);
469
470 # Make sure VMware::VmPerl loads.
471 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."); }
472 require_ok('VMware::VmPerl');
473 use VMware::VmPerl qw(VM_EXECUTION_STATE_ON VM_EXECUTION_STATE_OFF VM_EXECUTION_STATE_STUCK VM_EXECUTION_STATE_SUSPENDED);
474
475 # Make sure VMware::VmPerl::Server loads.
476 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."); }
477 require_ok('VMware::VmPerl::Server');
478 use VMware::VmPerl::Server;
479
480 # Make sure VMware::VmPerl::ConnectParams loads.
481 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."); }
482 require_ok('VMware::VmPerl::ConnectParams');
483 use VMware::VmPerl::ConnectParams;
484
485 # Make sure VMware::VmPerl::VM loads.
486 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."); }
487 require_ok('VMware::VmPerl::VM');
488 use VMware::VmPerl::VM;
489
490 # Make sure VMware::VmPerl::VM loads.
491 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."); }
492 require_ok('VMware::VmPerl::Question');
493 use VMware::VmPerl::Question;
494
495 # Make sure threads loads.
496 BEGIN { use_ok('threads') or diag("Can't load threads package.  Check to make sure the package library is correctly listed within the path."); }
497 require_ok('threads');
498 use threads;
499
500 # Make sure threads::shared loads.
501 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."); }
502 require_ok('threads::shared');
503 use threads::shared;
504
505 # Make sure Thread::Queue loads.
506 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."); }
507 require_ok('Thread::Queue');
508 use Thread::Queue;
509
510 # Make sure Thread::Semaphore loads.
511 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."); }
512 require_ok('Thread::Semaphore');
513 use Thread::Semaphore;
514
515 diag("About to run extended tests.");
516 # Generate a notice, to inform the tester that these tests are not
517 # exactly quick.
518 diag("Note: These extended tests will take *significant* time to complete (10-30 minutes).");
519
520 $question = prompt("# Do you want to run extended tests?", "no");
521 if ($question !~ /^y.*/i) {
522     exit;
523 }
524
525 =end testing
526
527 =cut
528
529 #######################################################################
530 # Path Variables                                                      #
531 #######################################################################
532
533 # Include Global Configuration Processing Library
534 use HoneyClient::Util::Config qw(getVar);
535
536 # Include Data Dumper API
537 use Data::Dumper;
538
539 # Include Logging Library
540 use Log::Log4perl qw(:easy);
541
542 # The global logging object.
543 our $LOG = get_logger();
544
545 # Make Dumper format more terse.
546 $Data::Dumper::Terse = 1;
547 $Data::Dumper::Indent = 0;
548
549 # Default absolute path to use when cloning new VMs.
550 our $DATASTORE_PATH = getVar(name => "datastore_path");
551
552 # Default absolute path to use when storing snapshots.
553 our $SNAPSHOT_PATH = getVar(name => "snapshot_path");
554
555 # Make sure the $DATASTORE_PATH is a valid directory and exists.
556 if (!-d $DATASTORE_PATH) {
557     $LOG->fatal("Current datastore path ($DATASTORE_PATH) does not exist!");
558     Carp::croak "Error: Current datastore path ($DATASTORE_PATH) does not exist!\n";
559 }
560
561 # Make sure the $SNAPSHOT_PATH is a valid directory and exists.
562 if (!-d $SNAPSHOT_PATH) {
563     $LOG->fatal("Error: Current datastore path ($SNAPSHOT_PATH) does not exist!");
564     Carp::croak "Error: Current datastore path ($SNAPSHOT_PATH) does not exist!\n";
565 }
566 #######################################################################
567
568 # Include the SOAP Utility Library
569 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
570
571 # Include the VMware APIs
572 use VMware::VmPerl;
573 use VMware::VmPerl::Server;
574 use VMware::VmPerl::ConnectParams;
575 use VMware::VmPerl::VM;
576 use VMware::VmPerl::Question;
577
578 # Include POSIX Libraries
579 use POSIX qw(strftime);
580
581 # Include File/Directory Manipulation Libraries
582 use File::Copy;
583 use File::Copy::Recursive qw(dircopy pathrmdir);
584 use File::Basename qw(dirname basename);
585 use Tie::File;
586 use Fcntl qw(O_RDONLY);
587
588 # Include Thread Libraries
589 use threads;
590 use threads::shared;
591 use Thread::Queue;
592 use Thread::Semaphore;
593
594 # Include MD5 Libraries
595 use Digest::MD5 qw(md5_hex);
596
597 # Include ISO8601 Date/Time Library
598 use DateTime::HiRes;
599
600 # Global fault queue.
601 # Used to convey faults that have occurred within
602 # asynchronous threads back to synchronous, external
603 # function calls.
604 our $faultQueue = Thread::Queue->new();
605
606 # Global semaphore, designed to limit the maximum
607 # number of child threads that run.
608 #
609 # By default, we limit the number of children to 5.
610 # If more than 5 child threads are created, subsequent
611 # ones will block, until one of the running threads
612 # finishes.
613 our $maxThreadSemaphore = Thread::Semaphore->new(5);
614
615 # Hashtable used to contain VM-specific semaphores,
616 # used to guarantee only one operation per VM is performed
617 # at any given time.
618 our %vmSemaphoreHash;
619
620 # Global semaphore, designed to limit exclusive access
621 # to the %vmSemaphoreHash object. This lock is designed
622 # to prevent multiple threads from creating/deleting entries
623 # simultaneously, which would cause nasty race conditions.
624 our $hashSemaphore = Thread::Semaphore->new(1);
625
626 # Global semaphore, designed to guarantee only one thread
627 # may set the master VM configuration file at any given
628 # time.
629 our $masterVMSemaphore = Thread::Semaphore->new(1);
630
631 # Global semaphore, designed to allow only 1 thread
632 # at a time to perform chdir operations.
633 our $chdirSemaphore = Thread::Semaphore->new(1);
634
635 # Constants used to authenticate with the VMware Server /
636 # GSX server.
637 # If username and password are left undefined,
638 # the process owner's credentials will be used.
639 our $serverName     : shared = undef;
640 our $tcpPort        : shared = getVar(name => "vmware_port");
641 our $username       : shared = undef;
642 our $passwd         : shared = undef;
643
644 # VmPerl Objects used only by the parent thread.
645 our $server         = undef;
646 our $connectParams  = undef;
647 our $vm             = undef;
648
649 # Path to master config file, for eventual cloning.
650 our $vmMasterConfig : shared = undef;
651
652 # Complete URL of SOAP server, when initialized.
653 our $URL_BASE       : shared = undef;
654 our $URL            : shared = undef;
655
656 # If connectivity to the VMware Server / GSX server is
657 # ever lost, this indicates how may reconnection attempts
658 # will be made before failing completely.
659 our $MAX_RETRIES    : shared = 5;
660
661 # The process ID of the SOAP server daemon, once created.
662 our $DAEMON_PID     : shared = undef;
663
664 # The maximum length of any VMID generated.
665 our $VM_ID_LENGTH   : shared = getVar(name => "vm_id_length");
666
667 # The log file that contains DHCP lease log entries.
668 our $DHCP_LOGFILE   : shared = getVar(name => "dhcp_log");
669
670 #######################################################################
671 # Daemon Initialization / Destruction                                 #
672 #######################################################################
673
674 =pod
675
676 =head1 LOCAL FUNCTIONS
677
678 The following init() and destroy() functions are the only direct
679 calls required to startup and shutdown the SOAP server.
680
681 All other interactions with this daemon should be performed as
682 C<SOAP::Lite> function calls, in order to ensure consistency across
683 client sessions.  See the L<"EXTERNAL SOAP FUNCTIONS"> section, for
684 more details.
685
686 =head2 HoneyClient::Manager::VM->init(address => $localAddr, port => $localPort)
687
688 =over 4
689
690 Starts a new SOAP server, within a child process.
691
692 I<Inputs>:
693  B<$localAddr> is an optional argument, specifying the IP address for the SOAP server to listen on.
694  B<$localPort> is an optional argument, specifying the TCP port for the SOAP server to listen on.
695
696 I<Output>: The full URL of the web service provided by the SOAP server.
697
698 =back
699
700 =begin testing
701
702 # Shared test variables.
703 my $PORT = getVar(name      => "port",
704                   namespace => "HoneyClient::Manager::VM");
705 my ($stub, $som, $URL);
706 my $testVM = $ENV{PWD} . "/" . getVar(name      => "test_vm_config",
707                                       namespace => "HoneyClient::Manager::VM::Test");
708
709 # Test init() method.
710 $URL = HoneyClient::Manager::VM->init();
711 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.");
712
713 =end testing
714
715 =cut
716
717 sub init {
718     # Extract arguments.
719     my ($class, %args) = @_;
720
721     # Sanity check.  Make sure the daemon isn't already running.
722     if (defined($DAEMON_PID)) {
723         $LOG->fatal( __PACKAGE__ . " daemon is already running (PID = $DAEMON_PID)!");
724         Carp::croak "Error: " . __PACKAGE__ . " daemon is already running (PID = $DAEMON_PID)!\n";
725     }
726
727     my $argsExist = scalar(%args);
728
729     if (!($argsExist &&
730           exists($args{'address'}) &&
731           defined($args{'address'}))) {
732         $args{'address'} = getVar(name => "address");
733     }
734
735     if (!($argsExist &&
736           exists($args{'port'}) &&
737           defined($args{'port'}))) {
738         $args{'port'} = getVar(name => "port");
739     }
740
741
742     $URL_BASE = "http://" . $args{'address'} . ":" . $args{'port'};
743     $URL = $URL_BASE . "/" . join('/', split(/::/, __PACKAGE__));
744
745     my $pid = undef;
746     if ($pid = fork()) {
747
748         # Wait at least a second, in order to initialize the daemon.
749         sleep (1);
750         $DAEMON_PID = $pid;
751         return ($URL);
752
753     } else {
754
755         # Make sure the fork was successful.
756         if (!defined($pid)) {
757             $LOG->fatal("Error: Unable to fork child process. $!");
758             Carp::croak "Error: Unable to fork child process.\n$!";
759         }
760
761         # Do not attempt to rejoin parent process tree,
762         # if any type of termination signal is received.
763         local $SIG{HUP} = sub { exit; };
764         local $SIG{INT} = sub { exit; };
765         local $SIG{QUIT} = sub { exit; };
766         local $SIG{ABRT} = sub { exit; };
767         local $SIG{PIPE} = sub { exit; };
768         local $SIG{TERM} = sub { exit; };
769
770         my $daemon = getServerHandle(address => $args{'address'},
771                                      port    => $args{'port'});
772
773         for (;;) {
774             $daemon->handle();
775         }
776     }
777 }
778
779 =pod
780
781 =head2 HoneyClient::Manager::VM->destroy()
782
783 =over 4
784
785 Terminates the SOAP server within the child process.
786
787 I<Output>: True if successful, false otherwise.
788
789 =back
790
791 =begin testing
792
793 # Shared test variables.
794 my $PORT = getVar(name      => "port",
795                   namespace => "HoneyClient::Manager::VM");
796 my ($stub, $som, $URL);
797 my $testVM = $ENV{PWD} . "/" . getVar(name      => "test_vm_config",
798                                       namespace => "HoneyClient::Manager::VM::Test");
799
800 # Test destroy() method.
801 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.");
802
803 =end testing
804
805 =cut
806
807 sub destroy {
808     my $ret = undef;
809     # Make sure the PID is defined and not
810     # the parent process...
811     if (defined($DAEMON_PID) && $DAEMON_PID) {
812         $ret = kill("QUIT", $DAEMON_PID);
813     }
814     if ($ret) {
815         $DAEMON_PID = undef;
816     }
817     return ($ret);
818 }
819
820 #######################################################################
821 # Private Methods Implemented                                         #
822 #######################################################################
823
824 # Helper function designed to connect to a specified VM.
825 # Requires specifying the full, absolute
826 # path to the VM's local configuration file.
827 #
828 # Inputs: config
829 # Outputs: None
830 sub _connectVM {
831     # Extract arguments.
832     my ($class, $config) = @_;
833
834     # Sanity check. Make sure we're connected.
835     if (!defined($server) || !defined($server->is_connected())) {
836         _connect();
837     }
838
839     # If possible, reuse the preexisting VM connection.
840     if (defined($vm) &&
841         $vm->is_connected() &&
842         ($vm->get_config_file_name() eq $config)) {
843         return;
844     }
845
846     # If we're trying to connect up to an unregistered VM, go ahead
847     # and register it...
848     if (!isRegisteredVM($class, (config => $config))) {
849         registerVM($class, (config => $config));
850     }
851
852     $vm = VMware::VmPerl::VM::new();
853
854     # Connect to the VM, using the same ConnectParams object.
855     # Throttle repeat connections to the VMware Server / GSX server.
856     my $count    = 0;
857     my $status = undef;
858     do {
859         sleep (2);
860         $status = $vm->connect($connectParams, $config);
861         $count++;
862     } while (!$status && $count < $MAX_RETRIES);
863     if ($count >= $MAX_RETRIES) {
864         my ($errorNumber, $errorString) = $server->get_last_error();
865         $LOG->warn("Could not connect to VM (" . $config . "). (" .
866                    $errorNumber . ": " . $errorString . ")");
867         die SOAP::Fault->faultcode(__PACKAGE__ . "->_connectVM()")
868                        ->faultstring("Could not connect to VM ($config).")
869                        ->faultdetail(bless { errNo  => $errorNumber,
870                                              errStr => $errorString },
871                                      'err');
872     }
873 }
874
875 # Helper function designed to disconnect from a previously specified VM.
876 #
877 # Inputs: None
878 # Outputs: None
879 sub _disconnectVM {
880     undef $vm;
881 }
882
883 # Helper function designed to emit the first queued fault.
884 # If any exist, automatically die with the earliest queued fault.
885 #
886 # Inputs: None
887 # Outputs: None
888 sub _emitQueuedFault {
889
890     my $fault = $faultQueue->dequeue_nb();
891     if (defined($fault)) {
892         my $deserializer = SOAP::Deserializer->new();
893         my $som = $deserializer->deserialize($fault);
894         if (defined($som->faultdetail)) {
895             die SOAP::Fault->faultcode($som->faultcode)
896                            ->faultstring($som->faultstring)
897                            ->faultdetail(bless { errNo  => $som->faultdetail->{"err"}->{"errNo"},
898                                                  errStr => $som->faultdetail->{"err"}->{"errStr"} },
899                                          'err');
900         } else {
901             die SOAP::Fault->faultcode($som->faultcode)
902                            ->faultstring($som->faultstring);
903         }
904     }
905 }
906
907 # Helper function designed to store faults in a globally shared queue.A
908 # Faults are serialized into XML form, then stored in the queue.
909 #
910 # Inputs: SOAP::Fault
911 # Outputs: None
912 sub _queueFault {
913
914     my $fault = shift;
915     my $serializer = SOAP::Serializer->new();
916     my $xml = $serializer->fault($fault->faultcode,
917                                  $fault->faultstring,
918                                  $fault->faultdetail,
919                                  $fault->faultactor);
920     $faultQueue->enqueue($xml);
921 }
922
923 # Helper function designed to return true if the "$serverName"
924 # is local; useful for functions that are designed to perform
925 # filesystem operations that can only be performed on a local
926 # server.
927 #
928 # Inputs: None
929 # Outputs: True if server is local, false otherwise.
930 sub _isServerLocal {
931    
932     return (!defined($serverName) ||
933             $serverName eq "localhost" ||
934             $serverName =~ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
935 }
936
937 # Helper function used by child threads to handle faults that
938 # occur during callbacks made to the parent SOAP server.
939 #
940 # When a fault is handled, it is converted back into a SOAP::Fault
941 # object and subsequently queued for final emission by the
942 # parent upon subsequent remote calls.
943 #
944 # Inputs: SOAP::SOM
945 # Outputs: None
946 sub _callbackFaultHandler {
947
948     # Extract arguments.
949     my ($class, $res) = @_;
950
951     # Reconstruct the SOAP::Fault.
952     # Figure out if the error occurred in transport or
953     # over on the other side.
954     my $errMsg = $class->transport->status; # Assume transport error.
955     if (ref $res) {
956        
957         if (defined($res->faultdetail)) {
958             # Detailed fault occurred.
959             die SOAP::Fault->faultcode($res->faultcode)
960                            ->faultstring($res->faultstring)
961                            ->faultdetail(bless { errNo  => $res->faultdetail->{"err"}->{"errNo"},
962                     &n