File Coverage

blib/lib/Storage/Nexsan/NMP.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 Storage::Nexsan::NMP;
2              
3 1     1   20431 use strict;
  1         3  
  1         37  
4 1     1   6 use warnings;
  1         1  
  1         47  
5 1     1   13 use v5.10; #make use of the say command and other nifty perl 10.0 onwards goodness
  1         8  
  1         41  
6 1     1   5 use Carp;
  1         1  
  1         77  
7 1     1   884 use IO::Socket::INET;
  1         23566  
  1         7  
8 1     1   1331 use IO::File;
  1         2017  
  1         173  
9 1     1   418 use File::Slurp;
  0            
  0            
10             use DateTime;
11             use Config::INI::Reader;
12              
13             use vars qw (@ISA @EXPORT);
14             use Exporter;
15             @ISA = qw(Exporter);
16             @EXPORT = qw(
17             ConnectToNexsan AuthenticateNexsan GetEventCount ShowSystemNexsan UploadFirmwareToNexsan
18             ShutdownNexsan RollingRebootNexsan TurnOffMAIDOnNexsan SetOpt);
19              
20              
21              
22             =head1 NAME
23              
24             Storage::Nexsan::NMP - The great new way to mange Nexsan's programattically!
25              
26             =head1 VERSION
27              
28             Version 0.05
29              
30             =cut
31              
32             our $VERSION = '0.05';
33              
34              
35             =head1 SYNOPSIS
36              
37             This module ecapsulates version 2.30 of the Nexsan Management Protocol (NMP), written to
38             automate some functions of the Nexsan storage device.
39              
40             As most commands are relatively atomic, each command will carp if it fails, unless specifically
41             otherwise noted.
42            
43             This module covers only the following functions:
44            
45             Status
46             System Information
47             Firmware upgrade
48             Shutdown
49             Rolling Reboot of controllers
50             Event Log count
51             Turning off MAID
52             Setting options via SETOPT/DAT files
53            
54             Further work will be done to implement the rest of the NMP as I require it,
55             unless anyone wants to pitch in and help...
56              
57              
58             Sample code snippet.
59              
60             use Storage::Nexsan::NMP;
61              
62             say "Connecting to Nexsan: $nexsan, Port: $port";
63             my %NexsanInfo = ConnectToNexsan ($nexsan, $port);
64              
65             my $sock = $NexsanInfo{sock};
66              
67             say "Nexsan Version: " . $NexsanInfo{NMP}{Major} . "." . $NexsanInfo{NMP}{Minor} . "." . $NexsanInfo{NMP}{Patch};
68             say "Nexsan Serial: " . $NexsanInfo{serial};
69              
70              
71             #fill in the user and password details, then authenticate
72             $NexsanInfo{username} = "ADMIN";
73             $NexsanInfo{password} = "password";
74              
75             AuthenticateNexsan (\%NexsanInfo);
76              
77             GetEventCount(\%NexsanInfo);
78             #this will now be populated
79             say "No Of Events: $NexsanInfo{eventCount}";
80              
81             ShowSystemNexsan(\%NexsanInfo);
82              
83             say "Status: $NexsanInfo{status}";
84             say "model: $NexsanInfo{model}";
85             say "firmware: $NexsanInfo{firmware}";
86             say "friendlyname $NexsanInfo{friendlyname}";
87             say "vendor: $NexsanInfo{vendor}";
88             say "productid $NexsanInfo{productid}";
89             say "enclosurenaaid: $NexsanInfo{enclosurenaaid}";
90              
91             unless (defined $firmware) { die "no firmware value passed\n"; }
92             $NexsanInfo{firmwarefile} = $firmware;
93              
94             UploadFirmwareToNexsan(\%NexsanInfo);
95              
96             close($sock);
97              
98             =head1 TODO
99              
100             * Rewrite the functions as OO - at the moment each function copies and pastes some standard functionality which is nasty and should be fixed, but out of TUITS..
101             * apply some error checking for correct INI structure in SetOpt()
102             * write a specific subroutine for the Powerlevel stanza (see notes in TurnOffMAIDInNexsan() )
103             * write tests that tries functions not passing a $nexsan veriable
104             * write tests that tries functions not passing a $port variable
105             * write a test suite
106             * write a Nexsan emulator that returns canned responses for the above test suite (definately in wish list territory here)
107              
108              
109             =head1 EXPORT
110              
111             A list of functions that can be exported. You can delete this section
112             if you don't export anything, such as for a purely object-oriented module.
113              
114             =head1 SUBROUTINES/METHODS
115              
116             =head2 ConnectToNexsan
117              
118             MUST be passed IP/fqdn of Nexsan & then MAY be passed port no
119              
120             Returns hash with following (example) information (thanks to Data::Dumper!);
121              
122             $VAR1 = 'banner';
123              
124             $VAR2 = '220 <237C2F1C> SATABeast2-059955A0-0 NMP V2.3.0 ready
125             ';
126              
127             $VAR3 = 'serial';
128              
129             $VAR4 = '237C2F1C';
130              
131             $VAR5 = 'NMP';
132              
133             $VAR6 = {
134             'Minor' => '3',
135             'Major' => '2',
136             'Patch' => '0'
137             };
138             $VAR7 = 'sock';
139              
140              
141             =cut
142              
143             =head2 WriteLog
144              
145             internal (i.e. not exported) function that takes the %NexsanInfo hash,
146             and prepends nexan name/ip and date/time to the ususal 'say' output.
147              
148             it assumes that ConnectToNexsan has been run, but nothing else.
149              
150             =cut
151              
152             sub WriteLog {
153              
154             #assign the variables, and set defaults if not passed
155             my $message;
156             my ($NexsanInfo) = shift;
157             #some lines (responses from NMP, usually) have newlines, some don't, so sanitise
158             chomp ($message = shift);
159            
160              
161             # check we've successfully connected, as this will only be populated if we did
162             if (!exists $NexsanInfo->{ip})
163             {
164             croak "ip not passed to Storage::Nexsan::NMP::Authenticate() - not connected?\n";
165             }
166              
167             #build the date information we are going to log
168             my ($dt) = DateTime->now;
169             my ($ymd) = $dt->ymd; # e.g. 2002-12-06
170             my ($hour) = $dt->hour; # 0-23
171             my ($minute) = $dt->minute; # 0-59 - also min
172             my ($second) = $dt->second; # 0-61 (leap seconds!) - also sec
173             my ($testtime) = $ymd . "T" . $hour . $minute . $second;
174              
175             #TODO really should use Log::Dispatch here, and overload all the carp messages.
176             # decision was taken to be sinple STDOUT messages to make logging via tee etc easy, and
177             # in the interestes of getting the script finished in time..
178             say "$NexsanInfo->{ip} :: $testtime :: $message";
179              
180             } #end of sub WriteLog
181              
182              
183             #TODO test that tries this not passing a $nexsan
184             #TODO test that tries this not passing a $port
185              
186             =head2 ConnectToNexsan
187              
188             Initial setup internal function used by all the utility functions to setup the telnet conenction
189              
190             =cut
191              
192             sub ConnectToNexsan {
193             #MUST be passed IP/fqdn of Nexsan & MAY be passed port no
194            
195             #check we've got the right number of parameters
196             if ( @_ < 1 || @_ > 2 )
197             {
198             croak "insufficient variables passed to Storage::Nexsan::NMP::ConnectToNexsan()\n";
199             }
200            
201             #assign the variables, and set defaults if not passed
202             my ($nexsanIP, $Port) = @_;
203             if (!defined $nexsanIP)
204             {
205             croak "nexsan variable not passed to Storage::Nexsan::NMP::ConnectToNexsan()\n";
206             }
207             my $nexsanPort;
208            
209             # if option given Use option Else default
210             $nexsanPort = defined $Port ? $Port : '44844';
211            
212             my $sock = IO::Socket::INET->new(PeerAddr => $nexsanIP,
213             PeerPort => $nexsanPort,
214             Timeout => 240, #add in extra timeout for the firmware upload
215             Proto => 'tcp')
216             or croak "Cannot connect to $nexsanIP on $nexsanPort because: $!";
217            
218             #get the banner prompt, which it should always provide
219             my $banner = <$sock>;
220             #say "banner:::: $banner";
221              
222             ##setup and populate the hash to return
223             my %NexsanInfo;
224            
225             #sample banner line is:
226             #220 <41C6167E> SATABeast2-059955A0-0 NMP V2.3.0 ready
227             #
228             $banner =~ m/NMP\sV(\d)\.(\d)\.(\d)\s/; #get the NMP version no
229            
230             $NexsanInfo{NMP}{Major} = $1;
231             $NexsanInfo{NMP}{Minor} = $2;
232             $NexsanInfo{NMP}{Patch} = $3;
233            
234             $NexsanInfo{banner} = $banner;
235             $NexsanInfo{sock} = $sock;
236             $NexsanInfo{ip} = $nexsanIP;
237            
238             $banner =~ m/<(\S+)>/; #get the NMP version no
239             $NexsanInfo{serial} = $1;
240            
241             return %NexsanInfo;
242             }
243              
244              
245             =head2 AuthenticateNexsan
246              
247             Authenticates to the NMP, without which any command but QUIT will fail.
248              
249             MUST be passed hash containing socket, username and password pairs, e.g.
250              
251             $NexsanInfo{sock}
252             $NexsanInfo{username}
253             $NexsanInfo{password}
254              
255             croak's on failure of username or password to authenticate,
256             as you can't do much without being logged in
257              
258             =cut
259              
260             sub AuthenticateNexsan
261             {
262              
263             #MUST be passed a reference to a hash containing sock, username and password pairs
264            
265             #assign the variables, and set defaults if not passed
266             my ($NexsanInfo) = shift;
267             my $sock; #can't use the hash version with say for some reason
268              
269             if (!exists $NexsanInfo->{sock})
270             {
271             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
272             }
273             else
274             {
275             $sock = $NexsanInfo->{sock};
276             }
277             if (!exists $NexsanInfo->{username})
278             {
279             croak "username not passed to Storage::Nexsan::NMP::Authenticate()\n";
280             }
281             if (!exists $NexsanInfo->{password})
282             {
283             croak "password not passed to Storage::Nexsan::NMP::Authenticate()\n";
284             }
285            
286              
287             ### username
288             say $sock "user $NexsanInfo->{username}";
289             my $answer = <$sock>;
290              
291             if ( $answer =~ /^331/ )
292             {
293             WriteLog($NexsanInfo, "username ok: $answer");
294             $NexsanInfo->{authentication}->{username} = $answer;
295             }
296             else
297             {
298             $NexsanInfo->{authentication}->{username} = $answer;
299             croak "problem with the username:::: $answer";
300             }
301              
302              
303              
304             #### password
305             say $sock "pass $NexsanInfo->{password}";
306             $answer = <$sock>;
307              
308             if ( $answer =~ /^230/ )
309             {
310             WriteLog($NexsanInfo, "password ok: $answer");
311             $NexsanInfo->{authentication}->{password} = $answer;
312             return 0;
313             }
314             else
315             {
316             $NexsanInfo->{authentication}->{password} = $answer;
317             croak "problem with the password:::: $answer";
318             }
319              
320             }
321            
322             =head2 GetEventCount
323              
324             #MUST be passed a reference to a hash containing socket
325            
326             It will update the hash passed with a name:value pair called eventCount.
327             It can be called as many times as you wish.
328              
329             =cut
330              
331             sub GetEventCount {
332              
333             #assign the variables, and set defaults if not passed
334             my ($NexsanInfo) = shift;
335             my $sock; #can't use the hash version with say for some reason
336              
337             if (!exists $NexsanInfo->{sock})
338             {
339             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
340             }
341             else
342             {
343             $sock = $NexsanInfo->{sock};
344             }
345             #also check we've successfully authenticated, as this will only be populated is we did
346             if (!exists $NexsanInfo->{password})
347             {
348             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
349             }
350              
351              
352             say $sock "event count";
353             my $answer = <$sock>;
354            
355             if ( $answer =~ /^2\d\d/ )
356             {
357            
358             #say $answer;
359              
360             $answer =~ m/<(\S+)>/; #get the NMP version no
361             $NexsanInfo->{eventCount} = $1;
362             return 0;
363             }
364             else
365             {
366            
367             $answer =~ /^(\d+)\s/;
368             carp "Error: $answer\n";
369             }
370              
371             }
372              
373             =head2 ShowSystemNexsan
374              
375             MUST be passed an reference to a hash that contains a socket, and the AuthenticateNexsan
376             subroutine must have been run beforehand to populate the other required hash's
377            
378             Populates hash with the following name:value pairs;
379             Parameter Description
380             Status of the RAID system (“HEALTHY”, “FAULT”)
381             Serial number of the RAID system (8 hexadecimal digits)
382             Model name of the RAID system
383             Firmware version of the RAID system
384             User-defined friendly name of the RAID system
385             “T10 Vendor Identification” field as reported in SCSI INQUIRY data [NMP 2.2.0]
386             “Product Identification” field as reported in SCSI INQUIRY data [NMP 2.2.0]
387             “Enclosure NAA identifier” field as reported in SCSI INQUIRY VPD page 0x83 identifier type 3 [NMP 2.2.0]
388              
389             =cut
390              
391             sub ShowSystemNexsan {
392              
393             #assign the variables, and set defaults if not passed
394             my ($NexsanInfo) = shift;
395             my $sock; #can't use the hash version with say for some reason
396              
397             if (!exists $NexsanInfo->{sock})
398             {
399             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
400             }
401             else
402             {
403             $sock = $NexsanInfo->{sock};
404             }
405             #also check we've successfully authenticated, as this will only be populated if we did
406             if (!exists $NexsanInfo->{password})
407             {
408             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
409             }
410              
411              
412             say $sock "show system";
413             my @answer;
414             my ($line, $count);
415             $count = 0;
416             #multi-line response, but we only care about line 0 and 1
417            
418             # example:
419             #221 Information follows
420             #SYSTEM:HEALTHY:059955A0:SATABeast2:Nj67:"nex-ge02 (SATA test)":NEXSAN:SATABeast2:6000402005FC55A0
421             #.
422             while ($line = <$sock>)
423             {
424             push @answer, $line;
425             # $line;
426             #only get the lines we want, as while loop never exits otherwise
427             last if $count == 2;
428             $count++;
429             }
430            
431            
432             if ( $answer[0] =~ /^2\d\d/ ) #successful command
433             {
434             my $dummy; #lazy way of stripping out the first variable out of the response
435             ($dummy, $NexsanInfo->{status}, $NexsanInfo->{serial},
436             $NexsanInfo->{model}, $NexsanInfo->{firmware},
437             $NexsanInfo->{friendlyname}, $NexsanInfo->{vendor},
438             $NexsanInfo->{productid}, $NexsanInfo->{enclosurenaaid} ) = split (/:/, $answer[1]);
439              
440             return 0;
441             }
442             else
443             {
444             say @answer;
445             #$answer =~ /^(\d+)\s/;
446             carp "Error: see above\n";
447             }
448              
449              
450             }
451              
452            
453             =head2 UploadFirmwareToNexsan
454              
455             Uploads a firmware file to the Nexsan. Requires NMP 2.3.0 or later - and this routine
456             checks for that, although it is safe to issue this command to an earlier version, as
457             it will safely reject the command, according to the developer. :-)
458            
459             MUST be passed an reference to a hash that contains a socket, and the AuthenticateNexsan
460             subroutine must have been run beforehand to populate the other required hash's
461             MUST have
462             MUST have firmware filename passed in the above hash in the above hash's firmwarefile
463             name:value, e.g. $NexsanInfo->{firmware}->{filename} .
464            
465             NOTE: DOES NOT reboot the system after the upgrade; you need to use another function to do that!
466              
467             =cut
468              
469             sub UploadFirmwareToNexsan {
470              
471             #assign the variables, and set defaults if not passed
472             my ($NexsanInfo) = shift;
473             my $sock; #can't use the hash version with say for some reason
474              
475             if (!exists $NexsanInfo->{sock})
476             {
477             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
478             }
479             else
480             {
481             $sock = $NexsanInfo->{sock};
482             }
483             #also check we've successfully authenticated, as this will only be populated if we did
484             if (!exists $NexsanInfo->{password})
485             {
486             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
487             }
488              
489             ##check we've got the filename to slurp in
490             if (!exists $NexsanInfo->{firmwarefile})
491             {
492             croak "firmware filename variable not passed to
493             Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
494             }
495              
496             #WriteLog($NexsanInfo, "$NexsanInfo->{firmwarefile}");
497             #test the firmware file exists & we can read it
498             unless ( -e $NexsanInfo->{firmwarefile} )
499             {
500             croak "can't read $NexsanInfo->{firmwarefile} aborting\n";
501             }
502              
503              
504             #TODO; do we need to sanitise this with File::Basename in future?
505             #slurp in the file
506             my $firmwareData = read_file( $NexsanInfo->{firmwarefile} )
507             or croak "Can't read in $NexsanInfo->{firmwarefile} because $!\n"; #using File::Slurp
508              
509              
510             #Sanity checking the version of the Nexsan Management Protocol;
511             unless ($NexsanInfo->{NMP}->{Major} >= 2 && $NexsanInfo->{NMP}->{Minor} >= 3)
512             {
513             croak "requires NMP 2.3.0 or later to understand the MAINT FWUPLOAD command";
514             #doesn't carp because presumably the purpose of running this script
515             #with this function was to actually upgrade the firmware! Feedback apprciated
516             # if this matters to you
517             }
518            
519             #sanity checking that there are no firmware upgrades going on in practice
520             WriteLog($NexsanInfo, "checking for upgrades already in progress");
521             say $sock "MAINT FWSTATUS";
522             my $answer = <$sock>;
523             #check for the only error code that means a firmware upgrade isn't in progress or the
524             #last script/GUI may not have completed yet
525             # looking for: '224 Firmware update not started or status has been reset'
526             unless ( $answer =~ /^224/ )
527             {
528             croak "Firmware upgrade already in progress or status not reset: $answer\n";
529             }
530             #say $answer;
531            
532             WriteLog($NexsanInfo, "** uploading firmware file: $NexsanInfo->{firmwarefile}");
533            
534             #Tell the Nexsan to expect a firmware file
535             say $sock "maint fwupload $NexsanInfo->{firmwarefile}";
536              
537             #check for the continue prompt
538             $answer = <$sock>;
539             unless ( $answer =~ /^1\d\d/ )#check for '100 Continue'
540             {
541             croak "Nexsan rejected 'maint fwupload' with: $answer\n";
542             }
543             WriteLog($NexsanInfo, $answer);
544            
545            
546             #set binmode just to be sure?
547            
548             #upload the file
549             say $sock $firmwareData;
550             #print $sock "\r\n";
551             sleep(1); #give it a chance to complete - will hang waiting for the completion otherwise
552             print $sock ".\r\n"; #send the last line to tell the nexsan the file has finished uploading
553             #print $sock "\n";
554             #say $sock ".";
555             $answer = <$sock>;
556             unless ( $answer =~ /^2\d\d/ )
557             {
558             croak "Nexsan firmware upload failed because: $answer\n";
559             }
560             WriteLog($NexsanInfo, $answer);
561             WriteLog($NexsanInfo, "completed uploading firmware");
562              
563             my @answer;
564             my $line;
565            
566             WriteLog($NexsanInfo, "checking firmware install status");
567             say $sock "MAINT FWSTATUS";
568             $answer = <$sock>;
569             WriteLog($NexsanInfo, $answer);
570            
571             #prep the while loop and keep checking for the completion code
572             #what we're looking for:
573             #
574             #Firmware update in progress (40%)
575             #222 Microcode Updated OK
576             #.
577            
578             sleep(1);
579             #$line = <$sock>;
580             say $sock "MAINT FWSTATUS";
581              
582             while ($line = <$sock>)
583             {
584             #push @answer, $line;
585             #say "checking firmware install status";
586             say $sock "MAINT FWSTATUS";
587             WriteLog($NexsanInfo, $line);
588             #only get the lines we want, as while loop never exits otherwise
589             if ($line =~ /^222/)
590             {
591             WriteLog($NexsanInfo, "Nexsan firmware upload successfull!");
592             last;
593             }
594             sleep(4);
595            
596             }
597              
598             #TODO write send command subroutine that cheks for conection having gone away
599             #TODO put log in of IP and date/time on any say command - subroutine?
600              
601            
602             #reset the status of the firmware upload for the GUI and other users
603             WriteLog($NexsanInfo, "Resetting status of last firmware update to return GUI to normal state");
604             say $sock "MAINT FWRESETSTATUS";
605             $answer = <$sock>;
606             unless ( $answer =~ /^2\d\d/ )
607             {
608             croak "Nexsan firmware status reset failed because: $answer\n";
609             }
610             WriteLog($NexsanInfo, $answer);
611            
612              
613            
614             } #end of sub UploadFirmwareToNexsan
615              
616             =head2 ShutdownNexsan
617              
618             To quote the NMP Reference Manual (v.2.3.0);
619             "This command will perform a shutdown of the RAID system , the NMP connection will be closed.
620             All cached data will be flushed to the disks, it is advised all host IO is stopped before
621             this command is used. There are circumstances where shutdown is blocked (such as during a
622             firmware update), these scenarios can be detected from the returned response."
623              
624              
625             =cut
626              
627             sub ShutdownNexsan {
628              
629             #assign the variables, and set defaults if not passed
630             my ($NexsanInfo) = shift;
631             my $sock; #can't use the hash version with say for some reason
632              
633             if (!exists $NexsanInfo->{sock})
634             {
635             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
636             }
637             else
638             {
639             $sock = $NexsanInfo->{sock};
640             }
641             #also check we've successfully authenticated, as this will only be populated if we did
642             if (!exists $NexsanInfo->{password})
643             {
644             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
645             }
646              
647             say $sock "MAINT SHUTDOWN";
648             my $answer = <$sock>;
649            
650             if ( $answer =~ /^2\d\d/ )
651             {
652            
653             WriteLog($NexsanInfo, $answer);
654             return 0;
655             }
656             else
657             {
658            
659             #$answer =~ /^(\d+)\s/;
660             carp "Error - failed to shutdown with response: $answer\n";
661             }
662              
663              
664             } #end of ShutdownNexsan()
665              
666             =head2 RollingRebootNexsan
667              
668             To quote the NMP Reference Manual (v.2.3.0);
669             "This command will perform a rolling restart of the RAID system , the NMP connection will be
670             closed. Each controller is restarted in turn and therefore should minimize the amount time
671             the storage is inaccessible. There are circumstances where rolling restart is blocked (such
672             as during a firmware update), these scenarios can be detected from the returned response."
673              
674              
675             =cut
676              
677             sub RollingRebootNexsan {
678              
679             #assign the variables, and set defaults if not passed
680             my ($NexsanInfo) = shift;
681             my $sock; #can't use the hash version with say for some reason
682              
683             if (!exists $NexsanInfo->{sock})
684             {
685             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
686             }
687             else
688             {
689             $sock = $NexsanInfo->{sock};
690             }
691             #also check we've successfully authenticated, as this will only be populated if we did
692             if (!exists $NexsanInfo->{password})
693             {
694             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
695             }
696              
697             say $sock "MAINT ROLLING";
698             my $answer = <$sock>;
699            
700             if ( $answer =~ /^2\d\d/ )
701             {
702            
703             WriteLog($NexsanInfo, $answer);
704             return 0;
705             }
706             else
707             {
708            
709             #$answer =~ /^(\d+)\s/;
710             carp "Error - failed to initiate a rolling reboot with response: $answer\n";
711             }
712              
713              
714             }#end of RollingRebootNexsan()
715            
716             =head2 TurnOffMAIDOnNexsan
717              
718             #MUST be passed a reference to a hash containing socket
719            
720             This uses the SETOPT command to add the following MAID entry, as if it were uploaded
721             via a settings.dat file;
722            
723             [PowerConfig]
724             PowerLevel1 = 2 ; 0, 2, 5 minutes
725             PowerLevel2 = 0 ; 0, 10, 20, 30, 40, 50, 60 minutes
726             PowerLevel3 = 0 ; 0, 15, 30, 60, 90, 120 minutes
727             MaxSpareLevel = 2 ; 0, 1, 2, 3
728              
729             ASSUMPTION: that PowerLevel4 is not configured..
730              
731             =cut
732              
733             sub TurnOffMAIDOnNexsan {
734              
735             #assign the variables, and set defaults if not passed
736             my ($NexsanInfo) = shift;
737             my $sock; #can't use the hash version with say for some reason
738              
739             if (!exists $NexsanInfo->{sock})
740             {
741             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
742             }
743             else
744             {
745             $sock = $NexsanInfo->{sock};
746             }
747             #also check we've successfully authenticated, as this will only be populated
748             # if we did
749             if (!exists $NexsanInfo->{password})
750             {
751             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
752             }
753              
754             # I really should turn this into a script with an array or something,
755             # but coding to a deadline..
756              
757             #NOTE Nexsan's require the Power levels to be set from most disruptive to least
758             say $sock "SETOPT PowerConfig PowerLevel3 0"; #0, 15, 30, 60, 90, 120 minutes
759             my $answer = <$sock>;
760            
761             if ( $answer =~ /^2\d\d/ )
762             {
763              
764              
765             WriteLog($NexsanInfo, $answer);
766             #return 0;
767             }
768             else
769             {
770            
771             carp "Error setting option with: $answer\n";
772             }
773              
774             say $sock "SETOPT PowerConfig PowerLevel2 0"; # 0, 10, 20, 30, 40, 50, 60 minutes
775             $answer = <$sock>;
776            
777             if ( $answer =~ /^2\d\d/ )
778             {
779              
780              
781             WriteLog($NexsanInfo, $answer);
782             #return 0;
783             #say $sock "GETOPT PowerConfig PowerLevel2";
784             #$answer = <$sock>;
785             #WriteLog($NexsanInfo, $answer);
786             }
787             else
788             {
789            
790             carp "Error setting option with: $answer\n";
791             }
792            
793             say $sock "SETOPT PowerConfig PowerLevel1 2"; # 0, 2, 5 minutes
794             $answer = <$sock>;
795            
796             if ( $answer =~ /^2\d\d/ )
797             {
798              
799              
800             WriteLog($NexsanInfo, $answer);
801             #return 0;
802             }
803             else
804             {
805            
806             carp "Error setting option with: $answer\n";
807             }
808            
809             say $sock "SETOPT PowerConfig MaxSpareLevel 2"; #0, 1, 2, 3
810             $answer = <$sock>;
811            
812             if ( $answer =~ /^2\d\d/ )
813             {
814              
815              
816             WriteLog($NexsanInfo, $answer);
817             #return 0;
818             }
819             else
820             {
821            
822             carp "Error setting option with: $answer\n";
823             }
824            
825             WriteLog($NexsanInfo, "MAID Disabled at levels 2 and 3, minimised at level 1");
826             return 0;
827              
828             } #end of sub TurnOffMAIDOnNexsan
829              
830             =head2 importDatFile
831              
832             Import an .ini file (called .dat by Nexsan) that provides many confuguration options.
833             The idea is that it can be used by another function to then upload a atandard config,
834             replicating the behaviour of the GUI.
835              
836             Requires a filename scalar passed to it, and returns a hash reference, for e.g.;
837              
838             $HASH1 = {
839             ActiveActive
840             => { ActiveActiveMode => 'APAL' },
841             Cache => {
842             AllowSCSIOverride
843             => 'Disabled',
844             Cache => 'Enabled',
845             CacheTuning => 'Mixed',
846             IgnoreForceUnitAccess
847             => 'Enabled',
848             Mirroring => 'Enabled',
849             ReadStreamMode
850             => 'Disabled',
851             StreamingMode => 'Disabled'
852             },
853             SYSLOG => {
854             Facility => 'LOCAL0',
855             SendToIP => '172.17.9.9',
856             UDPPort => 514,
857             WhenToSend => 'All'
858             }
859             };
860              
861             =cut
862              
863             sub importDatFile {
864             my $ConfigFileName = shift || croak "no filename provided to import";
865             my $hash = Config::INI::Reader->read_file($ConfigFileName)
866             || croak "error reading file because: $!\n";
867            
868             return $hash;
869              
870             } #end of sub importDatFile
871            
872            
873             =head2 SetOpt
874              
875             using the importDatFile internal function, apply a number of Nexsan Dat file stanza's.
876              
877             The sub expects $NexsanInfo->{datfile} populated with the name of the dat file to import.
878              
879             Note: no error checking is done on the hash to see it contains a correct INI structure
880             as it assumes that its been imported from an Nexsan created/modified DAT file.
881             Given the SETOPT command if not passed the write information, this is relatively low risk
882            
883             TODO apply some error checking for correct INI structure, as above!
884             TODO write a specific subroutine for the Powerlevel stanza
885             (see notes in TurnOffMAIDInNexsan)
886              
887             =cut
888              
889             sub SetOpt {
890            
891             #assign the variables, and set defaults if not passed
892             my ($NexsanInfo) = shift;
893             my $sock; #can't use the hash version with say for some reason
894              
895             if (!exists $NexsanInfo->{sock})
896             {
897             croak "socket not passed to Storage::Nexsan::NMP::Authenticate()\n";
898             }
899             else
900             {
901             $sock = $NexsanInfo->{sock};
902             }
903             #also check we've successfully authenticated, as this will only be populated
904             # if we did
905             if (!exists $NexsanInfo->{password})
906             {
907             croak "password not passed to Storage::Nexsan::NMP::Authenticate() - not authenticated?\n";
908             }
909            
910             #get the file to import
911             if (!exists $NexsanInfo->{datfile})
912             {
913             croak "dat filename not passed to Storage::Nexsan::NMP::SetOpt ?\n";
914             }
915            
916             #load the dat file into a hash
917             my $hash_ref = importDatFile($NexsanInfo->{datfile});
918            
919             my ($key, $value);
920             while ( ($key, $value) = each %$hash_ref ) #get list of stanzas
921             {
922             if ( ref($value) ) #check the stanza entry has a value
923             { #get hash's for each stanza
924             my ($element, $element_value);
925             while ( ($element, $element_value) = each %$value ) #get individual values
926             {
927             say $sock "SETOPT $key $element $element_value";
928             my $answer = <$sock>;
929            
930             if ( $answer =~ /^2\d\d/ )
931             {
932             WriteLog($NexsanInfo, $answer);
933             }
934             else
935             {
936             carp "Error setting option with: $answer\n";
937             }
938             }
939             }
940             #here for individual lines, which there shouldn't be
941             else { carp " dat file contains standalone lines: $key => $value"; }
942             }
943            
944             } #end of sub SetOpt
945            
946             #=head2 function2
947             #
948             #=cut
949             #
950             #sub function2 {
951             #}
952              
953             =head1 AUTHOR
954              
955             John Constable, C<< >>
956              
957             =head1 BUGS
958              
959             Please report any bugs or feature requests to C, or through
960             the web interface at L. I will
961             be notified, and then you'll automatically be notified of progress on your bug as I make changes.
962              
963              
964              
965              
966             =head1 SUPPORT
967              
968             You can find documentation for this module with the perldoc command.
969              
970             perldoc Storage::Nexsan::NMP
971              
972              
973             You can also look for information at:
974              
975             =over 4
976              
977             =item * RT: CPAN's request tracker (report bugs here)
978              
979             L
980              
981             =item * AnnoCPAN: Annotated CPAN documentation
982              
983             L
984              
985             =item * CPAN Ratings
986              
987             L
988              
989             =item * Search CPAN
990              
991             L
992              
993             =back
994              
995              
996             =head1 ACKNOWLEDGEMENTS
997              
998             James Peck at Nexsan dot com for helping approve the release of this and answering inumerable questions
999             Carl Elkins at sanger dot ac dot uk for allowing me the time to write this
1000              
1001              
1002             =head1 LICENSE AND COPYRIGHT
1003              
1004             Copyright 2011 John Constable.
1005              
1006             This program is free software; you can redistribute it and/or modify it
1007             under the terms of either: the GNU General Public License as published
1008             by the Free Software Foundation; or the Artistic License.
1009              
1010             See http://dev.perl.org/licenses/ for more information.
1011              
1012              
1013             =cut
1014              
1015             1; # End of Storage::Nexsan::NMP