The initial commit of 0.9.5-pre1 version
[mirror/scst/.git] / scstadmin / scst_db / scst_db
1 #!/usr/bin/perl
2 $Version  = 'SCST DB Configurator v0.51';
3
4 # Configures SCST
5 #
6 # Written by Mark R. Buechler 12/07/04
7
8 sub usage
9   {
10     die <<"EndUsage";
11 $Version
12
13 Usage:
14 General Operations
15      -config <config>     : Configure SCST given the specified configuration file.
16      -check               : Check database configuration against current configuration.
17
18 Options
19      -ForceConfig         : Force all confuration changes, even deletions (DANGER!).
20
21 Debugging (limited support)
22      -debug               : Debug mode - don\'t do anything destructive.
23      
24 Examples:
25      Configure SCST:
26        scst_db config scst_db.conf
27
28 EndUsage
29   }
30
31 use SCST::SCST;
32 use Getopt::Long;
33 use IO::Handle;
34 use IO::File;
35 use DBI;
36 use POSIX;
37 use strict;
38
39 my $_DEF_CONFIG_ = '/etc/scst_db.conf';
40
41 my $TRUE  = 1;
42 my $FALSE = 0;
43
44 my $_MAX_LUNS_      = 255;
45 my $_DEFAULT_GROUP_ = 'Default';
46
47 my $_MDADM_  = '/sbin/mdadm';
48 my $_MDSTAT_ = '/proc/mdstat';
49 my $_MD_DEV_ = '/dev/';
50
51 my $SCST;
52 my $DEVICES;
53 my %USERS;
54 my %ASSIGNMENTS;
55 my %HANDLERS;
56 my %GROUPS;
57 my $_DEBUG_;
58
59 my %MD_DEVICES;
60
61 my %_HANDLER_MAP_ = ('cdrom' => $SCST::SCST::CDROM_TYPE,
62                      'changer' => $SCST::SCST::CHANGER_TYPE,
63                      'disk' => $SCST::SCST::DISK_TYPE,
64                      'disk_fileio' => $SCST::SCST::DISKFILE_TYPE,
65                      'cdrom_fileio' => $SCST::SCST::CDROMFILE_TYPE,
66                      'disk_perf' => $SCST::SCST::DISKPERF_TYPE,
67                      'modisk' => $SCST::SCST::MODISK_TYPE,
68                      'modisk_perf' => $SCST::SCST::MODISKPERF_TYPE,
69                      'tape' => $SCST::SCST::TAPE_TYPE,
70                      'tape_perf' => $SCST::SCST::TAPEPERF_TYPE);
71                      
72 my %_REVERSE_MAP_ = ($SCST::SCST::CDROM_TYPE => 'cdrom',
73                      $SCST::SCST::CHANGER_TYPE => 'changer',
74                      $SCST::SCST::DISK_TYPE => 'disk',
75                      $SCST::SCST::DISKFILE_TYPE => 'disk_fileio',
76                      $SCST::SCST::CDROMFILE_TYPE => 'cdrom_fileio',
77                      $SCST::SCST::DISKPERF_TYPE => 'disk_perf',
78                      $SCST::SCST::MODISK_TYPE => 'modisk',
79                      $SCST::SCST::MODISKPERF_TYPE => 'modisk_perf',
80                      $SCST::SCST::TAPE_TYPE => 'tape',
81                      $SCST::SCST::TAPEPERF_TYPE => 'tape_perf');
82
83 $SIG{INT} = \&commitSuicide;
84
85 POSIX::setsid();
86
87 &main();
88
89 sub getArgs {
90         my $applyConfig;
91         my $checkConfig;
92         my $forceConfig;
93
94         my $p = new Getopt::Long::Parser;
95
96         if (!$p->getoptions('config:s'          => \$applyConfig,
97                             'check'             => \$checkConfig,
98                             'ForceConfig'       => \$forceConfig,
99                             'debug'             => \$_DEBUG_)) {
100                 &usage();
101         }
102
103         $applyConfig = $_DEF_CONFIG_ if (defined($applyConfig) && !$applyConfig);
104         $forceConfig = $TRUE if (defined($forceConfig));
105
106         return ($applyConfig, $checkConfig, $forceConfig);
107 }
108
109 sub main {
110         my $rc;
111
112         STDOUT->autoflush(1);
113
114         # We need to run as root
115         if ( $> ) {die("This program must run as root.\n");}
116
117         my ($config, $check, $force) = getArgs();
118
119         $SCST = new SCST::SCST($_DEBUG_);
120
121         readCurrentConfig();
122         scanDevices();
123
124         SWITCH: {
125                 $config && do {
126                         $rc = applyConfiguration($config, $force, $check);
127                         last SWITCH;
128                 };
129
130                 print "No valid operations specified.\n";
131                 usage();
132                 exit $TRUE;
133         }
134
135         print "All done.\n";
136
137         exit $rc;
138 }
139
140 sub readCurrentConfig {
141         print "Collecting current configuration.. ";
142
143         my $eHandlers = $SCST->handlers();
144
145         foreach my $handler (@{$eHandlers}) {
146                 $HANDLERS{$handler}++; # For quick lookups
147         }
148
149         $DEVICES = $SCST->devices();
150         my $_eGroups = $SCST->groups();
151
152         foreach my $group (@{$_eGroups}) {
153                 $GROUPS{$group}++; # For quick lookups
154                 $ASSIGNMENTS{$group} = $SCST->groupDevices($group);
155                 my $eUsers = $SCST->users($group);
156
157                 foreach my $user (@{$eUsers}) {
158                         $USERS{$group}->{$user}++; # For quick lookups
159                 }
160         }
161
162         print "done.\n";
163 }
164
165 sub scanDevices {
166         my $ch = new IO::Handle;
167         
168         print "Scanning available system devices.. ";
169         
170         my $mdstat = new IO::File $_MDSTAT_, O_RDONLY;
171         
172         die("FATAL: Unable to gather a list of available md devices.\n") if (!$mdstat);
173         
174         while (my $line = <$mdstat>) {
175                 if ($line =~ /^md/) {
176                         my($md, undef) = split(/\:/, $line);
177                         $md = cleanupString($md);
178                         $md = $_MD_DEV_.$md;
179
180                         open $ch, "$_MDADM_ --detail $md -b|" or 
181                           die("FATAL: Unable to gather information for md device $md.\n");
182                         
183                         my $buffer = <$ch>;
184                         chomp $buffer;
185                         
186                         close $ch;
187
188                         my(undef, $uuid_t) = split(/UUID\=/, $buffer);
189                         $uuid_t = cleanupString($uuid_t);
190
191                         $MD_DEVICES{$uuid_t} = $md;
192                 }
193         }
194         
195         print "done.\n";
196 }
197
198 sub applyConfiguration {
199         my $confile = shift;
200         my $force = shift;
201         my $check = shift;
202         my $config = readConfig($confile);
203         my $errs;
204         my $changes = 0;
205
206         my $database = $$config{'DATABASE'}->{'default'}->{'NAME'};
207         my $user     = $$config{'DATABASE'}->{'default'}->{'USERNAME'};
208         my $password = $$config{'DATABASE'}->{'default'}->{'PASSWORD'};
209         my $hostname = $$config{'DATABASE'}->{'default'}->{'HOST'};
210         
211         my $db = DBI->connect("DBI:mysql:database=$database;host=$hostname", "$user", "$password");
212         
213         my $sth = $db->prepare("SELECT device_id, device_path, options, blocksize, type_id, perf_id, md_uuid, handler_name ".
214                                "FROM devices, scst_handlers ".
215                                "WHERE devices.scst_handlr_id = scst_handlers.scst_handlr_id ".
216                                "ORDER BY device_id");
217         die("FATAL: Unable to obtain list of devices: ".$sth->errstr."\n") if (!$sth->execute());
218         
219         # Open new devices and assign them to handlers..
220         while (my $ref = $sth->fetchrow_hashref()) {
221                 if (!$HANDLERS{$_HANDLER_MAP_{$ref->{'handler_name'}}}) {
222                         print "WARNING: Handler '".$ref->{'handler_name'}."' does not exist.\n";
223                         $errs += 1;
224                         next;
225                 }
226                 
227                 my $vname = translateDeviceName($ref->{'device_id'}, $ref->{'type_id'}, $ref->{'perf_id'});
228
229                 next if (defined($$DEVICES{$vname}));
230
231                 my $options = $ref->{'options'};
232                 $options =~ s/\s+//; $options =~ s/\|/,/;
233
234                 if ($ref->{'md_uuid'}) {
235                         print "Using UUID for device ".$ref->{'device_id'}." ($vname).\n";
236
237                         my $handler = $ref->{'handler_name'};
238                         my $uuid = findMDDevice($ref->{'md_uuid'});
239                         my $blocksize = $ref->{'blocksize'};
240
241                         $changes++;
242
243                         if ($check) {
244                                 print "\t-> New device '$handler:$vname', UUID: '$uuid', ".
245                                   "Options: '$options' Blocksize: $blocksize.\n";
246                         } else {
247                                 $errs += addDevice($handler, $vname, $uuid, $options, $blocksize);
248                         }
249                 } elsif ($ref->{'device_path'}) {
250                         print "Using path for device ".$ref->{'device_id'}." ($vname).\n";
251
252                         my $handler = $ref->{'handler_name'};
253                         my $path = $ref->{'device_path'};
254                         my $blocksize = $ref->{'blocksize'};
255
256                         $changes++;
257
258                         if ($check) {
259                                 print "\t-> New device '$handler:$vname', Path: '$path', ".
260                                   "Options: '$options', Blocksize: $blocksize.\n";
261                         } else {
262                                 $errs += addDevice($handler, $vname, $path, $options, $blocksize);
263                         }
264                 } else {
265                         print "FATAL: No UUID or path configured for device ".$ref->{'device_id'}." ($vname).\n";
266                         $errs += 1;
267                         next;
268                 }
269         }
270
271         my $sth = $db->prepare("SELECT group_id, group_name FROM security_groups");
272         die("FATAL: Unable to obtain list of groups: ".$sth->errstr."\n") if (!$sth->execute());
273         
274         # Create new groups and add users..
275         while (my $ref = $sth->fetchrow_hashref()) {
276                 if (!defined($GROUPS{$ref->{'group_name'}})) {
277                         my $group = $ref->{'group_name'};
278
279                         $changes++;
280
281                         if ($check) {
282                                 print "\t-> New group definition '$group'.\n";
283                         } else {
284                                 $errs += addGroup($group);
285                         }
286                 }
287         }
288
289         my $sth = $db->prepare("SELECT group_name, user_id ".
290                                "FROM group_users, security_groups ".
291                                "WHERE security_groups.group_id = group_users.group_id");
292         die("FATAL: Unable to obtain list of users: ".$sth->errstr."\n") if (!$sth->execute());
293
294         while (my $ref = $sth->fetchrow_hashref()) {
295                 if (!defined($USERS{$ref->{'group_name'}}->{$ref->{'user_id'}})) {
296                         my $group = $ref->{'group_name'};
297                         my $user = $ref->{'user_id'};
298
299                         $changes++;
300
301                         if ($check) {
302                                 print "\t-> New user definition '$user' for group '$group'.\n";
303                         } else {
304                                 $errs += addUser($group, $user);
305                         }
306                 }
307         }
308
309         my $sth_a = $db->prepare("SELECT device_id, type_id, group_name, host_id, target_id, target_lun ".
310                                  "FROM assignments, security_groups ".
311                                  "WHERE assignments.group_id = security_groups.group_id");
312         die("FATAL: Unable to obtain list of assignments: ".$sth_a->errstr."\n") if (!$sth_a->execute());
313
314         # Assign new devices to groups..
315         while (my $ref_a = $sth_a->fetchrow_hashref()) {
316                 if (!defined($GROUPS{$ref_a->{'group_name'}})) {
317                         print "WARNING: Unable to assign to non-existant group '".$ref_a->{'group_name'}."'.\n";
318                         $errs += 1;
319                         next;
320                 }
321
322                 my $sth_d = $db->prepare("SELECT type_id, perf_id FROM devices ".
323                                          "WHERE device_id = ".$ref_a->{'device_id'}." ".
324                                          "AND type_id = '".$ref_a->{'type_id'}."'");
325                 die("FATAL: Unable to obtain list of device information: ".$sth_d->errstr."\n") if (!$sth_d->execute());
326                 
327                 my $ref_d = $sth_d->fetchrow_hashref();
328                 
329                 my $vname = translateDeviceName($ref_a->{'device_id'}, $ref_d->{'type_id'}, $ref_d->{'perf_id'});
330
331                 my $_assignments = $ASSIGNMENTS{$ref_a->{'group_name'}};
332                 next if (defined($$_assignments{$vname}));
333
334                 my $group = $ref_a->{'group_name'};
335                 my $lun = $ref_a->{'target_lun'};
336
337                 $changes++;
338
339                 if ($check) {
340                         print "\t-> New device assignment '$vname' for group '$group' at LUN $lun.\n";
341                 } else {
342                         $errs += assignDevice($group, $vname, $lun);
343                 }
344         }
345
346         print "Encountered $errs error(s) while processing.\n" if ($errs);
347
348         undef $db;
349
350         if ($check) {
351                 print "Configuration checked, $changes difference(s) found with current configuation.\n";
352         } else {
353                 print "Configuration applied.\n";
354         }
355
356         return $TRUE if ($errs);
357         return $FALSE;
358 }
359
360 sub addDevice {
361         my $handler = shift;
362         my $device = shift;
363         my $path = shift;
364         my $options = shift;
365
366         $options =~ s/\,/ /;
367
368         my $_handler = $_HANDLER_MAP_{$handler};
369
370         if (defined($$DEVICES{$device})) {
371                 print "WARNING: Device '$device' already defined.\n";
372                 return $TRUE;
373         }
374
375         print "Opening virtual device '$device' at path '$path' using handler '$handler'..\n";
376
377         if ($SCST->openDevice($_handler, $device, $path, $options)) {
378                 print "WARNING: Failed to open virtual device '$device' at path '$path' (Options: $options).\n";
379                 return $TRUE;
380         }
381
382         $$DEVICES{$device} = $_handler;
383
384         return $FALSE;
385 }
386
387 sub addGroup {
388         my $group = shift;
389
390         if (defined($GROUPS{$group})) {
391                 print "WARNING: Group '$group' already exists.\n";
392                 return $TRUE;
393         }
394
395         print "Creating security group '$group'..\n";
396
397         if ($SCST->addGroup($group)) {
398                 print "WARNING: Failed to create security group '$group'.\n";
399                 return $TRUE;
400         }
401
402         $GROUPS{$group}++;
403
404         return $FALSE;
405 }
406
407 sub addUser {
408         my $group = shift;
409         my $user = shift;
410
411         if (!defined($GROUPS{$group})) {
412                 print "WARNING: Failed to add user '$user' to group '$group', group does not exist.\n";
413                 return $TRUE;
414         }
415
416         if (defined($USERS{$group}->{$user})) {
417                 print "WARNING: User '$user' already exists in security group '$group'.\n";
418                 return $TRUE;
419         }
420
421         print "Adding user '$user' to security group '$group'..\n";
422
423         if ($SCST->addUser($user, $group)) {
424                 print "WARNING: Failed to add user '$user' to security group '$group'.\n";
425                 return $TRUE;
426         }
427
428         $USERS{$group}->{$user}++;
429
430         return $FALSE;
431 }
432
433 sub assignDevice {
434         my $group = shift;
435         my $device = shift;
436         my $lun = shift;
437         my %allLuns;
438
439         # Put luns into something easier to parse..
440         foreach my $_group (keys %ASSIGNMENTS) {
441                 my $_gAssigns = $ASSIGNMENTS{$_group};
442
443                 foreach my $_device (keys %{$_gAssigns}) {
444                         @{$allLuns{$_group}}[$$_gAssigns{$_device}] = $_device;
445                 }
446         }
447
448         # Use the next available LUN if none specified
449         if ($lun !~ /\d+/) {
450                 $lun = ($#{$allLuns{$group}} + 1);
451                 if ($lun > $_MAX_LUNS_) {
452                         print "ERROR: Unable to use next available LUN of $lun, lun out of range.\n";
453                         return $TRUE;
454                 }
455
456                 print "Device '$device': Using next available LUN of $lun for group '$group'.\n";
457         }
458                         
459         if (($lun < 0) || ($lun > $_MAX_LUNS_)) {
460                 print "ERROR: Unable to assign device '$device', lun '$lun' is out of range.\n";
461                 return $TRUE;
462         }
463
464         if (!defined($$DEVICES{$device})) {
465                 print "WARNING: Unable to assign non-existant device '$device' to group '$group'.\n";
466                 return $TRUE;
467         }
468
469         if (@{$allLuns{$group}}[$lun]) {
470                 print "ERROR: Device '$device': Lun '$lun' is already assigned to device '".@{$allLuns{$group}}[$lun]."'.\n";
471                 return $TRUE;
472         }
473
474         print "Assign virtual device '$device' to group '$group' at LUN '$lun'..\n";
475
476         if ($SCST->assignDeviceToGroup($device, $group, $lun)) {
477                 print "WARNING: Failed to assign device '$device' to group '$group'.\n";
478                 return $TRUE;
479         }
480
481         if (!defined($ASSIGNMENTS{$group})) {
482                 my %assignments_t;
483                 $ASSIGNMENTS{$group} = \%assignments_t;
484         }
485
486         my $_assignments = $ASSIGNMENTS{$group};
487
488         $$_assignments{$device} = $lun; 
489
490         return $FALSE;
491 }
492
493 sub readConfig {
494         my $confile = shift;
495         my %config;
496         my $section;
497         my $arg;
498
499         my $io = new IO::File $confile, O_RDONLY;
500
501         die("FATAL: Unable to open specified configuration file $confile: $!\n") if (!$io);
502
503         while (my $line = <$io>) {
504                 ($line, undef) = split(/\#/, $line, 2);
505                 $line = cleanupString($line);
506
507                 if ($line =~ /^\[(.*)\]$/) {
508                         ($section, $arg) = split(/\s+/, $1, 2);
509                 } elsif ($section && $arg && $line) {
510                         my($parameter, $value) = split(/\s+/, $line, 2);
511                         $config{$section}->{$arg}->{$parameter} = $value;
512                 }
513         }
514
515         close $io;
516
517         return \%config;
518 }
519
520 sub findMDDevice {
521         my $uuid = shift;
522
523         return $MD_DEVICES{$uuid};
524 }
525
526 sub translateDeviceName {
527         my $device_id = shift;
528         my $type_id = shift;
529         my $perf_id = shift;
530         
531         my $device_id = sprintf("%lx", $device_id);
532         my $device_id_t = $device_id;
533
534         for (my $t = length($device_id); $t <= 2; $t++) {
535                 $device_id_t = "0$device_id_t";
536         }
537
538         $device_id = $device_id_t;
539
540         return $type_id.$perf_id.$device_id;
541 }
542
543 sub cleanupString {
544         my $string = shift;
545
546         $string =~ s/^\s+//;
547         $string =~ s/\s+$//;
548
549         return $string;
550 }
551
552 # Hey! Stop that!
553 sub commitSuicide {
554         print "\n\nBut I haven\'t finished yet!\n";
555         exit 1;
556 }