Generate fan zone YAML from MRW XML

The script will generate YAML containing information
about the fans in the system, including inventory name,
associated sensor names, and cooling zone information.

Change-Id: I32a8cc2422d549399b0bada841a914c57bd71777
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/Build.PL b/Build.PL
index a69ee08..9782d09 100644
--- a/Build.PL
+++ b/Build.PL
@@ -6,6 +6,7 @@
     license => 'perl',
     script_files => ['gen_callouts.pl',
                      'gen_devtree.pl',
+                     'gen_fan_zone_yaml.pl',
                      'gen_fru_properties.pl',
                      'gen_ipmi_fru.pl',
                      'gen_ipmi_sensor.pl',
diff --git a/gen_fan_zone_yaml.pl b/gen_fan_zone_yaml.pl
new file mode 100755
index 0000000..68e31f5
--- /dev/null
+++ b/gen_fan_zone_yaml.pl
@@ -0,0 +1,154 @@
+#!/usr/bin/env perl
+
+#This script generates fan definitions from the MRW
+#and outputs them in a YAML file.
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use mrw::Inventory;
+use mrw::Targets;
+use mrw::Util;
+use Scalar::Util qw(looks_like_number);
+
+my $serverwizFile;
+my $outputFile;
+GetOptions("i=s" => \$serverwizFile,
+           "o=s" => \$outputFile) or printUsage();
+
+if ((not defined $serverwizFile) ||
+    (not defined $outputFile))
+{
+    printUsage();
+}
+
+my $targets = Targets->new;
+$targets->loadXML($serverwizFile);
+
+my @inventory = Inventory::getInventory($targets);
+
+my %fans = findFans();
+
+printFanYAML(\%fans, $outputFile);
+
+
+#This function returns a hash representing the fans in the system.
+#The hash looks like:
+#  $fans{<name>}{<zone>}
+#  $fans{<name>}{<profile>}
+#  @fans{<name>}{sensors}
+#
+#  Where:
+#    <name> = inventory name
+#    <zone> = cooling zone number
+#    <profile> = cooling zone profile, such as air, water, or all
+#    <sensors> = an array of the hwmon sensors for the fan's tachs
+sub findFans
+{
+    my %tachs;
+    my %fans;
+
+    #Find fans by looking at the TACH connections.  We could also find
+    #parts of type FAN, but we need the tach connection anyway to
+    #lookup the sensors on the other end...
+    for my $target (keys %{$targets->getAllTargets()})
+    {
+        my $connections = $targets->findConnections($target, "TACH");
+        next if ($connections eq "");
+
+        for my $tach (sort @{$connections->{CONN}})
+        {
+            #Because findConnections is recursive, we can hit this same
+            #connection multiple times, so only use it once.
+            next if (exists $tachs{$tach->{SOURCE}}{$tach->{DEST}});
+            $tachs{$tach->{SOURCE}}{$tach->{DEST}} = 1;
+
+            #Note: SOURCE = tach unit on fan, DEST = tach unit on fan ctlr
+
+            my $fru = Util::getEnclosingFru($targets, $tach->{SOURCE});
+            my $name = Util::getObmcName(\@inventory, $fru);
+
+            my $sensor = getSensor($tach->{DEST});
+            push @{$fans{$name}{sensors}}, $sensor;
+
+            #Get the cooling zone and profile from the fan controller part
+            my $part = $targets->getTargetParent($tach->{SOURCE});
+            my $zone = $targets->getAttribute($part, "COOLING_ZONE");
+            if (!looks_like_number($zone))
+            {
+                die "Cooling zone '$zone' on $part is not a number\n";
+            }
+
+            #If the profile isn't set, just set it to be 'all'.
+            my $profile = "";
+            if (!$targets->isBadAttribute($part, "COOLING_ZONE_PROFILE"))
+            {
+                $profile = $targets->getAttribute($part,
+                                                  "COOLING_ZONE_PROFILE");
+            }
+
+            if ($profile eq "")
+            {
+                $profile = "all";
+            }
+
+            $fans{$name}{profile} = lc $profile;
+            $fans{$name}{zone} = $zone;
+        }
+    }
+
+    return %fans;
+}
+
+
+#Find what hwmon will call this unit's reading by looking in
+#the child unit-hwmon-feature unit.
+sub getSensor
+{
+    my ($unit) = @_;
+
+    my @hwmons = Util::getChildUnitsWithTargetType($targets,
+                                                   "unit-hwmon-feature",
+                                                   $unit);
+    die "No HWMON children found for $unit\n" unless (scalar @hwmons != 0);
+
+    my $name = $targets->getAttributeField($hwmons[0],
+                                           "HWMON_FEATURE",
+                                           "DESCRIPTIVE_NAME");
+    die "No HWMON name for hwmon unit $hwmons[0]\n" if ($name eq "");
+
+    return $name;
+}
+
+
+#Creates the YAML representation of the data
+sub printFanYAML
+{
+    my ($fans, $file) = @_;
+
+    open (F, ">$file") or die "Could not open $file\n";
+
+    print F "fans:\n";
+
+    while (my ($fan, $data) = each(%{$fans}))
+    {
+        print F "  - inventory: $fan\n";
+        print F "    cooling_zone: $data->{zone}\n";
+        print F "    cooling_profile: $data->{profile}\n";
+        print F "    sensors:\n";
+        for my $s (@{$data->{sensors}})
+        {
+            print F "      - $s\n";
+        }
+    }
+
+    close F;
+}
+
+
+sub printUsage
+{
+    print "$0 -i [XML filename] -o [output YAML filename]\n";
+    exit(1);
+}