#!/opt/vmware/bin/python

import argparse
import os
import sys
import re
import string
import socket
from contextlib import contextmanager
import tempfile
import getpass
import logging
import urllib2
sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
from pyVim import sso
from ipaddr import IPAddress
from appliance.hostname import ipv6PreferredForm

if os.name == 'posix':
   sys.path.append('/usr/lib/vmware-vmafd/lib64')
   svcCtrlPath = '/bin/service-control'
   svcPath = '/usr/lib/vmware-visl-integration/config/services.json'
   cm  = 'vmware-cm'
   rhttpproxy = 'vmware-rhttpproxy'
   runfbBin = '/bin/run-firstboot-scripts'
else:
   sys.path.append(os.path.join(os.environ['VMWARE_CIS_HOME'], 'vmafdd'))
   svcCtrlPath = os.path.join(os.environ['VMWARE_CIS_HOME'],
                                         'bin', 'service-control.bat')
   svcPath = os.path.join(os.environ['VMWARE_CIS_HOME'], 'visl-integration',
                                     'config', 'services.json')
   runfbBin = os.path.join(os.environ['VMWARE_CIS_HOME'], 'bin',
                                      'run-firstboot-scripts.bat')
   cm  = 'VMwareComponentManager'
   sca_svc = 'vmwareServiceControlAgent'
   rhttpproxy = 'rhttpproxy'

# Defined this global variable to set command exection as quiet
QUIET = True

from cis.defaults import *
from cis.utils import (invoke_command, read_ssl_certificate, setupLogging,
                       get_deployment_nodetype, set_deployment_nodetype,
                       set_service_start_type, StartType, get_db_type,
                       is_windows, is_linux, get_winregistry, set_winregistry)
from cis.exceptions import (InvokeCommandException, ServiceNotFoundException,
                            SetServiceStartTypeException)
from cis.svcscfg import (loadServicesFile, getNodeSvcsList, getNodefbScriptsList )
from cis.tools import processSvcDeps
import vmafd
from identity.vmkeystore import VmKeyStore
from cis.cmhelper import CISCmHelper
from cis.vecs import Service
from cis.svcsController import update_services_startuptype, SvcsInfoMgr

comp_path = get_component_home_dir(def_by_os('vmafd', 'vmafdd'))
vmafd_cli_path = def_by_os(os.path.join(comp_path, 'bin/vmafd-cli'),
                         os.path.join(comp_path, 'vmafd-cli.exe'))
vecs_cli_path = def_by_os(os.path.join(comp_path, 'bin/vecs-cli'),
                         os.path.join(comp_path, 'vecs-cli.exe'))
comp_path = get_component_home_dir(def_by_os('vmdir', 'vmdird'))
vdcleavefed_path = def_by_os(os.path.join(comp_path, 'bin/vdcleavefed'),
                         os.path.join(comp_path, 'vdcleavefed.exe'))
vdcrepadmin_path = def_by_os(os.path.join(comp_path, 'bin/vdcrepadmin'),
                         os.path.join(comp_path, 'vdcrepadmin.exe'))

def getSolUserIds(machineId, allSolUserIds):
   """
   Return a list of solution user ids corresponding to given machineId.
   Apart from machineId takes as input a list of all solution user
   Ids in lotus.
   """
   userNames = ['machine', 'vsphere-webclient', 'vpxd-extension', 'vpxd']
   preInstalledSolIds = ['%s-%s' % (userName, machineId) for userName in userNames]
   return [s for s in allSolUserIds if s in preInstalledSolIds]


def getCMUrl(vmafdClient):
   return vmafdClient.GetCMLocation()


@contextmanager
def tempinput(data):
   temp = tempfile.NamedTemporaryFile(delete=False)
   temp.write(data)
   temp.close()
   yield temp.name
   os.unlink(temp.name)


def getMachineAccntCertAndKey():
   """
   Get certificate and key for the machine account.
   """
   ks = VmKeyStore('VKS')
   ks.load('machine')
   cert = ks.get_certificate('machine')
   key = ks.get_key('machine')
   return (cert, key)

def setMachineCert(vmafdClient, certfile, keyfile):
   """
   Set machine cert in VECS.
   """
   # XXX SetMachineCert seems to be throwing Error code 90001 on windows.
   #vmafdClient.SetMachineCert(certfile, keyfile)

   # Using vecs cli for now instead of the API.
   invoke_command([vecs_cli_path, 'entry', 'delete',
                   '--store', 'MACHINE_SSL_CERT',
                   '--alias', '__MACHINE_CERT', '-y'], quiet=QUIET)
   invoke_command([vecs_cli_path, 'entry', 'create', '--store', 'MACHINE_SSL_CERT',
                   '--alias', '__MACHINE_CERT',
                   '--cert', certfile,
                   '--key', keyfile], quiet=QUIET)


def removeComputerAccount(accountName, username, password):
   """
   Removes Computer Account.
   """
   invoke_command([vdcleavefed_path, '-h', accountName, '-u', username.split('@')[0]],
                  stdin=password, quiet=QUIET)


def CMSSOHelperCall(func):
   """
   Decorator to handle creation of cisCmHelper object.
   """
   def proxy(cmdArgs):
      vmafdClient = vmafd.client("localhost")
      if cmdArgs.username is None:
         # Use machine account.
         try:
            cert, key = getMachineAccntCertAndKey()
         except Exception as e:
            msg = ("Failed to get certificate and key for the machine account. "
                    "Error = %s" % e)
            sys.stderr.write(msg + '\n')
            logging.error(msg)
            return 1

         with tempinput(key) as tempkeyname,  tempinput(cert) as tempcertname:
            cisCmHelper = CISCmHelper(getCMUrl(vmafdClient),
                                      None, None,
                                      cert=tempcertname, key=tempkeyname)
            return func(cmdArgs, vmafdClient, cisCmHelper)
      else:
         # Use given user credentials.
         if cmdArgs.passwd is None:
            # Prompt user for password.
            cmdArgs.passwd = getpass.getpass()

         cisCmHelper = CISCmHelper(getCMUrl(vmafdClient), cmdArgs.username, cmdArgs.passwd)
         return func(cmdArgs, vmafdClient, cisCmHelper)

   return proxy


@CMSSOHelperCall
def machineCertCmdHandler(args, vmafdClient, cisCmHelper):
   """
   Implements machine certificate update.
   """
   try:
      # Stop all non core services.
      invoke_command([svcCtrlPath, '--stop', '--ignore'], quiet=QUIET)

      # Core and non core services list is still not concrete. Making sure
      # rhttpproxy service and that cm is running.
      invoke_command([svcCtrlPath, '--ignore', '--start', rhttpproxy, cm], quiet=QUIET)

      # Update SSL trust in CM.
      cisCmHelper.reregisterNodeServicesToCM(vmafdClient.GetPNID(),
         newHostame=None, sslTrust=read_ssl_certificate(args.certfile))
      # Set machine cert.
      setMachineCert(vmafdClient, args.certfile, args.keyfile)

      # Restart cm and rhttpproxy.
      invoke_command([svcCtrlPath, '--stop', cm, rhttpproxy], quiet=QUIET)
      invoke_command([svcCtrlPath, '--start', cm, rhttpproxy], quiet=QUIET)

      # Start all non core services.
      invoke_command([svcCtrlPath, '--start', '--ignore'], quiet=QUIET)

      if os.name == 'posix':
          #Restart the Appliance UI webserver.
          cmd="/usr/lib/applmgmt/support/scripts/postinstallscripts/lighttpd-vecs-integration.sh"
          invoke_command([cmd])

   except Exception as e:
      msg = "ERROR setting machine cert: %s" % e
      sys.stderr.write(msg + '\n')
      logging.error(msg)
      return 1
   return 0


@CMSSOHelperCall
def unregisterCmdHandler(args, vmafdClient, cisCmHelper):
   """
   Implements unregister of services from CM.
   """
   if get_deployment_nodetype() == 'management':
      msg = "This command is supported only on PSC and vCenter with embedded PSC nodes."
      sys.stderr.write(msg + '\n')
      logging.error(msg)
      return 1

   try:
      thisMachinePNID = vmafdClient.GetPNID()
      unregNodePnid = args.node_pnid or thisMachinePNID

      hostId = args.hostId or cisCmHelper.getHostIdFromHostname(unregNodePnid)
      if not hostId:
         msg = ("Could not find a host id which maps to %s in Component Manager" %
                unregNodePnid)
         sys.stderr.write(msg + "\n")
         raise Exception(msg)

      # Remove CM service endpoints.
      try:
         cisCmHelper.unregisterNodeFromCM(unregNodePnid, ignoreErrors=True)
      except Exception as e:
         msg = "ERROR unregistering node from Component Manager."
         logging.error(msg)
         sys.stderr.write(msg + '\n')

      # Remove solution users.
      dirServiceObj = Service(login=args.username, password=args.passwd)
      solnUserIds = getSolUserIds(hostId, dirServiceObj.list())
      for solnUserId in solnUserIds:
         try:
            dirServiceObj.delete(solnUserId)
         except Exception as e:
            msg = "ERROR unregistering soln user %s" % solnUserId
            logging.error(msg)
            sys.stderr.write(msg + '\n')

      # Remove computer account.
      try:
         removeComputerAccount(unregNodePnid, args.username, args.passwd)
      except Exception as e:
         msg = "ERROR unregistering computer account."
         logging.error(msg)
         sys.stderr.write(msg + '\n')
   except Exception as e:
      sys.stderr.write("Failed!!!\n")
      logging.error(str(e))
      return 1

   print("Success\n")
   return 0

def reconfig_to_external_psc(args):
   """
   Validates all the user given paramenters before performing reconfiguration tasks.
   Calls 'reconfig_repoint_embedded' that performs the tasks of reconfiguring of embedded
   node and repointing to an external PSC.
   """
   msg = 'Validating Provided Configuration ...'
   log_and_print(msg)
   # Validating all input parameters and platform type
   node_type = get_deployment_nodetype()
   if node_type != 'embedded':
      msg = ('Reconfigure command is only supported on vCenter Server with '
             'embedded Platform Services Controller(PSC).')

      log_and_print(msg, err=True)
      return 1

   # Config validation
   if not (validate_dc_port(args) and
           validate_sso_credential(args) and
           check_repl_psc(args)):
      return 1

   msg = 'Validation Completed Successfully.'
   log_and_print(msg)
   if not reconfig_repoint_embedded(args):
      return 1
   # Reconfigure and repoint succeedded without an error
   return 0


def repoint_to_external_psc(args):
   """
   Validates all the user given paramenters before performing repoint task.
   Calls the function that performs repointing of PCS services to an external PSC.
   """
   psc_host = args.psc_node
   msg = 'Validating Provided Configuration ...'
   log_and_print(msg)

   # Validating all input parameters and platform type
   node_type = get_deployment_nodetype()
   if node_type != 'management':
      msg = ('Repoint command is only supported on vCenter Server with '
             'external Platform Services Controller(PSC).')
      log_and_print(msg, err=True)
      return 1

   # Config validation
   if not (check_psc_node(args.psc_node) and
           validate_dc_port(args)):
       return 1

   msg = 'Validation Completed Successfully.'
   log_and_print(msg)

   if not repoint_vCenter(args):
       return 1
   # Repoint succeedded without an error
   return 0

def get_local_pnid():
   """
   Gets localhost's pnid
   """
   try:
      local_host = invoke_command([vmafd_cli_path, 'get-pnid', '--server-name', 'localhost'],
                                   quiet=QUIET )
      local_host = local_host.rstrip()
      return str(local_host)
   except (InvokeCommandException, Exception) as e:
      msg = 'Failed to get PNID of the localhost. Error: %s' % e
      log_and_print(msg, err=True)
      return None

def get_machine_id(server_name='localhost'):
   try:
      machine_id = invoke_command([vmafd_cli_path, 'get-machine-id',
                                   '--server-name', server_name], quiet=QUIET)
   except InvokeCommandException as e:
      msg = 'Failed to get machine id of "%s". Error: %s' % (server_name, e)
      log_and_print(msg, err=True)
      return None
   return machine_id.rstrip()

def set_machine_id(machine_id, server_name='localhost'):
   try:
      invoke_command([vmafd_cli_path, 'set-machine-id', '--id', machine_id,
                      '--server-name', server_name], quiet=QUIET)
   except InvokeCommandException as e:
      msg = ('Failed to set machine id "%s" for "%s". Error: %s' %
             (machine_id, server_name, e))
      log_and_print(msg, err=True)
      return False
   return True

def start_all_services():
   """
   Starts all the services on localhost
   """
   msg = 'Starting all the services ...'
   log_and_print(msg)
   try:
      invoke_command([svcCtrlPath, '--start', '--all'], quiet=QUIET)
      msg = 'Started all the services.'
      log_and_print(msg)
      return True
   except (InvokeCommandException, Exception) as e:
      msg = 'Failed to start all the services. Error %s' % e
      log_and_print(msg, err=True)
      return False

def stop_all_services():
   """
   Stops all the services on localhost
   """
   msg = 'Stopping all the services ...'
   log_and_print(msg)
   try:
      invoke_command([svcCtrlPath, '--stop', '--all'], quiet=QUIET)
      msg = 'All services stopped.'
      log_and_print(msg)
      return True
   except (InvokeCommandException, Exception) as e:
      msg = 'Failed to stop all the services. Error %s' % e
      log_and_print(msg, err=True)
      return False

def stop_non_core_services():
   """
   Stops all non core services
   """
   try:
      invoke_command([svcCtrlPath, '--stop'], quiet=QUIET)
      return True

   except (InvokeCommandException, Exception) as e:
      msg = 'Failed to stop non core services. Error %s' % e
      log_and_print(msg, err=True)
      return False


def repoint_vCenter(args):
   """
   Performs all the steps to repoint a vCenter server to an external PSC
   """
   # stop non core services
   psc_host =  args.psc_node
   dc_port = args.dcport
   msg = 'Executing repointing steps. This will take few minutes to complete.\n'\
         'Please wait ...'


   log_and_print(msg)

   if not stop_non_core_services():
       return False
   # Set dc-name and dc-port
   if not set_dc_config(psc_host, dc_port):
       msg = 'Failed to set %s as the new Platform Services Controller' % psc_host
       log_and_print(msg, err=True)
       return False
   if  not (stop_all_services() and start_all_services()):
       return False
   msg = 'The vCenter Server has been successfully repointed to '\
         'the external Platform Services Controller %s.' % psc_host
   log_and_print(msg)
   return True

def reconfig_repoint_embedded(args):
    """
    Performs all the steps to reconfigure an embedded node to vCenter server.
    After successful reconfiguration it repoints to an external PSC
    """
    # stop non core services
    psc_host =  args.psc_node
    username = args.username
    domain = args.domain
    dc_port = args.dcport
    password = args.password

    msg = 'Executing reconfiguring steps. This will take few minutes to complete.\n'\
          'Please wait ...'
    log_and_print(msg)

    local_host = get_local_pnid()
    if local_host is None:
        return False

    if not stop_non_core_services():
        return False

    machine_id = get_machine_id()
    if not machine_id:
        return False

    # Domote dc from controller to Client
    if not demote_dc(args.username, args.password):
        return False
    # Set dc-name and dc-port
    if not set_dc_config(psc_host, dc_port):
        msg = 'Failed to set %s as the new Platform Services Controller' % psc_host
        log_and_print(msg, err=True)
        return False

    # Replacing embedded with external
    if os.name == 'posix':
        files = ['/etc/issue', '/etc/applmgmt/appliance/appliance.conf']
        for name in files:
            if not update_psc_type(name):
                return False

    # Stopping all the services
    if not stop_all_services():
        return False

    # Disable the core services
    node_type = 'infrastructure'
    action = 'stop'

    if not disable_infraSvcs(node_type, action):
        return False

    # Change deployment nodetype
    if not set_deployment_nodetype('management'):
        return False


    # Updating dependencies
    processSvcDeps(loadServicesFile(svcPath))

    # Start the vmafd service
    msg = 'Starting vmafd service.'
    log_and_print(msg)
    try:
        invoke_command([svcCtrlPath, '--start', def_by_os("vmafdd", "VMWareAfdService")],
                       quiet=QUIET)

    except (InvokeCommandException, Exception) as e:
        msg= 'Error:%s' % (psc_host, e)
        log_and_print(msg, err=True)
        return False

    if not join_new_dc(args, local_host):
        return False

    if not set_machine_id(machine_id):
        return False

    # Set dc-name and dc-port
    do_reconfigure_cleanup()

    # Update registry with install_type on ciswin platform
    if is_windows():
        if not update_winreg():
            return False

    # Start all the services
    if not start_all_services():
        return False

    # Uninstall the core services
    if not uninstall_infraSvcs(node_type, action):
        return False

    msg = 'The vCenter Server has been successfully reconfigured and repointed to '\
          'the external Platform Services Controller %s.' % psc_host
    log_and_print(msg)
    return True


def validate_dc_port(args):
    """
    Get the title page of PSC using the dc-port and make sure the page exists
    """
    psc_host_name = get_ipv6_preferred_name(args)
    if not args.dcport:
        args.dcport = '443'
    if not (args.dcport).isdigit():
        msg = "Provided HTTPS port is not a number. Exiting ..."
        log_and_print(msg, err=True)
        return False
    websso_url = 'https://%s:%s/websso/' % (psc_host_name, args.dcport)
    try:
        source = urllib2.urlopen(websso_url, timeout=15).read()
        if source is not None:
            pattern = '<title>.*ID_WelcomePsc.*</title>'
            match = re.search(pattern, source)
            if match.group(0) not in source:
                msg = 'Sucessfully Validated HTTPS port %s but failed to match '\
                      'ID_WelcomePsc in title' % dc_port
                log_and_print(msg, warn=True)
        else:
             msg = 'Unable to read the content from  %s' % websso_url
             log_and_print(msg, warn=True)

        return True

    except (urllib2.URLError, Exception) as e:
        msg = ('Falied to open connection %s Error: %s\n'
               'Please check the configuration and retry' % (websso_url, e))
        log_and_print(msg, err=True)
        return False


def validate_sso_credential(args):
    """
    Validate sso password authentication by getting saml token.
    """
    sso_host_name = get_ipv6_preferred_name(args)
    try:
        password = args.password.decode(sys.getfilesystemencoding()).encode('utf-8')
        username = args.username.decode(sys.getfilesystemencoding()).encode('utf-8')
        domain = args.domain.decode(sys.getfilesystemencoding()).encode('utf-8')
    except Exception as e:
        msg = 'Falied to convert args %s to utf8 format.Error: %s' % (args, e)
        return False

    sts_endpoint = 'https://{}/sts/STSService/{}'
    sso_host = '%s:%s' %(sso_host_name, args.dcport)
    sts_url = sts_endpoint.format(sso_host, domain)
    try:
        sso.SsoAuthenticator(sts_url).get_bearer_saml_assertion(
                         '{}@{}'.format(username, domain), password)
        return True
    except (sso.SoapException, Exception) as e:
        msg = 'Failed to validate sso credential. Error %s' %e
        log_and_print(msg, err=True)
        return False

def get_ipv6_preferred_name(args):
   """
   Determines if the pnid is ipv6 type, it constructs IPv6 url hostname by
   adding square brackets around IP address and  set pnid as IPv6 preffered
   form.
   """
   try:
      pnid_addr = IPAddress(args.psc_node)
      if pnid_addr.version == 6:
          # Address type is IPv6
          ipv6_url_hostname = "[" + ipv6PreferredForm(pnid_addr) + "]"
          args.psc_node =  ipv6PreferredForm(args.psc_node)
      else:
          # Address type is IPv4
          ipv6_url_hostname = pnid
      return ipv6_url_hostname
   except:
      # Address not ip address
      return args.psc_node

def update_psc_type(filename):
   """
   Read the file content and replace the word embedded to external
   """
   try:
       with open(filename, 'r+') as fh:
           contents = fh.read()
           fh.seek(0)
           fh.write(contents.replace('embedded', 'external'))
       return True
   except IOError as e:
       msg = 'Failed to replace psc node type from embedded to external in %s '\
             'Error: %s' % (filename, e)
       log_and_print(msg, err=True)
       return False


def do_reconfigure_cleanup():
   """
   Performs the janitorial tasks after changing node type to make sure that
   other components recognize the node correctly.
   """
   log_and_print("Cleaning up...")
   # remove vmidentity.mfx since it should only be present on embedded or
   # infra node
   vmidentity_path = os.path.join(os.environ['VMWARE_CFG_DIR'], 'vm-support',
                                  'vmidentity.mfx')
   try:
      os.remove(vmidentity_path)
   except OSError as e:
      # errno == 2 means that this file does not exist - and that's ok.
      # all other errors should be reported.
      if not hasattr(e, "errno") or e.errno != 2:
         raise
      log_and_print("%s does not exist - skipping" % vmidentity_path, True)
   else:
      log_and_print("Cleanup completed")

def update_winreg():
    # updating node type from embedded to management in window registry
    key = r'SOFTWARE\VMware, Inc.\vCenter Server'
    variable = r'INSTALL_TYPE'
    val = r'management'
    if not set_winregistry(key, variable, val):
         return False
    # updating uninstall display name from embedded to external in window registry
    # Don't fail repointing in case display name update fails
    variable = r'DisplayName'
    key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\VMware-VCS'
    display = get_winregistry(key, variable)
    if display:
        new_display = display.replace('embedded', 'external')
        if not set_winregistry(key, variable, new_display):
            msg = 'Failed to set uninstall displayname in registry and skipping...'
    else:
        msg = 'Failed to get uninstall displayname and skipping...'
        log_and_print(msg, warn=True)
    return True

def disable_infraSvcs(node_type, action):

    """ Set service start type services to DISABLED state to prevent auto
        start during boot """


    svcConfig = dict(svcs=loadServicesFile(svcPath), deploymentType=node_type,
                    operation=action, dbType=get_db_type())

    svcList =  getNodeSvcsList(svcConfig, node_type)
    svcsInfoMgr = SvcsInfoMgr()
    avail_svcList = []

    for svc in svcList:
       svc_nodename =  svcsInfoMgr.get_svc_nodename(svc)
       if svcsInfoMgr.is_available(svc_nodename):
            avail_svcList.append(svc)

    if is_vmon_enabled():
        try:
            invoke_command([svcCtrlPath, '--start', '--vmon-profile', 'NONE'],
                           quiet=QUIET)
            update_services_startuptype(action, StartType.DISABLED,
                False, False, False, svc_names=avail_svcList)
        except Exception as e:
            msg = "Unable to disable services, Exception: %s" % e
            log_and_print(msg, err=True)
            return False
    else:
        for svc_name in avail_svcList:
            try:
                set_service_start_type(svc_name, StartType.DISABLED)
            except (SetServiceStartTypeException, ServiceNotFoundException) as e:
                msg = "Unable to disable service %s, Exception: %s" %(svc_name, e)
                log_and_print(msg, err=True)
                return False
    return True


def uninstall_infraSvcs(node_type, action):
    """ Uninstall purly infrastructure services. """

    # On ciswin unenstall core infrastructure node's services
    svcConfig = dict(svcs=loadServicesFile(svcPath), deploymentType=node_type,
                    operation=action, dbType=get_db_type())
    fbList = getNodefbScriptsList(svcConfig, node_type)
    fbScriptsList = []
    for fb_script in fbList:
        if is_linux():
            fb_script = fb_script[0].split('/')[-1]
            fb_script = fb_script.split('.')[0]
        else:
            fb_script = fb_script[0].split('.')[0]
        fbScriptsList.append(fb_script)
    # Converting list to a coma separated string
    fbScripts  = ','.join(fbScriptsList)
    msg = 'Uninstalling %s.' %fbScripts
    logging.info(msg)
    status = True
    if is_windows():
       if not sevice_stop_start('stop', sca_svc):
           return False

    if not set_deployment_nodetype('infrastructure'):
       msg = 'Unable to set the notetype as infrastructure node'
       log_and_print(msg, err=True)
       return False

    try:
        invoke_command([runfbBin, '--action', 'firstboot', '--subaction',
                        'uninstall', '--fbWhiteList', fbScripts], quiet=QUIET)
    except (InvokeCommandException, Exception) as e:
            msg = 'Unable to uninstall  %s, Exception: %s' %(fb_script, e)
            log_and_print(msg, err=True)
            status = False

    if not set_deployment_nodetype('management'):
            msg = 'Unable to set the notetype as management node'
            log_and_print(msg, err=True)
            status = False
    if is_windows():
        if not sevice_stop_start('start', sca_svc):
            status = False
    return status

def sevice_stop_start(action, svc_name):
   """ stop or start a single service"""
   action = '--%s' % action
   try:
       invoke_command([svcCtrlPath, action, svc_name],
                       quiet=QUIET)
       return True
   except (InvokeCommandException, Exception) as e:
       msg = "Unable to %s  %s, Exception: %s" % (action, svc_name, e)
       log_and_print(msg, err=True)
       return False


def log_and_print(msg, err=False, warn=False):
   """ print and log the msg"""
   print msg
   if err:
      logging.error(msg)
   elif warn:
      logging.warning(msg)
   else:
      logging.info(msg)

def sso_host_up(hostname):
   """
   Get hostname to make sure hostname is valid and host is up
   """
   try:
       socket.gethostbyname(hostname)
       return True
   except socket.error as e:
       msg = 'Unable to connect PSC: %s Error: %s \n'\
             'Please check if the PSC host up and running' % (hostname, e)
       log_and_print(msg, err=True)
       return False

def check_repl_psc(args):
   """
   Validates the provided PSC node is a replication partner of localhost.
   This validation is done only during reconfiguring case. Fon MxN configuration
   this command is not available on vCenter Server. In MxN configuration
   if the provided PSC is not a replication partner it will fail during set-dc-name
   execution.
   """
   new_psc = args.psc_node
   try:
      rep_partners = invoke_command([vdcrepadmin_path, '-f', 'showpartners', '-h',
          'localhost', '-u', args.username, '-w', args.password], quiet=True)
   except (InvokeCommandException, Exception) as e:
      msg = 'Error: %s' % e
      log_and_print(msg, err=True)
      return False
   if new_psc not in rep_partners:
      msg = 'Error: The provided Platform Services Controller(PSC) %s is not '\
            'a replication partner of the localhost\n. Please make sure to '\
            'provide the Primary Network Identifier (PNID) of the PSC.' % new_psc
      log_and_print(msg, err=True)
      return False
   return True

def check_psc_node(new_psc):
   """
   Validates the provided PSC node is not the current PSC of VC.
   This check is done only on MxN configuration. The check_repl_psc call performs
   similar check in reconfiguring case.
   """
   try:
      curr_psc = invoke_command([vmafd_cli_path, 'get-dc-name', '--server-name',
         'localhost'], quiet=QUIET)
   except (InvokeCommandException, Exception) as e:
      msg = 'Error: %s' % e
      log_and_print(msg, err=True)
      return False
   if curr_psc.rstrip() == new_psc:
      msg = 'Error: The provided Platform Services Controller(PSC) %s is already '\
            'the current active PSC of this vCenter Server' % new_psc
      log_and_print(msg, err=True)
      return False
   else:
      return True

def is_required_agrs_set(args):
   """
   If required arguments were not provided, prompt the users
   """
   # Get SSO domain name if not set
   if not args.domain:
      for retry in range(0,3):
         args.domain = raw_input('Enter SSO Domain Name: ')
         if args.domain:
               break
      else:
         msg = 'Reconfigure requires SSO domain name. Exiting ...'
         log_and_print(msg, err=True)
         return False

   # Get SSO username name if not set
   if not args.username:
      for retry in range(0,3):
         args.username = raw_input('Enter SSO Domain Admin Username: ')
         if args.username:
             break
      else:
         msg = 'Reconfigure requires SSO domain Admin Username. Exiting ...'
         log_and_print(msg, err=True)
         return False

   # Get SSO password  if not set
   if not args.password:
      for retry in range(0,3):
         args.password = getpass.getpass("Enter SSO Domain Password:")
         if args.password:
             break
      else:
         msg = 'Reconfigure requires SSO domain password. Exiting ...'
         log_and_print(msg, err=True)
         return False
   return True

def demote_dc(username, passwd):

    """
    Demotes embedded node to management node
    """
    dc_state = get_domain_state()
    if dc_state != 'Controller':
        msg = 'get-domain-state is not Controller but %s' %dc_state
        log_and_print(msg, err=True)
        return False
    try:
        invoke_command([vmafd_cli_path, 'demote-vmdir', '--user-name',
                        username, '--password', passwd], quiet=True)
        return True
    except (InvokeCommandException, Exception) as e:
        msg = "Error:%s" %e
        log_and_print(msg)
        return False

def get_domain_state():
    """
    Returns the domain state of localhost
    """
    try:
        dc_state = invoke_command([vmafd_cli_path, 'get-domain-state',
        '--server-name', 'localhost'], quiet=QUIET)
        return dc_state.rstrip()
    except:
        log_and_print(msg, err=True)
        return False

def join_new_dc(args, machine):

    """
    Join the external psc
    """
    username = args.username
    passwd = args.password
    psc_host = args.psc_node
    domain = args.domain
    dc_state = get_domain_state()
    if dc_state != 'None':
         msg = 'get-domain-state is not None but %s.' % dc_state
         log_and_print(msg, err=True)
         return False
    try:
        invoke_command([vmafd_cli_path, 'join-vmdir', '--server-name', psc_host,
            '--user-name', username, '--machine-name', machine, '--domain-name',
            domain, '--password', passwd], quiet=True)
        msg= 'Successfully joined the external PSC %s' % psc_host
        log_and_print(msg)
        return True
    except (InvokeCommandException, Exception) as e:
        msg = "Error:%s" %e
        log_and_print(msg)
        return False

def set_dc_config(dc_name, dc_port="443"):
   """
   Set the given PSC node for vmafdd
   """
   try:
      # set DC Name
      invoke_command([vmafd_cli_path, 'set-dc-name', '--server-name', 'localhost',
            '--dc-name', dc_name], quiet=QUIET)

      # Set DC port
      invoke_command([vmafd_cli_path, 'set-dc-port', '--server-name', 'localhost',
                      '--dc-port', dc_port], quiet=QUIET)
      return True
   except (InvokeCommandException, Exception) as e:
      msg = "Error:%s" %e
      log_and_print(msg)
      return False

def main():
   # Parse command line arguments.
   parser = argparse.ArgumentParser(
      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
      description="Tool for orchestrating changes"
      " to PNID, Machine Certificate, unregistering a node from Component Manager, SSO,"
      " reconfiguring a vCenter Server with embedded PSC and repointing a vCenter "
      " Server to an external PSC" )
   cmds = parser.add_subparsers()

   # Command to set machine cert.
   machineCertCmds = cmds.add_parser('machinecert',
                                     help=("Set machine SSL cert of this node."
                                     " Note:- Services will be Restarted."))
   machineCertCmds.set_defaults(func=machineCertCmdHandler)
   machineCertNodeGroup = machineCertCmds.add_argument_group()
   machineCertNodeGroup.add_argument("--certfile", required=True,
                                     help="Path to X509 certificate file.")
   machineCertNodeGroup.add_argument("--keyfile", required=True,
                                     help="Path to private key file.")
   machineCertNodeGroup.add_argument("--username",
      help="Admin user name. If not provided, will use machine account.")
   machineCertNodeGroup.add_argument("--passwd",
      help="Admin password. If not provided, will prompt for it.")
   machineCertNodeGroup.add_argument('--debug', dest='debug_mode', action='store_true', help='Set'\
       ' this flag to get command execution detail')
   # Commands to unregister a node.
   unregisterCmds = cmds.add_parser('unregister',
      help="Unregister node. By default solution users, computer account and "
            "service endpoints will be unregistered.")
   unregisterCmds.set_defaults(func=unregisterCmdHandler)
   unregisterNodeGroup = unregisterCmds.add_argument_group()
   unregisterNodeGroup.add_argument("--node-pnid",
      help="System Name of the node to unregister. If not provided will default to "
           "current node.")
   unregisterNodeGroup.add_argument("--hostId",
      help="Host id of the node corresponding to given System Name.")
   unregisterNodeGroup.add_argument("--username", required=True,
      help="Admin user name.")
   unregisterNodeGroup.add_argument("--passwd",
      help="Admin password. If not provided, will prompt for it.")
   unregisterNodeGroup.add_argument('--debug', dest='debug_mode', action='store_true', help='Set'\
       ' this flag to get command execution detail')
   # Commands to repoint a PSC node.
   repoint = '''
      # To repoint the vCenter Server with an external Platform Services
        Controller to another external Platform Services Controller with
        default HTTPS port(443).

      >cmsso-util repoint --repoint-psc <PSC-NODE-PNID>

      # To repoint the vCenter Server with an external Platform Services
        Controller to another external Platform Services Controller with
        custom HTTPS port.
      >cmsso-util repoint --repoint-psc <PSC-NODE-PNID> --dc-port <port number>
      '''

   reconfigusage = '''
      # To reconfigure vCenter Server with an embedded Platform Services
        Controller to another external Platform Services Controller with
        default HTTPS port(443).

      >cmsso-util reconfigure --repoint-psc <PSC-NODE-PNID>
       --username <SSO Domain Admin> --domain-name <SSO Domain Name>
       --passwd <SSO Domain Admin password>

      # To reconfigure vCenter Server with an embedded Platform Services
        Controller to another external Platform Services Controller with
        custom HTTPS port.

      >cmsso-util reconfigure --repoint-psc <PSC-NODE-PNID>
       --username <SSO Domain Admin> --domain-name <SSO Domain Name>
       --dc-port <port number> --passwd <SSO Domain Admin password>
      '''
   reconfigCmds = cmds.add_parser('reconfigure', usage=reconfigusage,
      help='Reconfigure a vCenter with an embedded Platform Services Controller(PSC)'\
      ' to a vCenter Server.\n Then it repoints to the provided external PSC node.')
   reconfigCmds.set_defaults(func=reconfig_to_external_psc)
   reconfigNodeGroup = reconfigCmds.add_argument_group()
   reconfigNodeGroup.add_argument('--repoint-psc', dest='psc_node', required=True,
       help='System Name (PNID) of the PSC node to repoint')
   reconfigNodeGroup.add_argument('--username', dest='username', required=True,
       help='SSO domain Admin user name. Required for reconfiguring an embedded '\
            'node.')
   reconfigNodeGroup.add_argument('--passwd', dest='password', required=True,
       help='SSO Admin password. Required for reconfiguring an embedded node.')
   reconfigNodeGroup.add_argument('--domain-name', dest='domain', required=True,
       help='SSO domain name. Required for reconfiguring an embedded node.')
   reconfigNodeGroup.add_argument('--dc-port', dest='dcport', default='443', help='External'\
       ' Platform Services Controller(PSC) HTTPS Port, if --dc-port is not set, default'\
       ' 443 will be used.')
   reconfigNodeGroup.add_argument('--debug', dest='debug_mode', action='store_true', help='Set'\
       ' this flag to get command execution detail')

   repointCmds = cmds.add_parser('repoint', usage=repoint,
       help='Repoints a vCenter with an external Platform Services Controller(PSC)'\
       ' to the provided external PSC node.')
   repointCmds.set_defaults(func=repoint_to_external_psc)
   repointNodeGroup = repointCmds.add_argument_group()
   repointNodeGroup.add_argument('--repoint-psc', dest='psc_node', required=True,
       help='System Name (PNID) of the PSC node to repoint')
   repointNodeGroup.add_argument('--dc-port', dest='dcport', default='443', help='External'\
       ' Platform Services Controller(PSC) HTTPS Port, if --dc-port is not set, default'\
       ' 443 will be used')
   repointNodeGroup.add_argument('--debug', dest='debug_mode', action='store_true', help='Set'\
       ' this flag to get command execution detail')
   args = parser.parse_args()
   # Call the cmd handler.
   global QUIET
   if args.debug_mode:
      QUIET = False
   return args.func(args)


if __name__ == "__main__":
   logDir = os.path.join(get_cis_log_dir(), 'cloudvm')
   setupLogging('cmsso_util', logMechanism='file', logDir=logDir)
   exit(main())
