File Coverage

blib/lib/IBM/StorageSystem.pm
Criterion Covered Total %
statement 120 233 51.5
branch 0 42 0.0
condition 0 2 0.0
subroutine 40 54 74.0
pod 2 2 100.0
total 162 333 48.6


line stmt bran cond sub pod time code
1             package IBM::StorageSystem;
2              
3 1     1   22470 use strict;
  1         3  
  1         38  
4 1     1   5 use warnings;
  1         3  
  1         28  
5              
6 1     1   559 use IBM::StorageSystem::Array;
  1         2  
  1         22  
7 1     1   450 use IBM::StorageSystem::Disk;
  1         3  
  1         22  
8 1     1   459 use IBM::StorageSystem::Drive;
  1         2  
  1         38  
9 1     1   526 use IBM::StorageSystem::Enclosure;
  1         4  
  1         41  
10 1     1   833 use IBM::StorageSystem::Export;
  1         3  
  1         31  
11 1     1   828 use IBM::StorageSystem::Fabric;
  1         3  
  1         27  
12 1     1   821 use IBM::StorageSystem::FileSystem;
  1         5  
  1         42  
13 1     1   908 use IBM::StorageSystem::Health;
  1         3  
  1         28  
14 1     1   679 use IBM::StorageSystem::Host;
  1         3  
  1         28  
15 1     1   629 use IBM::StorageSystem::Interface;
  1         4  
  1         33  
16 1     1   531 use IBM::StorageSystem::IOGroup;
  1         4  
  1         24  
17 1     1   492 use IBM::StorageSystem::Mount;
  1         2  
  1         25  
18 1     1   555 use IBM::StorageSystem::Node;
  1         2  
  1         25  
19 1     1   472 use IBM::StorageSystem::Pool;
  1         3  
  1         25  
20 1     1   573 use IBM::StorageSystem::Replication;
  1         3  
  1         29  
21 1     1   595 use IBM::StorageSystem::Service;
  1         3  
  1         27  
22 1     1   484 use IBM::StorageSystem::Task;
  1         2  
  1         24  
23 1     1   488 use IBM::StorageSystem::Quota;
  1         2  
  1         27  
24 1     1   596 use IBM::StorageSystem::VDisk;
  1         3  
  1         34  
25 1     1   564 use IBM::StorageSystem::Statistic;
  1         2  
  1         28  
26 1     1   631 use IBM::StorageSystem::StatisticsSet;
  1         3  
  1         29  
27 1     1   709 use IBM::StorageSystem::Statistic::ClusterThroughput;
  1         4  
  1         31  
28 1     1   570 use IBM::StorageSystem::Statistic::ClusterClientThroughput;
  1         2  
  1         33  
29 1     1   630 use IBM::StorageSystem::Statistic::ClusterCreateDeleteLatency;
  1         3  
  1         32  
30 1     1   642 use IBM::StorageSystem::Statistic::ClusterCreateDeleteOperations;
  1         3  
  1         27  
31 1     1   535 use IBM::StorageSystem::Statistic::ClusterOpenCloseLatency;
  1         3  
  1         27  
32 1     1   562 use IBM::StorageSystem::Statistic::ClusterOpenCloseOperations;
  1         3  
  1         31  
33 1     1   537 use IBM::StorageSystem::Statistic::ClusterReadWriteLatency;
  1         3  
  1         26  
34 1     1   562 use IBM::StorageSystem::Statistic::ClusterReadWriteOperations;
  1         3  
  1         28  
35 1     1   6 use IBM::StorageSystem::Statistic::Node::Memory;
  1         2  
  1         28  
36 1     1   7 use IBM::StorageSystem::Statistic::Node::CPU;
  1         2  
  1         21  
37 1     1   579 use IBM::StorageSystem::Statistic::Pool::Throughput;
  1         3  
  1         27  
38 1     1   1520 use Net::OpenSSH;
  1         51204  
  1         95  
39 1     1   14 use Carp qw(croak);
  1         2  
  1         551  
40              
41             our $VERSION = '0.045';
42              
43             our @ATTR = qw(auth_service_cert_set auth_service_configured auth_service_enabled
44             auth_service_pwd_set auth_service_type auth_service_url auth_service_user_name
45             bandwidth cluster_isns_IP_address cluster_locale cluster_ntp_IP_address code_level
46             console_IP email_contact email_contact2 email_contact2_alternate email_contact2_primary
47             email_contact_alternate email_contact_location email_contact_primary email_reply
48             email_state gm_inter_cluster_delay_simulation gm_intra_cluster_delay_simulation
49             gm_link_tolerance gm_max_host_delay has_nas_key id id_alias inventory_mail_interval
50             iscsi_auth_method iscsi_chap_secret layer location name partnership rc_buffer_size
51             relationship_bandwidth_limit space_allocated_to_vdisks space_in_mdisk_grps
52             statistics_frequency statistics_status stats_threshold tier tier_capacity tier_free_capacity
53             time_zone total_allocated_extent_capacity total_free_space total_mdisk_capacity
54             total_overallocation total_used_capacity total_vdisk_capacity total_vdiskcopy_capacity);
55              
56             our @STAT = qw(compression_cpu_pc cpu_pc drive_r_io drive_r_mb drive_r_ms drive_w_io
57             drive_w_mb drive_w_ms fc_io fc_mb iscsi_io iscsi_mb mdisk_r_io mdisk_r_mb mdisk_r_ms
58             mdisk_w_io mdisk_w_mb mdisk_w_ms sas_io sas_mb total_cache_pc vdisk_r_io vdisk_r_mb
59             vdisk_r_ms vdisk_w_io vdisk_w_mb vdisk_w_ms write_cache_pc);
60              
61             $|++;
62              
63             foreach my $attr ( @ATTR ) {
64             {
65 1     1   7 no strict 'refs';
  1         2  
  1         129  
66             *{ __PACKAGE__ .'::'. $attr } = sub {
67 0     0     my( $self, $val ) = @_;
68 0 0         $val =~ s/\#/no/ if $val;
69 0 0         $self->{$attr} = $val if $val;
70              
71 0           return $self->{$attr}
72             }
73             }
74             }
75              
76             foreach my $stat ( @STAT ) {
77             {
78 1     1   6 no strict 'refs';
  1         3  
  1         210  
79             *{ __PACKAGE__ .'::'. $stat } = sub {
80 0     0     my $self = shift;
81 0 0         $self->stats_threshold or return $self->{$stat};
82              
83 0 0         return ( ( time - $self->{$stat}->{ts} ) > $self->stats_threshold
84             ? $self->{$stat}->refresh
85             : $self->{$stat} )
86             }
87             }
88             }
89              
90             our $STATS = {
91             cluster_throughput => {
92             cmd => '-g cluster_throughput',
93             class => 'IBM::StorageSystem::Statistic::ClusterThroughput'
94             },
95             cluster_client_throughput => {
96             cmd => '-g client_throughput',
97             class => 'IBM::StorageSystem::Statistic::ClusterClientThroughput'
98             },
99             cluster_create_delete_latency => {
100             cmd => '-g cluster_create_delete_latency',
101             class => 'IBM::StorageSystem::Statistic::ClusterCreateDeleteLatency'
102             },
103             cluster_create_delete_operations => {
104             cmd => '-g cluster_create_delete_operations',
105             class => 'IBM::StorageSystem::Statistic::ClusterCreateDeleteOperations'
106             },
107             cluster_open_close_latency => {
108             cmd => '-g cluster_open_close_latency',
109             class => 'IBM::StorageSystem::Statistic::ClusterOpenCloseLatency'
110             },
111             cluster_open_close_operations => {
112             cmd => '-g cluster_open_close_operations',
113             class => 'IBM::StorageSystem::Statistic::ClusterOpenCloseOperations'
114             },
115             cluster_read_write_operations => {
116             cmd => '-g cluster_read_write_operations',
117             class => 'IBM::StorageSystem::Statistic::ClusterReadWriteOperations'
118             },
119             cluster_read_write_latency => {
120             cmd => '-g cluster_read_write_latency',
121             class => 'IBM::StorageSystem::Statistic::ClusterReadWriteLatency'
122             }
123             };
124              
125             foreach my $stat ( keys %{ $STATS } ) {
126             {
127 1     1   6 no strict 'refs';
  1         2  
  1         502  
128             *{ __PACKAGE__ .'::'. $stat } =
129             sub {
130 0     0     my( $self, $t ) = @_;
131 0   0       $t ||= 'minute';
132 0           my $stats = $self->__lsperfdata( cmd => "$STATS->{$stat}->{cmd} -t $t",
133             class => $STATS->{$stat}->{class}
134             );
135 0           return $stats
136             }
137             }
138             }
139              
140              
141             # Our object hash for programmatic generation of methods.
142             #
143             # Each hash represents an object type that we will generate methods for - for example, given the below;
144             #
145             # drive =>
146             # bcmd => 'lsdrive -nohdr -delim :',
147             # cmd => 'lsdrive',
148             # id => 'id',
149             # class => 'IBM::StorageSystem::Drive',
150             # type => 'drive'
151             # },
152             #
153             # drive - is the object type - in this case a drive in Storwize nomenclature representing a physical
154             # hard drive.
155             #
156             # bcmd - is the base cmd to be executed in the Storwize CLI to retrieve a list of drive objects. This
157             # parameter is only required for object types where object enumeration is required as a prequisite
158             # to execution of object specific commands.
159             #
160             # Using the example above for 'lsdrive -nohdr -delim :', the base cmd is necessary to first enumerate
161             # all drives to obtain a summary listing of all drives and their ID's. The ID's can then be used in
162             # the 'cmd' command (e.g. lsdrive 1) to obtain detailed information on each drive.
163             #
164             # As there is no way in which to obtain detailed information of some objects without specifying the
165             # object ID, the bcmd is necessary to first produce a list of these ID's. Contrast the operation of
166             # the lsdrive command and the need for an ID parameter to obtain detailed information with the operation
167             # of the lsnode command, which provides detailed information on all nodes without any parameters.
168             #
169             # *NOTE* that the bcmd is not necessary for all objects - only those which there is no single command
170             # to produce a detailed listing without specifying an ID type parameter. Note also, that you probably
171             # want to specify additional options to the bcmd command (like -nohdr and -delim :) to prevent header
172             # information from being parsed.
173             #
174             # cmd - is the CLI command that will be used to retrieve information about the object. *NOTE* the above information
175             # on the use of the bcmd value and how this command works in conjunction with it.
176             #
177             # id - is a single field in the CLI command output (or multiple fields concatenated by colon) that is
178             # able to uniquely identify each object in a global context for this object type. For example;
179             # in a systems with more than one enclosure it is necessary to identify an enclosure PSU by specifying
180             # both the enclosure id and the PSU id - e.g. Enclosure 2, PSU 1 - which when separated by a colon
181             # would become 2:1 which is sufficient to uniquely identify a enclsoure PSU in a system of any number
182             # of enclosures.
183             #
184             # class - the Perl package namespace into which this object will be blessed as a class.
185             #
186             # type - the 'type' of object - this is usually the same as the root key type, however it must also be
187             # globally unique within this class. This value is used internally to implement object caching
188             # and is not used in method naming so need not necessarily intuitive to a user.
189             #
190             # sl - this optional parameter is used to designate the CLI command output type as 'single-line' - this
191             # is in contrast to 'multi-line' output. In general, CLI command output falls into two categories;
192             # 'single-line' output contains output on multiple objects with each line representative of a unique
193             # object - this may also be referred to as 'row-based' data. An example of such output is the 'lsnode'
194             # command.
195             #
196             # 'multi-line' output contains output about a single object as key-value pairs over multiple lines,
197             # may also be referred to as 'columnar' data. An output of such output is the 'lsdrive' command when
198             # executed with a valid drive id parameter (e.g. lsdrive 1).
199             #
200             # By default, CLI commands are assumed to use columnar output, so it is necessary for any commands
201             # using row-based output to also specify a true value for the 'sl' key to ensure that the output is
202             # parsed correctly. CLI commands that output columnar data should not specify this command.
203              
204             our $OBJ = { drive => {
205             bcmd => 'lsdrive -nohdr -delim :',
206             cmd => 'lsdrive -bytes',
207             id => 'id',
208             class => 'IBM::StorageSystem::Drive',
209             type => 'drive'
210             },
211             vdisk => {
212             bcmd => 'lsvdisk -nohdr -delim :',
213             cmd => 'lsvdisk -bytes',
214             id => 'id',
215             class => 'IBM::StorageSystem::VDisk',
216             type => 'vdisk'
217             },
218             disk => {
219             cmd => 'lsdisk -Y -v',
220             id => 'Name',
221             class => 'IBM::StorageSystem::Disk',
222             type => 'disk',
223             sl => 1
224             },
225             enclosure => {
226             bcmd => 'lsenclosure -nohdr -delim :',
227             cmd => 'lsenclosure',
228             id => 'id',
229             class => 'IBM::StorageSystem::Enclosure',
230             type => 'enclosure'
231             },
232             host => {
233             bcmd => 'lshost -nohdr -delim :',
234             cmd => 'lshost',
235             id => 'id',
236             class => 'IBM::StorageSystem::Host',
237             # so we don't clobber IBM::StorageSystem::host variable
238             type => 'IBM::StorageSystem::Host::host'
239             },
240             enclosurebattery => {
241             cmd => 'lsenclosurebattery -delim :',
242             id => 'enclosure_id:battery_id',
243             class => 'IBM::StorageSystem::EnclosureBattery',
244             type => 'enclosurebattery',
245             sl => 1
246             },
247             fabric => {
248             cmd => 'lsfabric -delim :',
249             id => 'local_wwpn:remote_wwpn',
250             class => 'IBM::StorageSystem::Fabric',
251             type => 'fabric',
252             sl => 1
253             },
254             array => {
255             bcmd => 'lsarray -nohdr -delim :',
256             cmd => 'lsarray -bytes',
257             id => 'mdisk_id',
258             class => 'IBM::StorageSystem::Array',
259             type => 'array'
260             },
261             export => {
262             cmd => 'lsexport -Y -v',
263             id => 'Name:Path',
264             class => 'IBM::StorageSystem::Export',
265             type => 'export',
266             sl => 1
267             },
268             mount => {
269             cmd => 'lsmount -Y -v',
270             id => 'File_system',
271             class => 'IBM::StorageSystem::Mount',
272             type => 'mount',
273             sl => 1
274             },
275             node => {
276             cmd => 'lsnode -Y -v',
277             id => 'Hostname',
278             class => 'IBM::StorageSystem::Node',
279             type => 'node',
280             sl => 1
281             },
282             health => {
283             cmd => 'lshealth -Y',
284             id => 'Host:Sensor',
285             class => 'IBM::StorageSystem::Health',
286             type => 'health',
287             sl => 1
288             },
289             iogroup => {
290             bcmd => 'lsiogrp -nohdr -delim :',
291             cmd => 'lsiogrp -bytes',
292             id => 'id',
293             type => 'iogroup',
294             class => 'IBM::StorageSystem::IOGroup'
295             },
296             filesystem => {
297             cmd => 'lsfs -Y -v',
298             id => 'Device_name',
299             class => 'IBM::StorageSystem::FileSystem',
300             type => 'fs',
301             sl => 1
302             },
303             service => {
304             cmd => 'lsservice -Y',
305             id => 'Name',
306             class => 'IBM::StorageSystem::Service',
307             type => 'service',
308             sl => 1
309             },
310             task => {
311             cmd => 'lstask -Y -v',
312             id => 'Name',
313             class => 'IBM::StorageSystem::Task',
314             type => 'task',
315             sl => 1
316             },
317             replication => {
318             cmd => 'lsrepl -Y',
319             id => 'log_Id',
320             class => 'IBM::StorageSystem::Replication',
321             type => 'replication',
322             sl => 1
323             },
324             quota => {
325             cmd => 'lsquota -Y',
326             id => 'Cluster:Device:Type:ID',
327             class => 'IBM::StorageSystem::Quota',
328             type => 'quota',
329             sl => 1
330             },
331             interface => {
332             cmd => 'lsnwinterface -x -Y',
333             id => 'Node:Interface',
334             class => 'IBM::StorageSystem::Interface',
335             type => 'interface',
336             sl => 1
337             },
338             pool => {
339             cmd => 'lspool -Y',
340             id => 'Filesystem:Name',
341             class => 'IBM::StorageSystem::Pool',
342             type => 'pool',
343             sl => 1
344             }
345             };
346              
347             foreach my $obj ( keys %{ $OBJ } ) {
348             {
349 1     1   6 no strict 'refs';
  1         2  
  1         2436  
350             my $m = 'get_'.$obj.'s';
351              
352             *{ __PACKAGE__ ."::$obj" } = sub {
353 0     0     my ( $self, $id ) = @_;
354              
355 0 0         return ( $self->{$obj}->{$id} ? $self->{$obj}->{$id}
356             : $self->$m( $id ) )
357             };
358              
359             *{ __PACKAGE__ .'::get_'. $obj } = sub {
360 0     0     return $_[0]->$m( $_[1] )
361             };
362              
363             if ( $OBJ->{$obj}->{sl} ) {
364             *{ __PACKAGE__ . "::$m" } = sub {
365 0     0     my ( $self, $id ) = @_;
366 0           my %args = ( cmd => $OBJ->{$obj}->{cmd},
367             class => $OBJ->{$obj}->{class},
368             type => $OBJ->{$obj}->{type},
369             id => $OBJ->{$obj}->{id}
370             );
371 0           my @res = $self->__get_sl_objects( %args );
372            
373 0 0         return ( defined $id ? $self->{ $OBJ->{$obj}->{type} }->{$id} : @res )
374             }
375             }
376             else {
377             *{ __PACKAGE__ . "::$m" } = sub {
378 0     0     my ( $self, $id ) = @_;
379 0           my @objs = map { ( split /:/, $_ )[0] }
  0            
380             split /\n/, $self->__cmd( $OBJ->{$obj}->{bcmd} );
381              
382 0           my %args = ( cmd => $OBJ->{$obj}->{cmd},
383             objects => [@objs],
384             id => $OBJ->{$obj}->{id},
385             class => $OBJ->{$obj}->{class},
386             type => $OBJ->{$obj}->{type}
387             );
388 0           my @res = $self->__get_ml_objects( %args );
389              
390 0 0         return ( defined $id ? $self->{ $OBJ->{$obj}->{type} }->{$id} : @res )
391             }
392             }
393             }
394             }
395              
396             sub new {
397 0     0 1   my ($class, %args) = @_;
398 0           my $self = bless {} , $class;
399 0 0         $args{user} ? $self->{user} = $args{user} : croak 'Mandatory parameter "user" not given';
400 0 0         $args{host} ? $self->{host} = $args{host} : croak 'Mandatory parameter "host" not given';
401 0 0         $args{key_path} ? $self->{key_path} = $args{key_path} : croak 'Mandatory parameter "key_path" not given';
402 0           my %opts = ( user => $self->{user}, key_path => $self->{key_path}, batch_mode => 1, master_opts => '-q' );
403 0           $self->{ssh} = Net::OpenSSH->new( $args{host}, %opts );
404 0 0         $self->{ssh}->error and croak 'Could not create Net::OpenSSH object: ' . $self->{ssh}->error . "\n";
405              
406 0 0         unless ( $args{no_stats} ) {
407 0           $self->__lssystem;
408 0           $self->refresh_system_stats;
409 0 0         $self->{stats_threshold} = ( $args{stats_threshold} ? $args{stats_threshold} : 0 );
410             }
411              
412 0           return $self
413             }
414              
415             sub refresh_system_stats {
416 0     0 1   my $self = shift;
417              
418 0           foreach my $stat ( splice @{ [ split /\n/, $self->__cmd( 'lssystemstats -gui -delim :' ) ] }, 1 ) {
  0            
419 0           my ( $name, $epoch, $current, $peak, $peak_time, $peak_epoch ) = split /:/, $stat;
420 0           $self->{$name} = IBM::StorageSystem::Statistic->new( $self,
421             name => $name,
422             epoch => $epoch,
423             current => $current,
424             peak => $peak,
425             peak_time => $peak_time,
426             peak_epoch => $peak_epoch
427             );
428             }
429             }
430              
431             sub __lssystem {
432 0     0     my $self = shift;
433 0           my ( %a, %dkeys );
434 0           my @output = split /\n/, ( split /\n\n/, $self->__cmd( 'lssystem' ) )[0];
435 0           %dkeys = map { $_ => $dkeys{ $_ }++ }
  0            
436 0           map { ( split /\s/, $_ )[0] } @output;
437              
438 0           for ( @output ) {
439 0 0         last if /^\s*$/;
440 0           s/$/ -/;
441 0           my ( $var, $val ) = ( split /\s/ )[0,1];
442              
443 0 0         if ( $dkeys{ $var } >= 1 ) {
444 0           push @{ $self->{$var} }, $val
  0            
445             }
446             else {
447 0           $self->{$var} = $val
448             }
449             }
450              
451 0           return $self
452             }
453              
454             sub __lsperfdata {
455 0     0     my ( $self, %args ) = @_;
456 0           my $stats = IBM::StorageSystem::StatisticsSet->new;
457 0           my @output = split /\n/, $self->__cmd( "lsperfdata $args{cmd}" );
458 0           shift @output;
459 0           pop @output;
460              
461 0           foreach my $line ( @output ) {
462 0           my @values = split /,/, $line;
463 0           $stats->__push( $args{class}->new( @values ) )
464             }
465              
466 0           return $stats;
467             }
468              
469             sub __cmd {
470 0     0     return $_[0]->{ssh}->capture( $_[1] )
471             }
472              
473             # __get_sl_objects - Get Single Line Objects
474             # This method is used to parse CLI output where information is returned
475             # in a row-based format - e.g.
476             #
477             # Column_1, Column_2, Column_3, ... Column_N
478             # Value_1 , Value_2, Value_3, ... Value_N
479             # Value_1 , Value_2, Value_3, ... Value_N
480             # Value_1 , Value_2, Value_3, ... Value_N
481             #
482             # To parse this data into an array of objects, we treat each row as a single object.
483             # We split the column headers on a delimeter (usually :) and use each header as a hash
484             # key for the corresponding value in each row - i.e.
485             #
486             # Column_1 => Value_1,
487             # Column_2 => Value_2 ...etc.
488             #
489             # Each hash is then passed to the constructor for this object type (given as the 'class'
490             # argument in our %args hash), and the resultant object conditionally cached within our
491             # IBM::StorageSystem object and pushed onto our returned array.
492             #
493             # Note that this method includes a slight hack to cater for the lack of non-unique per-object
494             # keys in CLI output for some object types. In this scenario it is necessary to create
495             # a unique key via composite fields concatenated with a colon.
496              
497             sub __get_sl_objects {
498 0     0     my( $self, %args ) = @_;
499 0           my @objs = split /\n/, $self->__cmd( $args{ cmd } );
500 0           my @headers = map { s/ /_/g; s/\.//g; $_ }
  0            
  0            
  0            
501             split /:/, shift @objs;
502 0           my @res;
503              
504 0           foreach my $object ( @objs ) {
505 0           my (%a, $c);
506            
507 0           foreach my $val ( split /:/, $object ) {
508 0           $c++;
509 0 0         next if $headers[ $c - 1 ] =~ /^(lsnode|lsexport|lshealth|lsfs|SensorSummary|Share|CtdbHost|HEADER|reserved)$/;
510 0           $a{ $headers[ $c - 1 ] } = $val
511             }
512              
513 0 0         if ( $args{ id } =~ /:/ ) {
514 0           my ($nid, $nval);
515              
516 0           foreach my $id ( split /:/, $args{ id } ) {
517 0           $nid .= ":$id"; $nval .= ":$a{ $id }"
  0            
518             }
519              
520 0           $nid =~ s/^://; $nval =~ s/^://;
  0            
521 0           $a{ $nid } = $nval
522             }
523              
524 0           my $obj = $args{ class }->new( $self, %a );
525 0 0         $self->{ $args{ type } }->{ $a{ $args{ id } } } = $obj unless $args{ nocache };
526 0           push @res, $obj
527             }
528              
529             return @res
530 0           }
531              
532             # __get_ml_objects - Get Multi-Line Objects
533             # This method is used to parse CLI command where detailed information on
534             # single object is returned in a columnar format - e.g.
535             #
536             # Column_1 Value_1
537             # Column_2 Value_2
538             # ... ...
539             # Column_N Value_N
540             #
541             # To parse this data we split the output on a delimiter (usually whitespace)
542             # and treat each row as a name,value hash pair for an attribute of a single object.
543             # For example, the above example output would become:
544             #
545             # Column_1 => Value_1,
546             # Column_2 => Value_2,
547             # ...
548             # Column_N => Value_N,
549             #
550             # The resultant hash is passed as the argument to the constructor of the type
551             # specified by the $args{ class } value and the resultant object is optionally cached
552             # in our and pushed onto the return array.
553             #
554             # Things to note in this sub include the hack required to handle non-unique column names.
555             # For example; an 'lsfabric' output may include multiple 'WWPN' columns, so it is
556             # neccesary to identify duplicate column names and treat the corresponding hash values
557             # as anonymous arrays rather than scalars.
558              
559             sub __get_ml_objects {
560 0     0     my ( $self, %args ) = @_;
561 0           my @res;
562            
563 0           foreach my $object ( @{ $args{ objects } } ) {
  0            
564 0           my ( %a, %dkeys );
565 0           my @output = split /\n/, ( split /\n\n/, $self->__cmd( "$args{ cmd } $object" ) )[0];
566 0           %dkeys = map { $_ => $dkeys{ $_ }++ }
  0            
567 0           map { ( split /\s/, $_ )[0] } @output;
568              
569 0           for ( @output ) {
570 0 0         last if /^\s*$/;
571 0           s/$/ -/;
572 0           my ( $var, $val ) = ( split /\s/ )[0,1];
573              
574 0 0         if ( $dkeys{ $var } >= 1 ) {
575 0           push @{ $a{ $var } }, $val
  0            
576             }
577             else {
578 0           $a{ $var } = $val
579             }
580             }
581              
582 0           my $obj = $args{ class }->new( $self, %a );
583 0 0         $self->{ $args{ type } }->{ $a{ $args{ id } } } = $args{ class }->new( $self, %a ) unless ( $args{ nocache } );
584 0           push @res, $obj
585             }
586              
587             return @res
588 0           }
589              
590             1;
591              
592             __END__