wishartz has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monk, I am trying to write a program that will start certain services, eg apache etc by choice from a text menu. The problem is I want the program to check it is being run on the correct host before it starts the service. Some of the services will be able to be started on multiple hosts, but not on others hence the reason I have made a hash of arrays for the list of host(s). I don't know what data structure to use to store the details. I have tried with this data structure, but not having much luck derefrencing the associative array, when doing a check. For example the associative array I am using is the following:
use strict; use warnings; our %Services = ( { serviceid => "1", name => "servicea", host => [ qw(hosta hostb)], serviceid => "2", name => "serviceb", host => [ qw(hostc) ], } );
I know how to check which host it is being run on with the hostname command and storing in a variable, but I don't know how to check that against the values in the hash of arrays. I think I might be using the wrong data structure. Could anybody help please. Thanks

Replies are listed 'Best First'.
Re: Question about data structures
by kyle (Abbot) on Apr 04, 2007 at 13:19 UTC

    It looks as if you're trying to make an array of hashes.

    @services = ( { serviceid => '1', name => 'servicea', host => [ qw( hosta hostb ) ] }, { serviceid => '2', name => 'serviceb', host => [ qw( hostc ) ] }, );

    With this, you can loop through your services like so:

    foreach my $svc ( @services ) { # list of hosts: @{ $svc->{host} } # name: $svc->{name} }

    If you want, you can make a hash keyed on one of those fields instead. For example, keyed on the service name:

    %services = ( 'servicea' => { serviceid => '1', host => [ qw( hosta hostb ) ] }, 'serviceb' => { serviceid => '2', host => [ qw( hostc ) ] }, );

    With this, you can address a particular service by name and get the field you want. Examples: @{ $services{'servicea'}->{host} } and $services{'servicea'}->{serviceid}.

    Hope this helps.

Re: Question about data structures
by philcrow (Priest) on Apr 04, 2007 at 13:27 UTC
    You should probably make your host key's value a hash instead of an array, since hashes are good for lookups. But, I think the larger issue is the big hash. It appears to have duplicate keys. Try something more like this:
    our %services = { '1' => { name => '...', host => { hosta => 1, hostb => 1 }, } '2' => {...} };
    Then when someone asks for service 1, you can say:
    my $service = $services{ $user_request }; if ( not $service->{ host }{ $current_host } ) { # error here } else { # real work here }
    Or, you could reverse the roles of the menu item number and the service name if you wanted to make people remember the names ala the service command on linux. Speaking of which, is that an option? If so, it will be better at this sort of thing than home grown code. You could just put a little text menu front on it. But, maybe you don't have that option.

    Phil

      Thanks for your help guys, I will give some of those ideas a try. The reason for this program is because it's going to be a wrapper for the service command in linux. Offering a menu to start and stop services amongst doing other things. Thanks
Re: Question about data structures
by wfsp (Abbot) on Apr 04, 2007 at 13:23 UTC
    Nearly! I think you're on the right lines. Perhaps a hash of hashes would get you started?
    #!C:/Perl/bin/perl.exe use strict; use warnings; use Data::Dumper; my %Services = ( 1 => { serviceid => "1", name => "servicea", host => [ qw(hosta hostb)], }, 2 => { serviceid => "2", name => "serviceb", host => [ qw(hostc) ], }, ); print Dumper \%Services; my $service = 1; my @hosts = @{$Services{$service}{host}}; print "@hosts";
    output:
    $VAR1 = { '1' => { 'name' => 'servicea', 'serviceid' => '1', 'host' => [ 'hosta', 'hostb' ] }, '2' => { 'name' => 'serviceb', 'serviceid' => '2', 'host' => [ 'hostc' ] } }; hosta hostb
Re: Question about data structures
by graff (Chancellor) on Apr 05, 2007 at 02:24 UTC
    It sounds like you want one single script to run without modification on several hosts, and you want the list of available services to vary from one host to the next. So it seems like you should have a hash structure that is keyed by host name, and the hash value associated with each host name should be the list of services available on that host.

    Of course, there might be other reasons for keeping a separate hash structure that is keyed by the various services that you are controlling across all hosts, and the values for each key (service name) in that structure would be the list of hostnames where that service can be run.

    One of these two distinct hash structures can be considered as "primary", and can be used to derive/populate the other one. (Both structures are likely to be useful, and it probably doesn't matter which one is "primary".)

    I don't know what benefit there is in having a "serviceid" value in addition to a "servicename" -- I wouldn't expect two distinct services to have the same name; likewise, the host names should all be different as well. So the two hash structures can simply use the service names and host names as hash keys, and the values can be arrays (or hashes) for the lists of hosts (running a given service) or services (runnable on a given host).

    One minor point: I would also expect that some services might need to be started with different parameters or other minor differences in the command line, depending on which host is being used, so a hash of hashes, with the command line options/parameters as the sub-hash values, might be the way to go:

    # let's suppose the list of services is the primary HoH structure: # -- top-level hash is keyed by service name # -- next-level hash is keyed by host name # -- $service{service_name}{host_name} = "command line"; my %service = ( servicea => { host1 => "/usr/local/bin/servicea -foo", host2 => "/usr/bin/servicea -bar", ... }, serviceb => { host2 => "/usr/bin/serviceb -baz", host3 => "/usr/local/bin/serviceb -boo" ... }, ... ); # now populate %host HoH to store the same information, but # with host names as top-level keys and service names as sub-keys: my %host; for my $srvc ( keys %service ) { for my $hst ( keys %{$service{$srvc}} ) { $host{$hst}{$srvc} = $service{$srvc}{$hst}; } } # to set up an option menu of services for a particular host, # use the %host hash: my $hostname = `hostname`; chomp $hostname; my @available_services = sort keys %{$host{$hostname}}; ... # to show which hosts are available to run a given service, # use the %service hash: my $servicename = ...; # assuming there's some service of interest my @available_hosts = sort keys %{$service{$servicename}};
    Just an idea...
Re: Question about data structures
by DACONTI (Scribe) on Apr 06, 2007 at 02:48 UTC
    Hi wishartz, I have a standard approach for this kind of problem.

    Just put host and service information in anonymous hash trees and join them at the end.

    Checking for host or service existence is very straightforward (look at the die statements)

    Hope it helps.

    Regs.

    use strict; use warnings; our $hosts={}; $hosts->{hosta}->{ip}=192.168.1.1; $hosts->{hostb}->{ip}=192.168.1.2; $hosts->{hostc}->{ip}=192.168.1.3; our $services={}; $services->{1}->{ID}=1; $services->{1}->{name}='servicea'; $services->{1}->{startup}=sub { print "--> start service 1\n" }; $services->{2}->{ID}=2; $services->{2}->{name}='serviceb'; $services->{2}->{startup}=sub { print "--> start service 2\n" }; $hosts->{hosta}->{services}->{1}=$services->{1}; $hosts->{hostb}->{services}->{1}=$services->{1}; $hosts->{hostc}->{services}->{2}=$services->{2}; print "--> type hostname\n"; my $host=<STDIN>; chomp($host); die "host unknown " unless exists $hosts->{$host}; print "--> which service do you want to use? (Type ID)\n"; while (my ($ID, $service) = each %{$hosts->{$host}->{services}}) { print "- ID $ID, name ".$service->{name}."\n"; } my $ID=<STDIN>; chomp($ID); die "service unknown " unless exists $hosts->{$host}->{services}->{$ID}; &{$hosts->{$host}->{services}->{$ID}->{startup}}; exit; ---> output: --> type hostname hosta --> which service do you want to use? (Type ID) - ID 1, name servicea 1 --> start service 1