The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
##############################################################################
# This test file tests the functions found in the Host section.

use Test::Most qw( bail timeit );
use Test::NoWarnings;

use lib 't';
use MatchURL;

my $method = {
  addCluster => {
    description => "Adds a new cluster",
    isAsync     => "false",
    level       => 1,
    request     => {
      optional => {
        allocationstate => "Allocation state of this cluster for allocation of new resources",
        password        => "the password for the host",
        podid           => "the Pod ID for the host",
        url             => "the URL",
        username        => "the username for the cluster",
      },
      required => {
        clustername => "the cluster name",
        clustertype => "type of the cluster: CloudManaged, ExternalManaged",
        hypervisor  => "hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator",
        zoneid      => "the Zone ID for the cluster",
      },
    },
    response => {
      allocationstate => "the allocation state of the cluster",
      clustertype     => "the type of the cluster",
      hypervisortype  => "the hypervisor type of the cluster",
      id              => "the cluster ID",
      managedstate    => "whether this cluster is managed by cloudstack",
      name            => "the cluster name",
      podid           => "the Pod ID of the cluster",
      podname         => "the Pod name of the cluster",
      zoneid          => "the Zone ID of the cluster",
      zonename        => "the Zone name of the cluster",
    },
    section => "Host",
  },
  addHost => {
    description => "Adds a new host.",
    isAsync     => "false",
    level       => 3,
    request     => {
      optional => {
        allocationstate => "Allocation state of this Host for allocation of new resources",
        clusterid       => "the cluster ID for the host",
        clustername     => "the cluster name for the host",
        hosttags        => "list of tags to be added to the host",
        podid           => "the Pod ID for the host",
      },
      required => {
        hypervisor => "hypervisor type of the host",
        password   => "the password for the host",
        url        => "the host URL",
        username   => "the username for the host",
        zoneid     => "the Zone ID for the host",
      },
    },
    response => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  addSecondaryStorage => {
    description => "Adds secondary storage.",
    isAsync     => "false",
    level       => 1,
    request     => {
      optional => { zoneid => "the Zone ID for the secondary storage" },
      required => { url    => "the URL for the secondary storage" },
    },
    response => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  cancelHostMaintenance => {
    description => "Cancels host maintenance.",
    isAsync     => "true",
    level       => 1,
    request     => { required => { id => "the host ID" } },
    response    => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  deleteCluster => {
    description => "Deletes a cluster.",
    isAsync     => "false",
    level       => 1,
    request     => { required => { id => "the cluster ID" } },
    response    => {
      displaytext => "any text associated with the success or failure",
      success     => "true if operation is executed successfully",
    },
    section => "Host",
  },
  deleteHost => {
    description => "Deletes a host.",
    isAsync     => "false",
    level       => 3,
    request     => {
      optional => {
        forced =>
            "Force delete the host. All HA enabled vms running on the host will be put to HA; HA disabled ones will be stopped",
        forcedestroylocalstorage =>
            "Force destroy local storage on this host. All VMs created on this local storage will be destroyed",
      },
      required => { id => "the host ID" },
    },
    response => {
      displaytext => "any text associated with the success or failure",
      success     => "true if operation is executed successfully",
    },
    section => "Host",
  },
  listHosts => {
    description => "Lists hosts.",
    isAsync     => "false",
    level       => 3,
    request     => {
      optional => {
        allocationstate => "list hosts by allocation state",
        clusterid       => "lists hosts existing in particular cluster",
        details =>
            "give details.  1 = minimal; 2 = include static info; 3 = include events; 4 = include allocation and statistics",
        id       => "the id of the host",
        keyword  => "List by keyword",
        name     => "the name of the host",
        page     => "no description",
        pagesize => "no description",
        podid    => "the Pod ID for the host",
        state    => "the state of the host",
        type     => "the host type",
        virtualmachineid =>
            "lists hosts in the same cluster as this VM and flag hosts with enough CPU/RAm to host this VM",
        zoneid => "the Zone ID for the host",
      },
    },
    response => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  prepareHostForMaintenance => {
    description => "Prepares a host for maintenance.",
    isAsync     => "true",
    level       => 1,
    request     => { required => { id => "the host ID" } },
    response    => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  reconnectHost => {
    description => "Reconnects a host.",
    isAsync     => "true",
    level       => 1,
    request     => { required => { id => "the host ID" } },
    response    => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  updateCluster => {
    description => "Updates an existing cluster",
    isAsync     => "false",
    level       => 1,
    request     => {
      optional => {
        allocationstate => "Allocation state of this cluster for allocation of new resources",
        clustername     => "the cluster name",
        clustertype     => "hypervisor type of the cluster",
        hypervisor      => "hypervisor type of the cluster",
        managedstate    => "whether this cluster is managed by cloudstack",
      },
      required => { id => "the ID of the Cluster" },
    },
    response => {
      allocationstate => "the allocation state of the cluster",
      clustertype     => "the type of the cluster",
      hypervisortype  => "the hypervisor type of the cluster",
      id              => "the cluster ID",
      managedstate    => "whether this cluster is managed by cloudstack",
      name            => "the cluster name",
      podid           => "the Pod ID of the cluster",
      podname         => "the Pod name of the cluster",
      zoneid          => "the Zone ID of the cluster",
      zonename        => "the Zone name of the cluster",
    },
    section => "Host",
  },
  updateHost => {
    description => "Updates a host.",
    isAsync     => "false",
    level       => 1,
    request     => {
      optional => {
        allocationstate => "Allocation state of this Host for allocation of new resources",
        hosttags        => "list of tags to be added to the host",
        oscategoryid    => "the id of Os category to update the host with",
      },
      required => { id => "the ID of the host to update" },
    },
    response => {
      allocationstate         => "the allocation state of the host",
      averageload             => "the cpu average load on the host",
      capabilities            => "capabilities of the host",
      clusterid               => "the cluster ID of the host",
      clustername             => "the cluster name of the host",
      clustertype             => "the cluster type of the cluster that host belongs to",
      cpuallocated            => "the amount of the host's CPU currently allocated",
      cpunumber               => "the CPU number of the host",
      cpuspeed                => "the CPU speed of the host",
      cpuused                 => "the amount of the host's CPU currently used",
      cpuwithoverprovisioning => "the amount of the host's CPU after applying the cpu.overprovisioning.factor",
      created                 => "the date and time the host was created",
      disconnected            => "true if the host is disconnected. False otherwise.",
      disksizeallocated       => "the host's currently allocated disk size",
      disksizetotal           => "the total disk size of the host",
      events                  => "events available for the host",
      hasEnoughCapacity => "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise",
      hosttags          => "comma-separated list of tags for the host",
      hypervisor        => "the host hypervisor",
      id                => "the ID of the host",
      ipaddress         => "the IP address of the host",
      islocalstorageactive => "true if local storage is active, false otherwise",
      jobid =>
          "shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the host",
      jobstatus          => "shows the current pending asynchronous job status",
      lastpinged         => "the date and time the host was last pinged",
      managementserverid => "the management server ID of the host",
      memoryallocated    => "the amount of the host's memory currently allocated",
      memorytotal        => "the memory total of the host",
      memoryused         => "the amount of the host's memory currently used",
      name               => "the name of the host",
      networkkbsread     => "the incoming network traffic on the host",
      networkkbswrite    => "the outgoing network traffic on the host",
      oscategoryid       => "the OS category ID of the host",
      oscategoryname     => "the OS category name of the host",
      podid              => "the Pod ID of the host",
      podname            => "the Pod name of the host",
      removed            => "the date and time the host was removed",
      state              => "the state of the host",
      type               => "the host type",
      version            => "the host version",
      zoneid             => "the Zone ID of the host",
      zonename           => "the Zone name of the host",
    },
    section => "Host",
  },
  updateHostPassword => {
    description => "Update password of a host/pool on management server.",
    isAsync     => "false",
    level       => 1,
    request     => {
      optional => {
        clusterid => "the cluster ID. Either this parameter, or hostId has to be passed in",
        hostid    => "the host ID. Either this parameter, or clusterId has to be passed in",
      },
      required =>
          { password => "the new password for the host/cluster", username => "the username for the host/cluster", },
    },
    response => {
      displaytext => "any text associated with the success or failure",
      success     => "true if operation is executed successfully",
    },
    section => "Host",
  },
};

sub random_text {

  my @c = ( 'a' .. 'z', 'A' .. 'Z' );
  return join '', map { $c[ rand @c ] } 0 .. int( rand 16 ) + 8;

}

my $base_url   = 'http://somecloud.com';
my $api_path   = 'client/api?';
my $api_key    = random_text();
my $secret_key = random_text();

my $tests = 1;  # Start at 1 for Test::NoWarnings
$tests++;       # Test loading of Host group
$tests++;       # Test object isa Net::CloudStack::API
$tests++;       # Test api object isa Net::CloudStack
$tests += ( keys %$method ) * 6;  # Methods to be checked--times the number of tests in the loop

plan( tests => $tests );          # We're going to run this many tests ...

timeit { ok( eval "use Net::CloudStack::API ':Host'; 1", 'use statement' ) } 'use took';
explain $@ if $@;

my $obj = Net::CloudStack::API->new;
isa_ok( $obj, 'Net::CloudStack::API' );

my $oo_api = $obj->api( {

    base_url   => $base_url,
    api_path   => $api_path,
    api_key    => $api_key,
    secret_key => $secret_key,

} );

isa_ok( $oo_api, 'Net::CloudStack' );

# MatchURL::match expects
# $check_url, $base_url, $api_path, $api_key, $secret_key, $cmd, $pairs (optional)
my @data = ( $base_url, $api_path, $api_key, $secret_key );

for my $m ( keys %$method ) {

  explain( "Working on $m method" );

  my $work = $method->{ $m };

  SKIP: {

    skip 'no required parameters', 2
        if ! exists $work->{ request }{ required };

    # Test call with no arguments
    my $check_regex = qr/Mandatory parameters? .*? missing in call/i;
    throws_ok { $obj->$m } $check_regex, 'caught missing required params (oo)';

    no strict 'refs';
    throws_ok { $m->() } $check_regex, 'caught missing required params (functional)';

  }

  my ( %args, @args );

  if ( exists $work->{ request }{ required } ) {
    for my $parm ( keys %{ $work->{ request }{ required } } ) {

      $args{ $parm } = random_text();
      push @args, [ $parm, $args{ $parm } ];

    }
  }

  my $check_url;
  ok( $check_url = $obj->$m( \%args ), 'check_url created (oo)' );
  ok( MatchURL::match( $check_url, @data, $m, \@args ), 'urls matched (oo)' );

  { no strict 'refs'; ok( $check_url = $m->( \%args ), 'check_url created (functional)' ) }
  ok( MatchURL::match( $check_url, @data, $m, \@args ), 'urls matched (functional)' );

} ## end for my $m ( keys %$method)