File Coverage

blib/lib/Net/ILO.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Net::ILO;
2              
3 7     7   49024 use strict;
  7         15  
  7         229  
4 7     7   36 use warnings;
  7         9  
  7         215  
5              
6 7     7   32 use Carp;
  7         14  
  7         557  
7 7     7   6626 use Data::Dumper;
  7         105575  
  7         548  
8 7     7   10092 use English qw(-no_match_vars);
  7         35206  
  7         48  
9 7     7   23367 use IO::Socket::SSL;
  7         813574  
  7         68  
10 7     7   1002723 use XML::Simple;
  0            
  0            
11              
12             our $VERSION = '0.54';
13              
14              
15             my $METHOD_UNSUPPORTED = 'Method not supported by this iLO version';
16              
17              
18             sub address {
19              
20             my $self = shift;
21              
22             if (@_) {
23             $self->{address} = shift;
24             }
25              
26             return $self->{address};
27              
28             }
29              
30              
31             sub add_user {
32              
33             my $self = shift;
34              
35             if (@_) {
36              
37             my $arg_ref = shift;
38              
39             my $user_name = $arg_ref->{name} or croak 'name required';
40             my $user_login = $arg_ref->{username} or croak 'username required';
41             my $user_password = $arg_ref->{password} or croak 'password required';
42              
43             my $user_admin = $arg_ref->{admin} || 'No';
44             my $user_can_remote = $arg_ref->{remote_console_privilege} || 'No';
45             my $user_can_reset = $arg_ref->{reset_privilege} || 'No';
46             my $user_can_virtual = $arg_ref->{virtual_media_privilege} || 'No';
47             my $user_can_config = $arg_ref->{config_ilo_privilege} || 'No';
48             my $user_can_view_logs = $arg_ref->{view_logs_privilege} || 'No';
49             my $user_can_clear_logs = $arg_ref->{clear_logs_privilege} || 'No';
50             my $user_can_update = $arg_ref->{update_ilo_privilege} || 'No';
51              
52             my $ilo_command = qq|
53            
54            
55            
56            
57            
58            
59            
60            
61            
62            
63            
64            
65             |;
66              
67             $ilo_command = $self->_wrap($ilo_command);
68             my $response = $self->_send($ilo_command) or return;
69             my $xml = $self->_serialize($response) or return;
70              
71             if ( my $errmsg = _check_errors($xml) ) {
72             $self->error($errmsg);
73             return;
74             }
75              
76             }
77             else {
78              
79             croak 'add_user() requires parameters';
80              
81             }
82              
83             return 1;
84              
85             }
86              
87              
88             sub biosdate {
89              
90             my $self = shift;
91              
92             if (!$self->{biosdate}) {
93             $self->_populate_host_data or return;
94             }
95              
96             return $self->{biosdate};
97              
98             }
99              
100              
101             sub cpus {
102              
103             my $self = shift;
104              
105             if (!$self->{cpus}) {
106             $self->_populate_host_data or return;
107             }
108              
109             return $self->{cpus};
110              
111             }
112              
113              
114             sub del_user {
115              
116             my $self = shift;
117              
118             if (@_) {
119              
120             my $user_login = shift;
121              
122             my $ilo_command = qq|
123            
124            
125            
126             |;
127              
128             $ilo_command = $self->_wrap($ilo_command);
129             my $response = $self->_send($ilo_command) or return;
130             my $xml = $self->_serialize($response) or return;
131              
132             if ( my $errmsg = _check_errors($xml) ) {
133             $self->error($errmsg);
134             return;
135             }
136              
137             }
138             else {
139              
140             croak 'del_user() requires the username to delete';
141              
142             }
143              
144             return 1;
145              
146             }
147              
148              
149             sub dhcp_enabled {
150              
151             my $self = shift;
152              
153             if (!$self->{dhcp_enable}) {
154             $self->_populate_network_settings or return;
155             }
156              
157             return $self->{dhcp_enable};
158              
159             }
160              
161              
162             sub domain_name {
163              
164             my $self = shift;
165              
166             if (!$self->{domain_name}) {
167             $self->_populate_network_settings or return;
168             }
169              
170             return $self->{domain_name};
171              
172             }
173              
174              
175             sub error {
176              
177             my $self = shift;
178              
179             if (@_) {
180             $self->{error} = shift;
181             }
182              
183             return $self->{error};
184              
185             }
186              
187              
188             sub fans {
189              
190             my $self = shift;
191              
192             if (!$self->{fans}) {
193             $self->_populate_embedded_health or return;
194             }
195              
196             return $self->{fans};
197              
198             }
199              
200              
201             sub fw_date {
202              
203             my $self = shift;
204              
205             if (!$self->{fw_date}) {
206             $self->_populate_fw_version or return;
207             }
208              
209             return $self->{fw_date};
210              
211             }
212              
213              
214             sub fw_type {
215              
216             my $self = shift;
217              
218             if (!$self->{fw_type}) {
219             $self->_populate_fw_version or return;
220             }
221              
222             return $self->{fw_type};
223              
224             }
225              
226              
227             sub fw_version {
228              
229             my $self = shift;
230              
231             if (!$self->{fw_version}) {
232             $self->_populate_fw_version or return;
233             }
234              
235             return $self->{fw_version};
236              
237             }
238              
239              
240             sub gateway {
241              
242             my $self = shift;
243              
244             if (!$self->{gateway_ip_address}) {
245             $self->_populate_network_settings or return;
246             }
247              
248             return $self->{gateway_ip_address};
249              
250             }
251              
252              
253             sub hostname {
254              
255             my $self = shift;
256              
257             if (!$self->{dns_name}) {
258             $self->_populate_network_settings or return;
259             }
260              
261             return $self->{dns_name};
262              
263             }
264              
265              
266             sub http_port {
267              
268             my $self = shift;
269              
270             if (@_) {
271              
272             my $http_port = shift;
273              
274             _port_is_valid($http_port) or croak "HTTP port must be an integer between 0 and 65535";
275              
276             my $ilo_command = qq|
277            
278            
279            
280            
281            
282             |;
283              
284             $ilo_command = $self->_wrap($ilo_command);
285             my $response = $self->_send($ilo_command) or return;
286             my $xml = $self->_serialize($response) or return;
287              
288             if ( my $errmsg = _check_errors($xml) ) {
289             $self->error($errmsg);
290             return;
291             }
292              
293             $self->{http_port} = $http_port;
294              
295             }
296              
297             if (!$self->{http_port}) {
298             $self->_populate_global_settings or return;
299             }
300              
301             return $self->{http_port};
302              
303             }
304              
305              
306             sub https_port {
307              
308             my $self = shift;
309              
310             if (@_) {
311              
312             my $https_port = shift;
313              
314             _port_is_valid($https_port) or croak "HTTPS port must be an integer between 0 and 65535";
315              
316             my $ilo_command = qq|
317            
318            
319            
320            
321            
322             |;
323              
324             $ilo_command = $self->_wrap($ilo_command);
325             my $response = $self->_send($ilo_command) or return;
326             my $xml = $self->_serialize($response) or return;
327              
328             if ( my $errmsg = _check_errors($xml) ) {
329             $self->error($errmsg);
330             return;
331             }
332              
333             $self->{https_port} = $https_port;
334              
335             }
336              
337             if (!$self->{https_port}) {
338             $self->_populate_global_settings or return;
339             }
340              
341             return $self->{https_port};
342              
343             }
344              
345              
346             sub ip_address {
347              
348             my $self = shift;
349              
350             if (!$self->{ip_address}) {
351             $self->_populate_network_settings or return;
352             }
353              
354             return $self->{ip_address};
355              
356             }
357              
358              
359             sub license {
360              
361             my $self = shift;
362              
363             if (@_) {
364              
365             my $license_key = shift;
366              
367             my $ilo_command = qq|
368            
369            
370            
371            
372            
373             |;
374              
375             $ilo_command = $self->_wrap($ilo_command);
376             my $response = $self->_send($ilo_command) or return;
377             my $xml = $self->_serialize($response) or return;
378              
379             if ( my $errmsg = _check_errors($xml) ) {
380             $self->error($errmsg);
381             return;
382             }
383              
384             }
385             else {
386              
387             croak 'license() requires the license key as a paramater';
388              
389             }
390              
391             return 1;
392              
393             }
394              
395              
396             sub mac01 {
397              
398             my $self = shift;
399              
400             if (!$self->{mac01}) {
401             $self->_populate_host_data or return;
402             }
403              
404             if ($self->{mac01}) {
405             return $self->{mac01};
406             }
407             else {
408             $self->error($METHOD_UNSUPPORTED);
409             return;
410             }
411              
412             }
413              
414              
415             sub mac02 {
416              
417             my $self = shift;
418              
419             if (!$self->{mac02}) {
420             $self->_populate_host_data or return;
421             }
422              
423             if ($self->{mac02}) {
424             return $self->{mac02};
425             }
426             else {
427             $self->error($METHOD_UNSUPPORTED);
428             return;
429             }
430              
431             }
432              
433              
434             sub mac03 {
435              
436             my $self = shift;
437              
438             # if mac01 is defined but mac03 isn't we aren't going to get it
439             # this time around either
440              
441             if (!$self->{mac03} && !$self->{mac01}) {
442             $self->_populate_host_data or return;
443             }
444              
445             if ($self->{mac03}) {
446             return $self->{mac03};
447             }
448             else {
449             $self->error($METHOD_UNSUPPORTED);
450             return;
451             }
452              
453             }
454              
455              
456             sub mac04 {
457              
458             my $self = shift;
459              
460             # see above
461              
462             if (!$self->{mac04} && !$self->{mac01}) {
463             $self->_populate_host_data or return;
464             }
465              
466             if ($self->{mac04}) {
467             return $self->{mac04};
468             }
469             else {
470             $self->error($METHOD_UNSUPPORTED);
471             return;
472             }
473              
474             }
475              
476              
477             sub macilo {
478              
479             my $self = shift;
480              
481             if (!$self->{macilo}) {
482             $self->_populate_host_data or return;
483             }
484              
485             if ($self->{macilo}) {
486             return $self->{macilo};
487             }
488             else {
489             $self->error($METHOD_UNSUPPORTED);
490             return;
491             }
492              
493             }
494              
495              
496             sub model {
497              
498             my $self = shift;
499              
500             if (!$self->{model}) {
501             $self->_populate_host_data or return;
502             }
503              
504             return $self->{model};
505              
506             }
507              
508              
509             sub mod_user {
510              
511             my $self = shift;
512              
513             if (@_) {
514              
515             my $arg_ref = shift;
516              
517             my $mod_username = $arg_ref->{username} || $self->username;
518             my $mod_password = $arg_ref->{password} || $self->password;
519              
520             if (!$mod_username && !$mod_password) {
521              
522             croak "mod_user requires username to modify and new password";
523              
524             }
525              
526             my $ilo_command = qq|
527            
528            
529            
530            
531            
532             |;
533              
534             $ilo_command = $self->_wrap($ilo_command);
535             my $response = $self->_send($ilo_command) or return;
536             my $xml = $self->_serialize($response) or return;
537              
538             if ( my $errmsg = _check_errors($xml) ) {
539             $self->error($errmsg);
540             return;
541             }
542              
543             if ($self->username eq $mod_username) {
544              
545             $self->password($mod_password);
546              
547             }
548              
549             }
550             else {
551              
552             croak "mod_user() requires parameters";
553              
554             }
555              
556              
557             return 1;
558              
559             }
560              
561              
562             sub network {
563              
564             my $self = shift;
565              
566             if (@_) {
567              
568             my $arg_ref = shift;
569              
570             my $domain_name = $arg_ref->{domain_name} || $self->domain_name or croak "domain_name not set";
571             my $dns_name = $arg_ref->{hostname} || $self->hostname or croak "name not set";
572             my $dhcp_enable = $arg_ref->{dhcp_enabled} || $self->dhcp_enabled or croak "dhcp_enabled not set";
573             my $ip_address = $arg_ref->{ip_address} || $self->ip_address or croak "ip_address not set";
574             my $subnet_mask = $arg_ref->{subnet_mask} || $self->subnet_mask or croak "subnet_mask not set";
575             my $gateway = $arg_ref->{gateway} || $self->gateway or croak "gateway not set";
576              
577             my $ilo_command = qq|
578            
579            
580            
581            
582            
583            
584            
585            
586            
587            
588             |;
589              
590             $ilo_command = $self->_wrap($ilo_command);
591             my $response = $self->_send($ilo_command) or return;
592             my $xml = $self->_serialize($response) or return;
593              
594             if ( my $errmsg = _check_errors($xml) ) {
595             $self->error($errmsg);
596             return;
597             }
598              
599             # force module to refresh new settings from the remote server
600             foreach my $option_changed (keys %$arg_ref) {
601              
602             delete $self->{$option_changed};
603              
604             }
605              
606             # if IP was changed it should be updated, if not this won't hurt
607             $self->address($ip_address);
608              
609             }
610              
611             return 1;
612              
613             }
614              
615              
616             sub new {
617              
618             my ($class) = shift;
619              
620             # RT #65352: allow hash or hashref constructor args
621             my %options = ref $_[0] ? %{$_[0]} : @_;
622              
623             my $self = {};
624              
625             bless($self, $class);
626              
627             $self->address( $options{address} );
628             $self->username( $options{username} );
629             $self->password( $options{password} );
630              
631             if ($options{port}) {
632             $self->port($options{port});
633             }
634             else {
635             $self->port(443);
636             }
637              
638             # iLO version will be autodetected later if not specified
639             $self->{_version} = $options{version} || undef;
640             $self->{_debug} = $options{debug} || '0';
641              
642             return $self;
643              
644             }
645              
646              
647             sub password {
648              
649             my $self = shift;
650              
651             if ( @_ ) {
652             $self->{password} = shift;
653             }
654              
655             return $self->{password};
656              
657             }
658              
659              
660             sub port {
661              
662             my $self = shift;
663              
664             if (@_) {
665             my $port = shift;
666              
667             _port_is_valid($port) or croak "Port must be an integer between 0 and 65535";
668              
669             $self->{port} = $port;
670             }
671              
672             return $self->{port};
673              
674             }
675              
676              
677             sub power {
678              
679             my $self = shift;
680              
681             if ( @_ ) {
682              
683             my $state_requested = shift;
684              
685             my $ilo_command;
686              
687             if (lc($state_requested) eq 'on') {
688              
689             $ilo_command = $self->_generate_cmd('power_on');
690              
691             }
692             elsif (lc($state_requested) eq 'off') {
693              
694             $ilo_command = $self->_generate_cmd('power_off');
695              
696             }
697             elsif (lc($state_requested) eq 'reset') {
698              
699             $ilo_command = $self->_generate_cmd('power_reset');
700              
701             }
702             else {
703              
704             croak "State '$state_requested' is not valid";
705              
706             }
707              
708             my $response = $self->_send($ilo_command) or return;
709             my $xml = $self->_serialize($response) or return;
710              
711             if ( my $errmsg = _check_errors($xml) ) {
712             $self->error($errmsg);
713             return;
714             }
715              
716             return $state_requested;
717              
718             }
719              
720             my $ilo_command = $self->_generate_cmd('power_status');
721              
722             my $response = $self->_send($ilo_command) or return;
723             my $xml = $self->_serialize($response) or return;
724              
725             if ( my $errmsg = _check_errors($xml) ) {
726             $self->error($errmsg);
727             return;
728             }
729              
730             my $state = $xml->{GET_HOST_POWER}->{HOST_POWER};
731              
732             if (!$state) {
733             $self->error('Invalid response from remote ilo');
734             return;
735             }
736              
737             return lc($state);
738              
739             }
740              
741              
742             sub power_consumption {
743              
744             my $self = shift;
745              
746             my $ilo_command = $self->_generate_cmd('power_consumption');
747              
748             my $response = $self->_send($ilo_command) or return;
749             my $xml = $self->_serialize($response) or return;
750              
751             if ( my $errmsg = _check_errors($xml) ) {
752             $self->error($errmsg);
753             return unless $errmsg =~ /^Syntax error/;
754             }
755              
756             if ($self->{power_consumption} = $xml->{GET_POWER_READINGS}->{PRESENT_POWER_READING}->{VALUE}) {
757              
758             return $self->{power_consumption};
759              
760             }
761             else {
762              
763             $self->error($METHOD_UNSUPPORTED);
764             return;
765              
766             }
767              
768             }
769              
770              
771             sub power_supplies {
772              
773             my $self = shift;
774              
775             if (!$self->{power_supplies}) {
776             $self->_populate_embedded_health or return;
777             }
778              
779             return $self->{power_supplies};
780              
781             }
782              
783              
784             sub ramslots {
785              
786             my $self = shift;
787              
788             if (!$self->{ramslots}) {
789             $self->_populate_host_data or return;
790             }
791              
792             return $self->{ramslots};
793              
794             }
795              
796              
797             sub reset {
798              
799             my $self = shift;
800              
801             my $ilo_command = $self->_generate_cmd('reset');
802              
803             my $response = $self->_send($ilo_command) or return;
804             my $xml = $self->_serialize($response) or return;
805              
806             if ( my $errmsg = _check_errors($xml) ) {
807             $self->error($errmsg);
808             return;
809             }
810              
811             return 1;
812              
813             }
814              
815              
816             sub serialID {
817              
818             my $self = shift;
819              
820             if (!$self->{serialID}) {
821             $self->_populate_host_data or return;
822             }
823              
824             return $self->{serialID};
825              
826             }
827              
828              
829             sub session_timeout {
830              
831             my $self = shift;
832              
833             if (!$self->{session_timeout}) {
834             $self->_populate_global_settings or return;
835             }
836              
837             return $self->{session_timeout};
838              
839             }
840              
841              
842             sub ssh_port {
843              
844             my $self = shift;
845              
846             if (@_) {
847              
848             my $ssh_port = shift;
849              
850             _port_is_valid($ssh_port) or croak "ssh_port must be an integer between 0 and 65535";
851              
852             my $ilo_command = qq|
853            
854            
855            
856            
857            
858             |;
859              
860             $ilo_command = $self->_wrap($ilo_command);
861             my $response = $self->_send($ilo_command) or return;
862             my $xml = $self->_serialize($response) or return;
863              
864             if ( my $errmsg = _check_errors($xml) ) {
865             $self->error($errmsg);
866             return;
867             }
868              
869             $self->{ssh_port} = $ssh_port;
870              
871             }
872              
873             if (!$self->{ssh_port}) {
874             $self->_populate_global_settings or return;
875             }
876              
877             return $self->{ssh_port};
878              
879             }
880              
881              
882             sub ssh_status {
883              
884             my $self = shift;
885              
886             if (@_) {
887              
888             my $ssh_status = shift;
889              
890             my $ilo_command = qq|
891            
892            
893            
894            
895            
896             |;
897              
898             $ilo_command = $self->_wrap($ilo_command);
899             my $response = $self->_send($ilo_command) or return;
900             my $xml = $self->_serialize($response) or return;
901              
902             if ( my $errmsg = _check_errors($xml) ) {
903             $self->error($errmsg);
904             return;
905             }
906              
907             $self->{ssh_status} = $ssh_status;
908              
909             }
910              
911             if (!$self->{ssh_status}) {
912             $self->_populate_global_settings or return;
913             }
914              
915             return $self->{ssh_status};
916              
917             }
918              
919              
920             sub subnet_mask {
921              
922             my $self = shift;
923              
924             if (!$self->{subnet_mask}) {
925             $self->_populate_network_settings or return;
926             }
927              
928             return $self->{subnet_mask};
929              
930             }
931              
932              
933             sub temperatures {
934              
935             my $self = shift;
936              
937             if (!$self->{temperatures}) {
938             $self->_populate_embedded_health or return;
939             }
940              
941             return $self->{temperatures};
942              
943             }
944              
945              
946             sub uid {
947              
948             my $self = shift;
949              
950             if (@_) {
951              
952             my $state_requested = shift;
953              
954             my $ilo_command;
955              
956             if ($state_requested eq 'on') {
957              
958             $ilo_command = $self->_generate_cmd('uid_on');
959              
960             }
961             elsif ($state_requested eq 'off') {
962              
963             $ilo_command = $self->_generate_cmd('uid_off');
964              
965             }
966             else {
967              
968             $self->error("State '$state_requested' is not valid");
969             return;
970              
971             }
972              
973             my $response = $self->_send($ilo_command) or return;
974             my $xml = $self->_serialize($response) or return;
975              
976             if ( my $errmsg = _check_errors($xml) ) {
977             $self->error($errmsg);
978             return;
979             }
980              
981             return $state_requested;
982              
983             }
984              
985             my $ilo_command = $self->_generate_cmd('uid_status');
986              
987             my $response = $self->_send($ilo_command) or return;
988             my $xml = $self->_serialize($response) or return;
989              
990             if ( my $errmsg = _check_errors($xml) ) {
991             $self->error($errmsg);
992             return;
993             }
994              
995             my $uid_status = $xml->{GET_UID_STATUS}->{UID};
996              
997             return lc($uid_status);
998              
999             }
1000              
1001              
1002             sub username {
1003              
1004             my $self = shift;
1005              
1006             if (@_) {
1007             $self->{username} = shift;
1008             }
1009              
1010             return $self->{username};
1011              
1012             }
1013              
1014              
1015             sub _check_errors {
1016              
1017             my $xml = shift;
1018              
1019             my $errcode = $xml->{RESPONSE}->{STATUS};
1020             my $errmsg = $xml->{RESPONSE}->{MESSAGE};
1021              
1022             if ($errcode ne '0x0000') {
1023             return $errmsg;
1024             }
1025             else {
1026             return;
1027             }
1028              
1029             }
1030              
1031              
1032             sub _connect {
1033              
1034             my $self = shift;
1035              
1036             if ($self->{_client}) {
1037             return $self->{_client};
1038             }
1039              
1040             my $address = $self->address or croak "Can't connect: address not set";
1041             my $port = $self->port or croak "Can't connect: port not set";
1042              
1043             $self->{_client} = IO::Socket::SSL->new(
1044             PeerAddr => "$address:$port",
1045             );
1046              
1047             if (!$self->{_client}) {
1048             $self->error( "Unable to establish SSL connection with $address:$port [" . IO::Socket::SSL::errstr() . "]" );
1049             return;
1050             }
1051              
1052             return $self->{_client};
1053              
1054             }
1055              
1056              
1057             sub _debug {
1058              
1059             my $self = shift;
1060              
1061             if (@_) {
1062             $self->{_debug} = shift;
1063             }
1064              
1065             return $self->{_debug};
1066              
1067             }
1068              
1069              
1070             sub _detect_version {
1071              
1072             my $self = shift;
1073              
1074             # iLO 3 has a slightly different interface; it requires that
1075             # you preface commands with an HTTP header
1076              
1077             my $ilo_command = qq(
1078             POST /ribcl HTTP/1.1
1079             HOST: localhost
1080             Content-length: 30
1081             Connection: Close
1082              
1083            
1084             );
1085              
1086             my $response = $self->_send($ilo_command) or return;
1087              
1088             if ($response =~ /^HTTP\/1.1 200 OK/) {
1089             return 3;
1090             }
1091             else {
1092             return 2;
1093             }
1094              
1095             }
1096              
1097              
1098             sub _disconnect {
1099              
1100             my $self = shift;
1101              
1102             my $client = $self->{_client} or return;
1103              
1104             $client->close;
1105              
1106             delete $self->{_client};
1107              
1108             return 1;
1109              
1110             }
1111              
1112              
1113             sub _generate_cmd {
1114              
1115             my ($self, $command) = @_;
1116              
1117             my %commands = (
1118              
1119             'get_embedded_health' => qq(
1120            
1121             ),
1122              
1123             'get_fw_version' => qq(
1124            
1125             ),
1126              
1127             'get_global_settings' => qq(
1128            
1129             ),
1130              
1131             'get_host_data' => qq(
1132            
1133             ),
1134              
1135             'get_network_settings' => qq(
1136            
1137             ),
1138              
1139             'power_consumption' => qq(
1140            
1141             ),
1142              
1143             'power_off' => qq(
1144            
1145             ),
1146              
1147             'power_on' => qq(
1148            
1149             ),
1150              
1151             'power_reset' => qq(
1152            
1153             ),
1154              
1155             'power_status' => qq(
1156            
1157             ),
1158              
1159             'reset' => qq(
1160            
1161             ),
1162              
1163             'uid_off' => qq(
1164            
1165             ),
1166              
1167             'uid_on' => qq(
1168            
1169             ),
1170              
1171             'uid_status' => qq(
1172            
1173             ),
1174              
1175             );
1176              
1177             my $ilo_command = $commands{$command} or die "Internal error: command '$command' doesn't exist";
1178              
1179             $ilo_command = $self->_wrap($ilo_command);
1180              
1181             return $ilo_command;
1182              
1183             }
1184              
1185              
1186             sub _length {
1187              
1188             # for iLO 3 we need to know the length of the XML for the
1189             # Content-length field in the http header
1190              
1191             my ($self, $ilo_command) = @_;
1192              
1193             my $length = 0;
1194              
1195             foreach my $line (split(/\n/, $ilo_command)) {
1196              
1197             $line =~ s/^\s+//;
1198             $line =~ s/\s+$//;
1199              
1200             # each line has \r\n appended when sending, so + 2
1201             $length += length($line) + 2;
1202              
1203             }
1204              
1205             return $length;
1206              
1207             }
1208              
1209              
1210             sub _populate_embedded_health {
1211              
1212             my $self = shift;
1213              
1214             my $ilo_command = $self->_generate_cmd('get_embedded_health');
1215              
1216             my $response = $self->_send($ilo_command) or return;
1217             my $xml = $self->_serialize($response) or return;
1218              
1219             if ( my $errmsg = _check_errors($xml) ) {
1220             $self->error($errmsg);
1221             return;
1222             }
1223              
1224             my $fans = $xml->{GET_EMBEDDED_HEALTH_DATA}->{FANS}->{FAN};
1225             my $power_supplies = $xml->{GET_EMBEDDED_HEALTH_DATA}->{POWER_SUPPLIES}->{SUPPLY};
1226             my $temperatures = $xml->{GET_EMBEDDED_HEALTH_DATA}->{TEMPERATURE}->{TEMP};
1227              
1228             foreach my $fan (@$fans) {
1229              
1230             my $location = $fan->{ZONE}->{VALUE};
1231             my $name = $fan->{LABEL}->{VALUE};
1232             my $speed = $fan->{SPEED}->{VALUE};
1233             my $status = $fan->{STATUS}->{VALUE};
1234             my $unit = $fan->{SPEED}->{UNIT};
1235              
1236             next unless $speed && $speed =~ /^\d+$/;
1237              
1238             push( @{$self->{fans}}, {
1239             'location' => $location,
1240             'name' => $name,
1241             'speed' => $speed,
1242             'status' => $status,
1243             'unit' => $unit,
1244             });
1245              
1246             }
1247              
1248             foreach my $power_supply (@$power_supplies) {
1249              
1250             my $name = $power_supply->{LABEL}->{VALUE};
1251             my $status = $power_supply->{STATUS}->{VALUE};
1252              
1253             next if $status eq 'Not Installed';
1254              
1255             push( @{$self->{power_supplies}}, {
1256             'name' => $name,
1257             'status' => $status,
1258             });
1259              
1260             }
1261              
1262             foreach my $temperature (@$temperatures) {
1263              
1264             my $name = $temperature->{LABEL}->{VALUE};
1265             my $location = $temperature->{LOCATION}->{VALUE};
1266             my $value = $temperature->{CURRENTREADING}->{VALUE};
1267             my $unit = $temperature->{CURRENTREADING}->{UNIT};
1268             my $caution = $temperature->{CAUTION}->{VALUE};
1269             my $critical = $temperature->{CRITICAL}->{VALUE};
1270             my $status = $temperature->{STATUS}->{VALUE};
1271              
1272             next unless $value && $value =~ /^\d+$/;
1273              
1274             push( @{$self->{temperatures}}, {
1275             'name' => $name,
1276             'location' => $location,
1277             'value' => $value,
1278             'unit' => $unit,
1279             'caution' => $caution,
1280             'critical' => $critical,
1281             'status' => $status,
1282             });
1283              
1284             }
1285              
1286             return 1;
1287              
1288             }
1289              
1290              
1291             sub _populate_fw_version {
1292              
1293             my $self = shift;
1294              
1295             my $ilo_command = $self->_generate_cmd('get_fw_version');
1296              
1297             my $response = $self->_send($ilo_command) or return;
1298             my $xml = $self->_serialize($response) or return;
1299              
1300             if ( my $errmsg = _check_errors($xml) ) {
1301             $self->error($errmsg);
1302             return;
1303             }
1304              
1305             $self->{fw_type} = $xml->{GET_FW_VERSION}->{MANAGEMENT_PROCESSOR};
1306             $self->{fw_date} = $xml->{GET_FW_VERSION}->{FIRMWARE_DATE};
1307             $self->{fw_version} = $xml->{GET_FW_VERSION}->{FIRMWARE_VERSION};
1308              
1309             return 1;
1310              
1311             }
1312              
1313              
1314             sub _populate_global_settings {
1315              
1316             my $self = shift;
1317              
1318             my $ilo_command = $self->_generate_cmd('get_global_settings');
1319              
1320             my $response = $self->_send($ilo_command) or return;
1321             my $xml = $self->_serialize($response) or return;
1322              
1323             if ( my $errmsg = _check_errors($xml) ) {
1324             $self->error($errmsg);
1325             return;
1326             }
1327              
1328             my @fields = qw( session_timeout https_port http_port
1329             ssh_port ssh_status );
1330              
1331             foreach my $field (@fields) {
1332              
1333             $self->{$field} = $xml->{GET_GLOBAL_SETTINGS}->{uc($field)}->{VALUE};
1334              
1335             }
1336              
1337             return 1;
1338              
1339             }
1340              
1341              
1342             sub _populate_host_data {
1343              
1344             my $self = shift;
1345              
1346             my $ilo_command = $self->_generate_cmd('get_host_data');
1347              
1348             my $response = $self->_send($ilo_command) or return;
1349             my $xml = $self->_serialize($response) or return;
1350              
1351             if ( my $errmsg = _check_errors($xml) ) {
1352             $self->error($errmsg);
1353             return;
1354             }
1355              
1356             # SMBIOS data is stored in a big fat array
1357             #
1358             # data is not guaranteed to be in any particular index, so we have to
1359             # iterate through all the data looking for certain fields.
1360             #
1361             # thankfully, SMBIOS *types* are standard (eg. CPU data is type 4)
1362             # so we have a starting point
1363             #
1364             # this really sucks but I don't know of a better way
1365              
1366             for my $fieldnum (0 .. scalar @{$xml->{GET_HOST_DATA}->{SMBIOS_RECORD}}) {
1367              
1368             my $smbios_data = $xml->{GET_HOST_DATA}->{SMBIOS_RECORD}[$fieldnum]->{FIELD};
1369             my $smbios_type = $xml->{GET_HOST_DATA}->{SMBIOS_RECORD}[$fieldnum]->{TYPE};
1370              
1371             next unless defined $smbios_type;
1372              
1373             if ($smbios_type == 0) {
1374              
1375             for my $entry (0 .. scalar @$smbios_data) {
1376              
1377             my $field_name = $smbios_data->[$entry]->{NAME};
1378             my $field_value = $smbios_data->[$entry]->{VALUE};
1379              
1380             next unless $field_name && $field_value;
1381              
1382             if ($field_name eq 'Date') {
1383             $self->{biosdate} = $field_value;
1384             }
1385              
1386             }
1387              
1388             }
1389             elsif ($smbios_type == 1) {
1390              
1391             for my $entry (0 .. scalar @$smbios_data) {
1392              
1393             my $field_name = $smbios_data->[$entry]->{NAME};
1394             my $field_value = $smbios_data->[$entry]->{VALUE};
1395              
1396             next unless $field_name && $field_value;
1397              
1398             if ($field_name eq 'Product Name') {
1399             $self->{model} = $field_value;
1400             }
1401             elsif ($field_name eq 'Serial Number') {
1402             $self->{serialID} = $field_value;
1403             }
1404             elsif ($field_name eq 'UUID') {
1405             $self->{UUID} = $field_value;
1406             }
1407              
1408             }
1409              
1410             }
1411             elsif ($smbios_type == 4) {
1412              
1413             my ($name, $speed, $cores);
1414              
1415             for my $entry (0 .. scalar @$smbios_data) {
1416              
1417             my $field_name = $smbios_data->[$entry]->{NAME};
1418             my $field_value = $smbios_data->[$entry]->{VALUE};
1419              
1420             next unless $field_name && $field_value;
1421              
1422             if ($field_name eq 'Label') {
1423             $name = $field_value;
1424             }
1425             elsif ($field_name eq 'Speed') {
1426             $speed = $field_value;
1427             }
1428             elsif ($field_name eq 'Execution Technology') {
1429             $cores = $field_value || 'single core';
1430             }
1431              
1432             }
1433              
1434             # otherwise slot is empty
1435             next unless $speed && $speed =~ /^[1-9]/;
1436              
1437             push( @{$self->{cpus}}, {
1438             'name' => $name,
1439             'speed' => $speed,
1440             'cores' => $cores }
1441             );
1442              
1443             }
1444             elsif ($smbios_type == 17) {
1445              
1446             my ($location, $size, $speed);
1447              
1448             for my $entry (0 .. scalar @$smbios_data) {
1449              
1450             my $field_name = $smbios_data->[$entry]->{NAME};
1451             my $field_value = $smbios_data->[$entry]->{VALUE};
1452              
1453             next unless $field_name && $field_value;
1454              
1455             if ($field_name eq 'Label') {
1456             $location = $field_value;
1457             }
1458             elsif ($field_name eq 'Size') {
1459             $size = $field_value;
1460             }
1461             elsif ($field_name eq 'Speed') {
1462             $speed = $field_value;
1463             }
1464              
1465             }
1466              
1467             push( @{$self->{ramslots}}, {
1468             'location' => $location,
1469             'size' => $size,
1470             'speed' => $speed }
1471             );
1472              
1473             }
1474             elsif ($smbios_type == 209) {
1475              
1476             for my $entry (0 .. scalar @$smbios_data) {
1477              
1478             my $field_name = $smbios_data->[$entry]->{NAME};
1479             my $field_value = $smbios_data->[$entry]->{VALUE};
1480              
1481             next unless $field_name && $field_value;
1482             next unless $field_name eq 'Port';
1483              
1484             # MAC address is offset by one from port label
1485              
1486             my $current_mac = $smbios_data->[$entry + 1]->{VALUE};
1487              
1488             if ($field_value eq '1') {
1489             $self->{mac01} = $current_mac;
1490             }
1491             elsif ($field_value eq '2') {
1492             $self->{mac02} = $current_mac;
1493             }
1494             elsif ($field_value eq '3') {
1495             $self->{mac03} = $current_mac;
1496             }
1497             elsif ($field_value eq '4') {
1498             $self->{mac04} = $current_mac;
1499             }
1500             elsif ($field_value eq 'iLO') {
1501             $self->{macilo} = $current_mac;
1502             }
1503              
1504             }
1505              
1506             }
1507              
1508             }
1509              
1510             ($self->{mac01} = lc($self->{mac01})) =~ tr/-/:/;
1511             ($self->{mac02} = lc($self->{mac02})) =~ tr/-/:/;
1512             ($self->{mac03} = lc($self->{mac03})) =~ tr/-/:/;
1513             ($self->{mac04} = lc($self->{mac04})) =~ tr/-/:/;
1514             ($self->{macilo} = lc($self->{macilo})) =~ tr/-/:/;
1515              
1516             return 1;
1517              
1518             }
1519              
1520              
1521             sub _populate_network_settings {
1522              
1523             my $self = shift;
1524              
1525             my $ilo_command = $self->_generate_cmd('get_network_settings');
1526              
1527             my $response = $self->_send($ilo_command) or return;
1528             my $xml = $self->_serialize($response) or return;
1529              
1530             if ( my $errmsg = _check_errors($xml) ) {
1531             $self->error($errmsg);
1532             return;
1533             }
1534              
1535             my @fields = qw( dhcp_dns_server dhcp_gateway dns_name
1536             dhcp_domain_name ip_address domain_name
1537             dhcp_enable subnet_mask gateway_ip_address );
1538              
1539             foreach my $field (@fields) {
1540              
1541             $self->{$field} = $xml->{GET_NETWORK_SETTINGS}->{uc($field)}->{VALUE};
1542              
1543             }
1544              
1545             return 1;
1546              
1547             }
1548              
1549              
1550             sub _send {
1551              
1552             my ($self, $ilo_command) = @_;
1553              
1554             my $client = $self->_connect or return;
1555              
1556             foreach my $line ( split(/\n/, $ilo_command) ) {
1557              
1558             $line =~ s/^\s+//;
1559             $line =~ s/\s+$//;
1560              
1561             if ($self->_debug > 0) {
1562             print "'$line'\n";
1563             }
1564              
1565             my $ok = print {$client} $line . "\r\n";
1566              
1567             if (!$ok) {
1568             $self->error("Error transmitting command to server");
1569             return;
1570             }
1571              
1572             }
1573              
1574             chomp( my $response = join('', <$client>) );
1575              
1576             # iLO 3 returns a chunked http response
1577             # rather than parse it, just filter out the chunking data
1578             # janky, but a lightweight solution which works for all iLO versions
1579              
1580             $response =~ s/[\r\n]+[0-9a-f]{3}[\r\n]+//gs;
1581              
1582             $self->_disconnect or die "Internal error: disconnect failed, wtf!";
1583              
1584             if (!$response) {
1585             $self->error("No response received from remote machine");
1586             return;
1587             }
1588              
1589             if ($self->_debug > 0) {
1590             print Dumper $response;
1591             }
1592              
1593             return $response;
1594              
1595             }
1596              
1597              
1598             sub _serialize {
1599              
1600             my ($self, $data) = @_;
1601              
1602             if (!$data) {
1603             $self->error('Error parsing response: no data received');
1604             return;
1605             }
1606              
1607             # iLO returns multiple XML stanzas, all starting with a standard header.
1608             # We first need to break this glob of data into individual XML components,
1609             # while ignoring the HTTP header returned by iLO 3.
1610              
1611             chomp( my @stanzas = grep { !/HTTP\/1.1/ } split(/<\?xml.*?\?>/, $data) );
1612              
1613             # @stanzas now contains a number of valid XML sequences.
1614             # All but one is unnecessary; they contain short status messages and
1615             # nothing else. So, we want to parse only the longest message.
1616             #
1617             # NB: The same status codes are also included in the longest stanza.
1618              
1619             my $longest = ( sort {length($b) <=> length($a)} @stanzas )[0];
1620              
1621             if ($self->_debug > 3) {
1622             print Dumper $longest;
1623             }
1624              
1625             # XML::Simple croaks if it can't parse the data properly.
1626             # We want to capture any errors and propagate them on our own terms.
1627              
1628             my $xml;
1629              
1630             eval { $xml = XMLin( $longest, NormaliseSpace => 2 ) };
1631              
1632             if ($EVAL_ERROR) {
1633             $self->error("Error parsing response: $EVAL_ERROR");
1634             return;
1635             }
1636              
1637             if ($self->_debug >= 2) {
1638             print Dumper $xml;
1639             }
1640              
1641             return $xml;
1642              
1643             }
1644              
1645              
1646             sub _port_is_valid {
1647              
1648             my $port = shift;
1649              
1650             return unless defined $port && $port =~ /^\d{1,5}$/ && $port <= 65535;
1651              
1652             return 1;
1653              
1654             }
1655              
1656              
1657             sub _version {
1658              
1659             my $self = shift;
1660              
1661             if (@_) {
1662             $self->{_version} = shift;
1663             }
1664              
1665             return $self->{_version};
1666              
1667             }
1668              
1669              
1670             sub _wrap {
1671              
1672             my $self = shift;
1673              
1674             my $body = shift or die "Internal error: no data passed to _wrap()";
1675              
1676             my $username = $self->username or croak "Username not set";
1677             my $password = $self->password or croak "Password not set";
1678              
1679             if (!$self->_version) {
1680              
1681             my $ilo_version = $self->_detect_version or return;
1682              
1683             print "Detected iLO version $ilo_version\n" if $self->_debug > 2;
1684              
1685             $self->_version($ilo_version);
1686              
1687             }
1688              
1689             my $header = qq|
1690            
1691            
1692            
1693            
1694             |;
1695              
1696             my $footer = qq|
1697            
1698            
1699             |;
1700              
1701             my $ilo_command = $header . $body . $footer;
1702              
1703             if ($self->_version == 3) {
1704              
1705             my $command_length = $self->_length($ilo_command);
1706              
1707             my $http_header = qq|
1708             POST /ribcl HTTP/1.1
1709             HOST: localhost
1710             Content-length: $command_length
1711             Connection: Close
1712              
1713             |;
1714              
1715             $ilo_command = $http_header . $ilo_command;
1716              
1717             }
1718              
1719             return $ilo_command;
1720              
1721             }
1722              
1723              
1724             sub DESTROY {
1725              
1726             my $self = shift;
1727              
1728             my $client = $self->{_client} or return;
1729             $client->close;
1730              
1731             return;
1732             }
1733              
1734             1;
1735             __END__