| 467 | | ####################################################################### |
|---|
| 468 | | # Public Methods Implemented # |
|---|
| 469 | | ####################################################################### |
|---|
| 470 | | |
|---|
| 471 | | =pod |
|---|
| 472 | | |
|---|
| 473 | | =head1 METHODS IMPLEMENTED |
|---|
| 474 | | |
|---|
| 475 | | The following functions have been implemented by any Clone object. |
|---|
| 476 | | |
|---|
| 477 | | =head2 HoneyClient::Manager::VM::Clone->new($param => $value, ...) |
|---|
| 478 | | |
|---|
| 479 | | =over 4 |
|---|
| 480 | | |
|---|
| 481 | | Creates a new Clone object, which contains a hashtable |
|---|
| 482 | | containing any of the supplied "param => value" arguments. |
|---|
| 483 | | |
|---|
| 484 | | I<Inputs>: |
|---|
| 485 | | B<$param> is an optional parameter variable. |
|---|
| 486 | | B<$value> is $param's corresponding value. |
|---|
| 487 | | |
|---|
| 488 | | Note: If any $param(s) are supplied, then an equal number of |
|---|
| 489 | | corresponding $value(s) B<must> also be specified. |
|---|
| 490 | | |
|---|
| 491 | | I<Output>: The instantiated Clone B<$object>, fully initialized. |
|---|
| 492 | | |
|---|
| 493 | | =back |
|---|
| 494 | | |
|---|
| 495 | | =begin testing |
|---|
| 496 | | |
|---|
| 497 | | # Shared test variables. |
|---|
| 498 | | my ($stub, $som, $URL); |
|---|
| 499 | | my $testVM = $ENV{PWD} . "/" . getVar(name => "test_vm_config", |
|---|
| 500 | | namespace => "HoneyClient::Manager::VM::Test"); |
|---|
| 501 | | |
|---|
| 502 | | # Catch all errors, in order to make sure child processes are |
|---|
| 503 | | # properly killed. |
|---|
| 504 | | eval { |
|---|
| 505 | | |
|---|
| 506 | | $URL = HoneyClient::Manager::VM->init(); |
|---|
| 507 | | |
|---|
| 508 | | # Connect to daemon as a client. |
|---|
| 509 | | $stub = getClientHandle(namespace => "HoneyClient::Manager::VM"); |
|---|
| 510 | | |
|---|
| 511 | | # In order to test setMasterVM(), we're going to fully clone |
|---|
| 512 | | # the testVM, then set the newly created clone as a master VM. |
|---|
| 513 | | |
|---|
| 514 | | # Get the test VM's parent directory, |
|---|
| 515 | | # in order to create a temporary master VM. |
|---|
| 516 | | my $testVMDir = dirname($testVM); |
|---|
| 517 | | my $masterVMDir = dirname($testVMDir) . "/test_vm_master"; |
|---|
| 518 | | my $masterVM = $masterVMDir . "/" . basename($testVM); |
|---|
| 519 | | |
|---|
| 520 | | # Create the master VM. |
|---|
| 521 | | $som = $stub->fullCloneVM(src_config => $testVM, dest_dir => $masterVMDir); |
|---|
| 522 | | |
|---|
| 523 | | # Wait a small amount of time for the asynchronous clone |
|---|
| 524 | | # to complete. |
|---|
| 525 | | sleep (60); |
|---|
| 526 | | |
|---|
| 527 | | # The master VM should be on. |
|---|
| 528 | | $som = $stub->getStateVM(config => $masterVM); |
|---|
| 529 | | |
|---|
| 530 | | # Since the master VM doesn't have an OS installed on it, |
|---|
| 531 | | # the VM may be considered stuck. Go ahead and answer |
|---|
| 532 | | # this question, if need be. |
|---|
| 533 | | if ($som->result == VM_EXECUTION_STATE_STUCK) { |
|---|
| 534 | | $som = $stub->answerVM(config => $masterVM); |
|---|
| 535 | | } |
|---|
| 536 | | |
|---|
| 537 | | HoneyClient::Manager::VM->destroy(); |
|---|
| 538 | | sleep (1); |
|---|
| 539 | | |
|---|
| 540 | | # Create a generic clone, with test state data. |
|---|
| 541 | | my $clone = HoneyClient::Manager::VM::Clone->new(test => 1, master_vm_config => $masterVM); |
|---|
| 542 | | is($clone->{test}, 1, "new(test => 1, master_vm_config => '$masterVM')") or diag("The new() call failed."); |
|---|
| 543 | | isa_ok($clone, 'HoneyClient::Manager::VM::Clone', "new(test => 1, master_vm_config => '$masterVM')") or diag("The new() call failed."); |
|---|
| 544 | | |
|---|
| 545 | | # Destroy the master VM. |
|---|
| 546 | | $som = $stub->destroyVM(config => $masterVM); |
|---|
| 547 | | }; |
|---|
| 548 | | |
|---|
| 549 | | # Kill the child daemon, if it still exists. |
|---|
| 550 | | HoneyClient::Manager::VM->destroy(); |
|---|
| 551 | | sleep (1); |
|---|
| 552 | | |
|---|
| 553 | | # Report any failure found. |
|---|
| 554 | | if ($@) { |
|---|
| 555 | | fail($@); |
|---|
| 556 | | } |
|---|
| 557 | | |
|---|
| 558 | | =end testing |
|---|
| 559 | | |
|---|
| 560 | | =cut |
|---|
| 561 | | |
|---|
| 562 | | sub new { |
|---|
| 563 | | # - This function takes in an optional hashtable, |
|---|
| 564 | | # that contains various key => 'value' configuration |
|---|
| 565 | | # parameters. |
|---|
| 566 | | # |
|---|
| 567 | | # - For each parameter given, it overwrites any corresponding |
|---|
| 568 | | # parameters specified within the default hashtable, %PARAMS, |
|---|
| 569 | | # with custom entries that were given as parameters. |
|---|
| 570 | | # |
|---|
| 571 | | # - Finally, it returns a blessed instance of the |
|---|
| 572 | | # merged hashtable, as an 'object'. |
|---|
| 573 | | |
|---|
| 574 | | # Get the class name. |
|---|
| 575 | | my $self = shift; |
|---|
| 576 | | |
|---|
| 577 | | # Get the rest of the arguments, as a hashtable. |
|---|
| 578 | | # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle |
|---|
| 579 | | # hash references directly. Thus, flat hashtables are used throughout the code |
|---|
| 580 | | # for consistency. |
|---|
| 581 | | my %args = @_; |
|---|
| 582 | | |
|---|
| 583 | | # Check to see if the class name is inherited or defined. |
|---|
| 584 | | my $class = ref($self) || $self; |
|---|
| 585 | | |
|---|
| 586 | | # Initialize default parameters. |
|---|
| 587 | | $self = { }; |
|---|
| 588 | | my %params = %{dclone(\%PARAMS)}; |
|---|
| 589 | | @{$self}{keys %params} = values %params; |
|---|
| 590 | | |
|---|
| 591 | | # Now, overwrite any default parameters that were redefined |
|---|
| 592 | | # in the supplied arguments. |
|---|
| 593 | | @{$self}{keys %args} = values %args; |
|---|
| 594 | | |
|---|
| 595 | | # Now, assign our object the appropriate namespace. |
|---|
| 596 | | bless $self, $class; |
|---|
| 597 | | |
|---|
| 598 | | # Upon first use, start up a global instance of the VM manager. |
|---|
| 599 | | if ($OBJECT_COUNT <= 0) { |
|---|
| 600 | | HoneyClient::Manager::VM->init(); |
|---|
| 601 | | } |
|---|
| 602 | | |
|---|
| 603 | | # Set a valid handle for the VM daemon. |
|---|
| 604 | | $self->{'_vm_handle'} = getClientHandle(namespace => "HoneyClient::Manager::VM"); |
|---|
| 605 | | |
|---|
| 606 | | # Set the master VM. |
|---|
| 607 | | $LOG->info("Setting VM (" . $self->{'master_vm_config'} . ") as master."); |
|---|
| 608 | | my $som = $self->{'_vm_handle'}->setMasterVM(config => $self->{'master_vm_config'}); |
|---|
| 609 | | if (!$som->result()) { |
|---|
| 610 | | $LOG->fatal("Unable to set VM (" . $self->{'master_vm_config'} . ") as a master VM."); |
|---|
| 611 | | Carp::croak "Unable to set VM (" . $self->{'master_vm_config'} . ") as a master VM."; |
|---|
| 612 | | } |
|---|
| 613 | | |
|---|
| 614 | | # Update our global object count. |
|---|
| 615 | | $OBJECT_COUNT++; |
|---|
| 616 | | |
|---|
| 617 | | # Finally, return the blessed object. |
|---|
| 618 | | return $self; |
|---|
| 619 | | } |
|---|
| 620 | | |
|---|
| 621 | | =pod |
|---|
| 622 | | |
|---|
| 623 | | =head2 $object->start() |
|---|
| 624 | | |
|---|
| 625 | | =over 4 |
|---|
| 626 | | |
|---|
| 627 | | If not previously called, this method creates a new clone VM |
|---|
| 628 | | from the supplied master VM. Furthermore, this method will power |
|---|
| 629 | | on the clone, and wait until the clone VM has fully booted and |
|---|
| 630 | | has an operational Agent daemon running on it. |
|---|
| 631 | | |
|---|
| 632 | | During this power on process, the name, MAC address, and |
|---|
| 633 | | IP address of the running clone are recorded in the object. |
|---|
| 634 | | |
|---|
| 635 | | I<Output>: The updated Clone B<$object>, containing state information |
|---|
| 636 | | from starting the clone VM. Will croak if this operation fails. |
|---|
| 637 | | |
|---|
| 638 | | =back |
|---|
| 639 | | |
|---|
| 640 | | # XXX: FINISH THIS |
|---|
| 641 | | #=begin testing |
|---|
| 642 | | # |
|---|
| 643 | | # Create a generic driver, with test state data. |
|---|
| 644 | | #my $driver = HoneyClient::Agent::Driver->new(test => 1); |
|---|
| 645 | | #dies_ok {$driver->drive()} 'drive()' or diag("The drive() call failed. Expected drive() to throw an exception."); |
|---|
| 646 | | # |
|---|
| 647 | | #=end testing |
|---|
| 648 | | |
|---|
| 649 | | =cut |
|---|
| 650 | | |
|---|
| 651 | | sub start { |
|---|
| | 445 | # Initialized cloned VMs. |
|---|
| | 446 | # |
|---|
| | 447 | # If no existing configuration is supplied, then this method creates |
|---|
| | 448 | # a new clone VM from the supplied master VM. Furthermore, this method |
|---|
| | 449 | # will power on the clone, and wait until the clone VM has fully booted and |
|---|
| | 450 | # has an operational HoneyClient::Agent daemon running on it. |
|---|
| | 451 | # |
|---|
| | 452 | # During this power on process, the name, MAC address, and |
|---|
| | 453 | # IP address of the running clone are recorded in the object. |
|---|
| | 454 | # |
|---|
| | 455 | # Output: The updated Clone $object, containing state information |
|---|
| | 456 | # from starting the clone VM. Will croak if this operation fails. |
|---|
| | 457 | # |
|---|
| | 458 | # TODO: Need to configure a timeout failure operation -- in case |
|---|
| | 459 | # there's a problem and the VM operations hang. |
|---|
| | 460 | sub _init { |
|---|
| 769 | | #=begin testing |
|---|
| 770 | | # |
|---|
| 771 | | # Create a generic driver, with test state data. |
|---|
| 772 | | #my $driver = HoneyClient::Agent::Driver->new(test => 1); |
|---|
| 773 | | #dies_ok {$driver->isFinished()} 'isFinished()' or diag("The isFinished() call failed. Expected isFinished() to throw an exception."); |
|---|
| 774 | | # |
|---|
| 775 | | #=end testing |
|---|
| | 608 | =begin testing |
|---|
| | 609 | |
|---|
| | 610 | # Shared test variables. |
|---|
| | 611 | my ($stub, $som, $URL); |
|---|
| | 612 | my $testVM = $ENV{PWD} . "/" . getVar(name => "test_vm_config", |
|---|
| | 613 | namespace => "HoneyClient::Manager::VM::Test"); |
|---|
| | 614 | |
|---|
| | 615 | # Include notice, to clarify our assumptions. |
|---|
| | 616 | diag("About to run basic unit tests; these may take some time."); |
|---|
| | 617 | diag("Note: These tests *expect* VMware Server or VMware GSX to be installed and running on this system beforehand."); |
|---|
| | 618 | |
|---|
| | 619 | # Catch all errors, in order to make sure child processes are |
|---|
| | 620 | # properly killed. |
|---|
| | 621 | eval { |
|---|
| | 622 | |
|---|
| | 623 | $URL = HoneyClient::Manager::VM->init(); |
|---|
| | 624 | |
|---|
| | 625 | # Connect to daemon as a client. |
|---|
| | 626 | $stub = getClientHandle(namespace => "HoneyClient::Manager::VM"); |
|---|
| | 627 | |
|---|
| | 628 | # In order to test setMasterVM(), we're going to fully clone |
|---|
| | 629 | # the testVM, then set the newly created clone as a master VM. |
|---|
| | 630 | |
|---|
| | 631 | # Get the test VM's parent directory, |
|---|
| | 632 | # in order to create a temporary master VM. |
|---|
| | 633 | my $testVMDir = dirname($testVM); |
|---|
| | 634 | my $masterVMDir = dirname($testVMDir) . "/test_vm_master"; |
|---|
| | 635 | my $masterVM = $masterVMDir . "/" . basename($testVM); |
|---|
| | 636 | |
|---|
| | 637 | # Create the master VM. |
|---|
| | 638 | $som = $stub->fullCloneVM(src_config => $testVM, dest_dir => $masterVMDir); |
|---|
| | 639 | |
|---|
| | 640 | # Wait a small amount of time for the asynchronous clone |
|---|
| | 641 | # to complete. |
|---|
| | 642 | sleep (10); |
|---|
| | 643 | |
|---|
| | 644 | # The master VM should be on. |
|---|
| | 645 | $som = $stub->getStateVM(config => $masterVM); |
|---|
| | 646 | |
|---|
| | 647 | # Since the master VM doesn't have an OS installed on it, |
|---|
| | 648 | # the VM may be considered stuck. Go ahead and answer |
|---|
| | 649 | # this question, if need be. |
|---|
| | 650 | if ($som->result == VM_EXECUTION_STATE_STUCK) { |
|---|
| | 651 | $som = $stub->answerVM(config => $masterVM); |
|---|
| | 652 | } |
|---|
| | 653 | |
|---|
| | 654 | # Turn off the master VM. |
|---|
| | 655 | $som = $stub->stopVM(config => $masterVM); |
|---|
| | 656 | |
|---|
| | 657 | # Now, kill the VM daemon. |
|---|
| | 658 | HoneyClient::Manager::VM->destroy(); |
|---|
| | 659 | sleep (1); |
|---|
| | 660 | |
|---|
| | 661 | # Create a generic empty clone, with test state data. |
|---|
| | 662 | my $clone = HoneyClient::Manager::VM::Clone->new(test => 1, master_vm_config => $masterVM, _dont_init => 1); |
|---|
| | 663 | is($clone->{test}, 1, "new(test => 1, master_vm_config => '$masterVM', _dont_init => 1)") or diag("The new() call failed."); |
|---|
| | 664 | isa_ok($clone, 'HoneyClient::Manager::VM::Clone', "new(test => 1, master_vm_config => '$masterVM', _dont_init => 1)") or diag("The new() call failed."); |
|---|
| | 665 | $clone = undef; |
|---|
| | 666 | |
|---|
| | 667 | # Destroy the master VM. |
|---|
| | 668 | $som = $stub->destroyVM(config => $masterVM); |
|---|
| | 669 | |
|---|
| | 670 | my $question; |
|---|
| | 671 | $question = prompt("#\n" . |
|---|
| | 672 | "# Note: Testing real clone operations will *ONLY* work\n" . |
|---|
| | 673 | "# with a fully functional master VM that has the HoneyClient code\n" . |
|---|
| | 674 | "# loaded upon boot-up.\n" . |
|---|
| | 675 | "#\n" . |
|---|
| | 676 | "# Your master VM is: " . getVar(name => "master_vm_config", namespace => "HoneyClient::Manager::VM") . "\n" . |
|---|
| | 677 | "#\n" . |
|---|
| | 678 | "# Do you want to test cloning this master VM?", "no"); |
|---|
| | 679 | if ($question =~ /^y.*/i) { |
|---|
| | 680 | $clone = HoneyClient::Manager::VM::Clone->new(test => 1); |
|---|
| | 681 | is($clone->{test}, 1, "new(test => 1)") or diag("The new() call failed."); |
|---|
| | 682 | isa_ok($clone, 'HoneyClient::Manager::VM::Clone', "new(test => 1)") or diag("The new() call failed."); |
|---|
| | 683 | my $cloneConfig = $clone->{config}; |
|---|
| | 684 | $clone = undef; |
|---|
| | 685 | |
|---|
| | 686 | # Destroy the clone VM. |
|---|
| | 687 | $som = $stub->destroyVM(config => $cloneConfig); |
|---|
| | 688 | } |
|---|
| | 689 | }; |
|---|
| | 690 | |
|---|
| | 691 | # Kill the child daemon, if it still exists. |
|---|
| | 692 | HoneyClient::Manager::VM->destroy(); |
|---|
| | 693 | sleep (1); |
|---|
| | 694 | |
|---|
| | 695 | # Report any failure found. |
|---|
| | 696 | if ($@) { |
|---|
| | 697 | fail($@); |
|---|
| | 698 | } |
|---|
| | 699 | |
|---|
| | 700 | =end testing |
|---|
| 786 | | # Sanity check: Make sure we've been fed an object. |
|---|
| 787 | | unless (ref($self)) { |
|---|
| 788 | | $LOG->error("Error: Function must be called in reference to a " . |
|---|
| 789 | | __PACKAGE__ . "->new() object!"); |
|---|
| 790 | | Carp::croak "Error: Function must be called in reference to a " . |
|---|
| 791 | | __PACKAGE__ . "->new() object!"; |
|---|
| 792 | | } |
|---|
| 793 | | |
|---|
| 794 | | # Emit generic "not implemented" error message. |
|---|
| 795 | | $LOG->error($class . "->isFinished() is not implemented!"); |
|---|
| 796 | | Carp::croak "Error: " . $class . "->isFinished() is not implemented!\n"; |
|---|
| | 743 | # A variable containing the IP address of the cloned VM's primary |
|---|
| | 744 | # interface. |
|---|
| | 745 | ip_address => undef, |
|---|
| | 746 | |
|---|
| | 747 | # A variable containing the name the cloned VM. |
|---|
| | 748 | name => undef, |
|---|
| | 749 | |
|---|
| | 750 | # A SOAP handle to the VM manager daemon. (This internal variable |
|---|
| | 751 | # should never be modified externally.) |
|---|
| | 752 | _vm_handle => undef, |
|---|
| | 753 | |
|---|
| | 754 | # A variable indicated how long the object should wait for |
|---|
| | 755 | # between subsequent retries to the HoneyClient::Manager::VM |
|---|
| | 756 | # daemon (in seconds). (This internal variable should never |
|---|
| | 757 | # be modified externally.) |
|---|
| | 758 | _retry_period => 2, |
|---|
| | 759 | ); |
|---|
| | 760 | |
|---|
| | 761 | @{$self}{keys %params} = values %params; |
|---|
| | 762 | |
|---|
| | 763 | # Now, overwrite any default parameters that were redefined |
|---|
| | 764 | # in the supplied arguments. |
|---|
| | 765 | @{$self}{keys %args} = values %args; |
|---|
| | 766 | |
|---|
| | 767 | # Now, assign our object the appropriate namespace. |
|---|
| | 768 | bless $self, $class; |
|---|
| | 769 | |
|---|
| | 770 | # Upon first use, start up a global instance of the VM manager. |
|---|
| | 771 | if ($OBJECT_COUNT < 0) { |
|---|
| | 772 | HoneyClient::Manager::VM->init(); |
|---|
| | 773 | $OBJECT_COUNT = 0; |
|---|
| | 774 | } |
|---|
| | 775 | |
|---|
| | 776 | # Set a valid handle for the VM daemon. |
|---|
| | 777 | $self->{'_vm_handle'} = getClientHandle(namespace => "HoneyClient::Manager::VM"); |
|---|
| | 778 | |
|---|
| | 779 | # If the clone's configuration wasn't supplied initially, then |
|---|
| | 780 | # set the master VM to prepare for cloning. |
|---|
| | 781 | unless (defined($self->{'config'})) { |
|---|
| | 782 | $LOG->info("Setting VM (" . $self->{'master_vm_config'} . ") as master."); |
|---|
| | 783 | my $som = $self->{'_vm_handle'}->setMasterVM(config => $self->{'master_vm_config'}); |
|---|
| | 784 | if (!$som->result()) { |
|---|
| | 785 | $LOG->fatal("Unable to set VM (" . $self->{'master_vm_config'} . ") as a master VM."); |
|---|
| | 786 | Carp::croak "Unable to set VM (" . $self->{'master_vm_config'} . ") as a master VM."; |
|---|
| | 787 | } |
|---|
| | 788 | } |
|---|
| | 789 | |
|---|
| | 790 | # Update our global object count. |
|---|
| | 791 | $OBJECT_COUNT++; |
|---|
| | 792 | |
|---|
| | 793 | # Finally, return the blessed object, with a fully initialized |
|---|
| | 794 | # cloned VM unless otherwise specified. |
|---|
| | 795 | if ($self->{'_dont_init'}) { |
|---|
| | 796 | return $self; |
|---|
| | 797 | } else { |
|---|
| | 798 | return $self->_init(); |
|---|
| | 799 | } |
|---|
| 798 | | |
|---|
| 799 | | =pod |
|---|
| 800 | | |
|---|
| 801 | | =head2 $object->next() |
|---|
| 802 | | |
|---|
| 803 | | =over 4 |
|---|
| 804 | | |
|---|
| 805 | | Returns the next set of server hostnames and/or IP addresses that the |
|---|
| 806 | | back-end application will contact, upon the next subsequent call to |
|---|
| 807 | | the B<$object>'s drive() method. |
|---|
| 808 | | |
|---|
| 809 | | Specifically, the returned data is a reference to a hashtable, containing |
|---|
| 810 | | detailed information about which resources, hostnames, IPs, protocols, and |
|---|
| 811 | | ports that the application will contact upon the next iteration. |
|---|
| 812 | | |
|---|
| 813 | | Here is an example of such returned data: |
|---|
| 814 | | |
|---|
| 815 | | $hashref = { |
|---|
| 816 | | |
|---|
| 817 | | # The set of servers that the driver will contact upon |
|---|
| 818 | | # the next drive() operation. |
|---|
| 819 | | targets => { |
|---|
| 820 | | # The application will contact 'site.com' using |
|---|
| 821 | | # TCP ports 80 and 81. |
|---|
| 822 | | 'site.com' => { |
|---|
| 823 | | 'tcp' => [ 80, 81 ], |
|---|
| 824 | | }, |
|---|
| 825 | | |
|---|
| 826 | | # The application will contact '192.168.1.1' using |
|---|
| 827 | | # UDP ports 53 and 123. |
|---|
| 828 | | '192.168.1.1' => { |
|---|
| 829 | | 'udp' => [ 53, 123 ], |
|---|
| 830 | | }, |
|---|
| 831 | | |
|---|
| 832 | | # Or, more generically: |
|---|
| 833 | | 'hostname_or_IP' => { |
|---|
| 834 | | 'protocol_type' => [ portnumbers_as_list ], |
|---|
| 835 | | }, |
|---|
| 836 | | }, |
|---|
| 837 | | |
|---|
| 838 | | # The set of resources that the driver will operate upon |
|---|
| 839 | | # the next drive() operation. |
|---|
| 840 | | resources => { |
|---|
| 841 | | 'http://www.mitre.org/' => 1, |
|---|
| 842 | | }, |
|---|
| 843 | | }; |
|---|
| 844 | | |
|---|
| 845 | | B<Note>: For each hostname or IP address specified, if B<no> |
|---|
| 846 | | corresponding protocol/port sub-hastables are given, then it |
|---|
| 847 | | must be B<assumed> that the back-end application may contact |
|---|
| 848 | | the hostname or IP address using B<ANY> protocol/port. |
|---|
| 849 | | |
|---|
| 850 | | I<Output>: The aforementioned B<$hashref> containing the next set of |
|---|
| 851 | | resources that the back-end application will attempt to contact upon |
|---|
| 852 | | the next drive() iteration. |
|---|
| 853 | | |
|---|
| 854 | | # XXX: Resolve this. |
|---|
| 855 | | |
|---|
| 856 | | B<Note>: Eventually this B<$hashref> will become a structured object, |
|---|
| 857 | | created via a HoneyClient::Util::* package. However, the underlying |
|---|
| 858 | | structure of this hashtable is not expected to change. |
|---|
| 859 | | |
|---|
| 860 | | =back |
|---|
| 861 | | |
|---|
| 862 | | #=begin testing |
|---|
| 863 | | # |
|---|
| 864 | | # Create a generic driver, with test state data. |
|---|
| 865 | | #my $driver = HoneyClient::Agent::Driver->new(test => 1); |
|---|
| 866 | | #dies_ok {$driver->next()} 'next()' or diag("The next() call failed. Expected next() to throw an exception."); |
|---|
| 867 | | # |
|---|
| 868 | | #=end testing |
|---|
| 869 | | |
|---|
| 870 | | =cut |
|---|
| 871 | | |
|---|
| 872 | | sub next { |
|---|
| 873 | | # Get the class name. |
|---|
| 874 | | my $self = shift; |
|---|
| 875 | | |
|---|
| 876 | | # Check to see if the class name is inherited or defined. |
|---|
| 877 | | my $class = ref($self) || $self; |
|---|
| 878 | | |
|---|
| 879 | | # Sanity check: Make sure we've been fed an object. |
|---|
| 880 | | unless (ref($self)) { |
|---|
| 881 | | $LOG->error("Error: Function must be called in reference to a " . |
|---|
| 882 | | __PACKAGE__ . "->new() object!"); |
|---|
| 883 | | Carp::croak "Error: Function must be called in reference to a " . |
|---|
| 884 | | __PACKAGE__ . "->new() object!"; |
|---|
| 885 | | } |
|---|
| 886 | | |
|---|
| 887 | | # Emit generic "not implemented" error message. |
|---|
| 888 | | $LOG->error($class . "->next() is not implemented!"); |
|---|
| 889 | | Carp::croak "Error: " . $class . "->next() is not implemented!\n"; |
|---|
| 890 | | } |
|---|
| 891 | | |
|---|
| 892 | | =pod |
|---|
| 893 | | |
|---|
| 894 | | =head2 $object->status() |
|---|
| 895 | | |
|---|
| 896 | | =over 4 |
|---|
| 897 | | |
|---|
| 898 | | Returns the current status of the Driver B<$object>, as it's state |
|---|
| 899 | | exists, between subsequent calls to $object->driver(). |
|---|
| 900 | | |
|---|
| 901 | | Specifically, the data returned is a reference to a hashtable, |
|---|
| 902 | | containing specific statistical information about the status |
|---|
| 903 | | of the Driver's progress during back-end application automation. |
|---|
| 904 | | |
|---|
| 905 | | As such, the exact structure of this returned hashtable is not strictly |
|---|
| 906 | | defined. Instead, it is left up to each specific Driver implementation |
|---|
| 907 | | to return useful, statistical information back to the Agent that |
|---|
| 908 | | makes sense for the driven application. |
|---|
| 909 | | |
|---|
| 910 | | For example, if an Internet Explorer specific Driver were implemented, |
|---|
| 911 | | then the corresponding status hashtable reference returned may look |
|---|
| 912 | | something like: |
|---|
| 913 | | |
|---|
| 914 | | $hashref = { |
|---|
| 915 | | 'links_remaining' => 56, # Number of URLs left to process. |
|---|
| 916 | | 'links_processed' => 44, # Number of URLs processed. |
|---|
| 917 | | 'links_total' => 100, # Total number of URLs given. |
|---|
| 918 | | 'percent_complete' => '44.00%', # Percent complete. |
|---|
| 919 | | }; |
|---|
| 920 | | |
|---|
| 921 | | For another example, if an Outlook specific Driver were implemented, |
|---|
| 922 | | then the corresponding status hashtable reference returned may look |
|---|
| 923 | | something like: |
|---|
| 924 | | |
|---|
| 925 | | $hashref = { |
|---|
| 926 | | 'mail_remaining' => 56, # Number of messages left to process. |
|---|
| 927 | | 'mail_processed' => 44, # Number of messages processed. |
|---|
| 928 | | 'mail_total' => 100, # Total number of messages given. |
|---|
| 929 | | 'percent_complete' => '44.00%', # Percent complete. |
|---|
| 930 | | }; |
|---|
| 931 | | |
|---|
| 932 | | I<Output>: A corresponding B<$hashref>, containing statistical information |
|---|
| 933 | | about the Driver's progress, as previously mentioned. |
|---|
| 934 | | |
|---|
| 935 | | # XXX: Resolve this. |
|---|
| 936 | | |
|---|
| 937 | | B<Note>: The exact structure of this status hashtable may become more |
|---|
| 938 | | concrete, as we define a generic concept of a "unit of work" per every |
|---|
| 939 | | iteration of the $object->drive() method. For example, it may be |
|---|
| 940 | | likely that each Driver will attempt to contact a series of resources |
|---|
| 941 | | per every "unit of work" iteration. As such, we may generically |
|---|
| 942 | | record how many "work units" are remaining, processed, and total -- |
|---|
| 943 | | rather than specifically state "links" or "mail" within the hashtable |
|---|
| 944 | | key names, accordingly. |
|---|
| 945 | | |
|---|
| 946 | | At the least, it can be assumed that even if a generic structure were |
|---|
| 947 | | defined, we would leave room available in the status hashtable to |
|---|
| 948 | | capture additional, implementation-specific statistics that are not |
|---|
| 949 | | generic among every Driver implementation. |
|---|
| 950 | | |
|---|
| 951 | | =back |
|---|
| 952 | | |
|---|
| 953 | | #=begin testing |
|---|
| 954 | | # |
|---|
| 955 | | # Create a generic driver, with test state data. |
|---|
| 956 | | #my $driver = HoneyClient::Agent::Driver->new(test => 1); |
|---|
| 957 | | #dies_ok {$driver->status()} 'status()' or diag("The status() call failed. Expected status() to throw an exception."); |
|---|
| 958 | | # |
|---|
| 959 | | #=end testing |
|---|
| 960 | | |
|---|
| 961 | | =cut |
|---|
| 962 | | |
|---|
| 963 | | sub status { |
|---|
| 964 | | # Get the class name. |
|---|
| 965 | | my $self = shift; |
|---|
| 966 | | |
|---|
| 967 | | # Check to see if the class name is inherited or defined. |
|---|
| 968 | | my $class = ref($self) || $self; |
|---|
| 969 | | |
|---|
| 970 | | # Sanity check: Make sure we've been fed an object. |
|---|
| 971 | | unless (ref($self)) { |
|---|
| 972 | | $LOG->error("Error: Function must be called in reference to a " . |
|---|
| 973 | | __PACKAGE__ . "->new() object!"); |
|---|
| 974 | | Carp::croak "Error: Function must be called in reference to a " . |
|---|
| 975 | | __PACKAGE__ . "->new() object!"; |
|---|
| 976 | | } |
|---|
| 977 | | |
|---|
| 978 | | # Emit generic "not implemented" error message. |
|---|
| 979 | | $LOG->error($class . "->next() is not implemented!"); |
|---|
| 980 | | Carp::croak "Error: " . $class . "->next() is not implemented!\n"; |
|---|
| 981 | | } |
|---|
| 982 | | |
|---|