#!/usr/bin/python

# setnet
#
# This script sets the network configuration base on the following
# install parameters:
#   appliance.net.addr.family ('ipv4' or 'ipv6')
#   appliance.net.mode ('static', 'dhcp', or 'autoconf' (IPv6 only))
#   appliance.net.addr (static IPv4 or IPv6 address)
#   appliance.net.prefix (0-32 for IPv4 and 0-128 for IPv6)
#   appliance.net.gateway (IP address)
#   appliance.net.dns.servers (comma-separated list of IP addresses)
#   appliance.net.pnid (an FQDN or IP address)

import ipaddr
import os
import sys
import codecs
import platform
import re
import traceback
import json
import time

sys.path.append(os.environ['VMWARE_PYTHON_PATH'])

from cis import tools, utils
from cis.utils import log, isIPAddress, FileBuffer, runCommand
from appliance.hostname import IPResolver
from cis.baseCISException import BaseInstallException
from cis.l10n import localizedString
from cis.l10n import msgMetadata as _T
from cis.componentStatus import ErrorInfo, ExecutionStatusInfo
from cis.json_utils import ObjectJsonConverter
from cis.dnsmasq_utils import configureDnsCaching

# set up path and import method from detwist module
sys.path.append('/usr/lib/applmgmt/base/py/vmware/vherd/base/')
sys.path.append('/usr/lib/applmgmt/base/bin/')
from detwist import invokePintMethod

# VAMI scripts that do the actual reconfiguration work
VAMI_SET_NETWORK = '/opt/vmware/share/vami/vami_set_network'
VAMI_SET_HOSTNAME = '/opt/vmware/share/vami/vami_set_hostname'

HOSTNAME_CTL = '/usr/bin/hostnamectl'
DHCP_SYSCONFIG = '/etc/sysconfig/network/dhcp'

DHCP_IF_TIMEOUT = 300
DEFAULT_IF_TIMEOUT = 30
APPLMGMT_API_TIMEOUT = 900

CLOUDVM_STATUS_REPORT_INT_FILE = '/var/log/firstboot/cloudvmInstallStatus.json'

_isPhotonOS = os.path.isfile('/etc/photon-release')

#
# Utility functions.
#

def isPhotonOS():
    return _isPhotonOS

def raiseError(locErrMsg, locResolution=None):
    raise BaseInstallException(ErrorInfo([locErrMsg],
                                         resolution=locResolution,
                                         componentKey='setnet'))


def checkException(func):
    '''Decorator to handle exceptions'''
    def proxy(msg, *args, **kwargs):
        '''
        @param msg: a LocalizableMessage to append to BaseInstallException
        '''
        try:
            return func(*args, **kwargs)
        except BaseInstallException as e:
            e.appendErrorStack(msg)
            # pass through instead of throw a new exception so that the caller
            # gets a complete stack trace
            raise
        except Exception as e:
            raiseError(localizedString(_T('install.setnet.error.internal',
                           'setnet command failed. Operation "%s". Details: %s'),
                           (msg.l10nMsg, traceback.format_exc())))

    return proxy


def invokePintErrorCheck(f, args):
    retval = invokePintMethod(f, args, timeout=APPLMGMT_API_TIMEOUT)
    if retval != {}:
        raise Exception('%s. Code: %s' %
                        (retval['message'], retval['code']))


def DisableDhcpHostname():
    """
    Update DHCP client configuration to not set hostname
    """
    content = []
    with open(DHCP_SYSCONFIG, 'r') as f:
        for line in f:
            if line.startswith('DHCLIENT_SET_HOSTNAME='):
                line = 'DHCLIENT_SET_HOSTNAME="no"\n'
            content.append(line)

    with open(DHCP_SYSCONFIG, 'w') as f:
        f.writelines(content)


def setIfupTimeout(dhcpTimeout, generalIfTimeout=0):
    '''
    Set timeout for when the ifup <iface> returns.
    '''
    # DHCP timeout should not be greater than interface timeout.
    if dhcpTimeout > generalIfTimeout:
        generalIfTimeout = dhcpTimeout

    generalNWCfg = '/etc/sysconfig/network/config'
    dhcpNWCfg = '/etc/sysconfig/network/dhcp'

    cfgDataList = [(generalNWCfg, 'WAIT_FOR_INTERFACES', '"%d"' % generalIfTimeout),
                   (dhcpNWCfg, 'DHCLIENT_WAIT_AT_BOOT', '"%d"' % dhcpTimeout)]

    for cfgData in cfgDataList:
        f = FileBuffer()
        f.readFile(cfgData[0])
        f.updateKeyValue(cfgData[1], cfgData[2])
        f.writeFile(cfgData[0])
        f.clearBuffer()


def dhcp6AddressLeases():
    """
    Returns list of IPv6 leases acquired. It reads dhcpv6 client lease
    files to figure this out.
    """
    leases = set()
    leaseDir = '/var/lib/dhcpv6/'
    client6LeasesRE = \
        re.compile(
            r'^lease (?P<addr>[0-9A-Fa-f:\.]+)/(?P<net>\d+) {', re.MULTILINE)
    for name in [n for n in os.listdir(leaseDir) if n.startswith('client6.leases')]:
        try:
            with open(os.path.join(leaseDir, name), 'r') as f:
                data = f.read()
                result = client6LeasesRE.finditer(data)
                for r in result:
                    leases.add(r.group('addr'))
        except IOError:
            # the file was deleted after being listed.
            pass

    return leases


def isValidDHCPAcquired(family='', ifname='eth0'):
   """
   Check if a valid DHCP ip address has been acquired.
   Should be called only if the mode is dhcp.
   """
   ipResolver = IPResolver.getIPResolver()
   dhcp4IPAcquired = False
   dhcp6IPAcquired = False
   if platform.dist()[1] == '11':
       dhcp6Leases = dhcp6AddressLeases()
       for ipAddrEx in ipResolver.getIPAddressExList():
           if (not dhcp6IPAcquired and str(ipAddrEx) in dhcp6Leases
                   and ipAddrEx.adapterInfo == ifname):
               dhcp6IPAcquired = True
           if (not dhcp4IPAcquired and ipAddrEx.ipAddrObj.version == 4
                   and ipAddrEx.adapterInfo == ifname):
               dhcp4IPAcquired = True
   elif isPhotonOS():
       # For PhotonOS, use ip addr show, and look for 'global dynamic'
       retries = 30
       while True:
           rc, out, err = runCommand(['ip', 'addr', 'show', ifname])
           if not rc:
               if ('tentative' not in out):
                   break
           # Allow a generous 30 seconds for IP duplicate address detection.
           import time
           time.sleep(1)
           retries = retries - 1
           if retries == 0:
               log("Timed out waiting for IP addr tentative flag to clear.")
               break
           log("Waiting for IP addr tentative flag to clear.")
       if not rc:
           client6LeasesRE = \
               re.compile(r'inet6 (.*) scope global dynamic', re.MULTILINE)
           client6NoPrefixRouteLeasesRE = \
               re.compile(r'inet6 (.*) scope global noprefixroute dynamic', re.MULTILINE)
           if client6LeasesRE.search(out) or client6NoPrefixRouteLeasesRE.search(out):
               dhcp6IPAcquired = True
           elif family != 'ipv4':
               log('Failed to acquire dhcp IPV6 address.')

           client4LeasesRE = \
               re.compile(r'inet (.*) scope global dynamic', re.MULTILINE)
           if client4LeasesRE.search(out):
               dhcp4IPAcquired = True
           elif family != 'ipv6':
               log('Failed to acquire dhcp IPV4 address.')
   else:
       rc, out, err = runCommand(['wicked', 'show', ifname])
       if not rc:
           client6LeasesRE = \
               re.compile(r'ipv6 dhcp granted', re.MULTILINE)
           if client6LeasesRE.search(out):
               dhcp6IPAcquired = True
           elif family != 'ipv4':
               log('Failed to acquire dhcp IPV6 address.')

           client4LeasesRE = \
               re.compile(r'ipv4 dhcp granted', re.MULTILINE)
           if client4LeasesRE.search(out):
               dhcp4IPAcquired = True
           elif family != 'ipv6':
               log('Failed to acquire dhcp IPV4 address.')

   if not ((family == 'ipv6' and dhcp6IPAcquired) or
           (family == 'ipv4' and dhcp4IPAcquired) or
           (family == '' and (dhcp6IPAcquired or dhcp4IPAcquired))):
       if platform.dist()[1] == '11':
           log('dhcp6Leases=%s\n IP addresses=%s' %
               (dhcp6Leases, ipResolver.dump()))
       else:
           log('IP addresses=%s' % ipResolver.dump())
       return False
   # TODO: Please remove this hack once vami_network fixes the problem with
   # vami_network.
   import time
   time.sleep(1)
   return True


def getMacAddress(ifName):
    mac = []
    rc, out, err  = runCommand(["/sbin/ifconfig", ifName])
    if not rc:
        macMatcher = re.search('([0-9a-f]{2}[:-]){5}[0-9a-f]{2}', out, re.I)
        if macMatcher:
            mac = macMatcher.group().strip()
        else:
            log('Failed to match MAC regular expression for %s' % ifName)
    else:
        log('Failed to get MAC address for %s' % ifName)
    return mac


def waitForIP():
    rc = 0
    mode = tools.get_install_parameter('appliance.net.mode', '')
    if mode:
        mode = mode.lower()
        if mode in ['static']:
            log('Found static mode. No need to wait for IP.')
            return rc

    family = tools.get_install_parameter('appliance.net.addr.family', '')
    if family:
        family = family.lower()
    if 'ipv4' in family:
        retryCount = 1
        ipv4InfoRE_Photon = re.compile(r'^(\d+): (?P<name>\w+)\s* inet '
                                        '(?P<addr>[\d\.]+)/(?P<net>\d+)\s* '
                                        '[a-z]+ (?P<gateway>[\d\.]+)\s* '
                                        'scope (?P<scope>\w+)\s* '
                                        '(?P<dynamic>dynamic)?', re.MULTILINE)
        ipv4Found = False
        while (not ipv4Found) and (retryCount <= 5):
            cmd = ['/sbin/ip', '-o', '-f', 'inet', 'addr', 'show']
            rc, out, err = runCommand(cmd)
            result = ipv4InfoRE_Photon.finditer(out)
            for r in result:
                if not rc and \
                   'global' in r.group('scope') and \
                   r.group('dynamic'):
                    try:
                        ipaddr.IPv4Address(r.group('addr'))
                        log('Found global IPv4 address %s assigned by DHCP'
                            % r.group('addr'))
                        ipv4Found = True
                        break
                    except:
                        log('Exception in validating IPV4 address %s: %s'
                            %(r.group('addr'), e))
                        pass
            retryCount += 1
            time.sleep(5)

    elif 'ipv6' in family:
        retryCount = 1
        ipv6InfoRE_Photon = re.compile(r'^(\d+): (?P<name>\w+)\s* inet6 '
                                        '(?P<addr>[0-9A-Fa-f:\.]+)/(?P<net>\d+)'
                                        ' scope (?P<scope>\w+)\s*'
                                        '(noprefixroute|mngtmpaddr)? '
                                        '(?P<dynamic>dynamic)?', re.MULTILINE)
        ipv6Found = False
        while (not ipv6Found) and (retryCount <= 5):
            cmd = ['/sbin/ip', '-o', '-f', 'inet6', 'addr', 'show']
            rc, out, err = runCommand(cmd)
            result = ipv6InfoRE_Photon.finditer(out)
            for r in result:
                if not rc and \
                   'global' in r.group('scope') and \
                   r.group('dynamic'):
                    try:
                        ip6Addr = ipaddr.IPv6Address(r.group('addr'))
                        mac = getMacAddress(r.group('name')).split(':')
                        autoIp6Bytes = 'fe%s:%s%s' % (mac[3], mac[4], mac[5])
                        if autoIp6Bytes not in ip6Addr.exploded:
                            log('Found global non-autoconf IPv6 address %s '
                                'assigned by DHCP' % ip6Addr)
                            ipv6Found = True
                            break
                    except Exception as e:
                        log('Exception in validating IPV6 address %s: %s'
                            %(r.group('addr'), e))
                        pass
            retryCount += 1
            time.sleep(5)
    return rc


def getIPAddressObj(ip, versionCheck=None):
    try:
        ipAddrObj = ipaddr.IPAddress(ip)
        if versionCheck is not None and ipAddrObj.version != versionCheck:
            raise ValueError()
        return ipAddrObj
    except ValueError:
        raiseError(localizedString(_T("install.setnet.set.network.badip",
                                      'Invalid IP Address provided %s'), str(ip)))
    return ipAddrObj


def getIPNetworkObj(nw, versionCheck=None):
    try:
        ipNwObj = ipaddr.IPNetwork(nw)
        if versionCheck is not None and ipNwObj.version != versionCheck:
            raise ValueError()
    except ValueError:
        raiseError(localizedString(_T('install.setnet.set.network.badnw',
                                      'Invalid IP Address/Prefix provided %s'), str(nw)))
    return ipNwObj


def readNetworkInstallParameters():
    """
    Reads the network install parameters userful to set up the network.

    Those are: appliance.net.addr
               appliance.net.prefix
               appliance.net.gateway

    Returns a tuple of the parameters.
    """
    addr = tools.get_install_parameter('appliance.net.addr', '')
    if addr == '':
        log('ERROR: No static address provided for static mode.')
        return ('', '', '')

    prefix = tools.get_install_parameter('appliance.net.prefix', '')
    if prefix == '':
        log('ERROR: No prefix provided for static mode.')
        return ('', '', '')

    # Augment command with default gateway.
    gateway = tools.get_install_parameter('appliance.net.gateway', '')
    if gateway == '':
        log('ERROR: No gateway provided for static mode.')
        return ('', '', '')

    log("appliance.net.addr is set to '%s'" % addr)
    log("appliance.net.prefix is set to '%s'" % prefix)
    log("appliance.net.gateway is set to '%s'" % gateway)
    return (addr, prefix, gateway)


def disableIPV6():
    # During reboot, we need this to disable SLAAC addresses
    with codecs.open('/etc/sysctl.d/99-sysctl.conf', 'a') as f:
        f.write('net.ipv6.conf.all.disable_ipv6=1\n')

    # During installation, we need this to disable SLAAC addresses
    if os.path.exists("/proc/sys/net/ipv6/conf/eth0"):
        with codecs.open('/proc/sys/net/ipv6/conf/eth0/disable_ipv6', 'w') as g:
            g.write('1')

#
# Basic network setup.
#

# For reference, here's a simplified summary of vami_set_network usage.
#   vami_set_network <interface> (DHCPV4|DHCPV6|AUTOV6|DHCPV4+DHCPV6|DHCPV4+AUTOV6)
#   vami_set_network <interface> (STATICV4|STATICV4+DHCPV6|STATICV4+AUTOV6) <ipv4_addr> <netmask> <gatewayv4>
#   vami_set_network <interface> (STATICV6|DHCPV4+STATICV6) <ipv6_addr> <prefix> (<gatewayv6>|default)
# vami_set_network <interface> STATICV4+STATICV6 <ipv4_addr> <netmask>
# <gatewayv4> <ipv6_addr> <prefix> (<gatewayv6>


@checkException
def setNetwork():
    """
    Setup network on the following install parameters:
      appliance.net.addr.family
      applaince.net.mode
      appliance.net.addr
      appliance.net.prefix
      appliance.net.gateway

    Returns True iff network setup succeeded.
    """
    command = [VAMI_SET_NETWORK, 'eth0']

    # Validate address family.
    family = tools.get_install_parameter('appliance.net.addr.family', '')
    if family:
        family = family.lower()
    if family not in ['ipv4', 'ipv6', '']:
        raiseError(localizedString(_T('install.setnet.set.network.family',
                                      "Illegal address family '%s'"),
                                   family))
    log("appliance.net.addr.family is set to '%s'" %
        'ipv4+ipv6' if family == '' else family)
    suffix = 'V6' if family == 'ipv6' else 'V4'

    # Validate networking mode.
    mode = tools.get_install_parameter('appliance.net.mode', '')

    if mode == '':
        log("INFO: No 'appliance.net.mode' install parameter provided; using"
            "dhcp.")
        mode = 'dhcp'
    else:
        mode = mode.lower()

    if mode not in ['static', 'dhcp', 'autoconf']:
        raiseError(localizedString(_T('install.setnet.set.network.mode',
                                      "The following mode is incorrect: '%s'"),
                                   mode))
    log("appliance.net.mode is set to '%s'" % mode)

    if not isPhotonOS():
        if mode == 'dhcp':
            setIfupTimeout(DHCP_IF_TIMEOUT)
        else:
            setIfupTimeout(DEFAULT_IF_TIMEOUT / 2, DEFAULT_IF_TIMEOUT)

    # Augment command line based on mode.
    if mode == 'autoconf':
        if family != 'ipv6':
            raiseError(localizedString(_T('install.setnet.set.network.autoavail',
                                          'Autoconf only available with IPv6')))
        command.append('AUTOV6')
    elif mode == 'dhcp':
        pnid = tools.get_install_parameter('appliance.net.pnid', '')
        if pnid != '':
            if isIPAddress(pnid):
                raiseError(localizedString(_T('install.setnet.set.network.invpnid',
                                              'DHCP address cannot be used as '
                                              'Primary Network Identifier (PNID).')))

            # pnid is an FQDN.
            # Note:- Validation for DDNS case is already done as part of the
            # visl-support-script validate pnid step.

            # Stop dhcp client from updating the hostname.
            if not isPhotonOS():
                DisableDhcpHostname()
            hostname = pnid.split('.')[0]
            log('DDNS: Attempt to map %s to dhcp address' % hostname)
            hostnameCmd = [HOSTNAME_CTL, 'set-hostname', hostname]
            rc_hn, stdout_hn, stderr_hn = runCommand(hostnameCmd)
            if rc_hn != 0:
                raiseError(localizedString(_T('install.setnet.set.hostname.vami',
                                              'Error setting hostname. Details : '
                                              '%s'), str(stderr_hn)))

        if family != '':
            if family == 'ipv4':
                disableIPV6()
            command.append('%s%s' % (mode.upper(), suffix))
        else:
            command.append('DHCPV4+DHCPV6')
    elif mode == 'static' and family == 'ipv4':
        (addr, prefix, gateway) = readNetworkInstallParameters()
        if addr == '' and prefix == '' and gateway == '':
            raiseError(localizedString(_T('install.setnet.set.network.invinput',
                                          'Ensure that you provide addresses, '
                                          'prefix and gateway settings in the '
                                          'static configuration.')))

        ip = getIPNetworkObj('%s/%s' % (addr, prefix), versionCheck=4)
        gw = getIPAddressObj(gateway, versionCheck=4)

        disableIPV6()
        command.extend(['STATICV4', str(ip.ip), str(ip.netmask), str(gw)])
    elif mode == 'static' and family == 'ipv6':
        (addr, prefix, gateway) = readNetworkInstallParameters()
        if addr == '' and prefix == '' and gateway == '':
            raiseError(localizedString(_T('install.setnet.set.network.invinput',
                                          'Ensure that you provide addresses, '
                                          'prefix and gateway settings in the '
                                          'static configuration.')))

        ip = getIPNetworkObj('%s/%s' % (addr, prefix), versionCheck=6)
        gw = getIPAddressObj(
            gateway, versionCheck=6) if gateway != 'default' else 'default'

        command.extend(['STATICV6', str(ip.ip), str(ip.prefixlen), str(gw)])
    elif mode == 'static' and family == '':
        (addr, prefix, gateway) = readNetworkInstallParameters()
        if addr == '' and prefix == '' and gateway == '':
            raiseError(localizedString(_T('install.setnet.set.network.invinput',
                                          'Ensure that you provide addresses, '
                                          'prefix and gateway settings in the '
                                          'static configuration.')))

        #
        # Check if more then one address is provided
        #
        addrs = addr.split(',')
        prefixes = prefix.split(',')
        gateways = gateway.split(',')

        if len(addrs) != len(prefixes) or len(addrs) != len(gateways):
            raiseError(localizedString(_T('install.setnet.set.network.ipcount',
                                          'The number of the IP addresses does not match '
                                          'the number of the prefixes/gateways provided.')))

        if len(addrs) == 2:
            command.append('STATICV4+STATICV6')
        elif len(addrs) == 1:
            addrType = getIPAddressObj(addrs[0])
            if addrType.version == 4:
                command.append('STATICV4+DHCPV6')
            elif addrType.version == 6:
                command.append('DHCPV4+STATICV6')
        else:
            raiseError(localizedString(_T('install.setnet.set.network.ipcount1',
                                          'You have specified more than two '
                                          'IP addresses.')))

        numIPV4 = 0
        numIPV6 = 0
        ipv4Config = []
        ipv6Config = []

        for addr in zip(addrs, prefixes, gateways):
            # 0 = IP Address, 1 = Prefix, 2 = Gateway
            ip = getIPNetworkObj('%s/%s' % (addr[0], addr[1]))

            if ip.version == 6:
                gw = getIPAddressObj(
                    addr[2], 6) if addr[2] != 'default' else 'default'
                ipv6Config = [str(ip.ip), str(ip.prefixlen), str(gw)]
                numIPV6 += 1
            elif ip.version == 4:
                gw = getIPAddressObj(addr[2], 4)
                ipv4Config = [str(ip.ip), str(ip.netmask), str(gw)]
                numIPV4 += 1

        if numIPV4 > 1 or numIPV6 > 1:
            raiseError(localizedString(_T('install.setnet.set.network.ipcount2',
                                          'You have provided more than one '
                                          'address of type IPv4 or IPv6.')))

        if ipv4Config:
            command.extend(ipv4Config)
        if ipv6Config:
            command.extend(ipv6Config)

    # Run command and return result.
    log('Executing command: %s' % command)

    # Run command.
    rc, stdout, stderr = runCommand(command)
    if mode == 'dhcp':
        if rc == 1 or (family != 'ipv4' and not isValidDHCPAcquired(family)):
            # Note:- No point doing the dhcp validation in case of ipv4 since
            # vami_set_network will fail in that case.
            log('ERROR: Timedout waiting to acquire DHCP address.')
            raiseError(localizedString(_T('install.setnet.set.network.timeout',
                                          'The request for acquiring a DHCP '
                                          'address has timed out.')),
                       locResolution=localizedString(_T('install.setnet.set.network.timeout.res',
                                                        'Make sure that a DHCP '
                                                        'server is reachable.')))
    if rc != 0:
        raiseError(localizedString(_T('install.setnet.set.network.vami',
                                      'Error setting network. Details : %s'),
                                   str(stderr)))

#
# DNS setup (if necessary).
#


@checkException
def setDNS():
    """
    Configure DNS servers based on following install parameters.
      appliance.net.dns.servers
      appliance.net.dns.searchlist

    Returns False if setup was not successful.
    """
    dnsServersString = tools.get_install_parameter(
                            'appliance.net.dns.servers','')
    dnsServers = [
            x.strip() for x in dnsServersString.split(',')
            if len(x.strip())
            ]
    log('DNS servers: %s' % ', '.join(dnsServers))
    if dnsServers:
        config = dict()
        config['mode'] = 'static'
        config['servers'] = dnsServers
        try:
            invokePintErrorCheck(
                'com.vmware.appliance.version1.networking.dns.servers.set',
                config)
        except Exception as e:
            raiseError(localizedString(_T('install.setnet.set.dns.vami',
                                          'Error setting DNS configuration. '
                                          'Details : %s'), str(e)))


    dnsSearchString = tools.get_install_parameter('appliance.net.dns.searchlist',
                                                   '')
    dnsSearchSuffixes = [
            x.strip() for x in dnsSearchString.split(',')
            if len(x.strip())
            ]
    log('DNS search: %s' % ', '.join(dnsSearchSuffixes))
    if not dnsSearchSuffixes:
        return
    try:
        invokePintErrorCheck(
            'com.vmware.appliance.version1.networking.dns.domains.set',
            dnsSearchSuffixes)
    except Exception as e:
        raiseError(localizedString(_T('install.setnet.set.dns.vami',
                                      'Error setting DNS configuration. '
                                      'Details : %s'), str(e)))



#
# Hostname setup (use PNID if set and not an IP address).
#


@checkException
def setHostname():
    """
    Setup system hostname based on install parameter appliance.net.pnid.
    If this install parameter is not set, we attempt to automatically
    derive the hostname.

    Returns True iff hostname setup was successful.
    """
    #For DHCP acquired IP, RFC requires server to send 
    #a couple of ARP packets to verify no address collision
    #before handing out IP.
    #Keeping this change guarded with Photon as the failures
    #due to this was observed with Photon
    if isPhotonOS():
        rc = waitForIP()
        if rc != 0:
           raiseError(localizedString(_T('install.setnet.set.waitforip',
                                      'Error acquiring ip.')))

    command = [VAMI_SET_HOSTNAME]
    pnid = tools.get_install_parameter('appliance.net.pnid', '')
    if pnid != '':
        log("appliance.net.pnid is set to '%s'" % pnid)
        if not isIPAddress(pnid):
            log("PNID '%s' appears to be an FQDN; using it for the hostname" %
                pnid)
            command.append(pnid)
        else:
            log("PNID '%s' appears to be an IP address; not using it for the "
                "hostname" % pnid)
    rc, stdout, stderr = runCommand(command)
    if rc != 0:
        raiseError(localizedString(_T('install.setnet.set.hostname.vami',
                                      'Error setting hostname. Details : '
                                      '%s'), str(stderr)))

#
# Timesync setup
#


@checkException
def setTimeSync():
    """
    Setup the time sync based on the following install parameters:
    appliance.time.tools-sync
    appliance.ntp.servers

    If tools-sync is true then set the timesync mode to 'host' via the applmgmt appliance.
    This sets the time synchronization to be vmware-tools based

    If the tools-sync parameter is False, and there are ntp servers specified, I set the ntp servers
    and then the timesync mode is set to ntp

    """

    ntpServers = tools.get_install_parameter('appliance.ntp.servers', '')
    if len(ntpServers) > 0:
        log('setting time sync to ntp')
        ntpServersList = [x.strip() for x in ntpServers.split(',') if len(x.strip())]
        try:
            invokePintErrorCheck(
                'com.vmware.appliance.version1.ntp.server.set', ntpServersList)
            # The ntptimeset option is invoked to do an immediate timesync based on the ntp
            # configuration
            # for sles 12, we need to query ntpd daemon
            if platform.dist()[1] == '12':
                setTime, stdout, stderr = runCommand(
                    ['/sbin/service', 'ntpd', 'ntptimeset'])
            elif isPhotonOS():
                setTime, stdout, stderr = runCommand(
                    ['/sbin/service', 'ntpd', 'reload-or-restart'])
            else:
                setTime, stdout, stderr = runCommand(
                    ['/sbin/service', 'ntp', 'ntptimeset'])
            if setTime != 0:
                raise Exception(stderr)
            invokePintErrorCheck(
                'com.vmware.appliance.version1.timesync.set', {'mode': 'NTP'})
        except Exception as e:
            raiseError(
                localizedString(_T('install.setnet.set.timesync.ntp',
                                   'Failed to set the time via NTP. '
                                   'Details: %s'), str(e)),
                locResolution=localizedString(_T('install.setnet.set.timesync.ntp.res',
                                                 'Verify that provided ntp '
                                                 'servers are valid.')))
    else:
        toolsTimeSyncFlag = tools.get_install_parameter(
            'appliance.time.tools-sync', '').lower()
        if toolsTimeSyncFlag == 'true':
            log('setting time sync to vmware tools based')
            try:
                invokePintErrorCheck(
                    'com.vmware.appliance.version1.timesync.set', {'mode': 'host'})
            except Exception as e:
                raiseError(localizedString(_T('install.setnet.set.timesync.vmw',
                                              'Failed to set time sync to vmware '
                                              'tools based. Details: %s'), str(e)))

#
# Set network, DNS servers, hostname and timesync.
#
if __name__ == '__main__':
    rc = 0
    try:
        msg = localizedString(
            _T('install.setnet.set.network', 'Failed to set network'))
        setNetwork(msg)
        msg = localizedString(
            _T('install.setnet.set.dns', 'Failed to set DNS'))
        setDNS(msg)
        msg = localizedString(
            _T('install.setnet.set.hostname', 'Failed to set hostname'))
        setHostname(msg)
        localizedString(_T('install.setnet.set.timesync',
                           'Could not set up time synchronization.'))
        setTimeSync(msg)

        # Configure DNS Caching using DNSMASQ
        configureDnsCaching()
    except BaseInstallException as ex:
        log('ERROR: setnet failed : %s' % ex)
        with codecs.open(CLOUDVM_STATUS_REPORT_INT_FILE, encoding='utf-8', mode='w') as fp:
            json.dump(ExecutionStatusInfo(error=ex.getErrorInfo()), fp,
                      default=ObjectJsonConverter(False).encode, indent=4)
        rc = 1
    finally:
        try:
            if not isPhotonOS():
                setIfupTimeout(DEFAULT_IF_TIMEOUT / 2, DEFAULT_IF_TIMEOUT)
        except Exception as e:
            log('Failed to reset dhcp configuration : %s' % e)
    exit(rc)
