You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2017/01/27 16:53:35 UTC
[14/36] incubator-trafficcontrol git commit: Updated to include full
new postinstall with new perl modules
Updated to include full new postinstall with new perl modules
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/bae43757
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/bae43757
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/bae43757
Branch: refs/heads/master
Commit: bae4375757ceb18358c4bd2dcf5ce873ec225e02
Parents: 4a2657b
Author: peryder <pe...@cisco.com>
Authored: Thu Dec 1 17:05:28 2016 -0500
Committer: Dan Kirkwood <da...@gmail.com>
Committed: Fri Jan 27 09:52:53 2017 -0700
----------------------------------------------------------------------
traffic_ops/install/bin/input.json | 138 +++-
traffic_ops/install/bin/postinstall-new | 15 +-
.../install/bin/postinstall-new-integrated | 763 +++++++++++++++++++
traffic_ops/install/lib/BuildPerlDeps.pm | 99 +++
traffic_ops/install/lib/GenerateCert.pm | 232 ++++++
traffic_ops/install/lib/InstallUtils.pm | 268 ++++---
traffic_ops/install/lib/ProfileCleanup.pm | 192 +++++
7 files changed, 1580 insertions(+), 127 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/input.json
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/input.json b/traffic_ops/install/bin/input.json
index 8428eec..fd9f0e6 100644
--- a/traffic_ops/install/bin/input.json
+++ b/traffic_ops/install/bin/input.json
@@ -1,11 +1,11 @@
{
- "testdb.conf": [
+ "/opt/traffic_ops/app/conf/production/database.conf": [
{
"Database type": "mysql",
"config_var": "type"
},
{
- "Database name": "traffic_ops",
+ "Database name": "traffic_ops_db",
"config_var": "dbname"
},
{
@@ -16,26 +16,28 @@
"Database port number": "3306",
"config_var": "port"
},
- {
- "Root database user": "root",
- "config_var": "root_user"
- },
- {
- "Root database password": "default",
- "config_var": "root_passwd"
- }
+ {
+ "Traffic Ops database user": "traffic_ops",
+ "config_var": "user"
+ },
+ {
+ "Traffic Ops database password": "default",
+ "config_var": "password",
+ "hidden": "1"
+ }
],
- "testtodb.conf": [
- {
- "Traffic Ops database user": "root",
- "config_var": "dbAdminUser"
- },
+ "/opt/traffic_ops/app/db/dbconf.yml": [
+ {
+ "Database server root (admin) username": "root",
+ "config_var": "dbAdminUser"
+ },
{
- "Password for Traffic Ops database user": "default",
- "config_var": "dbAdminPw"
- }
+ "Database server admin password": "default",
+ "config_var": "dbAdminPw",
+ "hidden": "1"
+ }
],
- "testcdn.conf": [
+ "/opt/traffic_ops/app/conf/cdn.conf": [
{
"Generate a new secret?": "yes",
"config_var": "genSecret"
@@ -45,7 +47,7 @@
"config_var": "keepSecrets"
}
],
- "testldap.conf": [
+ "/opt/traffic_ops/app/conf/ldap.conf": [
{
"Do you want to set up LDAP?": "no",
"config_var": "setupLdap"
@@ -60,23 +62,101 @@
},
{
"LDAP Admin Password": "",
- "config_var": "password"
+ "config_var": "password",
+ "hidden": "1"
},
{
"LDAP Search Base": "",
"config_var": "search_base"
}
],
- "testpost_install.json": [],
- "testusers.json": [
+ "/opt/traffic_ops/install/data/json/users.json": [
+ {
+ "Administration username for Traffic Ops": "root",
+ "config_var": "tmAdminUser"
+ },
+ {
+ "Password for the admin user": "default",
+ "config_var": "tmAdminPw",
+ "hidden": "1"
+ }
+ ],
+ "/opt/traffic_ops/install/data/profiles/": [],
+ "/opt/traffic_ops/install/bin/openssl_configuration.json": [
{
- "Administration username for Traffic Ops": "admin",
- "config_var": "tmAdminUser"
+ "Country Name (2 letter code)": "XX",
+ "config_var": "country"
},
{
- "Password for the admin user": "default",
- "config_var": "tmAdminPw"
- }
+ "State or Province Name (full name)": "Default State",
+ "config_var": "state"
+ },
+ {
+ "Locality Name (eg, city)": "Default City",
+ "config_var": "locality"
+ },
+ {
+ "Organization Name (eg, company)": "Default Company Ltd",
+ "config_var": "company"
+ },
+ {
+ "Organizational Unit Name (eg, section)": "",
+ "config_var": "org_unit"
+ },
+ {
+ "Common Name (eg, your name or your server's hostname)": "example.com",
+ "config_var": "common_name"
+ },
+ {
+ "RSA Passphrase": "password",
+ "config_var": "rsaPassword",
+ "hidden": "1"
+ }
],
- "testprofiles/": []
+ "/opt/traffic_ops/install/data/json/profiles.json": [
+ {
+ "Traffic Ops url": "https://localhost",
+ "config_var": "tm.url"
+ },
+ {
+ "Human-readable CDN Name. (No whitespace, please)": "kabletown_cdn",
+ "config_var": "cdn_name"
+ },
+ {
+ "Health Polling Interval (milliseconds)": "8000",
+ "config_var": "health_polling_int"
+ },
+ {
+ "DNS sub-domain for which your CDN is authoritative": "cdn1.kabletown.net",
+ "config_var": "dns_subdomain"
+ },
+ {
+ "TLD SOA admin": "traffic_ops",
+ "config_var": "soa_admin"
+ },
+ {
+ "TrafficServer Drive Prefix": "/dev/sd",
+ "config_var": "driver_prefix"
+ },
+ {
+ "TrafficServer RAM Drive Prefix": "/dev/ram",
+ "config_var": "ram_drive_prefix"
+ },
+ {
+ "TrafficServer RAM Drive Letters (comma separated)": "0,1,2,3,4,5,6,7",
+ "config_var": "ram_drive_letters"
+ },
+ {
+ "Health Threshold Load Average": "25",
+ "config_var": "health_thresh_load_avg"
+ },
+ {
+ "Health Threshold Available Bandwidth in Kbps": "1750000",
+ "config_var": "health_thresh_kbps"
+ },
+ {
+ "Traffic Server Health Connection Timeout (milliseconds)": "2000",
+ "config_var": "health_connect_timeout"
+ }
+ ]
}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/postinstall-new
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/postinstall-new b/traffic_ops/install/bin/postinstall-new
index b97d58d..cb4cc56 100755
--- a/traffic_ops/install/bin/postinstall-new
+++ b/traffic_ops/install/bin/postinstall-new
@@ -39,6 +39,7 @@ sub errorOut {
}
# outputs logging messages to terminal and log file
+# TODO: move to InstallUtils
sub logger {
my $output = shift;
my $type = shift;
@@ -247,7 +248,8 @@ sub sanityCheckDefaults {
# userInput: The entire input config file which is either user input or the defaults
#
# Checks the input config file against the default inputs. If there is a question located in the default inputs which
-# is not located in the input config file it will output a warning message.
+# is not located in the input config file it will output a warning message. If in auto mode the default answer will be
+# used. If in interactive mode the user will be prompted with the default question missing from the config file
#
# This does not check the other way meaning questions which are present in defaults but not present in the input config
# file will not be checked
@@ -259,9 +261,8 @@ sub sanityCheckConfig {
foreach my $file ( ( keys $::defaultInputs ) ) {
if ( !defined $userInput->{$file} ) {
logger( "File \'$file\' found in defaults but not config file", "warn" );
- next;
- }
-
+ $userInput->{$file} = [];
+ }
foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
my $found = 0;
@@ -308,10 +309,6 @@ sub sanityCheckConfig {
logger( "File sanity check complete - found $diffs difference(s)", "info" ) if ( $diffs > 0 );
}
-sub writeOutputConf {
- writeJson( $::outputConfigFile, \%::outputConf );
-}
-
# A function which returns the default inputs data structure. These questions and answers will be used if there is no
# user input config file or if there are questions in the input config file which do not have answers
@@ -331,7 +328,7 @@ sub getDefaults {
"config_var" => "hostname",
},
{
- "Database port number" => 3306,
+ "Database port number" => "3306",
"config_var" => "port"
},
{
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/postinstall-new-integrated
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/postinstall-new-integrated b/traffic_ops/install/bin/postinstall-new-integrated
new file mode 100755
index 0000000..529cce8
--- /dev/null
+++ b/traffic_ops/install/bin/postinstall-new-integrated
@@ -0,0 +1,763 @@
+#!/usr/bin/perl
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/install/lib/perl5 /opt/traffic_ops/app/local/lib/perl5 /opt/traffic_ops/app/lib);
+$ENV{PATH} = "/opt/traffic_ops/install/bin:$ENV{PATH}";
+$ENV{PERL5LIB} = "/opt/traffic_ops/install/lib:/opt/traffic_ops/install/lib/perl5:/opt/traffic_ops/app/local/lib/perl5:/opt/traffic_ops/app/lib";
+
+use strict;
+use warnings;
+
+use Safe;
+use POSIX;
+use File::Basename qw{dirname};
+use File::Path qw{make_path};
+use InstallUtils qw{ :all };
+use BuildPerlDeps qw{ :all };
+use GenerateCert qw{ :all };
+use ProfileCleanup qw { :all };
+use Digest::SHA1 qw(sha1_hex);
+use Data::Dumper qw(Dumper);
+use Scalar::Util qw(looks_like_number);
+
+use Getopt::Long;
+
+# paths of the output configuration files
+our $databaseConfFile = "/opt/traffic_ops/app/conf/production/database.conf";
+our $dbConfFile = "/opt/traffic_ops/app/db/dbconf.yml";
+our $cdnConfFile = "/opt/traffic_ops/app/conf/cdn.conf";
+our $ldapConfFile = "/opt/traffic_ops/app/conf/ldap.conf";
+our $usersConfFile = "/opt/traffic_ops/install/data/json/users.json";
+our $profilesConfFile = "/opt/traffic_ops/install/data/profiles/";
+our $opensslConfFile = "/opt/traffic_ops/install/bin/openssl_configuration.json";
+our $paramConfFile = "/opt/traffic_ops/install/data/json/profiles.json";
+
+our $profile_dir = "/opt/traffic_ops/install/data/profiles/";
+our $post_install_cfg = "/opt/traffic_ops/install/data/json/post_install.json";
+
+our $reconfigure_defaults = "/opt/traffic_ops/.reconfigure_defaults";
+
+our $parameters;
+
+my $reconfigure_file = "/opt/traffic_ops/.reconfigure";
+my $reconfigure;
+my $dumpDefaults;
+
+# log file for the installer
+our $logFile = "/var/log/traffic_ops/postinstall.log";
+
+# maximum size the log file should be before rotating it - rotating it copies the current log
+# file to the same name appended with .bkp replacing the old backup if any is there
+my $maxLogSize = 1000000; #bytes
+
+# log file for cpan - this log becomes large and is rotated every install
+our $cpanLogFile = "/var/log/traffic_ops/cpan.log";
+
+# configuration file output with answers which can be used as input to postinstall
+our $outputConfigFile = "/var/log/traffic_ops/configuration_file.json";
+
+sub getDbDriver {
+ return "mymysql";
+}
+
+sub getInstallPath {
+ my $relPath = shift;
+ return join( '/', "/tmp/traffic_ops", $relPath );
+}
+
+# given a var to the hash of config_var and question, will return the question
+sub getConfigQuestion {
+ my $var = shift;
+ foreach my $key ( keys $var ) {
+ if ( $key ne "hidden" && $key ne "config_var" ) {
+ return $key;
+ }
+ }
+}
+
+# question: The question given in the config file
+# config_answer: The answer given in the config file - if no config file given will be defaultInput
+#
+# Determines if the script is being run in complete interactive mode and prompts user - otherwise
+# returns answer to question in config or defaults
+
+sub getField {
+ my $question = shift;
+ my $config_answer = shift;
+ my $hidden = shift;
+
+ # if there is no config file and not in automatic mode prompt for all questions with default answers
+ if ( !$::inputFile && !$::automatic ) {
+ if ($hidden) {
+ return promptPasswordVerify($question);
+ }
+ else {
+ return promptUser( $question, $config_answer );
+ }
+ }
+
+ return $config_answer;
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The name of the output config file given by the input config file
+#
+# Loops through an input config file and determines answers to each question using getField
+# and returns the hash of answers
+
+sub getConfig {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my %config;
+
+ if ( !defined $userInput->{$fileName} ) {
+ logger( "No $fileName found in config", "error" );
+ }
+
+ logger( "===========$fileName===========", "info" );
+
+ foreach my $var ( @{ $userInput->{$fileName} } ) {
+
+ my $question = getConfigQuestion($var);
+ my $hidden = $var->{"hidden"} if ( exists $var->{"hidden"} );
+ my $answer = $config{ $var->{"config_var"} } =
+ getField( $question, $var->{$question}, $hidden );
+
+ $config{ $var->{"config_var"} } = $answer;
+ if ( !$hidden ) {
+ logger( "$question: $answer", "info" );
+ }
+ }
+ return %config;
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# dbFileName: The filename of the output config file for the database
+# toDBFileName: The filename of the output config file for the Traffic Ops database
+#
+# Generates a config file for the database based on the questions and answers in the input config file
+
+sub generateDbConf {
+ my $userInput = shift;
+ my $dbFileName = shift;
+ my $toDBFileName = shift;
+
+ my %dbconf = getConfig( $userInput, $dbFileName );
+
+ make_path( dirname($dbFileName), { mode => 0755 } );
+ writeJson( $dbFileName, \%dbconf );
+ logger( "Database configuration has been saved", "info" );
+
+ # broken out into separate file/config area
+ my %todbconf = getConfig( $userInput, $toDBFileName );
+
+ # No YAML library installed, but this is a simple file..
+ open( my $fh, '>', $toDBFileName )
+ or errorOut("Can't write to $toDBFileName!");
+ print $fh "version: 1.0\n";
+ print $fh "name: dbconf.yml\n\n";
+ print $fh "production:\n";
+ print $fh " driver: ", getDbDriver() . "\n";
+ print $fh " open: tcp:$dbconf{hostname}:$dbconf{port}*$dbconf{dbname}/$dbconf{user}/$dbconf{password}\n";
+ close $fh;
+
+ return \%todbconf;
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The filename of the output config file
+#
+# Generates a config file for the CDN
+
+sub generateCdnConf {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my %cdnConfiguration = getConfig( $userInput, $fileName );
+
+ # First, read existing one -- already loaded with a bunch of stuff
+ my $cdnConf;
+ if ( -f $fileName ) {
+ $cdnConf = Safe->new->rdo($fileName)
+ or errorOut("Error loading $fileName: $@");
+ }
+ if ( lc $cdnConfiguration{genSecret} =~ /^y(?:es)?/ ) {
+ my @secrets = @{ $cdnConf->{secrets} };
+ my $newSecret = randomWord();
+ unshift @secrets, randomWord();
+ if ( $cdnConfiguration{keepSecrets} > 0
+ && $#secrets > $cdnConfiguration{keepSecrets} - 1 )
+ {
+
+ # Shorten the array to requested length
+ $#secrets = $cdnConfiguration{keepSecrets} - 1;
+ }
+ }
+ writePerl( $fileName, $cdnConf );
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The filename of the output config file
+#
+# Generates an LDAP config file
+
+sub generateLdapConf {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my $useLdap = $userInput->{$fileName}[0]->{"Do you want to set up LDAP?"};
+
+ if ( $useLdap eq "no" || $useLdap eq "n" ) {
+ logger( "Not setting up ldap", "info" );
+
+ return;
+ }
+
+ my %ldapConf = getConfig( $userInput, $fileName );
+
+ make_path( dirname($fileName), { mode => 0755 } );
+ writeJson( $fileName, \%ldapConf );
+}
+
+sub generateUsersConf {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my %user = ();
+ my %config = getConfig( $userInput, $fileName );
+
+ $user{username} = $config{tmAdminUser};
+ $user{password} = sha1_hex( $config{tmAdminPw} );
+
+ writeJson( $fileName, \%user );
+ $user{password} = $config{tmAdminPw};
+ return \%user;
+}
+
+sub generateProfilesDir {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my $userIn = $userInput->{$fileName};
+}
+
+sub generateOpenSSLConf {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ if ( !defined $userInput->{$fileName} ) {
+ logger( "No OpenSSL Configuration - questions will be asked", "info" );
+ writeJson( $fileName, my %emptyConfig );
+ return;
+ }
+
+ my %config = getConfig( $userInput, $fileName );
+
+ writeJson( $fileName, \%config );
+}
+
+sub generateParamConf {
+ my $userInput = shift;
+ my $fileName = shift;
+
+ my %config = getConfig( $userInput, $fileName );
+ return \%config;
+}
+
+# check default values for missing config_var parameter
+sub sanityCheckDefaults {
+ foreach my $file ( ( keys $::defaultInputs ) ) {
+ foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
+ my $question = getConfigQuestion($defaultValue);
+
+ if ( !defined $defaultValue->{"config_var"}
+ || $defaultValue->{"config_var"} eq "" )
+ {
+ errorOut( "Question \'$question\' in file \'$file\' has no config_var" );
+ }
+ }
+ }
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+#
+# Checks the input config file against the default inputs. If there is a question located in the default inputs which
+# is not located in the input config file it will output a warning message.
+#
+# This does not check the other way meaning questions which are present in defaults but not present in the input config
+# file will not be checked
+
+sub sanityCheckConfig {
+ my $userInput = shift;
+ my $diffs = 0;
+
+ foreach my $file ( ( keys $::defaultInputs ) ) {
+ if ( !defined $userInput->{$file} ) {
+ logger( "File \'$file\' found in defaults but not config file", "warn" );
+ $userInput->{$file} = [];
+ }
+
+ foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
+
+ my $found = 0;
+ foreach my $configValue ( @{ $userInput->{$file} } ) {
+ if ( $defaultValue->{"config_var"} eq $configValue->{"config_var"} ) {
+ $found = 1;
+ }
+ }
+
+ # if the question is not found in the config file add it from defaults
+ if ( !$found ) {
+ logger( "Value " . Dumper($defaultValue) . "found in defaults but not in \'$file\'", "warn" );
+
+ my $question = getConfigQuestion($defaultValue);
+
+ my %temp;
+
+ # in automatic mode add the missing question with default answer
+ if ($::automatic) {
+ logger( "Adding question \'$question\' with default answer \'$defaultValue->{$question}\'", "info" );
+
+ %temp = (
+ "config_var" => $defaultValue->{"config_var"},
+ $question => $defaultValue->{$question}
+ );
+ }
+
+ # in interactive mode prompt the user for answer to missing question
+ else {
+ logger( "Prompting user for answer", "info" );
+ my $answer;
+ if ( exists $defaultValue->{"hidden"}
+ && $defaultValue->{"hidden"} )
+ {
+ $answer = promptPasswordVerify($question);
+ %temp = (
+ "config_var" => $defaultValue->{"config_var"},
+ $question => $answer,
+ "hidden" => "true"
+ );
+ }
+ else {
+ $answer = promptUser( $question, $defaultValue->{$question} );
+ %temp = (
+ "config_var" => $defaultValue->{"config_var"},
+ $question => $answer
+ );
+ }
+ }
+ push $userInput->{$file}, \%temp;
+
+ $diffs++;
+ }
+ }
+ }
+
+ logger( "File sanity check complete - found $diffs differences", "info" )
+ if ( $::debug && $diffs == 0 );
+ logger( "File sanity check complete - found $diffs difference(s)", "info" )
+ if ( $diffs > 0 );
+}
+
+# A function which returns the default inputs data structure. These questions and answers will be used if there is no
+# user input config file or if there are questions in the input config file which do not have answers
+
+sub getDefaults {
+ return {
+ $::databaseConfFile => [
+ {
+ "Database type" => "mysql",
+ "config_var" => "type",
+ },
+ {
+ "Database name" => "traffic_ops",
+ "config_var" => "dbname",
+ },
+ {
+ "Database server hostname IP or FQDN" => "localhost",
+ "config_var" => "hostname",
+ },
+ {
+ "Database port number" => "3306",
+ "config_var" => "port"
+ },
+ {
+ "Traffic Ops database user" => "traffic_ops",
+ "config_var" => "user"
+ },
+ {
+ "Password for Traffic Ops database user" => "",
+ "config_var" => "password",
+ "hidden" => "true"
+ }
+ ],
+ $::dbConfFile => [
+ {
+ "Database server root (admin) user" => "root",
+ "config_var" => "dbAdminUser"
+ },
+ {
+ "Password for database server admin" => "",
+ "config_var" => "dbAdminPw",
+ "hidden" => "true"
+ }
+ ],
+ $::cdnConfFile => [
+ {
+ "Generate a new secret?" => "yes",
+ "config_var" => "genSecret"
+ },
+ {
+ "Number of secrets to keep?" => "10",
+ "config_var" => "keepSecrets"
+ }
+ ],
+ $::ldapConfFile => [
+ {
+ "Do you want to set up LDAP?" => "no",
+ "config_var" => "setupLdap"
+ },
+ {
+ "LDAP server hostname" => "",
+ "config_var" => "hostname"
+ },
+ {
+ "LDAP Admin DN" => "",
+ "config_var" => "admin_dn"
+ },
+ {
+ "LDAP Admin Password" => "",
+ "config_var" => "password",
+ "hidden" => "true"
+ },
+ {
+ "LDAP Search Base" => "",
+ "config_var" => "search_base"
+ }
+ ],
+ $::usersConfFile => [
+ {
+ "Administration username for Traffic Ops" => "admin",
+ "config_var" => "tmAdminUser"
+ },
+ {
+ "Password for the admin user" => "",
+ "config_var" => "tmAdminPw",
+ "hidden" => "true"
+ }
+ ],
+ $::profilesConfFile => [],
+ $::opensslConfFile => [
+ {
+ "Country Name (2 letter code)" => "XX",
+ "config_var" => "country"
+ },
+ {
+ "State or Province Name (full name)" => "San Jose",
+ "config_var" => "state"
+ },
+ {
+ "Locality Name (eg, city)" => "Default City",
+ "config_var" => "locality"
+ },
+ {
+ "Organization Name (eg, company)" => "Default Company Ltd",
+ "config_var" => "company"
+ },
+ {
+ "Organizational Unit Name (eg, section)" => "",
+ "config_var" => "org_unit"
+ },
+ {
+ "Common Name (eg, your name or your server's hostname)" => "cisco.com",
+ "config_var" => "common_name"
+ },
+ {
+ "RSA Passphrase" => "",
+ "config_var" => "rsaPassword",
+ "hidden" => "true"
+ }
+ ],
+ $::paramConfFile = [
+ {
+ "Traffic Ops url" => "https://localhost",
+ "config_var" => "tm.url"
+ },
+ {
+ "Human-readable CDN Name. (No whitespace, please)" => "kabletown_cdn",
+ "config_var" => "cdn_name"
+ },
+ {
+ "Health Polling Interval (milliseconds)" => "8000",
+ "config_var" => "health_polling_int"
+ },
+ {
+ "DNS sub-domain for which your CDN is authoritative" => "cdn1.kabletown.net",
+ "config_var" => "dns_subdomain"
+ },
+ {
+ "TLD SOA admin" => "traffic_ops",
+ "config_var" => "soa_admin"
+ },
+ {
+ "TrafficServer Drive Prefix" => "/dev/sd",
+ "config_var" => "driver_prefix"
+ },
+ {
+ "TrafficServer RAM Drive Prefix" => "/dev/ram",
+ "config_var" => "ram_drive_prefix"
+ },
+ {
+ "TrafficServer RAM Drive Letters (comma separated)" => "0,1,2,3,4,5,6,7",
+ "config_var" => "ram_drive_letters"
+ },
+ {
+ "Health Threshold Load Average" => "25",
+ "config_var" => "health_thresh_load_avg"
+ },
+ {
+ "Health Threshold Available Bandwidth in Kbps" => "1750000",
+ "config_var" => "health_thresh_kbps"
+ },
+ {
+ "Traffic Server Health Connection Timeout (milliseconds)" => "2000",
+ "config_var" => "health_connect_timeout"
+ }
+
+ ]
+ };
+}
+
+sub setupDatabase {
+ my $todbconf = shift;
+ my $opensslconf = shift;
+
+ #
+ # Call mysql initialization script.
+ #
+ logger( "Creating database with user: $todbconf->{dbAdminUser}", "info" );
+ my $result = execCommand( "/opt/traffic_ops/install/bin/create_db", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} );
+ if ( $result != 0 ) {
+ errorOut("Failed to create the database");
+ }
+
+ logger( "Setting up database", "info" );
+ chdir("/opt/traffic_ops/app");
+ $result = execCommand( "/usr/bin/perl", "db/admin.pl", "--env=production", "setup" );
+
+ if ( $result != 0 ) {
+ errorOut("Database initialization failed");
+ }
+ else {
+ logger( "Database initialization succeeded", "info" );
+ }
+
+ $result = execCommand( "/opt/traffic_ops/install/bin/dataload", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} );
+ if ( $result != 0 ) {
+ logger( "Failed to load seed data", "error" );
+ }
+
+ logger( "Downloading MaxMind data", "info" );
+ chdir("/opt/traffic_ops/app/public/routing");
+ $result = execCommand( "/usr/bin/wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" );
+ if ( $result != 0 ) {
+ logger( "Failed to download MaxMind data", "error" );
+ }
+
+ logger( "Copying coverage zone file to public dir", "info" );
+ $result = execCommand("/bin/mv /opt/traffic_ops/app/public/coverage-zone.json .");
+ if ( $result != 0 ) {
+ logger( "Failed to copy coverage zone file", "error" );
+ }
+
+ if ( -x "/usr/bin/openssl" ) {
+ logger( "Installing SSL Certificates", "info" );
+ $result = GenerateCert::createCert($opensslconf);
+
+ if ( $result != 0 ) {
+ errorOut("SSL Certificate Installation failed");
+ }
+ else {
+ logger( "SSL Certificates have been installed", "info" );
+ }
+ }
+ else {
+ logger( "Unable to install SSL certificates as openssl is not installed", "error" );
+ logger( "Install openssl and then run /opt/traffic_ops/install/bin/generateCert to install SSL certificates", "error" );
+ exit 4;
+ }
+
+}
+
+# -d - Debug Mode: More output to the terminal
+# -a - Automatic mode: If there are questions in the config file which do not have answers, the script
+# will look to the defaults for the answer. If the answer is not in the defaults
+# the script will exit
+# -h - Help: Basic command line help menu
+# -cfile - Input File: The input config file used to ask and answer questions
+
+sub main {
+ our $inputFile = "";
+ our $automatic = 0;
+ our $debug = 0;
+ my $help = 0;
+
+ my $usageString = "Usage: postinstall [-a] [-d] -cfile=[config_file]\n";
+
+ GetOptions(
+ "cfile=s" => \$inputFile,
+ "automatic" => \$automatic,
+ "reconfigure" => \$reconfigure,
+ "defaults" => \$dumpDefaults,
+ "debug" => \$debug,
+ "help" => \$help
+ ) or die($usageString);
+
+ # stores the default questions and answers
+ our $defaultInputs = getDefaults();
+
+ if ($help) {
+ print $usageString;
+ exit(0);
+ }
+
+ if ( $ENV{USER} ne "root" ) {
+ errorOut("You must run this script as the root user");
+ }
+
+ logger( "Starting postinstall", "info" );
+
+ if ($::debug) {
+ logger( "Debug is on", "info" );
+ }
+
+ if ($::automatic) {
+ logger( "Running in automatic mode", "info" );
+ }
+
+ if ( -f $reconfigure_file ) {
+ logger( "$reconfigure_file file is reprecated - please remove and rerun postinstall", "error" );
+ exit(-1);
+ }
+
+ if ($dumpDefaults) {
+ logger( "Writing default configuration file to $outputConfigFile", "info" );
+ writeJson( $outputConfigFile, $::defaultInputs );
+ exit(0);
+ }
+
+ if ($reconfigure) {
+ logger( "Postinstall is in reconfigure mode", "info" );
+ }
+ else {
+ logger( "Postinstall not in reconfigure mode", "info" );
+ }
+
+ # check if the user has root access
+ if ( $ENV{USER} ne "root" ) {
+ errorOut("You must run this script as the root user");
+ }
+
+ rotateLog($cpanLogFile);
+
+ if ( -s $::logFile > $maxLogSize ) {
+ logger( "Postinstall log above max size of $maxLogSize bytes - rotating", "info" );
+ rotateLog($logFile);
+ }
+
+ # used to store the questions and answers provided by the user
+ my $userInput;
+
+ # if no input file provided use the defaults
+ if ( $::inputFile eq "" ) {
+ logger( "No input file given - using defaults", "info" );
+ $userInput = $::defaultInputs;
+ }
+ else {
+ logger( "Using input file $::inputFile", "info" );
+
+ # check if the input file exists
+ errorOut("File \'$::inputFile\' not found") if ( !-f $::inputFile );
+
+ # read and store the input file
+ $userInput = readJson($::inputFile);
+ }
+
+ # sanity check the defaults if running them automatically
+ sanityCheckDefaults();
+
+ # check the input config file against the defaults to check for missing questions
+ sanityCheckConfig($userInput) if ( $inputFile ne "" );
+
+ chdir("/opt/traffic_ops/install/bin");
+
+ # if the reconfigure file exists or reconfigure is set then rebuild the perl deps
+ if ( -f $reconfigure_file || $reconfigure ) {
+ my $rc = BuildPerlDeps::build(1);
+ if ( $rc != 0 ) {
+ errorOut( "Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error" );
+ }
+ $rc = execCommand( "./download_web_deps", "-i" );
+ if ( $rc != 0 ) {
+ errorOut( "Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error" );
+ }
+ }
+ else {
+ my $rc = BuildPerlDeps::build();
+ if ( $rc != 0 ) {
+ errorOut( "Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error" );
+ }
+ $rc = execCommand( "./download_web_deps", "-i" );
+ if ( $rc != 0 ) {
+ errorOut( "Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error" );
+ }
+ }
+
+ # The generator functions handle checking input/default/automatic mode
+
+ # todbconf will be used later when setting up the database
+ my $todbconf = generateDbConf( $userInput, $::databaseConfFile, $::dbConfFile );
+ generateCdnConf( $userInput, $::cdnConfFile );
+ generateLdapConf( $userInput, $::ldapConfFile );
+ my $adminconf = generateUsersConf( $userInput, $::usersConfFile );
+ generateProfilesDir( $userInput, $::profilesConfFile );
+ generateOpenSSLConf( $userInput, $::opensslConfFile );
+ my $paramconf = generateParamConf( $userInput, $::paramConfFile );
+
+ # if the reconfigure file exists or the reconfigure command line arg is set then setup the database
+ if ( -f $reconfigure_file || $reconfigure ) {
+ if ($::automatic) {
+ setupDatabase( $todbconf, $::opensslConfFile );
+ }
+ else {
+ setupDatabase( $todbconf, 0 );
+ }
+ }
+
+ # remove the reconfigure file if it exists
+ if ( -f $reconfigure_file ) {
+ logger( "Removing reconfigure file", "info" );
+ unlink($reconfigure_file);
+ }
+
+ logger( "Starting Traffic Ops", "info" );
+ execCommand("/sbin/service traffic_ops start");
+
+ logger( "Waiting for Traffic Ops to start", "info" );
+
+ if ( !profiles_exist( $adminconf, $paramconf->{"tm.url"} ) ) {
+ print "Creating default profiles...\n";
+ replace_profile_templates( $paramconf, $paramconf->{"tm.url"} );
+ import_profiles($adminconf);
+ profiles_exist($adminconf); # call again to create $reconfigure_defaults file if import was successful
+ }
+ else {
+ print "Not creating default profiles.\n";
+ }
+}
+
+main;
+
+logger( "Postinstall complete\n", "info" );
+
+# vi:syntax=perl
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/BuildPerlDeps.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/BuildPerlDeps.pm b/traffic_ops/install/lib/BuildPerlDeps.pm
new file mode 100644
index 0000000..302d5c3
--- /dev/null
+++ b/traffic_ops/install/lib/BuildPerlDeps.pm
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+#
+# Copyright 2015 Comcast Cable Communications Management, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/lib/perl5 /opt/traffic_ops/app/lib);
+
+package BuildPerlDeps;
+
+use InstallUtils qw{ :all };
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ build };
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+sub build {
+ my $opt_i = shift;
+
+ my @dependencies = ( "expat-devel", "mod_ssl", "mkisofs", "libpcap", "libpcap-devel", "libcurl", "libcurl-devel", "mysql-server", "mysql-devel", "openssl", "openssl-devel", "cpan", "gcc", "make", "pkgconfig", "automake", "autoconf", "libtool", "gettext", "libidn-devel" );
+
+ my $msg = << 'EOF';
+
+This script will build and package the required Traffic Ops perl modules.
+In order to complete this operation, Development tools such as the gcc
+compiler will be installed on this machine.
+
+EOF
+
+ $ENV{PERL_MM_USE_DEFAULT} = 1;
+ $ENV{PERL_MM_NONINTERACTIVE} = 1;
+ $ENV{AUTOMATED_TESTING} = 1;
+
+ my $result;
+
+ if ( $ENV{USER} ne "root" ) {
+ errorOut("You must run this script as the root user");
+ }
+
+ logger( $msg, "info" );
+
+ chdir("/opt/traffic_ops/app");
+
+ if ( defined $opt_i && $opt_i == 1 ) {
+ if ( !-x "/usr/bin/yum" ) {
+ errorOut("You must install 'yum'");
+ }
+
+ logger( "Installing dependencies", "info" );
+ $result = execCommand( "/usr/bin/yum", "install", @dependencies );
+ if ( $result != 0 ) {
+ errorOut("Dependency installation failed, look through the output and correct the problem");
+ }
+ logger( "Building perl modules", "info" );
+
+ $result = execCommand( "/opt/traffic_ops/install/bin/cpan.sh", "pi_custom_log=" . $::cpanLogFile, "/opt/traffic_ops/install/bin/yaml.txt" );
+ if ( $result != 0 ) {
+ errorOut("Failed to install YAML, look through the output and correct the problem");
+ }
+
+ $result = execCommand( "/opt/traffic_ops/install/bin/cpan.sh", "pi_custom_log=" . $::cpanLogFile, "/opt/traffic_ops/install/bin/carton.txt" );
+ if ( $result != 0 ) {
+ errorOut("Failed to install Carton, look through the output and correct the problem");
+ }
+ }
+
+ $result = execCommand( "/usr/local/bin/carton", "install", "--deployment", "--cached" );
+ if ( $result != 0 ) {
+ errorOut("Failure to build required perl modules, check the output and correct the problem");
+ }
+
+ if ( !-s "/opt/traffic_ops/lib/perl5" ) {
+ logger( "Linking perl libraries...", "info" );
+ if ( !-d "/opt/traffic_ops/lib" ) {
+ mkdir("/opt/traffic_ops/lib");
+ }
+ symlink( "/opt/traffic_ops/app/local/lib/perl5", "/opt/traffic_ops/lib/perl5" );
+ execCommand( "/bin/chown", "-R", "trafops:trafops", "/opt/traffic_ops/lib" );
+ }
+ logger( "Installing perl scripts", "info" );
+ chdir("/opt/traffic_ops/app/local/bin");
+ my $rc = execCommand( "/bin/cp", "-R", ".", "/opt/traffic_ops/app/bin" );
+ if ( $rc != 0 ) {
+ logger( "Failed to copy perl scripts to /opt/traffic_ops/app/bin", "error" );
+ }
+
+ return 0;
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/GenerateCert.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/GenerateCert.pm b/traffic_ops/install/lib/GenerateCert.pm
new file mode 100644
index 0000000..efdcc96
--- /dev/null
+++ b/traffic_ops/install/lib/GenerateCert.pm
@@ -0,0 +1,232 @@
+#!/usr/bin/perl
+
+#
+# Copyright 2015 Comcast Cable Communications Management, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+package GenerateCert;
+
+use strict;
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/lib/perl5 /opt/traffic_ops/app/lib);
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ createCert };
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+use JSON;
+use InstallUtils;
+use File::Temp;
+use Data::Dumper;
+use File::Copy;
+use InstallUtils qw{ :all };
+
+my $ca = "/etc/pki/tls/certs/localhost.ca";
+my $csr = "/etc/pki/tls/certs/localhost.csr";
+my $cert = "/etc/pki/tls/certs/localhost.crt";
+my $cdn_conf = "/opt/traffic_ops/app/conf/cdn.conf";
+my $key = "/etc/pki/tls/private/localhost.key";
+my $msg = << 'EOF';
+
+ We're now running a script to generate a self signed X509 SSL certificate.
+
+EOF
+
+sub writeCdn_conf {
+ my $cdn_conf = shift;
+
+ # listen param to be inserted
+ my $listen_str = "https://[::]:443?cert=${cert}&key=${key}&ca=${ca}&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED";
+
+ # load as perl hash to find string to be replaced
+ my $cdnh = do $cdn_conf;
+ if ( exists $cdnh->{hypnotoad} ) {
+ $cdnh->{hypnotoad}{listen} = [$listen_str];
+ }
+ else {
+
+ # add the whole hypnotoad config without affecting anything else in the config
+ $cdnh->{hypnotoad} = {
+ listen => [$listen_str],
+ user => 'trafops',
+ group => 'trafops',
+ pid_file => '/var/run/traffic_ops.pid',
+ workers => 48,
+ };
+ }
+
+ # dump conf data in compact but readable form
+ my $dumper = Data::Dumper->new( [$cdnh] );
+ $dumper->Indent(1)->Terse(1)->Quotekeys(0);
+
+ # write whole config to temp file in pwd (keeps in same filesystem)
+ my $tmpfile = File::Temp->new( DIR => '.' );
+ print $tmpfile $dumper->Dump();
+ close $tmpfile;
+
+ # make backup of current file
+ my $backup_num = 0;
+ my $backup_name;
+ do {
+ $backup_num++;
+ $backup_name = "$cdn_conf.backup$backup_num";
+ } while ( -e $backup_name );
+ rename( $cdn_conf, $backup_name ) or die("rename(): $!");
+
+ # rename temp file to cdn.conf and set ownership/permissions same as backup
+ my @stats = stat($backup_name);
+ my ( $uid, $gid, $perm ) = @stats[ 4, 5, 2 ];
+ move( "$tmpfile", $cdn_conf ) or die("move(): $!");
+
+ chown $uid, $gid, $cdn_conf;
+ chmod $perm, $cdn_conf;
+}
+
+# execOpenssl takes a description of the command being done, and an array of arguments to OpenSSL,
+# and tries to execute the command, on failure prompting the user to retry.
+# The description should be capitalized, but not terminated with punctuation.
+# Returns the OpenSSL exit code.
+sub execOpenssl {
+ my ( $description, @args ) = @_;
+ logger( $description, "info" );
+ my $result = 1;
+ while ( $result != 0 ) {
+ $result = InstallUtils::execCommand( "openssl", @args );
+ if ( $result != 0 ) {
+ my $ans = "";
+ while ( $ans !~ /^[yY]/ && $ans !~ /^[nN]/ ) {
+ $ans = InstallUtils::promptUser( $description . " failed. Try again (y/n)", "y" );
+ }
+ if ( $ans =~ /^[nN]/ ) {
+ return $result;
+ }
+ }
+ }
+ return $result;
+}
+
+sub createCert {
+
+ # the file used for ssl configuration
+ my $opensslconf = shift;
+
+ if ( !defined $opensslconf ) {
+ logger( "No input file - running openssl configuration in interactive mode", "info" );
+ }
+
+ logger( $msg, "info" );
+
+ logger( "Postinstall SSL Certificate Creation", "info" );
+
+ my $params;
+ my $passphrase;
+
+ # load the parameters for the certificate
+ if ( defined $opensslconf ) {
+ my $config = InstallUtils::readJson($opensslconf);
+ if ( defined $config->{country} ) {
+
+ # the parameters to auto generate the certificate
+ $params = "/C=$config->{country}/ST=$config->{state}/L=$config->{locality}/O=$config->{company}/OU=$config->{org_unit}/CN=$config->{common_name}/";
+
+ $passphrase = $config->{rsaPassword};
+ }
+ }
+
+ if ( execOpenssl( "Generating an RSA Private Server Key", "genrsa", "-des3", "-out", "server.key", "-passout", "pass:$passphrase", "1024" ) != 0 ) {
+ exit 1;
+ }
+ logger( "The server key has been generated", "info" );
+
+ if ($params) {
+ if ( execOpenssl( "Creating a Certificate Signing Request (CSR)", "req", "-new", "-key", "server.key", "-out", "server.csr", "-passin", "pass:$passphrase", "-subj", $params ) != 0 ) {
+ exit 1;
+ }
+ }
+ else {
+ if ( execOpenssl( "Creating a Certificate Signing Request (CSR)", "req", "-new", "-key", "server.key", "-out", "server.csr", "-passin", "pass:$passphrase" ) != 0 ) {
+ exit 1;
+ }
+ }
+
+ logger( "The Certificate Signing Request has been generated", "info" );
+
+ InstallUtils::execCommand( "/bin/mv", "server.key", "server.key.orig" );
+
+ if ( execOpenssl( "Removing the pass phrase from the server key", "rsa", "-in", "server.key.orig", "-out", "server.key", "-passin", "pass:$passphrase" ) != 0 ) {
+ exit 1;
+ }
+ logger( "The pass phrase has been removed from the server key", "info" );
+
+ if ( execOpenssl( "Generating a Self-signed certificate", "x509", "-req", "-days", "365", "-in", "server.csr", "-signkey", "server.key", "-out", "server.crt" ) != 0 ) {
+ exit 1;
+ }
+ logger( "A server key and self signed certificate has been generated", "info" );
+
+ logger( "Installing the server key and server certificate", "info" );
+
+ my $result = InstallUtils::execCommand( "/bin/cp", "server.key", "$key" );
+ if ( $result != 0 ) {
+ errorOut("Failed to install the private server key");
+ }
+ $result = InstallUtils::execCommand( "/bin/chmod", "600", "$key" );
+ $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", "$key" );
+
+ if ( $result != 0 ) {
+ errorOut("Failed to install the private server key");
+ }
+
+ logger( "The private key has been installed", "info" );
+ logger( "Installing the self signed certificate", "info" );
+
+ $result = InstallUtils::execCommand( "/bin/cp", "server.crt", "$cert" );
+
+ if ( $result != 0 ) {
+ errorOut("Failed to install the self signed certificate");
+ }
+
+ $result = InstallUtils::execCommand( "/bin/chmod", "600", "$cert" );
+ $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", "$cert" );
+
+ if ( $result != 0 ) {
+ errorOut("Failed to install the self signed certificate");
+ }
+
+ logger( "Saving the self signed csr", "info" );
+ $result = InstallUtils::execCommand( "/bin/cp", "server.csr", "$csr" );
+
+ if ( $result != 0 ) {
+ errorOut("Failed to save the self signed csr");
+ }
+ $result = InstallUtils::execCommand( "/bin/chmod", "664", "$csr" );
+ $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", "$csr" );
+
+ writeCdn_conf($cdn_conf);
+
+ my $msg = << 'EOF';
+
+ The self signed certificate has now been installed.
+
+ You may obtain a certificate signed by a Certificate Authority using the
+ server.csr file saved in the current directory. Once you have obtained
+ a signed certificate, copy it to /etc/pki/tls/certs/localhost.crt and
+ restart Traffic Ops.
+
+EOF
+
+ logger( $msg, "info" );
+
+ return 0;
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/InstallUtils.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/InstallUtils.pm b/traffic_ops/install/lib/InstallUtils.pm
index 4d45e7d..72665d1 100644
--- a/traffic_ops/install/lib/InstallUtils.pm
+++ b/traffic_ops/install/lib/InstallUtils.pm
@@ -24,126 +24,216 @@ package InstallUtils;
use Term::ReadPassword;
+use JSON;
+use IO::Pipe;
use base qw{ Exporter };
-our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired promptPassword promptPasswordVerify trim readJson writeJson writePerl};
+our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired promptPassword promptPasswordVerify trim readJson writeJson writePerl errorOut logger rotateLog};
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
sub execCommand {
- my ( $cmd, @args ) = @_;
- system( $cmd, @args );
- my $result = $? >> 8;
- return $result;
+ my ( $command, @args ) = @_;
+
+ my $pipe = IO::Pipe->new;
+ my $pid;
+ my $result = 0;
+ my $customLog = "";
+
+ # find log file in args and remove if found
+ # TODO: More documentation here
+ foreach my $var (@args) {
+ if ( index($var, "pi_custom_log=") != -1 ) {
+ $customLog = (split(/=/, $var))[1];
+ splice(@args, index($var, "pi_custom_log="), 1);
+ logger("Using custom log \'$customLog\'", "info");
+ }
+ }
+
+ # create pipe between child and parent and redirect output from child to parent for logging
+ my $child = open READER, '-|';
+ defined $child or die "pipe/fork: $!\n";
+ if ($child) { #parent
+ while ( $line = <READER> ) {
+ # log all output from child pipe
+ if ($customLog ne "") {
+ logger($line, "info", $customLog);
+ }
+ else {
+ logger($line, "info");
+ }
+ }
+ }
+ else { #child
+ # redirect stderr to stdout so parent can read
+ open STDERR, '>&STDOUT';
+ exec($command, @args) or exit(1);
+ }
+}
+
+sub errorOut {
+ logger( @_, "error" );
+ die;
+}
+
+# moves a log to file to a backup file with the same name appended with .bkp
+# This function is intended to keep log file sizes low and is called from postinstall
+sub rotateLog {
+ my $logFileName = shift;
+
+ if ( !-f $logFileName ) {
+ logger("Log file \'$logFileName\' does not exist - not rotating log", "error");
+ return;
+ }
+
+ execCommand('/bin/mv', '-f', $logFileName, $logFileName . '.bkp');
+ logger("Rotated log $logFileName", "info");
+}
+
+# outputs logging messages to terminal and log file
+sub logger {
+ my $output = shift;
+ my $type = shift;
+
+ # optional custom log file to use instead of main log file used by postinstall
+ # cpan uses a custom log file because of its size
+ my $customLogFile = shift;
+
+ my $message = $output;
+ if (index($message, "\n") == -1) {
+ $message = $message . "\n";
+ }
+
+ # if in debug mode or message is more critical than info print to console
+ if ( $::debug || ( defined $type && $type ne "" && $type ne "info" ) ) {
+ print($message);
+ }
+
+ # output to log file
+ my $fh;
+ my $result = 0;
+ if ( defined $customLogFile && $customLogFile ne "" ) {
+ open $fh, '>>', $customLogFile or die("Couldn't open log file \'$::customLogFile\'");
+ $result = 1;
+ }
+ else {
+ if ($::logFile) {
+ open $fh, '>>', $::logFile or die("Couldn't open log file \'$::logFile\'");
+ $result = 1;
+ }
+ }
+
+ if ( $result ) {
+ print $fh localtime . ": " . uc($type) . ' ' . $message;
+ close $fh;
+ }
}
sub randomWord {
- my $length = shift || 12;
- my $secret = '';
- while ( length($secret) < $length ) {
- my $c = chr( rand(0x7F) );
- if ( $c =~ /\w/ ) {
- $secret .= $c;
- }
- }
- return $secret;
+ my $length = shift || 12;
+ my $secret = '';
+ while ( length($secret) < $length ) {
+ my $c = chr( rand(0x7F) );
+ if ( $c =~ /\w/ ) {
+ $secret .= $c;
+ }
+ }
+ return $secret;
}
sub promptUser {
- my ( $promptString, $defaultValue, $noEcho ) = @_;
-
- if ($defaultValue) {
- print $promptString, " [", $defaultValue, "]: ";
- }
- else {
- print $promptString, ": ";
- }
-
- if ( defined $noEcho && $noEcho ) {
- my $response = read_password('');
- if ( ( !defined $response || $response eq '' ) && ( defined $defaultValue && $defaultValue ne '' ) ) {
- $response = $defaultValue;
- }
- return $response;
- }
- else {
- $| = 1;
- $_ = <STDIN>;
- chomp;
-
- if ("$defaultValue") {
- return $_ ? $_ : $defaultValue;
- }
- else {
- return $_;
- }
- return $_;
- }
+ my ( $promptString, $defaultValue, $noEcho ) = @_;
+
+ if ($defaultValue) {
+ print $promptString, " [", $defaultValue, "]: ";
+ }
+ else {
+ print $promptString, ": ";
+ }
+
+ if ( defined $noEcho && $noEcho ) {
+ my $response = read_password('');
+ if ( ( !defined $response || $response eq '' ) && ( defined $defaultValue && $defaultValue ne '' ) ) {
+ $response = $defaultValue;
+ }
+ return $response;
+ }
+ else {
+ $| = 1;
+ $_ = <STDIN>;
+ chomp;
+
+ if ("$defaultValue") {
+ return $_ ? $_ : $defaultValue;
+ }
+ else {
+ return $_;
+ }
+ return $_;
+ }
}
sub promptRequired {
- my $val = '';
- while ( length($val) == 0 ) {
- $val = promptUser(@_);
- }
- return $val;
+ my $val = '';
+ while ( length($val) == 0 ) {
+ $val = promptUser(@_);
+ }
+ return $val;
}
sub promptPassword {
- my $prompt = shift;
- my $pw = promptRequired( $prompt, '', 1 );
- return $pw;
+ my $prompt = shift;
+ my $pw = promptRequired( $prompt, '', 1 );
+ return $pw;
}
sub promptPasswordVerify {
- my $prompt = shift;
- my $pw = shift;
-
- while (1) {
- $pw = promptPassword($prompt);
- my $verify = promptPassword("Re-Enter $prompt");
- last if $pw eq $verify;
- print "\nError: passwords do not match, try again.\n\n";
- }
- return $pw;
+ my $prompt = shift;
+ my $pw = shift;
+
+ while (1) {
+ $pw = promptPassword($prompt);
+ my $verify = promptPassword("Re-Enter $prompt");
+ last if $pw eq $verify;
+ print "\nError: passwords do not match, try again.\n\n";
+ }
+ return $pw;
}
sub trim {
- my $str = shift;
+ my $str = shift;
- $str =~ s/^\s+//;
- $str =~ s/^\s+$//;
+ $str =~ s/^\s+//;
+ $str =~ s/^\s+$//;
- return $str;
+ return $str;
}
sub readJson {
- my $file = shift;
- open( my $fh, '<', $file ) or return;
- local $/; # slurp mode
- my $text = <$fh>;
- undef $fh;
- return JSON->new->utf8->decode($text);
+ my $file = shift;
+ open( my $fh, '<', $file ) or die("open(): $!");
+ local $/; # slurp mode
+ my $text = <$fh>;
+ undef $fh;
+ return JSON->new->utf8->decode($text);
}
sub writeJson {
- my $file = shift;
- open( my $fh, '>', $file ) or die("open(): $!");
- foreach my $data (@_) {
- my $json_text = JSON->new->utf8->pretty->encode($data);
- print $fh $json_text, "\n";
- }
- close $fh;
+ my $file = shift;
+ open( my $fh, '>', $file ) or die("open(): $!");
+ foreach my $data (@_) {
+ my $json_text = JSON->new->utf8->pretty->encode($data);
+ print $fh $json_text, "\n";
+ }
+ close $fh;
}
sub writePerl {
- my $file = shift;
- my $data = shift;
-
- open( my $fh, '>', $file ) or die("open(): $!");
- my $dumper = Data::Dumper->new([ $data ]);
-
- # print without var names and with simple indentation
- print $fh $dumper->Terse(1)->Dump();
- close $fh;
-}
+ my $file = shift;
+ my $data = shift;
-1;
+ open( my $fh, '>', $file ) or die("open(): $!");
+ my $dumper = Data::Dumper->new( [$data] );
+
+ # print without var names and with simple indentation
+ print $fh $dumper->Terse(1)->Dump();
+ close $fh;
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/ProfileCleanup.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/ProfileCleanup.pm b/traffic_ops/install/lib/ProfileCleanup.pm
new file mode 100644
index 0000000..3606002
--- /dev/null
+++ b/traffic_ops/install/lib/ProfileCleanup.pm
@@ -0,0 +1,192 @@
+
+package ProfileCleanup;
+
+use InstallUtils qw{ :all };
+use WWW::Curl::Easy;
+use LWP::UserAgent;
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ replace_profile_templates import_profiles profiles_exist };
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+sub profile_replace {
+ my ($profile) = @_;
+ my $profile_bak = $profile . ".bak";
+ rename( $profile, $profile_bak ) or die("rename(): $!");
+ open( my $fh, '<', $profile_bak ) or die("open(): $!");
+ open( my $ofh, '>', $profile ) or die("open(): $!");
+ while (<$fh>) {
+ s/{{.TmUrl}}/$::parameters->{'tm.url'}/g;
+ s/{{.TmInfoUrl}}/$::parameters->{"tminfo.url"}/g;
+ s/{{.TmInstanceName}}/$::parameters->{"cdnname"}/g;
+ s/{{.GeolocationPollingUrl}}/$::parameters->{"geolocation.polling.url"}/g;
+ s/{{.Geolocation6PollingUrl}}/$::parameters->{"geolocation6.polling.url"}/g;
+ s/{{.TmUrl}}/$::parameters->{'tm.url'}/g;
+ s/{{.TmToolName}}/Traffic Ops/g;
+ s/{{.HealthPollingInterval}}/$::parameters->{"health.polling.interval"}/g;
+ s/{{.CoveragezonePollingUrl}}/$::parameters->{"coveragezone.polling.url"}/g;
+ s/{{.DomainName}}/$::parameters->{"domainname"}/g;
+ s/{{.TldSoaAdmin}}/$::parameters->{"tld.soa.admin"}/g;
+ s/{{.DrivePrefix}}/$::parameters->{"Drive_Prefix"}/g;
+ s/{{.HealthThresholdLoadavg}}/$::parameters->{"health.threshold.loadavg"}/g;
+ s/{{.HealthThresholdAvailableBandwidthInKbps}}/$::parameters->{"health.threshold.availableBandwidthInKbps"}/g;
+ s/{{.RAMDrivePrefix}}/$::parameters->{"RAM_Drive_Prefix"}/g;
+ s/{{.RAMDriveLetters}}/$::parameters->{"RAM_Drive_Letters"}/g;
+ s/{{.HealthConnectionTimeout}}/$::parameters->{"health.connection.timeout"}/g;
+ s#{{.CronOrtSyncds}}#*/15 * * * * root /opt/ort/traffic_ops_ort.pl syncds warn $::parameters->{'tm.url'} $tmAdminUser:$tmAdminPw > /tmp/ort/syncds.log 2>&1#g;
+ print $ofh $_;
+ }
+ close $fh;
+ close $ofh;
+ unlink $profile_bak;
+}
+
+sub replace_profile_templates {
+ my $conf = shift;
+
+ $::parameters->{'tm.url'} = $conf->{"tm.url"};
+ $::parameters->{"tminfo.url"} = "$::parameters->{'tm.url'}/info";
+ $::parameters->{"cdnname"} = $conf->{"cdn_name"};
+ $::parameters->{"geolocation.polling.url"} = "$::parameters->{'tm.url'}/routing/GeoIP2-City.mmdb.gz";
+ $::parameters->{"geolocation6.polling.url"} = "$::parameters->{'tm.url'}/routing/GeoIP2-Cityv6.mmdb.gz";
+ $::parameters->{"health.polling.interval"} = $conf->{"health_polling_int"};
+ $::parameters->{"coveragezone.polling.url"} = "$::parameters->{'tm.url'}/routing/coverage-zone.json";
+ $::parameters->{"domainname"} = $conf->{"dns_subdomain"};
+ $::parameters->{"tld.soa.admin"} = $conf->{"soa_admin"};
+ $::parameters->{"Drive_Prefix"} = $conf->{"driver_prefix"};
+ $::parameters->{"RAM_Drive_Prefix"} = $conf->{"ram_drive_prefix"};
+ $::parameters->{"RAM_Drive_Letters"} = $conf->{"ram_drive_letters"};
+ $::parameters->{"health.threshold.loadavg"} = $conf->{"health_thresh_load_avg"};
+ $::parameters->{"health.threshold.availableBandwidthInKbps"} = $conf->{"health_thresh_kbps"};
+ $::parameters->{"health.connection.timeout"} = $conf->{"health_connect_timeout"};
+
+ profile_replace( $::profile_dir . "profile.global.traffic_ops" );
+ profile_replace( $::profile_dir . "profile.traffic_monitor.traffic_ops" );
+ profile_replace( $::profile_dir . "profile.traffic_router.traffic_ops" );
+ profile_replace( $::profile_dir . "profile.trafficserver_edge.traffic_ops" );
+ profile_replace( $::profile_dir . "profile.trafficserver_mid.traffic_ops" );
+ writeJson( $::post_install_cfg, $::parameters );
+}
+
+# Takes the Traffic Ops URI, user, and password.
+# Returns the cookie, or the empty string on error
+sub get_traffic_ops_cookie {
+ my ( $uri, $user, $pass ) = @_;
+
+ my $loginUri = "/api/1.2/user/login";
+
+ my $curl = WWW::Curl::Easy->new;
+ my $response_body = "";
+ open( my $fileb, ">", \$response_body );
+ my $loginData = JSON::encode_json( { u => $user, p => $pass } );
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_URL, $uri . $loginUri );
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_SSL_VERIFYPEER, 0 );
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_HEADER, 1 ); # include header in response
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_NOBODY, 1 ); # disclude body in response
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_POST, 1 );
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_POSTFIELDS, $loginData );
+ $curl->setopt( WWW::Curl::Easy::CURLOPT_WRITEDATA, $fileb ); # put response in this var
+ $curl->perform();
+
+ my $cookie = $response_body;
+ if ( $cookie =~ /mojolicious=(.*); expires/ ) {
+ $cookie = $1;
+ }
+ else {
+ $cookie = "";
+ }
+ return $cookie;
+}
+
+# Takes the filename of a Traffic Ops (TO) profile to import, the TO URI, and the TO login cookie
+sub profile_import_single {
+ my ( $profileFilename, $uri, $trafficOpsCookie ) = @_;
+ logger( "Importing Profiles with: " . "curl -v -k -X POST -H \"Cookie: mojolicious=$trafficOpsCookie\" -F \"filename=$profileFilename\" -F \"profile_to_import=\@$profileFilename\" $uri/profile/doImport", "info" );
+ my $rc = execCommand("curl -v -k -X POST -H \"Cookie: mojolicious=$trafficOpsCookie\" -F \"filename=$profileFilename\" -F \"profile_to_import=\@$profileFilename\" $uri/profile/doImport");
+ if ( $rc != 0 ) {
+ logger( "Failed to import Traffic Ops profile, check the console output and rerun postinstall once you've resolved the error", "error" );
+ }
+}
+
+sub import_profiles {
+ my $config = shift;
+ logger( "Importing profiles...", "info" );
+
+ my $toUri = $::parameters->{'tm.url'};
+ my $toUser = $config->{"username"};
+ my $toPass = $config->{"password"};
+
+ my $toCookie = get_traffic_ops_cookie( $toUri, $toUser, $toPass );
+
+ logger( "Got cookie: " . $toCookie, "info" );
+
+ # \todo use an array?
+ logger( "Importing Global profile...", "info" );
+ profile_import_single( $::profile_dir . "profile.global.traffic_ops", $toUri, $toCookie );
+ logger( "Importing Traffic Monitor profile...", "info" );
+ profile_import_single( $::profile_dir . "profile.traffic_monitor.traffic_ops", $toUri, $toCookie );
+ logger( "Importing Traffic Router profile...", "info" );
+ profile_import_single( $::profile_dir . "profile.traffic_router.traffic_ops", $toUri, $toCookie );
+ logger( "Importing TrafficServer Edge profile...", "info" );
+ profile_import_single( $::profile_dir . "profile.trafficserver_edge.traffic_ops", $toUri, $toCookie );
+ logger( "Importing TrafficServer Mid profile...", "info" );
+ profile_import_single( $::profile_dir . "profile.trafficserver_mid.traffic_ops", $toUri, $toCookie );
+ logger( "Finished Importing Profiles.", "info" );
+}
+
+sub profiles_exist {
+ my $config = shift;
+ my $tmurl = shift;
+
+ if ( -f $::reconfigure_defaults ) {
+ logger( "Default profiles were previously created. Remove " . $::reconfigure_defaults . " to create again", "warn" );
+ return 1;
+ }
+
+ $::parameters->{'tm.url'} = $tmurl;
+
+ my $uri = $::parameters->{'tm.url'};
+ my $toCookie = get_traffic_ops_cookie( $::parameters->{'tm.url'}, $config->{"username"}, $config->{"password"} );
+
+ my $profileEndpoint = "/api/1.2/profiles.json";
+
+ my $ua = LWP::UserAgent->new;
+ $ua->ssl_opts( verify_hostname => 0, SSL_verify_mode => 0x00 );
+ my $req = HTTP::Request->new( GET => $uri . $profileEndpoint );
+ $req->header( 'Cookie' => "mojolicious=" . $toCookie );
+ my $resp = $ua->request($req);
+
+ if ( !$resp->is_success ) {
+ logger( "Error checking if profiles exist: " . $resp->status_line, "error" );
+ return 1; # return true, so we don't attempt to create profiles
+ }
+ my $message = $resp->decoded_content;
+
+ my $profiles = JSON->new->utf8->decode($message);
+ if ( ( !defined $profiles->{"response"} )
+ || ( ref $profiles->{"response"} ne 'ARRAY' ) )
+ {
+ logger( "Error checking if profiles exist: invalid JSON: $message", "error" );
+ return 1; # return true, so we don't attempt to create profiles
+ }
+
+ my $num_profiles = scalar( @{ $profiles->{"response"} } );
+ logger( "Existing Profile Count: $num_profiles", "info" );
+
+ my %initial_profiles = (
+ "INFLUXDB" => 1,
+ "RIAK_ALL" => 1,
+ "TRAFFIC_STATS" => 1
+ );
+
+ my $profiles_response = $profiles->{"response"};
+ foreach my $profile (@$profiles_response) {
+ if ( !exists $initial_profiles{ $profile->{"name"} } ) {
+ logger( "Found existing profile (" . $profile->{"name"} . ")", "info" );
+ open( my $reconfigure_defaults_file, '>', $::reconfigure_defaults ) or die("Failed to open() $reconfigure_defaults: $!");
+ close($reconfigure_defaults_file);
+ return 1;
+ }
+ }
+ return 0;
+}