Imported Upstream version 3.3.3
[debian/amanda] / perl / Amanda / Changer / aggregate.pm
1 # Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11 # for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19
20 package Amanda::Changer::aggregate;
21
22 use strict;
23 use warnings;
24 use Carp;
25 use vars qw( @ISA );
26 @ISA = qw( Amanda::Changer );
27
28 use File::Glob qw( :glob );
29 use File::Path;
30 use Amanda::Config qw( :getconf );
31 use Amanda::Paths;
32 use Amanda::Debug qw( debug warning );
33 use Amanda::Util qw( :alternates );
34 use Amanda::Changer;
35 use Amanda::MainLoop;
36 use Amanda::Device qw( :constants );
37
38 =head1 NAME
39
40 Amanda::Changer::aggregate
41
42 =head1 DESCRIPTION
43
44 This changer operates several child changers.
45 Slot are numbered:
46   0:0  changer 0 slot 0
47   0:1  changer 0 slot 1
48   1:0  changer 1 slot 0
49   3:4  changer 3 slot 4
50
51 See the amanda-changers(7) manpage for usage information.
52
53 =cut
54
55 sub new {
56     my $class = shift;
57     my ($config, $tpchanger) = @_;
58     my ($kidspecs) = ( $tpchanger =~ /chg-aggregate:(.*)/ );
59
60     my @kidspecs = Amanda::Util::expand_braced_alternates($kidspecs);
61     if (@kidspecs < 2) {
62         return Amanda::Changer->make_error("fatal", undef,
63             message => "chg-aggregate needs at least two child changers");
64     }
65
66     my @children = map {
67         ($_ eq "ERROR")? "ERROR" : Amanda::Changer->new($_)
68     } @kidspecs;
69
70     my $fail_on_error;
71     if (defined $config->{'properties'}->{'fail-on-error'}) {
72         $fail_on_error = string_to_boolean($config->{'properties'}->{'fail-on-error'}->{'values'}[0]);
73     } else {
74         $fail_on_error = 1;
75     }
76
77     if (grep { $_->isa("Amanda::Changer::Error") } @children) {
78         my @annotated_errs;
79         my $valid = 0;
80         for my $i (0 .. @children-1) {
81             if ($children[$i]->isa("Amanda::Changer::Error")) {
82                 push @annotated_errs,
83                     [ $kidspecs[$i], $children[$i] ];
84             } else {
85                 $valid++;
86             }
87         }
88         if ($valid == 0 || $fail_on_error) {
89             return Amanda::Changer->make_combined_error(
90                 "fatal", [ @annotated_errs ]);
91         }
92     }
93
94     my $state_filename;
95     my $state_filename_prop = $config->{'properties'}->{'state_filename'};
96     my $lock_timeout = $config->{'properties'}->{'lock-timeout'};
97
98     if (defined $state_filename_prop) {
99         $state_filename = $state_filename_prop->{'values'}[0];
100     }
101     if (!defined $state_filename) {
102         $state_filename = $Amanda::Paths::CONFIG_DIR . '/' . $config->{'name'} . ".state";
103     }
104
105     my $self = {
106         config => $config,
107         child_names => \@kidspecs,
108         children => \@children,
109         num_children => scalar @children,
110         current_slot => undef,
111         state_filename => $state_filename,
112         'lock-timeout' => $lock_timeout,
113     };
114     bless ($self, $class);
115     return $self;
116 }
117
118 sub quit {
119     my $self = shift;
120
121     # quit each child
122     foreach my $child (@{$self->{'children'}}) {
123         $child->quit();
124     }
125
126     $self->SUPER::quit();
127 }
128
129 sub _get_current_slot
130 {
131     my $self = shift;
132     my $cb = shift;
133
134     $self->with_locked_state($self->{'state_filename'}, $cb, sub {
135         my ($state, $cb) = @_;
136         $self->{'current_slot'} = $state->{'current_slot'};
137         $self->{'current_slot'} = "0:first" if !defined $self->{'current_slot'};
138         $cb->();
139     });
140 }
141
142 sub _set_current_slot
143 {
144     my $self = shift;
145     my $cb = shift;
146
147     $self->with_locked_state($self->{'state_filename'}, $cb, sub {
148         my ($state, $cb) = @_;
149         $state->{'current_slot'} = $self->{'current_slot'};
150         $cb->();
151     });
152 }
153
154 sub load {
155     my $self = shift;
156     my %params = @_;
157     my $aggregate_res;
158
159     return if $self->check_error($params{'res_cb'});
160
161     my $res_cb = $params{'res_cb'};
162     my $orig_slot = $params{'slot'};
163     $self->validate_params('load', \%params);
164
165     my $steps = define_steps
166         cb_ref => \$res_cb;
167
168     step which_slot => sub {
169         if (exists $params{'relative_slot'} &&
170             $params{'relative_slot'} eq "current") {
171             if (defined $self->{'current_slot'}) {
172                 return $steps->{'set_from_current'}->();
173             } else {
174                 return $self->_get_current_slot($steps->{'set_from_current'});
175             }
176         } elsif (exists $params{'relative_slot'} &&
177                  $params{'relative_slot'} eq "next") {
178             if (defined $self->{'current_slot'}) {
179                 return $steps->{'get_inventory_next'}->();
180             } else {
181                 return $self->_get_current_slot($steps->{'get_inventory_next'});
182             }
183         } elsif (exists $params{'label'}) {
184             return $self->inventory(inventory_cb => $steps->{'got_inventory_label'});
185         }
186         return $steps->{'slot_set'}->();
187     };
188
189     step get_inventory_next => sub {
190         return $self->inventory(inventory_cb => $steps->{'got_inventory_next'})
191     };
192
193     step got_inventory_next => sub {
194         my ($err, $inv) = @_;
195         my $slot;
196         if ($err) {
197             $res_cb->($err);
198         }
199         my $found = -1;
200         for my $i (0.. scalar(@$inv)-1) {
201             $slot = @$inv[$i]->{'slot'};
202             if ($slot eq $self->{'current_slot'}) {
203                 $found = $i;
204             } elsif ($found >= 0 && (!exists $params{'except_slots'} ||
205                                 !exists $params{'except_slots'}->{$slot})) {
206                 $orig_slot = $slot;
207                 return $steps->{'slot_set'}->();
208             }
209         }
210         if ($found >= 0) {
211             for my $i (0..($found-1)) {
212                 $slot = @$inv[$i]->{'slot'};
213                 if (!exists($params{'except_slots'}) ||
214                     !exists($params{'except_slots'}->{$slot})) {
215                     $orig_slot = $slot;
216                     return $steps->{'slot_set'}->();
217                 }
218             }
219         }
220         return $self->make_error("failed", $res_cb,
221                 reason => "notfound",
222                 message => "all slots have been loaded");
223     };
224
225     step got_inventory_label => sub {
226         my ($err, $inv) = @_;
227         my $slot;
228         if ($err) {
229             $res_cb->($err);
230         }
231         for my $i (0.. scalar(@$inv)-1) {
232             my $slot = @$inv[$i]->{'slot'};
233             my $label = @$inv[$i]->{'label'};
234             if ($label eq $params{'label'}) {
235                 $orig_slot = $slot;
236                 return $steps->{'slot_set'}->();
237             }
238         }
239         return $self->make_error("failed", $res_cb,
240                 reason => "notfound",
241                 message => "label $params{'label'} not found");
242     };
243
244     step set_from_current => sub {
245         $orig_slot = $self->{'current_slot'};
246         return $steps->{'slot_set'}->();
247     };
248
249     step slot_set => sub {
250         my ($kid, $slot) = split(':', $orig_slot, 2);
251         my $child = $self->{'children'}[$kid];
252         if (!defined $child) {
253             return $self->make_error("failed", $res_cb,
254                 reason => "invalid",
255                 message => "no changer $kid");
256         }
257         delete $params{'relative_slot'};
258         $params{'slot'} = $slot;
259         $params{'res_cb'} = sub {
260             my ($err, $res) = @_;
261             if ($res) {
262                 if ($slot ne "first" && $res->{'this_slot'} != $slot) {
263                     return $self->make_error("failed", $res_cb,
264                         reason => "invalid",
265                         message => "slot doesn't match: $res->{'this_slot'} != $slot");
266                 } else {
267                     $self->{'current_slot'} = "$kid:$res->{'this_slot'}";
268                     $aggregate_res = Amanda::Changer::aggregate::Reservation->new($self, $res, $self->{'current_slot'});
269                     return $self->_set_current_slot($steps->{'done'});
270                 }
271             }
272             return $res_cb->($err, undef);
273         };
274         return $child->load(%params);
275     };
276
277     step done => sub {
278         $res_cb->(undef, $aggregate_res);
279     };
280 }
281
282 sub info_key {
283     my $self = shift;
284     my ($key, %params) = @_;
285
286     return if $self->check_error($params{'info_cb'});
287
288     my $check_and_report_errors = sub {
289         my ($kid_results) = @_;
290
291         if (grep { defined($_->[0]) } @$kid_results) {
292             # we have errors, so collect them and make a "combined" error.
293             my @annotated_errs;
294             my @err_slots;
295             for my $i (0 .. $self->{'num_children'}-1) {
296                 my $kr = $kid_results->[$i];
297                 next unless defined($kr->[0]);
298                 push @annotated_errs,
299                     [ $self->{'child_names'}[$i], $kr->[0] ];
300                 push @err_slots, $kr->[0]->{'slot'}
301                     if (defined $kr->[0] and defined $kr->[0]->{'slot'});
302             }
303
304             my @slotarg;
305             if (@err_slots == $self->{'num_children'}) {
306                 @slotarg = (slot => collapse_braced_alternates([@err_slots]));
307             }
308
309             $self->make_combined_error(
310                 $params{'info_cb'}, [ @annotated_errs ],
311                 @slotarg);
312             return 1;
313         }
314     };
315
316     if ($key eq 'num_slots') {
317         my $all_kids_done_cb = sub {
318             my ($kid_results) = @_;
319             return if ($check_and_report_errors->($kid_results));
320
321             # Sum the result
322             my $num_slots;
323             for (@$kid_results) {
324                 my ($err, %kid_info) = @$_;
325                 next unless exists($kid_info{'num_slots'});
326                 my $kid_num_slots = $kid_info{'num_slots'};
327                 $num_slots += $kid_num_slots;
328             }
329             $params{'info_cb'}->(undef, num_slots => $num_slots) if $params{'info_cb'};
330         };
331
332         $self->_for_each_child(
333             oksub => sub {
334                 my ($kid_chg, $kid_cb) = @_;
335                 $kid_chg->info(info => [ 'num_slots' ], info_cb => $kid_cb);
336             },
337             errsub => undef,
338             parent_cb => $all_kids_done_cb,
339         );
340     } elsif ($key eq "vendor_string") {
341         my $all_kids_done_cb = sub {
342             my ($kid_results) = @_;
343             return if ($check_and_report_errors->($kid_results));
344
345             my @kid_vendors =
346                 grep { defined($_) }
347                 map { my ($e, %r) = @$_; $r{'vendor_string'} }
348                 @$kid_results;
349             my $vendor_string;
350             if (@kid_vendors) {
351                 $vendor_string = collapse_braced_alternates([@kid_vendors]);
352                 $params{'info_cb'}->(undef, vendor_string => $vendor_string) if $params{'info_cb'};
353             } else {
354                 $params{'info_cb'}->(undef) if $params{'info_cb'};
355             }
356         };
357
358         $self->_for_each_child(
359             oksub => sub {
360                 my ($kid_chg, $kid_cb) = @_;
361                 $kid_chg->info(info => [ 'vendor_string' ], info_cb => $kid_cb);
362             },
363             errsub => undef,
364             parent_cb => $all_kids_done_cb,
365         );
366     } elsif ($key eq 'fast_search') {
367         my $all_kids_done_cb = sub {
368             my ($kid_results) = @_;
369             return if ($check_and_report_errors->($kid_results));
370
371             my @kid_fastness =
372                 grep { defined($_) }
373                 map { my ($e, %r) = @$_; $r{'fast_search'} }
374                 @$kid_results;
375             if (@kid_fastness) {
376                 my $fast_search = 1;
377                 # conduct a logical AND of all child fastnesses
378                 for my $f (@kid_fastness) {
379                     $fast_search = $fast_search && $f;
380                 }
381                 $params{'info_cb'}->(undef, fast_search => $fast_search) if $params{'info_cb'};
382             } else {
383                 $params{'info_cb'}->(undef, fast_search => 0) if $params{'info_cb'};
384             }
385         };
386
387         $self->_for_each_child(
388             oksub => sub {
389                 my ($kid_chg, $kid_cb) = @_;
390                 $kid_chg->info(info => [ 'fast_search' ], info_cb => $kid_cb);
391             },
392             errsub => undef,
393             parent_cb => $all_kids_done_cb,
394         );
395     }
396 }
397
398 # reset, clean, etc. are all *very* similar to one another, so we create them
399 # generically
400 sub _mk_simple_op {
401     my ($op, $has_drive) = @_;
402     sub {
403         my $self = shift;
404         my %params = @_;
405
406         return if $self->check_error($params{'finished_cb'});
407
408         if (exists $params{'drive'}) {
409             return $self->make_error("failed", $params{'finished_cb'},
410                     reason => "notimpl",
411                     message => "Can't specify drive fo $op command");
412         }
413
414         my $all_kids_done_cb = sub {
415             my ($kid_results) = @_;
416             if (grep { defined($_->[0]) } @$kid_results) {
417                 # we have errors, so collect them and make a "combined" error.
418                 my @annotated_errs;
419                 for my $i (0 .. $self->{'num_children'}-1) {
420                     my $kr = $kid_results->[$i];
421                     next unless defined($kr->[0]);
422                     push @annotated_errs,
423                         [ $self->{'child_names'}[$i], $kr->[0] ];
424                 }
425                 $self->make_combined_error(
426                     $params{'finished_cb'}, [ @annotated_errs ]);
427                 return 1;
428             }
429             $params{'finished_cb'}->() if $params{'finished_cb'};
430         };
431
432         $self->_for_each_child(
433             oksub => sub {
434                 my ($kid_chg, $kid_cb) = @_;
435                 $kid_chg->$op(%params, finished_cb => $kid_cb);
436             },
437             errsub => undef,
438             parent_cb => $all_kids_done_cb,
439         );
440     };
441 }
442
443 {
444     # perl doesn't like that these symbols are only mentioned once
445     no warnings;
446
447     *reset = _mk_simple_op("reset");
448     *clean = _mk_simple_op("clean");
449     *eject = _mk_simple_op("eject");
450 }
451
452 sub update {
453     my $self = shift;
454     my %params = @_;
455     my %changed;
456     my @kid_args;
457
458     my $user_msg_fn = $params{'user_msg_fn'};
459     $user_msg_fn ||= sub { Amanda::Debug::info("chg-aggregate: " . $_[0]); };
460
461     if (exists $params{'changed'}) {
462         for my $range (split ',', $params{'changed'}) {
463             my ($first, $last) = ($range =~ /([:\d]+)(?:-([:\d]+))?/);
464             $last = $first unless defined($last);
465             if ($first =~ /:/) {
466                 my ($f_kid, $f_slot) = split(':', $first, 2);
467                 my ($l_kid, $l_slot) = split(':', $last, 2);
468                 if ($f_kid != $l_kid) {
469                     return;
470                 }
471                 if ($changed{$f_kid} != 1) {
472                     for my $slot ($f_slot..$l_slot) {
473                         $changed{$f_kid}{$slot} = 1;
474                     }
475                 }
476             } else {
477                 for my $kid ($first..$last) {
478                     $changed{$kid} = 1;
479                 }
480             }
481         }
482         for my $kid (0..$self->{'num_children'}-1) {
483             if ($changed{$kid} == 1) {
484                 $kid_args[$kid] = "ALL";
485             } elsif (keys %{$changed{$kid}} > 0) {
486                 $kid_args[$kid] = { changed => join(',',sort(keys %{$changed{$kid}})) };
487             } else {
488                 $kid_args[$kid] = "NONE";
489             }
490         }
491     } else {
492         for my $kid (0..$self->{'num_children'}-1) {
493             $kid_args[$kid] = "ALL";
494         }
495     }
496
497     my $all_kids_done_cb = sub {
498         my ($kid_results) = @_;
499         if (grep { defined($_->[0]) } @$kid_results) {
500             # we have errors, so collect them and make a "combined" error.
501             my @annotated_errs;
502             for my $i (0 .. $self->{'num_children'}-1) {
503                 my $kr = $kid_results->[$i];
504                 next unless defined($kr->[0]);
505                 push @annotated_errs,
506                     [ $self->{'child_names'}[$i], $kr->[0] ];
507             }
508             $self->make_combined_error(
509                 $params{'finished_cb'}, [ @annotated_errs ]);
510             return 1;
511         }
512         $params{'finished_cb'}->() if $params{'finished_cb'};
513     };
514
515     $self->_for_each_child(
516         oksub => sub {
517             my ($kid_chg, $kid_cb, $args) = @_;
518             if (ref($args) eq "HASH") {
519                 $kid_chg->update(%params, finished_cb => $kid_cb, %$args);
520             } elsif ($args eq "ALL") {
521                 $kid_chg->update(%params, finished_cb => $kid_cb);
522             } else {
523                 $kid_cb->();
524             }
525         },
526         errsub => undef,
527         parent_cb => $all_kids_done_cb,
528         args => \@kid_args,
529     );
530 }
531
532 sub inventory {
533     my $self = shift;
534     my %params = @_;
535
536     return if $self->check_error($params{'inventory_cb'});
537
538     my $steps = define_steps
539         cb_ref => \$params{'inventory_cb'};
540
541     step get_current => sub {
542         return $self->_get_current_slot($steps->{'got_current_slot'});
543     };
544
545     step got_current_slot => sub {
546         $self->_for_each_child(
547             oksub => sub {
548                 my ($kid_chg, $kid_cb) = @_;
549                 $kid_chg->inventory(inventory_cb => $kid_cb);
550             },
551             errsub => undef,
552             parent_cb => $steps->{'all_kids_done_cb'},
553         );
554     };
555
556     step all_kids_done_cb => sub {
557         my ($kid_results) = @_;
558         if (grep { defined($_->[0]) } @$kid_results) {
559             # we have errors, so collect them and make a "combined" error.
560             my @annotated_errs;
561             for my $i (0 .. $self->{'num_children'}-1) {
562                 my $kr = $kid_results->[$i];
563                 next unless defined($kr->[0]);
564                 push @annotated_errs,
565                     [ $self->{'child_names'}[$i], $kr->[0] ];
566             }
567             return $self->make_combined_error(
568                 $params{'inventory_cb'}, [ @annotated_errs ]);
569         }
570
571         my $inv = $self->_merge_inventories($kid_results);
572         if (!defined $inv) {
573             return $self->make_error("failed", $params{'inventory_cb'},
574                     reason => "notimpl",
575                     message => "could not generate consistent inventory from aggregate child changers");
576         }
577
578         $params{'inventory_cb'}->(undef, $inv);
579     };
580
581 }
582
583 sub set_meta_label {
584     my $self = shift;
585     my %params = @_;
586     my $state;
587
588     return if $self->check_error($params{'finished_cb'});
589
590     my $finished_cb = $params{'finished_cb'};
591     my $orig_slot = $params{'slot'};
592
593     if (!defined $params{'slot'}) {
594         return $self->make_error("failed", $finished_cb,
595             reason => "invalid",
596             message => "no 'slot' params set.");
597     }
598
599     if (!defined $params{'meta'}) {
600         return $self->make_error("failed", $finished_cb,
601             reason => "invalid",
602             message => "no 'meta' params set.");
603     }
604
605     my ($kid, $slot) = split(':', $orig_slot, 2);
606     my $child = $self->{'children'}[$kid];
607     if (!defined $child) {
608         return $self->make_error("failed", $finished_cb,
609             reason => "invalid",
610             message => "no changer $kid");
611     }
612
613     $params{'slot'} = $slot;
614     return $child->set_meta_label(%params);
615 }
616
617 sub get_meta_label {
618     my $self = shift;
619     my %params = @_;
620     my $state;
621
622     return if $self->check_error($params{'finished_cb'});
623
624     my $finished_cb = $params{'finished_cb'};
625     my $orig_slot = $params{'slot'};
626
627     if (!defined $params{'slot'}) {
628         return $self->make_error("failed", $finished_cb,
629             reason => "invalid",
630             message => "no 'slot' params set.");
631     }
632
633     my ($kid, $slot) = split(':', $orig_slot, 2);
634     my $child = $self->{'children'}[$kid];
635     if (!defined $child) {
636         return $self->make_error("failed", $finished_cb,
637             reason => "invalid",
638             message => "no changer $kid");
639     }
640
641     $params{'slot'} = $slot;
642     return $child->get_meta_label(%params);
643 }
644
645 # Takes keyword parameters 'oksub', 'errsub', 'parent_cb', and 'args'.  For
646 # each child, runs $oksub (or, if the child is "ERROR", $errsub), passing it
647 # the changer, an aggregating callback, and the corresponding element from
648 # @$args (if specified).  The callback combines its results with the results
649 # from other changers, and when all results are available, calls $parent_cb.
650 #
651 # This forms a kind of "AND" combinator for a parallel operation on multiple
652 # changers, providing the caller with a simple collection of the results of
653 # the operation. The parent_cb is called as
654 #   $parent_cb->([ [ <chg_1_results> ], [ <chg_2_results> ], .. ]).
655 sub _for_each_child {
656     my $self = shift;
657     my %params = @_;
658     my ($oksub, $errsub, $parent_cb, $args) =
659         ($params{'oksub'}, $params{'errsub'}, $params{'parent_cb'}, $params{'args'});
660
661     if (defined($args)) {
662         confess "number of args did not match number of children"
663             unless (@$args == $self->{'num_children'});
664     } else {
665         $args = [ ( undef ) x $self->{'num_children'} ];
666     }
667
668     my $remaining = $self->{'num_children'};
669     my @results = ( undef ) x $self->{'num_children'};
670     my $maybe_done = sub {
671         return if (--$remaining);
672         $parent_cb->([ @results ]);
673     };
674
675     for my $i (0 .. $self->{'num_children'}-1) {
676         my $child = $self->{'children'}[$i];
677         my $arg = @$args? $args->[$i] : undef;
678
679         my $child_cb = sub {
680             $results[$i] = [ @_ ];
681             $maybe_done->();
682         };
683
684         if ($child eq "ERROR") {
685             if (defined $errsub) {
686                 $errsub->("ERROR", $child_cb, $arg);
687             } else {
688                 # no errsub; just call $child_cb directly
689                 $child_cb->(undef) if $child_cb;
690             }
691         } else {
692             $oksub->($child, $child_cb, $arg) if $oksub;
693         }
694     }
695 }
696
697 sub _merge_inventories {
698     my $self = shift;
699     my ($kid_results) = @_;
700
701     my @combined;
702     my $nb = 0;
703     for my $kid_result (@$kid_results) {
704         my $kid_inv = $kid_result->[1];
705
706         for my $x (@$kid_inv) {
707             my $slotname = "$nb:" . $x->{'slot'};
708             my $current = $slotname eq $self->{'current_slot'};
709             push @combined, {
710                     state => $x->{'state'},
711                     device_status => $x->{'device_status'},
712                     f_type => $x->{'f_type'},
713                     label => $x->{'label'},
714                     barcode => $x->{'barcode'},
715                     reserved => $x->{'reserved'},
716                     slot => $slotname,
717                     import_export => $x->{'import_export'},
718                     loaded_in => $x->{'loaded_in'},
719                     current => $current,
720                 };
721         }
722         $nb++;
723     }
724
725     return [ @combined ];
726 }
727
728 package Amanda::Changer::aggregate::Reservation;
729 use vars qw( @ISA );
730 @ISA = qw( Amanda::Changer::Reservation );
731
732 sub new {
733     my $class = shift;
734     my ($chg, $kid_res, $slot) = @_;
735     my $self = Amanda::Changer::Reservation::new($class);
736
737     $self->{'chg'} = $chg;
738     $self->{'kid_res'} = $kid_res;
739     $self->{'device'} = $kid_res->{'device'};
740     $self->{'barcode'} = $kid_res->{'barcode'};
741     $self->{'this_slot'} = $slot;
742
743     return $self;
744 }
745
746 sub do_release {
747     my $self = shift;
748     my %params = @_;
749
750     # unref the device, for good measure
751     $self->{'device'} = undef;
752
753     $self->{'kid_res'}->release(%params);
754     $self->{'kid_res'} = undef;
755 }
756
757 sub get_meta_label {
758     my $self = shift;
759     my %params = @_;
760
761     $self->{'kid_res'}->get_meta_label(%params);
762 }
763
764 sub set_meta_label {
765     my $self = shift;
766     my %params = @_;
767
768     $self->{'kid_res'}->set_meta_label(%params);
769 }