File Coverage

lib/Illumos/Zones.pm
Criterion Covered Total %
statement 10 182 5.4
branch 0 86 0.0
condition 0 12 0.0
subroutine 4 28 14.2
pod 25 26 96.1
total 39 334 11.6


line stmt bran cond sub pod time code
1             package Illumos::Zones;
2              
3 1     1   515 use strict;
  1         1  
  1         22  
4 1     1   3 use warnings;
  1         1  
  1         5660  
5              
6             # version
7             our $VERSION = '0.1.4';
8              
9             # commands
10             my $ZONEADM = '/usr/sbin/zoneadm';
11             my $ZONECFG = '/usr/sbin/zonecfg';
12             my $ZONENAME = '/usr/bin/zonename';
13              
14             my %ZMAP = (
15             zoneid => 0,
16             zonename => 1,
17             state => 2,
18             zonepath => 3,
19             uuid => 4,
20             brand => 5,
21             'ip-type' => 6,
22             );
23              
24             # properties that can only be set on creation
25             my @CREATEPROP = qw(zonename zonepath brand ip-type);
26             my @LXNETPROPS = qw(gateway ips primary);
27              
28             my $regexp = sub {
29             my $rx = shift;
30             my $msg = shift;
31              
32             return sub {
33             my $value = shift;
34             return $value =~ /$rx/ ? undef : "$msg ($value)";
35             }
36             };
37              
38             my $elemOf = sub {
39             my $elems = [ @_ ];
40              
41             return sub {
42             my $value = shift;
43             return (grep { $_ eq $value } @$elems) ? undef
44             : 'expected a value from the list: ' . join(', ', @$elems);
45             }
46             };
47              
48             my $TEMPLATE = {
49             zonename => '',
50             zonepath => '',
51             brand => 'lipkg',
52             'ip-type' => 'exclusive',
53             };
54              
55             my $SCHEMA = {
56             zonename => {
57             description => 'name of zone',
58             validator => $regexp->(qr/^[-\w]+$/, 'zonename not valid'),
59             },
60             zonepath => {
61             description => 'path to zone root',
62             example => '"zonepath" : "/zones/mykvm"',
63             validator => $regexp->(qr/^\/[-\w\/]+$/, 'zonepath is not a valid path'),
64             },
65             autoboot => {
66             optional => 1,
67             description => 'boot zone automatically',
68             validator => $elemOf->(qw(true false)),
69             },
70             bootargs => {
71             optional => 1,
72             description => 'boot arguments for zone',
73             validator => sub { return undef },
74             },
75             pool => {
76             optional => 1,
77             description => 'name of the resource pool this zone must be bound to',
78             validator => sub { return undef },
79             },
80             limitpriv => {
81             description => 'the maximum set of privileges any process in this zone can obtain',
82             default => 'default',
83             validator => $regexp->(qr/^[-\w,]+$/, 'limitpriv not valid'),
84             },
85             brand => {
86             description => "the zone's brand type",
87             default => 'lipkg',
88             validator => $elemOf->(qw(ipkg lipkg lx)),
89             },
90             'ip-type' => {
91             description => 'ip-type of zone. can either be "exclusive" or "shared"',
92             default => 'exclusive',
93             validator => $elemOf->(qw(exclusive shared)),
94             },
95             hostid => {
96             optional => 1,
97             description => 'emulated 32-bit host identifier',
98             validator => $regexp->(qr/^(?:[\da-f]{1,8}|)$/i, 'hostid not valid'),
99             },
100             'cpu-shares' => {
101             optional => 1,
102             description => 'the number of Fair Share Scheduler (FSS) shares',
103             validator => $regexp->(qr/^\d+$/, 'cpu-shares not valid'),
104             },
105             'max-lwps' => {
106             optional => 1,
107             description => 'the maximum number of LWPs simultaneously available',
108             validator => $regexp->(qr/^\d+$/, 'max-lwps not valid'),
109             },
110             'max-msg-ids' => {
111             optional => 1,
112             description => 'the maximum number of message queue IDs allowed',
113             validator => $regexp->(qr/^\d+$/, 'max-msg-ids not valid'),
114             },
115             'max-sem-ids' => {
116             optional => 1,
117             description => 'the maximum number of semaphore IDs allowed',
118             validator => $regexp->(qr/^\d+$/, 'max-sem-ids not valid'),
119             },
120             'max-shm-ids' => {
121             optional => 1,
122             description => 'the maximum number of shared memory IDs allowed',
123             validator => $regexp->(qr/^\d+$/, 'max-shm-ids not valid'),
124             },
125             'max-shm-memory' => {
126             optional => 1,
127             description => 'the maximum amount of shared memory allowed',
128             validator => $regexp->(qr/^\d+[KMGT]?$/i, 'max-shm-memory not valid'),
129             },
130             'scheduling-class' => {
131             optional => 1,
132             description => 'Specifies the scheduling class used for processes running',
133             validator => sub { return undef },
134             },
135             'fs-allowed' => {
136             optional => 1,
137             description => 'a comma-separated list of additional filesystems that may be mounted',
138             validator => $regexp->(qr/^(?:[-\w,]+|)$/, 'fs-allowed not valid'),
139             },
140             attr => {
141             optional => 1,
142             array => 1,
143             description => 'generic attributes',
144             members => {
145             name => {
146             description => 'attribute name',
147             validator => sub { return undef },
148             },
149             type => {
150             description => 'attribute type',
151             validator => sub { return undef },
152             },
153             value => {
154             description => 'attribute value',
155             validator => sub { return undef },
156             },
157             },
158             },
159             'capped-cpu' => {
160             optional => 1,
161             description => 'limits for CPU usage',
162             members => {
163             ncpus => {
164             description => 'sets the limit on the amount of CPU time. value is the percentage of a single CPU',
165             validator => $regexp->(qr/^(?:\d*\.\d+|\d+\.\d*)$/, 'ncpus value not valid. check man zonecfg'),
166             },
167             },
168             },
169             'capped-memory' => {
170             optional => 1,
171             description => 'limits for physical, swap, and locked memory',
172             members => {
173             physical => {
174             optional => 1,
175             description => 'limits of physical memory. can be suffixed by (K, M, G, T)',
176             validator => $regexp->(qr/^\d+[KMGT]?$/i, 'physical capped-memory is not valid. check man zonecfg'),
177             },
178             swap => {
179             optional => 1,
180             description => 'limits of swap memory. can be suffixed by (K, M, G, T)',
181             validator => $regexp->(qr/^\d+[KMGT]?$/i, 'swap capped-memory is not valid. check man zonecfg'),
182             },
183             locked => {
184             optional => 1,
185             description => 'limits of locked memory. can be suffixed by (K, M, G, T)',
186             validator => $regexp->(qr/^\d+[KMGT]?$/i, 'locked capped-memory is not valid. check man zonecfg'),
187             },
188             },
189             },
190             dataset => {
191             optional => 1,
192             array => 1,
193             description => 'ZFS dataset',
194             members => {
195             name => {
196             description => 'the name of a ZFS dataset to be accessed from within the zone',
197             validator => $regexp->(qr/^\w[-\w\/]+$/, 'dataset name not valid. check man zfs'),
198             },
199             },
200             },
201             'dedicated-cpu' => {
202             optional => 1,
203             description => "subset of the system's processors dedicated to this zone while it is running",
204             members => {
205             ncpus => {
206             description => "the number of cpus that should be assigned for this zone's exclusive use",
207             validator => $regexp->(qr/^\d+(?:-\d+)?$/, 'dedicated-cpu ncpus not valid. check man zonecfg'),
208             },
209             importance => {
210             optional => 1,
211             description => 'specifies the pset.importance value for use by poold',
212             validator => sub { return undef },
213             },
214             },
215             },
216             device => {
217             optional => 1,
218             array => 1,
219             description => 'device',
220             members => {
221             match => {
222             description => 'device name to match',
223             validator => sub { return undef },
224             },
225             },
226             },
227             fs => {
228             optional => 1,
229             array => 1,
230             description => 'file-system',
231             members => {
232             dir => {
233             description => 'directory of the mounted filesystem',
234             validator => $regexp->(qr/^\/[-\w\/\.]+$/, 'dir is not a valid directory'),
235             },
236             special => {
237             description => 'path of fs to be mounted',
238             validator => $regexp->(qr/^[-\w\/\.]+$/, 'special is not valid'),
239             },
240             raw => {
241             optional => 1,
242             description => 'path of raw disk',
243             validator => $regexp->(qr/^\/[-\w\/]+$/, 'raw is not valid'),
244             },
245             type => {
246             description => 'type of fs',
247             validator => $elemOf->(qw(lofs zfs)),
248             },
249             options => {
250             optional => 1,
251             description => 'mounting options',
252             validator => $regexp->(qr/^\[?[\w,]+\]?$/, 'options not valid'),
253             },
254             },
255             },
256             net => {
257             optional => 1,
258             array => 1,
259             description => 'network interface',
260             members => {
261             address => {
262             optional => 1,
263             description => 'IP address of network interface',
264             validator => $regexp->(qr/^\d{1,3}(?:\.\d{1,3}){3}(?:\/\d{1,2})?$/, 'IP address not valid'),
265             },
266             physical => {
267             description => 'network interface',
268             validator => $regexp->(qr/^[-\w]+/, 'physical not valid'),
269             },
270             defrouter => {
271             optional => 1,
272             description => 'IP address of default router',
273             validator => $regexp->(qr/^\d{1,3}(?:\.\d{1,3}){3}$/, 'IP address not valid'),
274             },
275             ips => {
276             optional => 1,
277             array => 1,
278             description => 'IPs for LX zones',
279             validator => $regexp->(qr/^\d{1,3}(?:\.\d{1,3}){3}(?:\/\d{1,2})$/, 'Not a valid CIDR IP address'),
280             },
281             gateway => {
282             optional => 1,
283             description => 'Gateway for LX zones',
284             validator => $regexp->(qr/^\d{1,3}(?:\.\d{1,3}){3}$/, 'IP address not valid'),
285             },
286             primary => {
287             optional => 1,
288             description => 'Primary Interface for LX zones',
289             validator => $elemOf->(qw(true false)),
290             },
291             },
292             },
293             rctl => {
294             optional => 1,
295             array => 1,
296             description => 'resource control',
297             members => {
298             name => {
299             description => 'resource name',
300             validator => sub { return undef },
301             },
302             value => {
303             description => 'resource value',
304             validator => sub { return undef },
305             },
306             },
307             },
308             };
309              
310             # private methods
311             my $RESOURCES = sub {
312             return [ map { $SCHEMA->{$_}->{members} ? $_ : () } keys %$SCHEMA ];
313             };
314              
315             my $resIsArray = sub {
316             my $self = shift;
317             my $res = shift;
318              
319             return $SCHEMA->{$res}->{array};
320             };
321              
322             my $RESARRAYS = sub {
323             return [ map { $SCHEMA->{$_}->{array} ? $_ : () } @{$RESOURCES->()} ];
324             };
325              
326             my $zoneCmd = sub {
327             my $self = shift;
328             my $zoneName = shift;
329             my $cmd = shift;
330             my @opts = @_;
331              
332             my @cmd = ($ZONEADM, '-z', $zoneName, $cmd, @opts);
333              
334             print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
335             system(@cmd) and die "ERROR: cannot $cmd zone $zoneName\n";
336             };
337              
338             my $encodeLXnetProp = sub {
339             my $self = shift;
340             my $prop = shift;
341             my $value = shift;
342              
343             $value = ref $value eq 'ARRAY' ? "(name=$prop,value=\"" . join (',', @$value) . '")'
344             : "(name=$prop,value=\"$value\")";
345             $prop = 'property';
346              
347             return ($prop, $value);
348             };
349              
350             my $decodeLXnetProp = sub {
351             my $self = shift;
352             my $prop = shift;
353             my $value = shift;
354              
355             return ($prop, $value) if !($prop eq 'property');
356              
357             ($prop) = $value =~ /name=(\w+)/;
358             my @values = split /,/, ($value =~ /value="([^"]+)"/)[0];
359             if (!$SCHEMA->{net}->{members}->{$prop}->{array}) {
360             return ($prop, $values[0]);
361             }
362             return ($prop, [ @values ]);
363             };
364              
365             # constructor
366             sub new {
367 1     1 0 857 my $class = shift;
368 1         3 my $self = { @_ };
369 1         2 return bless $self, $class
370             }
371              
372             # public methods
373             sub schema {
374 1     1 1 7572 return $SCHEMA;
375             }
376              
377             sub template {
378 0     0 1   return $TEMPLATE;
379             }
380              
381             sub resources {
382 0     0 1   return $RESOURCES->();
383             }
384              
385             sub resourceArrays {
386 0     0 1   return $RESARRAYS->();
387             }
388              
389             # zoneName is a static method
390             sub zoneName {
391 0     0 1   my @cmd = ($ZONENAME);
392              
393 0 0         open my $zones, '-|', @cmd
394             or die "ERROR: cannot get zonename\n";
395              
396 0           chomp (my $zonename = <$zones>);
397              
398 0           return $zonename;
399             }
400              
401             # isGZ is a static method
402             sub isGZ {
403 0     0 1   return zoneName() eq 'global';
404             }
405              
406             sub listZones {
407 0     0 1   my $self = shift;
408              
409 0           my @cmd = ($ZONEADM, qw(list -cp));
410              
411 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
412 0 0         open my $zones, '-|', @cmd
413             or die "ERROR: cannot get list of Zones\n";
414              
415 0           my $zoneList = [];
416 0           while (my $zone = <$zones>) {
417 0           chomp $zone;
418 0           push @$zoneList, { map { $_ => (split /:/, $zone, 7)[$ZMAP{$_}] } keys %ZMAP };
  0            
419             }
420              
421 0           return $zoneList;
422             }
423              
424             sub listZone {
425 0     0 1   my $self = shift;
426 0           my $zoneName = shift;
427              
428 0           my ($zone) = grep { $_->{zonename} eq $zoneName } @{$self->listZones};
  0            
  0            
429              
430 0           return $zone;
431             }
432              
433             sub zoneState {
434 0     0 1   my $self = shift;
435 0           my $zoneName = shift;
436              
437 0           my $zone = $self->listZone($zoneName);
438              
439 0 0         return $zone ? $zone->{state} : undef;
440             }
441              
442             sub boot {
443 0     0 1   my $self = shift;
444              
445 0           $self->$zoneCmd(shift, 'boot');
446             }
447              
448             sub shutdown {
449 0     0 1   my $self = shift;
450 0           my $zoneName = shift;
451 0 0         my @reboot = $_[0] ? qw(-r) : ();
452              
453 0           $self->$zoneCmd($zoneName, 'shutdown', @reboot);
454             }
455              
456             sub reboot {
457 0     0 1   my $self = shift;
458              
459 0           $self->shutdown(shift, 1);
460             };
461              
462             sub createZone {
463 0     0 1   my $self = shift;
464 0           my $zoneName = shift;
465 0           my $props = shift;
466              
467 0           my @cmd = ($ZONECFG, '-z', $zoneName, qw(create -b ;));
468              
469 0           for my $prop (keys %$props) {
470 0           push @cmd, ('set', $prop, '=', $props->{$prop}, ';');
471             }
472              
473 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
474 0 0         system(@cmd) and die "ERROR: cannot create zone $zoneName\n";
475             }
476              
477             sub deleteZone {
478 0     0 1   my $self = shift;
479 0           my $zoneName = shift;
480              
481 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'delete');
482              
483 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
484 0 0         system(@cmd) and die "ERROR: cannot delete zone $zoneName\n";
485             }
486              
487             sub installZone {
488 0     0 1   my $self = shift;
489 0           my $zoneName = shift;
490 0           my $img = shift;
491              
492 0 0         $self->$zoneCmd($zoneName, 'install', ($img ? ('-s', $img) : ()));
493             }
494              
495             sub uninstallZone {
496 0     0 1   my $self = shift;
497              
498 0           $self->$zoneCmd(shift, 'uninstall');
499             }
500              
501             sub zoneExists {
502 0     0 1   my $self = shift;
503 0           my $zoneName = shift;
504              
505 0           my $zoneList = $self->listZones();
506              
507 0           for my $zone (@$zoneList){
508 0 0         return 1 if $zone->{zonename} eq $zoneName;
509             }
510              
511 0           return 0;
512             }
513              
514             sub getZoneProperties {
515 0     0 1   my $self = shift;
516 0           my $zoneName = shift;
517 0           my $properties = {};
518              
519 0 0         return {} if !$self->zoneExists($zoneName);
520              
521 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'info');
522              
523 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
524 0 0         open my $props, '-|', @cmd
525             or die "ERROR: cannot get properties of zone '$zoneName'\n";
526              
527 0           my $resName;
528 0           while (<$props>) {
529 0           chomp;
530             # remove square brackets at beginning and end of line
531 0 0         s/^(\s*)\[/$1/ && s/\]\s*//;
532 0           my ($isres, $property, $value) = /^(\s+)?([^:]+):(?:\s+(.*))?$/;
533             # at least property must be valid
534 0 0         $property or next;
535              
536 0 0 0       if (defined $isres && length $isres > 0) {
537             # transform net properties for LX zones
538 0 0         ($property, $value) = $self->$decodeLXnetProp($property, $value) if $resName eq 'net';
539              
540             # check if property exists in schema
541 0 0         grep { $_ eq $property } keys %{$SCHEMA->{$resName}->{members}} or next;
  0            
  0            
542 0 0         if ($self->$resIsArray($resName)) {
543 0           $properties->{$resName}->[-1]->{$property} = $value;
544             }
545             else {
546 0           $properties->{$resName}->{$property} = $value;
547             }
548             }
549             else {
550             # check if property exists in schema
551 0 0         grep { $_ eq $property } keys %$SCHEMA or next;
  0            
552             # check if property is a resource
553 0 0         grep { $_ eq $property } @{$RESOURCES->()} and do {
  0            
  0            
554 0           $resName = $property;
555 0 0         if ($self->$resIsArray($property)) {
556 0           push @{$properties->{$property}}, {};
  0            
557             }
558 0           next;
559             };
560 0           $properties->{$property} = $value;
561             }
562             }
563            
564 0           return $properties;
565             }
566              
567             sub setZoneProperties {
568 0     0 1   my $self = shift;
569 0           my $zoneName = shift;
570 0           my $props = shift;
571 0           my $img = shift;
572 0           my $oldProps = $self->getZoneProperties($zoneName);
573              
574             $self->zoneExists($zoneName) || $self->createZone($zoneName,
575 0 0         { map { $_ => $props->{$_} } @CREATEPROP });
  0            
576              
577             # remove props that cannot be changed after creation
578 0           delete $props->{$_} for @CREATEPROP;
579              
580 0           my $state = $self->zoneState($zoneName);
581 0 0         $self->installZone($zoneName, $img) if $state eq 'configured';
582              
583             # clean up all resources
584 0           $self->clearResources($zoneName);
585              
586 0           for my $prop (keys %$props) {
587 0 0         if (ref $props->{$prop} eq 'ARRAY') {
    0          
588 0           for my $elem (@{$props->{$prop}}) {
  0            
589 0           $self->addResource($zoneName, $prop, $elem);
590             }
591             }
592 0           elsif (grep { $_ eq $prop } @{$RESOURCES->()}) {
  0            
593 0           $self->addResource($zoneName, $prop, $props->{$prop});
594             }
595             else {
596 0 0 0       next if $oldProps->{$prop} && $oldProps->{$prop} eq $props->{$prop};
597 0 0         if ($props->{$prop}) {
598 0           $self->setProperty($zoneName, $prop, $props->{$prop});
599             }
600             else {
601 0           $self->clearProperty($zoneName, $prop);
602             }
603             }
604             }
605             }
606              
607             sub resourceExists {
608 0     0 1   my $self = shift;
609 0           my $zoneName = shift;
610 0           my $resource = shift;
611 0           my $property = shift;
612 0           my $value = shift;
613              
614 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'info', $resource);
615              
616 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
617 0 0         open my $res, '-|', @cmd
618             or die "ERROR: cannot get resource '$resource' of zone '$zoneName'\n";
619              
620 0           chomp (my @resources = <$res>);
621              
622 0 0 0       return $property && $value ? grep { /\s+$property:\s+$value/ } @resources : @resources;
  0            
623             }
624              
625             sub addResource {
626 0     0 1   my $self = shift;
627 0           my $zoneName = shift;
628 0           my $resource = shift;
629 0           my $props = shift;
630              
631 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'add', "$resource;");
632              
633 0           for my $property (keys %$props) {
634             # check if it is an LX net property
635 0 0         if (grep { $_ eq $property } @LXNETPROPS) {
  0            
636 0           my ($prop, $value) = $self->$encodeLXnetProp($property, $props->{$property});
637 0           push @cmd, ('add', $prop, $value, ';');
638             }
639             else {
640 0           push @cmd, ('set', "$property=$props->{$property};");
641             }
642             }
643 0           push @cmd, qw(end);
644              
645 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
646 0 0         system(@cmd) and die "ERROR: cannot set properties for resource '$resource' of $zoneName\n";
647             }
648              
649             sub delResource {
650 0     0 1   my $self = shift;
651 0           my $zoneName = shift;
652 0           my $resource = shift;
653 0           my $property = shift;
654 0           my $value = shift;
655              
656 0 0         return if !$self->resourceExists($zoneName, $resource, $property, $value);
657              
658 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'remove');
659 0 0 0       if ($property && $value) {
660 0           push @cmd, ($resource, $property, '=', $value);
661             }
662             else {
663 0           push @cmd, ('-F', $resource);
664             }
665            
666 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
667 0 0         system(@cmd) and die "ERROR: cannot remove resource '$resource' of $zoneName\n";
668             }
669              
670             sub clearResources {
671 0     0 1   my $self = shift;
672 0           my $zoneName = shift;
673              
674 0           for my $res (@{$RESOURCES->()}) {
  0            
675 0           $self->delResource($zoneName, $res);
676             }
677             }
678              
679             sub setProperty {
680 0     0 1   my $self = shift;
681 0           my $zoneName = shift;
682 0           my $property = shift;
683 0           my $value = shift;
684              
685 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'set', $property, '=', "\"$value\"");
686              
687 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
688 0 0         system(@cmd) and die "ERROR: cannot set property $property of $zoneName\n";
689             }
690              
691             sub clearProperty {
692 0     0 1   my $self = shift;
693 0           my $zoneName = shift;
694 0           my $property = shift;
695              
696 0           my @cmd = ($ZONECFG, '-z', $zoneName, 'clear', $property);
697              
698 0 0         print STDERR '# ' . join(' ', @cmd) . "\n" if $self->{debug};
699 0 0         system(@cmd) and die "ERROR: cannot remove property $property of $zoneName\n";
700             }
701              
702             1;
703              
704             __END__