File Coverage

blib/lib/Device/WWN.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Device::WWN;
2 5     5   197362 use strict; use warnings;
  5     5   13  
  5         155  
  5         24  
  5         10  
  5         197  
3             our $VERSION = '1.01';
4 5     5   2262 use Moose;
  0            
  0            
5             use Module::Find ();
6             use Device::OUI;
7             use Device::WWN::Carp qw( croak );
8             use overload (
9             '<=>' => 'overload_cmp',
10             'cmp' => 'overload_cmp',
11             '""' => 'overload_stringify',
12             fallback => 1,
13             );
14             use Sub::Exporter -setup => {
15             exports => [qw( wwn_to_integers normalize_wwn wwn_cmp )],
16             };
17              
18             our @HANDLERS;
19             sub find_subclasses {
20             my $class = shift;
21             unless ( @HANDLERS ) {
22             @HANDLERS = grep {
23             /::/ && $_->isa( __PACKAGE__ )
24             } Module::Find::useall( __PACKAGE__ );
25             }
26             my $wwn = normalize_wwn( shift )
27             || croak "Must specify a WWN for find_subclass";
28             grep { $_->accept_wwn( $wwn ) } @HANDLERS;
29             }
30              
31             has 'wwn' => (
32             is => 'rw',
33             isa => 'Maybe[Str]',
34             predicate => 'has_wwn',
35             clearer => 'clear_wwn',
36             trigger => sub {
37             my ( $self, $val ) = @_;
38             if ( Scalar::Util::blessed( $self ) eq __PACKAGE__ ) { # not subclass
39             my @possible = $self->find_subclasses( $val );
40             if ( @possible == 1 ) {
41             $self->rebless_class( $possible[0] )
42             }
43             } elsif ( $self->can( 'accept_wwn' ) ) { # it is a subclass
44             $self->accept_wwn( normalize_wwn( $val ) )
45             || croak "Invalid WWN '$val' for " . ref( $self );
46             }
47             if ( $val ) {
48             $self->clear_wwn_dependent
49             } else {
50             $self->clear_wwn;
51             }
52             },
53             );
54              
55             sub rebless_class {
56             my ( $self, $new_class ) = @_;
57             die "$new_class is not a subclass of ".__PACKAGE__
58             unless $self->isa( __PACKAGE__ );
59             bless( $self, $new_class );
60             }
61              
62             sub clear_wwn_dependent {
63             my $self = shift;
64              
65             $self->clear_normalized;
66             $self->clear_naa;
67             $self->clear_oui;
68             $self->clear_vendor_code;
69             $self->clear_vendor_id;
70             }
71              
72             has 'naa' => ( is => 'rw', isa => 'Int', lazy_build => 1 );
73             sub _build_naa {
74             my $self = shift;
75             my $norm = $self->normalized;
76             my $naa = substr( $norm, 0, 1 );
77             if ( $naa eq '1' ) {
78             # 1 - IEEE 803.2 standard 48 bit ID
79             # A WWN starting with 1 must start with 1000
80             substr( $norm, 1, 3 ) eq '000' || croak "Invalid WWN";
81             return 1;
82             } elsif ( $naa =~ /^[256]$/ ) {
83             # 2 - IEEE 803.2 extended 48-bit ID
84             # 5 - IEEE Registered Name
85             # 6 - IEEE Extended Registered Name
86             return int( $naa );
87             } else {
88             # everything else is invalid for a WWN
89             croak "Invalid WWN (NAA == $naa )";
90             }
91             }
92              
93             has 'oui' => ( is => 'rw', isa => 'Device::OUI', lazy_build => 1 );
94             sub _build_oui {
95             my $self = shift;
96             my $naa = $self->naa;
97             my $wwn = $self->normalized;
98             my $oui;
99             if ( $naa == 1 || $naa == 2 ) {
100             $oui = substr( $wwn, 4, 6 );
101             } elsif ( $naa == 5 ) {
102             $oui = substr( $wwn, 1, 6 );
103             } elsif ( $naa == 6 ) {
104             die "TODO"; # TODO
105             }
106             return Device::OUI->new( $oui );
107             }
108              
109             has 'vendor_code' => (
110             is => 'rw',
111             isa => 'Maybe[Str]',
112             lazy_build => 1,
113             trigger => sub {
114             my $self = shift;
115             unless ( $self->naa == 2 ) {
116             croak "Cannot set vendor_code unless naa is '2'";
117             }
118             },
119             );
120             sub _build_vendor_code {
121             my $self = shift;
122             my $naa = $self->naa;
123             if ( $naa == 2 ) {
124             my $wwn = $self->normalized;
125             return substr( $wwn, 1, 3 );
126             }
127             return;
128             }
129              
130             has 'vendor_id' => ( is => 'rw', isa => 'Str', lazy_build => 1 );
131             sub _build_vendor_id {
132             my $self = shift;
133             my $naa = $self->naa;
134             my $wwn = $self->normalized;
135             if ( $naa == 1 || $naa == 2 ) {
136             return substr( $wwn, 10, 6 );
137             } elsif ( $naa == 5 || $naa == 6 ) {
138             return substr( $wwn, 7, 9 );
139             }
140             }
141              
142             sub wwn_to_integers {
143             my $wwn = shift || return;
144              
145             my @parts = grep { length } split( /[^a-f0-9]+/i, "$wwn" );
146             if ( @parts == 1 && length $parts[0] == 16 ) { # 200000e069415402
147             local $_ = shift( @parts );
148             while ( /([a-f0-9]{2})/ig ) { push( @parts, $1 ) }
149             return map { hex } @parts;
150             } elsif ( @parts == 8 ) { # 20:00:00:e0:69:41:54:02
151             return map { hex } @parts;
152             } elsif ( @parts == 4 ) { # 2000:00e0:6941:5402
153             return map { /^(\w\w)(\w\w)$/ && ( hex( $1 ), hex( $2 ) ) } @parts;
154             } else {
155             croak "Invalid WWN format '$wwn'";
156             }
157             }
158              
159             sub normalize_wwn {
160             my @ints = wwn_to_integers( shift );
161             croak "Invalid WWN: must be 8 bytes (16 hex characters) long"
162             unless @ints == 8;
163             return join( '', map { sprintf( '%02x', $_ ) } @ints );
164             }
165              
166             sub BUILDARGS {
167             my $class = shift;
168             if ( @_ == 1 && ! ref $_[0] ) { return { wwn => shift() } }
169             $class->SUPER::BUILDARGS( @_ );
170             }
171              
172             sub overload_stringify {
173             my $self = shift;
174             if ( $self->has_wwn ) { return $self->normalized }
175             return overload::StrVal( $self );
176             }
177              
178             sub overload_cmp { return wwn_cmp( pop( @_ ) ? reverse @_ : @_ ) }
179             sub wwn_cmp {
180             my @l = wwn_to_integers( shift );
181             my @r = wwn_to_integers( shift );
182              
183             while ( @l && @r ) {
184             if ( $l[0] == $r[0] ) { shift( @l ); shift( @r ); }
185             return $l[0] <=> $r[0];
186             }
187             return 0;
188             }
189              
190             has 'normalized' => ( is => 'rw', isa => 'Maybe[Str]', lazy_build => 1 );
191             sub _build_normalized { normalize_wwn( shift->wwn ) }
192              
193             1;
194             __END__
195              
196             =head1 NAME
197              
198             Device::WWN - Encode/Decode Fiber Channel World Wide Names
199              
200             =head1 SYNOPSIS
201              
202             use Device::WWN;
203            
204             my $wwn = Device::WWN->new( '500604872363ee43' );
205             print "Serial Number: ".$wwn->serial_number."\n";
206             print "Vendor ".$wwn->oui->organization."\n";
207              
208             =head1 DESCRIPTION
209              
210             This module provides an interface to decode fiber channel World Wide Name
211             values (WWN, also called World Wide Identifier or WWID). The WWN value is
212             similar to a network cards hardware MAC address, but for fiber channel SAN
213             networks.
214              
215             =head1 METHODS
216              
217             =head2 Device::WWN->find_subclasses( $wwn )
218              
219             This class method searches through the installed L<Device::WWN> subclasses,
220             and returns a list of class names of the subclasses that reported they were
221             able to handle the provided WWN.
222              
223             =head2 Device::WWN->new( $wwn )
224              
225             Creates and returns a new Device::WWN object. The WWN value is required. Note
226             that the object you get back might be a subclass of L<Device::WWN>, if there is
227             a more specific handler class for the WWN you provided. This is the case for
228             example when the WWN indicates that it belongs to an EMC Symmetrix or Clariion
229             array, in which case you will get back a
230             L<Device::WWN::EMC::Symmetrix|Device::WWN::EMC::Symmatrix> or
231             L<Device::WWN::EMC::Clariion|Device::WWN::EMC::Clariion> object. These handler
232             subclasses are intended to be able to decode the vendor-specific portions of
233             the WWN, and may be able to give you information such as the storage system
234             serial number and the port number.
235              
236             =head2 $wwn->wwn
237              
238             Return the WWN that this object was created with.
239              
240             =head2 $wwn->oui
241              
242             Returns a L<Device::OUI|Device::OUI> object representing the OUI
243             (Organizationally Unique Identifier) for the WWN. This object can give you
244             information about the vendor of the SAN port represented by this WWN.
245              
246             =head2 $wwn->naa
247              
248             Returns the 'Network Address Authority' value. This is the first character of
249             the WWN, and indicates the format of the WWN itself. The possible values are:
250              
251             1 - IEEE 803.2 standard 48 bit ID
252             2 - IEEE 803.2 extended 48-bit ID
253             5 - IEEE Registered Name
254             6 - IEEE Extended Registered Name
255              
256             =head2 $wwn->normalized
257              
258             Return a 'normalized' WWN value for this object. The normalized value is in
259             lower-case hex, with no separators (such as '500604872363ee43').
260              
261             L<Device::WWN|Device::WWN> objects have stringification overloaded to return
262             this value. If the object doesn't have a WWN assigned, stringification will
263             return an object address value just as if it were not overloaded.
264              
265             =head2 $wwn->vendor_id
266              
267             Returns the unique vendor ID value for the WWN.
268              
269             =head2 $wwn->vendor_code
270              
271             NAA Type 2 defines a 1.5 byte section of the WWN as a 'vendor specific code'.
272             Some vendors use this to identify the port on a specific device, some use it
273             simply as an extension of the serial number. Generally this won't be a very
274             useful value on it's own, unless there is a L<Device::WWN|Device::WWN> subclass
275             for the vendor which can decode it.
276              
277             =head1 FUNCTIONS / EXPORTS
278              
279             Although this module is entirely object oriented, there are a handful of
280             utility functions that you can import from this module if you find a need
281             for them. Nothing is exported by default, so if you want to import any of
282             them you need to say so explicitly:
283              
284             use Device::WWN qw( ... );
285              
286             You can get all of them by importing the ':all' tag:
287              
288             use Device::WWN ':all';
289              
290             The exporting is handled by L<Sub::Exporter|Sub::Exporter>.
291              
292             =head2 normalize_wwn( $wwn )
293              
294             Given a WWN in any common format, normalizes it into a lower-case, zero padded,
295             hexadecimal format.
296              
297             =head2 wwn_cmp( $wwn1, $wwn2 )
298              
299             This is a convenience method, given two Device::WWN objects, or two WWNs (in
300             any format acceptable to L</normalize_wwn>) will return -1, 0, or 1, depending
301             on whether the first WWN is less than, equal to, or greater than the second
302             one.
303              
304             L<Device::WWN|Device::WWN> objects have C<cmp> and C<< <=> >> overloaded so that
305             simply comparing them will work as expected.
306              
307             =head2 wwn_to_integers( $wwn )
308              
309             Decodes a WWN into a list of 8 integers. This is primarily used internally,
310             but may be useful in some circumstances.
311              
312             =head1 INTERNAL METHODS
313              
314             These are internal methods that you generally won't have to worry about.
315              
316             =head2 BUILDARGS
317              
318             The BUILDARGS method overloads L<Moose::Object|Moose::Object> to allow you
319             to pass a single string argument containing the WWN when calling L</new>.
320              
321             =head2 overload_cmp
322              
323             A utility method that calls wwn_cmp with the appropriate arguments. Used
324             by L<overload|overload>.
325              
326             =head2 overload_stringify
327              
328             Internal method for L<overload> to call when attempting to stringify the
329             object. If the object has a WWN value, then it will stringify to the
330             output of L</normalized>, otherwise it will stringify the same as if it had
331             not been overloaded (using the output of L<overload/StrVal>.
332              
333             =head2 clear_wwn_dependent
334              
335             This utility method clears the values of any attributes that depend on the
336             WWN. It is called when the WWN attribute it set. Normally you shouldn't
337             need to care, but if you are creating a new L<Device::WWN> subclass, then
338             you should wrap this with a L<Moose/after|Moose 'after' modifier> to also
339             clear any attributes you add that are dependent on the WWN.
340              
341             =head1 MODULE HOME PAGE
342              
343             The home page of this module is
344             L<http://www.jasonkohles.com/software/device-wwn>. This is where you can
345             always find the latest version, development versions, and bug reports. You
346             will also find a link there to report bugs.
347              
348             =head1 SEE ALSO
349              
350             L<http://www.jasonkohles.com/software/device-wwn>
351              
352             L<http://en.wikipedia.org/wiki/World_Wide_Name>
353              
354             L<Device::OUI|Device::OUI>
355              
356             =head1 AUTHOR
357              
358             Jason Kohles C<< <email@jasonkohles.com> >>
359              
360             L<http://www.jasonkohles.com>
361              
362             =head1 COPYRIGHT AND LICENSE
363              
364             Copyright 2008, 2009 Jason Kohles
365              
366             This program is free software; you can redistribute it and/or modify it
367             under the same terms as Perl itself.
368              
369             =cut
370