#!/usr/local/lp/lwprov/bin/perl
#============================================================================
# NAME:  xvps_autosetup.pl
#
# DESCRIPTION: Configure server hostname, networking, and root password.
#
# AUTHOR:  Scott Sullivan (ssullivan), ssullivan@liquidweb.com
# COMPANY:  Liquid Web, Inc.
#============================================================================

use strict;
use warnings;
use Time::localtime;

#
# Configuration 
#############################################################################

my %conf = (
	version => '0.9.0',
	lock_file => '/root/xvps_autosetup.lock',
	log_file => '/var/log/xvps_autosetup.log',
	ptype_file => '/usr/local/lp/etc/PTYPE',
	conf_file => '/root/xvps_autosetup.conf',
	halted_file => '/root/xvps_autosetup_HALTED',
);

my %api_store = ();

#
# Methods
#############################################################################

sub verify_nic_intel {
	my $opts = shift;

	system("which ethtool &> /dev/null");
	if ( $? != 0 ) {
		logger('c','ethtool is not in the $PATH. Unable to determine link speed.');
	}

	my $ethtool_output = `ethtool -i $opts->{interface}`; 
	if ( $ethtool_output =~ /driver:\s+e1000/i || $ethtool_output =~ /driver:\s+igb/i || $ethtool_output =~ /driver:\s+ixgbe/i || $ethtool_output =~ /driver:\s+i40e/i ) {
		logger('i',"Good; $opts->{interface} is using an Intel driver");
	}
	else {
		# Place file signifying autosetup intentionally halted.
		open(FH,">$conf{'halted_file'}") or logger('c', "Unable to place $conf{'halted_file'}: $!");
		close(FH);
		logger('c',"$opts->{interface} is not an Intel NIC; Halting autosetup.");
	}
}

sub verify_link_speed {
	my $opts = shift;

	system("which ethtool &> /dev/null");
	if ( $? != 0 ) {
		logger('c','ethtool is not in the $PATH. Unable to determine link speed.');
	}

	if ( `ethtool $opts->{interface}` =~ /\s+Speed:\s+1000Mb\/s/i || `ethtool $opts->{interface}` =~ /\s+Speed:\s+10000Mb\/s/i ) {
		logger('i',"Good; $opts->{interface} is at least 1GB");
	}
	else {
		# Place file signifying autosetup intentionally halted.
		open(FH,">$conf{'halted_file'}") or logger('c', "Unable to place $conf{'halted_file'}: $!");
		close(FH);
		logger('c',"$opts->{interface} is not 1GB; Halting autosetup.");
	}
}

sub read_conf_file {
	my %config;
	open my $config, '<', "$conf{'conf_file'}" || logger('c',"Can't open $conf{'conf_file'}: $!");
	while(<$config>) {
		chomp;
		s/^\s+//;  # rm leading white
		s/\s+$//;  # rm trailing white
		next unless length;
		(my $key, my @value) = split /\s*=\s*/, $_;
		$config{$key} = join '=', @value;
	}
	return \%config;
}

sub populate_api_store {
	my $zone = shift;
	if ( json_ok() && ua_ok() ) {
		my $ptype = get_ptype();
		my $args = {
			type => $ptype,
			zone => $zone,
		};
		my $api_ret = call('provisioning/parent/initialize.json',$args);
		if ( $api_ret->{assignment} && $api_ret->{subaccnt} ) {
			$api_store{'hostname'}	= $api_ret->{subaccnt}->{domain};
			$api_store{'ip_addr'} 	= $api_ret->{subaccnt}->{ip};
			$api_store{'uniq_id'}	= $api_ret->{subaccnt}->{uniq_id};
			$api_store{'netmask'}	= $api_ret->{assignment}->{netmask};
			$api_store{'gateway'}	= $api_ret->{assignment}->{gateway};
			$api_store{'vlan'}	= $api_ret->{assignment}->{vlan};
		} 
		else {
			logger('c',"provisioning/parent/initialize.json bad response! Error class: $api_ret->{error_class} Details:\n$api_ret->{full_message}");
		}
	}
}

sub get_first_line {
	my $file = shift;
	open(FH,"<$file") or logger('c',"Couldn't open file ($file): $!");
	my $char = <FH>;
	chomp($char);
	return $char;
}

sub get_ptype {
	my $ptype = get_first_line("$conf{'ptype_file'}");
	return $ptype;
}

sub call {
	my $method = shift or logger('c',"call() called with no method given!");
	my $params = shift or logger('c',"call() called with no params given!");
	require LWP::UserAgent;
	require JSON;
	my $ua = LWP::UserAgent->new;
	$ua->ssl_opts( verify_hostname => 0 );
	$ua->timeout(120);
	$ua->agent("xvps_autosetup-$conf{'version'}" . $ua->agent);
	my $conf_file = read_conf_file();  
	my $api_host = $conf_file->{'api_url'};
	my $api_string = "$api_host/$method";
	my $req = HTTP::Request->new(POST => $api_string);
	my $username = $conf_file->{'api_user'};
	my $password = $conf_file->{'api_pass'};
	$req->authorization_basic($username,$password);
	my $parser = JSON->new->utf8(1);
	my $json = $parser->encode({
		metadata => { ip => $ENV{REMOTE_ADDR} },
		params => $params,
	});
	$req->content($json);
	my $response = $ua->request($req);
	my $code = $response->code;
	my $content = $response->content;
	if ( $code == 200 ) {
		logger('i',"Good; got response code 200 from API.");
		my $results  = $parser->decode($content);
		return $results;
	}
	else {
		logger('c',"Bad; got response code $code from API. Content:\n$content");
	}
}

sub ua_ok {
	my $ret = (eval { require LWP::UserAgent; 1 }) || 0;
	if ( $ret == 0 ) {
		logger('c',"Couldn't load LWP::UserAgent!");
	}
	return $ret;
}

sub json_ok {
	my $ret = (eval { require JSON; 1 }) || 0;
	if ( $ret == 0 ) {
		logger('c',"Couldn't load the JSON module!");
	}
	return $ret;
}

sub stop_agent {
	system("screen -X -S pagent quit");
	logger('i','Sent p-agent quit.');
}

sub write_billUID {
	logger('c','write_billUID() unable to determine UNIQ_ID.') unless ( $api_store{'uniq_id'} );
	open(FH,">/usr/local/lp/etc/lp-UID") or logger('c',"Unable to open /usr/local/lp/etc/lp-UID for write: $!");
	print FH "$api_store{'uniq_id'}\n";
	close(FH);
	logger('i','Configured /usr/local/lp/etc/lp-UID');	
}

sub set_hosts {
	my $hostname = shift or logger('c',"set_hosts() didnt get hostname to set!");
	my $ip_address = shift or logger('c',"set_hosts() didnt get ip_address to set!");
	my @shortname = split(/\./, $hostname);
	open(FH,">/etc/hosts") or logger('c',"Unable to open /etc/hosts! $!");
	my $contents = "127.0.0.1       localhost localhost.localdomain localhost4 localhost4.localdomain4
::1	     localhost localhost.localdomain localhost6 localhost6.localdomain6
$ip_address      $hostname $shortname[0]";
	print FH "$contents";
	close(FH);
	logger('i',"Configured /etc/hosts.");
}

sub set_hostname {
	my $hostname = shift or logger('c',"set_hostname() didnt get hostname to set!");
	if ( -f '/etc/hostname' ) {
		open(FH,">/etc/hostname") or logger("c", "Unable to open [/etc/hostname]: $!");
		print FH "$hostname\n";
		close(FH);
		logger('i',"Set system hostname to $hostname.");
	}
	elsif ( -f '/etc/sysconfig/network' ) {
		my $sysconf_network;
		open FILE, "</etc/sysconfig/network" or logger("c", "Unable to open [/etc/sysconfig/network]: $!");
		while ( my $line = <FILE> ) {
			$sysconf_network = $line;
		}
		close(FILE);
		if ( $sysconf_network =~ /HOSTNAME=\S+/ ) {
			$sysconf_network =~ s/HOSTNAME=\S+/HOSTNAME=$hostname/;
		}
		else {
			$sysconf_network .= "HOSTNAME=$hostname\n"
		}
		open FILE, ">/etc/sysconfig/network" or logger('c', "Unable to open [/etc/sysconfig/network]: $!");
		print FILE $sysconf_network;	
		close(FILE);
		logger('i',"Set system hostname to $hostname.");
	}
	else { 
		logger('c',"Unable to set system hostname to $hostname.");
	}
}

sub config_networking {
	my $config_file = shift or logger('c',"config_networking() didnt get config_file to modify!");
	my $contents = shift or logger('c',"config_networking() didnt get contents to place!");
	my $skip_auto_hwaddr = shift;

	unless ($skip_auto_hwaddr) {
		# Get  eth0 HWADDR
		for my $hwaddr ( split /^/, `ip addr show eth0` ) {
			if ( $hwaddr =~ /^\s+link\/ether\s+(\S+)\s+/i ) {
				$contents .= uc"\nHWADDR=\"$1\"\n";
			}
		}
	}
	open(FH,">$config_file") or logger('c',"Unable to open $config_file: $!");
	print FH "$contents";
	close(FH);
	logger('i',"Configured $config_file.");
}

sub rest_networking {
	my $vlan = shift or logger('c',"rest_networking() didn't get vlan!");
	system("ifdown eth0 ; ifup eth0 ; ifup eth0.$vlan");
	logger('i','Restarted networking.');
}

sub place_lock {
	open(FH,">$conf{'lock_file'}") or logger('c',"Unable to place lock file! Got: $!");
	close(FH);
}

sub rm_lock {
	if ( -e $conf{'lock_file'} ) {
		unlink($conf{'lock_file'});
	}
}

sub do_exit {
	rm_lock();
	exit 1;
}

sub clean_self {
	unlink("$conf{'conf_file'}");
	unlink('/root/xvps_autosetup.pl');
}

sub get_time {
	my $cur_time = ctime();
	return $cur_time;
}

sub logger {
	my $level = shift or logger('i',"logger() called but log level not given!");
	my $msg = shift or logger('i',"logger() called but no message to log!");
	my $time_stamp = get_time();
	my $label = ( $level eq 'c' ) ? 'CRIT:' : 'INFO:';
	open(LOGFILE,">>$conf{'log_file'}") or die "Unable to open $conf{'log_file'} for writing! $!";
	print LOGFILE "[ $time_stamp ] $label $msg\n";
	close(LOGFILE);
	if ( $level eq 'c' ) {
		print STDERR "\n$0 encountered one or more fatal errors. Be sure to review the log ($conf{'log_file'}).\n";
		clean_self();
		do_exit();
	}
}

sub write_routes {
	my ($zone, $vlan) = @_;

	# $zone is a string because we got it from ARGV
	if ($vlan eq '800') {
		my $content = "10.30.84.0/22 dev eth0.$vlan
10.30.128.0/21 dev eth0.$vlan
10.30.136.0/21 dev eth0.$vlan
10.30.184.0/21 dev eth0.$vlan\n";
		config_networking("/etc/sysconfig/network-scripts/route-eth0.$vlan","$content", 1);
	} elsif ($vlan eq '900') {
		my $content = "10.30.92.0/22 dev eth0.$vlan
10.30.46.0/23 dev eth0.$vlan
10.30.96.0/23 dev eth0.$vlan
10.30.196.0/22 dev eth0.$vlan
10.30.208.0/22 dev eth0.$vlan
10.30.32.0/22 dev eth0.$vlan
10.30.109.0/24 dev eth0.$vlan\n";
		config_networking("/etc/sysconfig/network-scripts/route-eth0.$vlan","$content", 1);
	}
}

#
# Main
#############################################################################

my $zone = $ARGV[0] || logger('c',"Required argument zone was not passed!");
my $setup_mode = $ARGV[1] // 'auto';
my $vlan = $ARGV[2] // 'unset';

print STDERR "zone [$zone] setup_mode [$setup_mode] vlan [$vlan]\n";

if ($setup_mode eq 'rekick') {
	write_routes($zone, $vlan);
	exit 0;
}

#verify_nic_intel({ interface => 'eth0' });
#verify_link_speed({ interface => 'eth0' });
place_lock();
populate_api_store("$zone");
stop_agent();
set_hosts("$api_store{'hostname'}","$api_store{'ip_addr'}");
set_hostname("$api_store{'hostname'}");
write_billUID();
my $content = "DEVICE=\"eth0\"
BOOTPROTO=\"none\"
NM_CONTROLLED=\"no\"
ONBOOT=\"yes\"";
config_networking('/etc/sysconfig/network-scripts/ifcfg-eth0',"$content");
$content = "DEVICE=\"eth0.$api_store{vlan}\"
PHYSDEV=\"eth0\"
BOOTPROTO=\"static\"
NM_CONTROLLED=\"no\"
ONBOOT=\"yes\"
VLAN=\"yes\"
TYPE=\"Ethernet\"
IPADDR=\"$api_store{ip_addr}\"
NETMASK=\"$api_store{netmask}\"
GATEWAY=\"$api_store{gateway}\"";
config_networking("/etc/sysconfig/network-scripts/ifcfg-eth0.$api_store{'vlan'}","$content");
write_routes($zone, $api_store{vlan});
rest_networking("$api_store{'vlan'}");
rm_lock();
clean_self();
