@ -3,6 +3,7 @@
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# Copyright: (c) 2017, Ansible Project
# Copyright: (c) 2017, Ansible Project
# Copyright: (c) 2018, Christian Kotte <christian.kotte@gmx.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import , division , print_function
from __future__ import absolute_import , division , print_function
@ -17,19 +18,24 @@ ANSIBLE_METADATA = {
DOCUMENTATION = r '''
DOCUMENTATION = r '''
- - -
- - -
module : vmware_host
module : vmware_host
short_description : Add / Remove ESXi host to / from vCenter
short_description : Add , remove , or move an ESXi host to , from , or within vCenter
description :
description :
- This module can be used to add / remove / reconnect an ESXi host to / from vCenter .
- This module can be used to add , reconnect , or remove an ESXi host to or from vCenter .
- This module can also be used to move an ESXi host to a cluster or folder , or vice versa , within the same datacenter .
version_added : ' 2.0 '
version_added : ' 2.0 '
author :
author :
- Joseph Callen ( @jcpowermac )
- Joseph Callen ( @jcpowermac )
- Russell Teague ( @mtnbikenc )
- Russell Teague ( @mtnbikenc )
- Maxime de Roucy ( @tchernomax )
- Maxime de Roucy ( @tchernomax )
- Christian Kotte ( @ckotte )
notes :
notes :
- Tested on vSphere 5.5 , 6.0 and 6.5
- Tested on vSphere 5.5 , 6.0 , 6.5 and 6.7
requirements :
requirements :
- python > = 2.6
- python > = 2.6
- PyVmomi
- PyVmomi
- ssl
- socket
- hashlib
options :
options :
datacenter_name :
datacenter_name :
description :
description :
@ -37,12 +43,14 @@ options:
- Aliases added in version 2.6 .
- Aliases added in version 2.6 .
required : yes
required : yes
aliases : [ ' datacenter ' ]
aliases : [ ' datacenter ' ]
type : str
cluster_name :
cluster_name :
description :
description :
- Name of the cluster to add the host .
- Name of the cluster to add the host .
- If C ( folder ) is not set , then this parameter is required .
- If C ( folder ) is not set , then this parameter is required .
- Aliases added in version 2.6 .
- Aliases added in version 2.6 .
aliases : [ ' cluster ' ]
aliases : [ ' cluster ' ]
type : str
folder :
folder :
description :
description :
- Name of the folder under which host to add .
- Name of the folder under which host to add .
@ -55,24 +63,28 @@ options:
- " - ' /Site2/dc2/Asia-Cluster/host ' "
- " - ' /Site2/dc2/Asia-Cluster/host ' "
- " - ' /dc3/Asia-Cluster/host ' "
- " - ' /dc3/Asia-Cluster/host ' "
version_added : " 2.6 "
version_added : " 2.6 "
aliases : [ ' folder_name ' ]
type : str
add_connected :
add_connected :
description :
description :
- If set to C ( True ) , then the host should be connected as soon as it is added .
- If set to C ( True ) , then the host should be connected as soon as it is added .
- This parameter is ignored if state is set to a value other than C ( present ) .
- This parameter is ignored if state is set to a value other than C ( present ) .
default : True
default : True
type : ' bool '
type : bool
version_added : " 2.6 "
version_added : " 2.6 "
esxi_hostname :
esxi_hostname :
description :
description :
- ESXi hostname to manage .
- ESXi hostname to manage .
required : yes
required : yes
type : str
esxi_username :
esxi_username :
description :
description :
- ESXi username .
- ESXi username .
- Required for adding a host .
- Required for adding a host .
- Optional for reconnect .
- Optional for reconnect . If both C ( esxi_username ) and C ( esxi_password ) are used
- Unused for removing .
- Unused for removing .
- No longer a required parameter from version 2.5 .
- No longer a required parameter from version 2.5 .
type : str
esxi_password :
esxi_password :
description :
description :
- ESXi password .
- ESXi password .
@ -80,23 +92,53 @@ options:
- Optional for reconnect .
- Optional for reconnect .
- Unused for removing .
- Unused for removing .
- No longer a required parameter from version 2.5 .
- No longer a required parameter from version 2.5 .
type : str
state :
state :
description :
description :
- If set to C ( present ) , then add the host if host is absent .
- If set to C ( present ) , add the host if host is absent .
- If set to C ( present ) , then do nothing if host already exists .
- If set to C ( present ) , update the location of the host if host already exists .
- If set to C ( absent ) , then remove the host if host is present .
- If set to C ( absent ) , remove the host if host is present .
- If set to C ( absent ) , then do nothing if host already does not exists .
- If set to C ( absent ) , do nothing if host already does not exists .
- If set to C ( add_or_reconnect ) , then add the host if it ' s absent else reconnect it .
- If set to C ( add_or_reconnect ) , add the host if it ' s absent else reconnect it and update the location .
- If set to C ( reconnect ) , then reconnect the host if it ' s present else fail .
- If set to C ( reconnect ) , then reconnect the host if it ' s present and update the location .
default : present
default : present
choices : [ ' present ' , ' absent ' , ' add_or_reconnect ' , ' reconnect ' ]
choices : [ ' present ' , ' absent ' , ' add_or_reconnect ' , ' reconnect ' ]
type : str
esxi_ssl_thumbprint :
esxi_ssl_thumbprint :
description :
description :
- " Specifying the hostsystem certificate ' s thumbprint. "
- " Specifying the hostsystem certificate ' s thumbprint. "
- " Use following command to get hostsystem certificate ' s thumbprint - "
- " Use following command to get hostsystem certificate ' s thumbprint - "
- " # openssl x509 -in /etc/vmware/ssl/rui.crt -fingerprint -sha1 -noout "
- " # openssl x509 -in /etc/vmware/ssl/rui.crt -fingerprint -sha1 -noout "
- Only used if C ( fetch_thumbprint ) isn ' t set to C(true).
version_added : 2.5
version_added : 2.5
default : ' '
default : ' '
type : str
aliases : [ ' ssl_thumbprint ' ]
fetch_ssl_thumbprint :
description :
- Fetch the thumbprint of the host ' s SSL certificate.
- This basically disables the host certificate verification ( check if it was signed by a recognized CA ) .
- Disable this option if you want to allow only hosts with valid certificates to be added to vCenter .
- If this option is set to C ( false ) and the certificate can ' t be verified, an add or reconnect will fail.
- Unused when C ( esxi_ssl_thumbprint ) is set .
- Optional for reconnect , but only used if C ( esxi_username ) and C ( esxi_password ) are used .
- Unused for removing .
type : bool
version_added : 2.8
default : True
force_connection :
description :
- Force the connection if the host is already being managed by another vCenter server .
type : bool
version_added : 2.8
default : True
reconnect_disconnected :
description :
- Reconnect disconnected hosts .
- This is only used if C ( state ) is set to C ( present ) and if the host already exists .
type : bool
version_added : 2.8
default : True
extends_documentation_fragment : vmware . documentation
extends_documentation_fragment : vmware . documentation
'''
'''
@ -106,8 +148,8 @@ EXAMPLES = r'''
hostname : ' {{ vcenter_hostname }} '
hostname : ' {{ vcenter_hostname }} '
username : ' {{ vcenter_username }} '
username : ' {{ vcenter_username }} '
password : ' {{ vcenter_password }} '
password : ' {{ vcenter_password }} '
datacenter _name : datacenter_name
datacenter : datacenter_name
cluster _name : cluster_name
cluster : cluster_name
esxi_hostname : ' {{ esxi_hostname }} '
esxi_hostname : ' {{ esxi_hostname }} '
esxi_username : ' {{ esxi_username }} '
esxi_username : ' {{ esxi_username }} '
esxi_password : ' {{ esxi_password }} '
esxi_password : ' {{ esxi_password }} '
@ -119,7 +161,7 @@ EXAMPLES = r'''
hostname : ' {{ vcenter_hostname }} '
hostname : ' {{ vcenter_hostname }} '
username : ' {{ vcenter_username }} '
username : ' {{ vcenter_username }} '
password : ' {{ vcenter_password }} '
password : ' {{ vcenter_password }} '
datacenter _name : datacenter_name
datacenter : datacenter_name
folder : ' /Site2/Asia-Cluster/host '
folder : ' /Site2/Asia-Cluster/host '
esxi_hostname : ' {{ esxi_hostname }} '
esxi_hostname : ' {{ esxi_hostname }} '
esxi_username : ' {{ esxi_username }} '
esxi_username : ' {{ esxi_username }} '
@ -133,8 +175,8 @@ EXAMPLES = r'''
hostname : ' {{ vcenter_hostname }} '
hostname : ' {{ vcenter_hostname }} '
username : ' {{ vcenter_username }} '
username : ' {{ vcenter_username }} '
password : ' {{ vcenter_password }} '
password : ' {{ vcenter_password }} '
datacenter _name : datacenter_name
datacenter : datacenter_name
cluster _name : cluster_name
cluster : cluster_name
esxi_hostname : ' {{ esxi_hostname }} '
esxi_hostname : ' {{ esxi_hostname }} '
esxi_username : ' {{ esxi_username }} '
esxi_username : ' {{ esxi_username }} '
esxi_password : ' {{ esxi_password }} '
esxi_password : ' {{ esxi_password }} '
@ -146,8 +188,8 @@ EXAMPLES = r'''
hostname : ' {{ vcenter_hostname }} '
hostname : ' {{ vcenter_hostname }} '
username : ' {{ vcenter_username }} '
username : ' {{ vcenter_username }} '
password : ' {{ vcenter_password }} '
password : ' {{ vcenter_password }} '
datacenter _name : datacenter_name
datacenter : datacenter_name
cluster _name : cluster_name
cluster : cluster_name
esxi_hostname : ' {{ esxi_hostname }} '
esxi_hostname : ' {{ esxi_hostname }} '
state : reconnect
state : reconnect
delegate_to : localhost
delegate_to : localhost
@ -157,8 +199,8 @@ EXAMPLES = r'''
hostname : ' {{ vcenter_hostname }} '
hostname : ' {{ vcenter_hostname }} '
username : ' {{ vcenter_username }} '
username : ' {{ vcenter_username }} '
password : ' {{ vcenter_password }} '
password : ' {{ vcenter_password }} '
datacenter _name : datacenter_name
datacenter : datacenter_name
cluster _name : cluster_name
cluster : cluster_name
esxi_hostname : ' {{ esxi_hostname }} '
esxi_hostname : ' {{ esxi_hostname }} '
esxi_username : ' {{ esxi_username }} '
esxi_username : ' {{ esxi_username }} '
esxi_password : ' {{ esxi_password }} '
esxi_password : ' {{ esxi_password }} '
@ -172,23 +214,30 @@ result:
description : metadata about the new host system added
description : metadata about the new host system added
returned : on successful addition
returned : on successful addition
type : str
type : str
sample : " 'vim.ComputeResource:domain-s222 ' "
sample : " Host already connected to vCenter ' vcenter01 ' in cluster ' cluster01 ' "
'''
'''
try :
try :
from pyVmomi import vim , vmodl
from pyVmomi import vim , vmodl
import ssl
import socket
import hashlib
except ImportError :
except ImportError :
pass
pass
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . _text import to_native
from ansible . module_utils . _text import to_native
from ansible . module_utils . vmware import ( PyVmomi , TaskError , vmware_argument_spec ,
from ansible . module_utils . vmware import (
wait_for_task , find_host_by_cluster_datacenter )
PyVmomi , TaskError , vmware_argument_spec ,
wait_for_task , find_host_by_cluster_datacenter , find_hostsystem_by_name
)
class VMwareHost ( PyVmomi ) :
class VMwareHost ( PyVmomi ) :
""" Class to manage vCenter connection """
def __init__ ( self , module ) :
def __init__ ( self , module ) :
super ( VMwareHost , self ) . __init__ ( module )
super ( VMwareHost , self ) . __init__ ( module )
self . vcenter = module . params [ ' hostname ' ]
self . datacenter_name = module . params [ ' datacenter_name ' ]
self . datacenter_name = module . params [ ' datacenter_name ' ]
self . cluster_name = module . params [ ' cluster_name ' ]
self . cluster_name = module . params [ ' cluster_name ' ]
self . folder_name = module . params [ ' folder ' ]
self . folder_name = module . params [ ' folder ' ]
@ -197,25 +246,32 @@ class VMwareHost(PyVmomi):
self . esxi_password = module . params [ ' esxi_password ' ]
self . esxi_password = module . params [ ' esxi_password ' ]
self . state = module . params [ ' state ' ]
self . state = module . params [ ' state ' ]
self . esxi_ssl_thumbprint = module . params . get ( ' esxi_ssl_thumbprint ' , ' ' )
self . esxi_ssl_thumbprint = module . params . get ( ' esxi_ssl_thumbprint ' , ' ' )
self . cluster = self . folder = self . host = None
self . force_connection = module . params . get ( ' force_connection ' )
self . fetch_ssl_thumbprint = module . params . get ( ' fetch_ssl_thumbprint ' )
self . reconnect_disconnected = module . params . get ( ' reconnect_disconnected ' )
self . host_update = self . host = self . cluster = self . folder = self . host_parent_compute_resource = None
def process_state ( self ) :
def process_state ( self ) :
# Currently state_update_host is not implemented.
""" Check the current state """
host_states = {
host_states = {
' absent ' : {
' absent ' : {
' present ' : self . state_remove_host ,
' present ' : self . state_remove_host ,
' update ' : self . state_remove_host ,
' absent ' : self . state_exit_unchanged ,
' absent ' : self . state_exit_unchanged ,
} ,
} ,
' present ' : {
' present ' : {
' present ' : self . state_exit_unchanged ,
' present ' : self . state_exit_unchanged ,
' update ' : self . state_update_host ,
' absent ' : self . state_add_host ,
' absent ' : self . state_add_host ,
} ,
} ,
' add_or_reconnect ' : {
' add_or_reconnect ' : {
' present ' : self . state_reconnect_host ,
' present ' : self . state_reconnect_host ,
' update ' : self . state_update_host ,
' absent ' : self . state_add_host ,
' absent ' : self . state_add_host ,
} ,
} ,
' reconnect ' : {
' reconnect ' : {
' present ' : self . state_reconnect_host ,
' present ' : self . state_reconnect_host ,
' update ' : self . state_update_host ,
}
}
}
}
@ -228,156 +284,508 @@ class VMwareHost(PyVmomi):
except Exception as e :
except Exception as e :
self . module . fail_json ( msg = to_native ( e ) )
self . module . fail_json ( msg = to_native ( e ) )
def check_host_state ( self ) :
""" Check current state """
# Check if the host is already connected to vCenter
self . host_update = find_hostsystem_by_name ( self . content , self . esxi_hostname )
if self . host_update :
# The host name is unique in vCenter; A host with the same name cannot exist in another datacenter
# However, the module will fail later if the target folder/cluster is in another datacenter as the host
# Check if the host is connected under the target cluster
if self . cluster_name :
self . host , self . cluster = self . search_cluster ( self . datacenter_name , self . cluster_name , self . esxi_hostname )
if self . host :
state = ' present '
else :
state = ' update '
# Check if the host is connected under the target folder
elif self . folder_name :
self . folder = self . search_folder ( self . folder_name )
for child in self . folder . childEntity :
if not child or not isinstance ( child , vim . ComputeResource ) :
continue
try :
if isinstance ( child . host [ 0 ] , vim . HostSystem ) and child . name == self . esxi_hostname :
self . host_parent_compute_resource = child
self . host = child . host [ 0 ]
break
except IndexError :
continue
if self . host :
state = ' present '
else :
state = ' update '
else :
state = ' absent '
return state
def search_folder ( self , folder_name ) :
"""
Search folder in vCenter
Returns : folder object
"""
search_index = self . content . searchIndex
folder_obj = search_index . FindByInventoryPath ( folder_name )
if not ( folder_obj and isinstance ( folder_obj , vim . Folder ) ) :
self . module . fail_json ( msg = " Folder ' %s ' not found " % folder_name )
return folder_obj
def search_cluster ( self , datacenter_name , cluster_name , esxi_hostname ) :
"""
Search cluster in vCenter
Returns : host and cluster object
"""
return find_host_by_cluster_datacenter (
self . module , self . content , datacenter_name , cluster_name , esxi_hostname
)
def state_exit_unchanged ( self ) :
""" Exit with status message """
if self . reconnect_disconnected and self . host_update . runtime . connectionState == ' disconnected ' :
self . state_reconnect_host ( )
else :
if self . folder_name :
result = " Host already connected to vCenter ' %s ' in folder ' %s ' " % ( self . vcenter , self . folder_name )
elif self . cluster_name :
result = " Host already connected to vCenter ' %s ' in cluster ' %s ' " % ( self . vcenter , self . cluster_name )
self . module . exit_json ( changed = False , result = str ( result ) )
def state_add_host ( self ) :
""" Add ESXi host to a cluster of folder in vCenter """
changed = True
result = None
if self . module . check_mode :
result = " Host would be connected to vCenter ' %s ' " % self . vcenter
else :
host_connect_spec = self . get_host_connect_spec ( )
as_connected = self . params . get ( ' add_connected ' )
esxi_license = None
resource_pool = None
task = None
if self . folder_name :
self . folder = self . search_folder ( self . folder_name )
try :
task = self . folder . AddStandaloneHost (
spec = host_connect_spec , compResSpec = resource_pool ,
addConnected = as_connected , license = esxi_license
)
except vim . fault . InvalidLogin as invalid_login :
self . module . fail_json (
msg = " Cannot authenticate with the host : %s " % to_native ( invalid_login )
)
except vim . fault . HostConnectFault as connect_fault :
self . module . fail_json (
msg = " An error occurred during connect : %s " % to_native ( connect_fault )
)
except vim . fault . DuplicateName as duplicate_name :
self . module . fail_json (
msg = " The folder already contains a host with the same name : %s " %
to_native ( duplicate_name )
)
except vmodl . fault . InvalidArgument as invalid_argument :
self . module . fail_json (
msg = " An argument was specified incorrectly : %s " % to_native ( invalid_argument )
)
except vim . fault . AlreadyBeingManaged as already_managed :
self . module . fail_json (
msg = " The host is already being managed by another vCenter server : %s " %
to_native ( already_managed )
)
except vmodl . fault . NotEnoughLicenses as not_enough_licenses :
self . module . fail_json (
msg = " There are not enough licenses to add this host : %s " % to_native ( not_enough_licenses )
)
except vim . fault . NoHost as no_host :
self . module . fail_json (
msg = " Unable to contact the host : %s " % to_native ( no_host )
)
except vmodl . fault . NotSupported as not_supported :
self . module . fail_json (
msg = " The folder is not a host folder : %s " % to_native ( not_supported )
)
except vim . fault . NotSupportedHost as host_not_supported :
self . module . fail_json (
msg = " The host is running a software version that is not supported : %s " %
to_native ( host_not_supported )
)
except vim . fault . AgentInstallFailed as agent_install :
self . module . fail_json (
msg = " Error during vCenter agent installation : %s " % to_native ( agent_install )
)
except vim . fault . AlreadyConnected as already_connected :
self . module . fail_json (
msg = " The host is already connected to the vCenter server : %s " % to_native ( already_connected )
)
except vim . fault . SSLVerifyFault as ssl_fault :
self . module . fail_json (
msg = " The host certificate could not be authenticated : %s " % to_native ( ssl_fault )
)
elif self . cluster_name :
self . host , self . cluster = self . search_cluster (
self . datacenter_name ,
self . cluster_name ,
self . esxi_hostname
)
try :
task = self . cluster . AddHost_Task (
spec = host_connect_spec , asConnected = as_connected ,
resourcePool = resource_pool , license = esxi_license
)
except vim . fault . InvalidLogin as invalid_login :
self . module . fail_json (
msg = " Cannot authenticate with the host : %s " % to_native ( invalid_login )
)
except vim . fault . HostConnectFault as connect_fault :
self . module . fail_json (
msg = " An error occurred during connect : %s " % to_native ( connect_fault )
)
except vim . fault . DuplicateName as duplicate_name :
self . module . fail_json (
msg = " The cluster already contains a host with the same name : %s " %
to_native ( duplicate_name )
)
except vim . fault . AlreadyBeingManaged as already_managed :
self . module . fail_json (
msg = " The host is already being managed by another vCenter server : %s " %
to_native ( already_managed )
)
except vmodl . fault . NotEnoughLicenses as not_enough_licenses :
self . module . fail_json (
msg = " There are not enough licenses to add this host : %s " % to_native ( not_enough_licenses )
)
except vim . fault . NoHost as no_host :
self . module . fail_json (
msg = " Unable to contact the host : %s " % to_native ( no_host )
)
except vim . fault . NotSupportedHost as host_not_supported :
self . module . fail_json (
msg = " The host is running a software version that is not supported; "
" It may still be possible to add the host as a stand-alone host : %s " %
to_native ( host_not_supported )
)
except vim . fault . TooManyHosts as too_many_hosts :
self . module . fail_json (
msg = " No additional hosts can be added to the cluster : %s " % to_native ( too_many_hosts )
)
except vim . fault . AgentInstallFailed as agent_install :
self . module . fail_json (
msg = " Error during vCenter agent installation : %s " % to_native ( agent_install )
)
except vim . fault . AlreadyConnected as already_connected :
self . module . fail_json (
msg = " The host is already connected to the vCenter server : %s " % to_native ( already_connected )
)
except vim . fault . SSLVerifyFault as ssl_fault :
self . module . fail_json (
msg = " The host certificate could not be authenticated : %s " % to_native ( ssl_fault )
)
try :
changed , result = wait_for_task ( task )
result = " Host connected to vCenter ' %s ' " % self . vcenter
except TaskError as task_error :
self . module . fail_json (
msg = " Failed to add host to vCenter ' %s ' : %s " % ( self . vcenter , to_native ( task_error ) )
)
self . module . exit_json ( changed = changed , result = result )
def get_host_connect_spec ( self ) :
def get_host_connect_spec ( self ) :
"""
"""
Function to return Host connection specification
Function to return Host connection specification
Returns : host connection specification
Returns : host connection specification
"""
"""
host_connect_spec = vim . host . ConnectSpec ( )
host_connect_spec = vim . host . ConnectSpec ( )
host_connect_spec . hostName = self . esxi_hostname
host_connect_spec . hostName = self . esxi_hostname
host_connect_spec . userName = self . esxi_username
host_connect_spec . userName = self . esxi_username
host_connect_spec . password = self . esxi_password
host_connect_spec . password = self . esxi_password
host_connect_spec . force = True
host_connect_spec . force = self . force_connection
host_connect_spec . sslThumbprint = self . esxi_ssl_thumbprint
# Get the thumbprint of the SSL certificate
if self . fetch_ssl_thumbprint and self . esxi_ssl_thumbprint == ' ' :
# We need to grab the thumbprint manually because it's not included in
# the task error via an SSLVerifyFault exception anymore
sock = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
sock . settimeout ( 1 )
wrapped_socket = ssl . wrap_socket ( sock )
try :
wrapped_socket . connect ( ( self . esxi_hostname , 443 ) )
except socket . error as socket_error :
self . module . fail_json ( msg = " Cannot connect to host : %s " % socket_error )
else :
der_cert_bin = wrapped_socket . getpeercert ( True )
# thumb_md5 = hashlib.md5(der_cert_bin).hexdigest()
thumb_sha1 = self . format_number ( hashlib . sha1 ( der_cert_bin ) . hexdigest ( ) )
# thumb_sha256 = hashlib.sha256(der_cert_bin).hexdigest()
wrapped_socket . close ( )
host_connect_spec . sslThumbprint = thumb_sha1
else :
host_connect_spec . sslThumbprint = self . esxi_ssl_thumbprint
return host_connect_spec
return host_connect_spec
def add_host_to_vcenter ( self ) :
@staticmethod
host_connect_spec = self . get_host_connect_spec ( )
def format_number ( number ) :
as_connected = self . params . get ( ' add_connected ' )
""" Format number """
esxi_license = None
string = str ( number )
resource_pool = None
return ' : ' . join ( a + b for a , b in zip ( string [ : : 2 ] , string [ 1 : : 2 ] ) )
for count in range ( 0 , 2 ) :
def state_reconnect_host ( self ) :
try :
""" Reconnect host to vCenter """
task = None
changed = True
if self . folder :
result = None
task = self . folder . AddStandaloneHost ( spec = host_connect_spec , addConnected = as_connected )
elif self . cluster :
if self . module . check_mode :
task = self . cluster . AddHost_Task ( host_connect_spec , as_connected , resource_pool , esxi_license )
result = " Host would be reconnected to vCenter ' %s ' " % self . vcenter
success , result = wait_for_task ( task )
else :
return success , result
self . reconnect_host ( self . host )
except TaskError as task_error_exception :
result = " Host reconnected to vCenter ' %s ' " % self . vcenter
task_error = task_error_exception . args [ 0 ]
self . module . exit_json ( changed = changed , result = str ( result ) )
if len ( task_error_exception . args ) == 2 :
host_thumbprint = task_error_exception . args [ 1 ]
def reconnect_host ( self , host_object ) :
else :
""" Reconnect host to vCenter """
host_thumbprint = None
if self . esxi_ssl_thumbprint == ' ' and host_thumbprint :
# User has not specified SSL Thumbprint for ESXi host,
# try to grab it using SSLVerifyFault exception
host_connect_spec . sslThumbprint = host_thumbprint
else :
self . module . fail_json ( msg = " Failed to add host %s to vCenter: %s " % ( self . esxi_hostname ,
to_native ( task_error ) ) )
except vmodl . fault . NotSupported :
self . module . fail_json ( msg = " Failed to add host %s to vCenter as host is "
" being added to a folder %s whose childType "
" property does not contain "
" \" ComputeResource \" . " % ( self . esxi_hostname , self . folder_name ) )
except Exception as generic_exc :
self . module . fail_json ( msg = " Failed to add host %s to vCenter: %s " % ( self . esxi_hostname ,
to_native ( generic_exc ) ) )
self . module . fail_json ( msg = " Failed to add host %s to vCenter " % self . esxi_hostname )
def reconnect_host_to_vcenter ( self ) :
reconnecthost_args = { }
reconnecthost_args = { }
reconnecthost_args [ ' reconnectSpec ' ] = vim . HostSystem . ReconnectSpec ( )
reconnecthost_args [ ' reconnectSpec ' ] = vim . HostSystem . ReconnectSpec ( )
reconnecthost_args [ ' reconnectSpec ' ] . syncState = True
reconnecthost_args [ ' reconnectSpec ' ] . syncState = True
if self . esxi_username is not None or self . esxi_password is not None :
if self . esxi_username and self . esxi_password :
# Build the connection spec as well and fetch thumbprint if enabled
# Usefull if you reinstalled a host and it uses a new self-signed certificate
reconnecthost_args [ ' cnxSpec ' ] = self . get_host_connect_spec ( )
reconnecthost_args [ ' cnxSpec ' ] = self . get_host_connect_spec ( )
try :
for count in range ( 0 , 2 ) :
task = host_object . ReconnectHost_Task ( * * reconnecthost_args )
try :
except vim . fault . InvalidLogin as invalid_login :
task = self . host . ReconnectHost_Task ( * * reconnecthost_args )
self . module . fail_json (
success , result = wait_for_task ( task )
msg = " Cannot authenticate with the host : %s " % to_native ( invalid_login )
return success , result
)
except TaskError as task_error_exception :
except vim . fault . InvalidState as invalid_state :
task_error = task_error_exception . args [ 0 ]
self . module . fail_json (
if self . esxi_ssl_thumbprint == ' ' and isinstance ( task_error , vim . fault . SSLVerifyFault ) :
msg = " The host is not disconnected : %s " % to_native ( invalid_state )
# User has not specified SSL Thumbprint for ESXi host,
)
# try to grab it using SSLVerifyFault exception
except vim . fault . InvalidName as invalid_name :
reconnecthost_args [ ' cnxSpec ' ] . sslThumbprint = task_error . thumbprint
self . module . fail_json (
else :
msg = " The host name is invalid : %s " % to_native ( invalid_name )
self . module . fail_json ( msg = " Failed to reconnect host %s to vCenter: %s " % ( self . esxi_hostname ,
)
to_native ( task_error . msg ) ) )
except vim . fault . HostConnectFault as connect_fault :
self . module . fail_json ( msg = " Failed to reconnect host %s to vCenter " % self . esxi_hostname )
self . module . fail_json (
else :
msg = " An error occurred during reconnect : %s " % to_native ( connect_fault )
try :
)
task = self . host . ReconnectHost_Task ( * * reconnecthost_args )
except vmodl . fault . NotSupported as not_supported :
success , result = wait_for_task ( task )
self . module . fail_json (
return success , result
msg = " No host can be added to this group : %s " % to_native ( not_supported )
except TaskError as task_error_exception :
)
task_error = task_error_exception . args [ 0 ]
except vim . fault . AlreadyBeingManaged as already_managed :
self . module . fail_json ( msg = " Failed to reconnect host %s to vCenter due to %s " % ( self . esxi_hostname ,
self . module . fail_json (
to_native ( task_error . msg ) ) )
msg = " The host is already being managed by another vCenter server : %s " % to_native ( already_managed )
)
def state_exit_unchanged ( self ) :
except vmodl . fault . NotEnoughLicenses as not_enough_licenses :
self . module . exit_json ( changed = False )
self . module . fail_json (
msg = " There are not enough licenses to add this host : %s " % to_native ( not_enough_licenses )
)
except vim . fault . NoHost as no_host :
self . module . fail_json (
msg = " Unable to contact the host : %s " % to_native ( no_host )
)
except vim . fault . NotSupportedHost as host_not_supported :
self . module . fail_json (
msg = " The host is running a software version that is not supported : %s " %
to_native ( host_not_supported )
)
except vim . fault . SSLVerifyFault as ssl_fault :
self . module . fail_json (
msg = " The host certificate could not be authenticated : %s " % to_native ( ssl_fault )
)
try :
changed , result = wait_for_task ( task )
except TaskError as task_error :
self . module . fail_json (
msg = " Failed to reconnect host to vCenter ' %s ' due to %s " %
( self . vcenter , to_native ( task_error ) )
)
def state_remove_host ( self ) :
def state_remove_host ( self ) :
""" Remove host from vCenter """
changed = True
changed = True
result = None
result = None
if not self . module . check_mode :
if self . module . check_mode :
if not self . host . runtime . inMaintenanceMode :
result = " Host would be removed from vCenter ' %s ' " % self . vcenter
maintenance_mode_task = self . host . EnterMaintenanceMode_Task ( 300 , True , None )
else :
changed , result = wait_for_task ( maintenance_mode_task )
# Check parent type
parent_type = self . get_parent_type ( self . host_update )
if changed :
if parent_type == ' cluster ' :
task = self . host . Destroy_Task ( )
self . put_host_in_maintenance_mode ( self . host_update )
try :
if self . folder_name :
task = self . host_parent_compute_resource . Destroy_Task ( )
elif self . cluster_name :
task = self . host . Destroy_Task ( )
except vim . fault . VimFault as vim_fault :
self . module . fail_json ( msg = vim_fault )
try :
changed , result = wait_for_task ( task )
changed , result = wait_for_task ( task )
else :
result = " Host removed from vCenter ' %s ' " % self . vcenter
raise Exception ( result )
except TaskError as task_error :
self . module . fail_json (
msg = " Failed to remove the host from vCenter ' %s ' : %s " % ( self . vcenter , to_native ( task_error ) )
)
self . module . exit_json ( changed = changed , result = str ( result ) )
self . module . exit_json ( changed = changed , result = str ( result ) )
def state_update_host ( self ) :
def put_host_in_maintenance_mode ( self , host_object ) :
self . module . exit_json ( changed = False , msg = " Currently not implemented. " )
""" Put host in maintenance mode, if not already """
if not host_object . runtime . inMaintenanceMode :
try :
try :
maintenance_mode_task = host_object . EnterMaintenanceMode_Task ( 300 , True , None )
except vim . fault . InvalidState as invalid_state :
self . module . fail_json (
msg = " The host is already in maintenance mode : %s " % to_native ( invalid_state )
)
except vim . fault . Timedout as timed_out :
self . module . fail_json (
msg = " The maintenance mode operation timed out : %s " % to_native ( timed_out )
)
except vim . fault . Timedout as timed_out :
self . module . fail_json (
msg = " The maintenance mode operation was canceled : %s " % to_native ( timed_out )
)
wait_for_task ( maintenance_mode_task )
except TaskError as task_err :
self . module . fail_json (
msg = " Failed to put the host in maintenance mode : %s " % to_native ( task_err )
)
def get_parent_type ( self , host_object ) :
"""
Get the type of the parent object
Returns : string with ' folder ' or ' cluster '
"""
object_type = None
# check 'vim.ClusterComputeResource' first because it's also an
# instance of 'vim.ComputeResource'
if isinstance ( host_object . parent , vim . ClusterComputeResource ) :
object_type = ' cluster '
elif isinstance ( host_object . parent , vim . ComputeResource ) :
object_type = ' folder '
return object_type
def state_add_host ( self ) :
def state_update_host ( self ) :
""" Move host to a cluster or a folder, or vice versa """
changed = True
changed = True
result = None
result = None
reconnect = False
if not self . module . check_mode :
# Check if the host is disconnected if reconnect disconnected hosts is true
changed , result = self . add_host_to_vcenter ( )
if self . reconnect_disconnected and self . host_update . runtime . connectionState == ' disconnected ' :
self . module . exit_json ( changed = changed , result = str ( result ) )
reconnect = True
def state_reconnect_host ( self ) :
# Check parent type
changed = True
parent_type = self . get_parent_type ( self . host_update )
result = None
if not self . module . check_mode :
if self . folder_name :
changed , result = self . reconnect_host_to_vcenter ( )
if self . module . check_mode :
self . module . exit_json ( changed = changed , result = str ( result ) )
if reconnect or self . state == ' add_or_reconnect ' or self . state == ' reconnect ' :
result = " Host would be reconnected and moved to folder ' %s ' " % self . folder_name
else :
result = " Host would be moved to folder ' %s ' " % self . folder_name
else :
# Reconnect the host if disconnected or if specified by state
if reconnect or self . state == ' add_or_reconnect ' or self . state == ' reconnect ' :
self . reconnect_host ( self . host_update )
try :
try :
if parent_type == ' folder ' :
# Move ESXi host from folder to folder
task = self . folder . MoveIntoFolder_Task ( [ self . host_update . parent ] )
elif parent_type == ' cluster ' :
self . put_host_in_maintenance_mode ( self . host_update )
# Move ESXi host from cluster to folder
task = self . folder . MoveIntoFolder_Task ( [ self . host_update ] )
except vim . fault . DuplicateName as duplicate_name :
self . module . fail_json (
msg = " The folder already contains an object with the specified name : %s " %
to_native ( duplicate_name )
)
except vim . fault . InvalidFolder as invalid_folder :
self . module . fail_json (
msg = " The parent of this folder is in the list of objects : %s " %
to_native ( invalid_folder )
)
except vim . fault . InvalidState as invalid_state :
self . module . fail_json (
msg = " Failed to move host, this can be due to either of following : "
" 1. The host is not part of the same datacenter, 2. The host is not in maintenance mode : %s " %
to_native ( invalid_state )
)
except vmodl . fault . NotSupported as not_supported :
self . module . fail_json (
msg = " The target folder is not a host folder : %s " %
to_native ( not_supported )
)
except vim . fault . DisallowedOperationOnFailoverHost as failover_host :
self . module . fail_json (
msg = " The host is configured as a failover host : %s " %
to_native ( failover_host )
)
except vim . fault . VmAlreadyExistsInDatacenter as already_exists :
self . module . fail_json (
msg = " The host ' s virtual machines are already registered to a host in "
" the destination datacenter : %s " % to_native ( already_exists )
)
changed , result = wait_for_task ( task )
except TaskError as task_error_exception :
task_error = task_error_exception . args [ 0 ]
self . module . fail_json (
msg = " Failed to move host %s to folder %s due to %s " %
( self . esxi_hostname , self . folder_name , to_native ( task_error ) )
)
if reconnect or self . state == ' add_or_reconnect ' or self . state == ' reconnect ' :
result = " Host reconnected and moved to folder ' %s ' " % self . folder_name
else :
result = " Host moved to folder ' %s ' " % self . folder_name
elif self . cluster_name :
if self . module . check_mode :
result = " Host would be moved to cluster ' %s ' " % self . cluster_name
else :
if parent_type == ' cluster ' :
# Put host in maintenance mode if moved from another cluster
self . put_host_in_maintenance_mode ( self . host_update )
resource_pool = None
try :
try :
task = self . cluster . MoveHostInto_Task (
host = self . host_update , resourcePool = resource_pool
)
except vim . fault . TooManyHosts as too_many_hosts :
self . module . fail_json (
msg = " No additional hosts can be added to the cluster : %s " % to_native ( too_many_hosts )
)
except vim . fault . InvalidState as invalid_state :
self . module . fail_json (
msg = " The host is already part of a cluster and is not in maintenance mode : %s " %
to_native ( invalid_state )
)
except vmodl . fault . InvalidArgument as invalid_argument :
self . module . fail_json (
msg = " Failed to move host, this can be due to either of following : "
" 1. The host is is not a part of the same datacenter as the cluster, "
" 2. The source and destination clusters are the same : %s " %
to_native ( invalid_argument )
)
changed , result = wait_for_task ( task )
except TaskError as task_error_exception :
task_error = task_error_exception . args [ 0 ]
self . module . fail_json (
msg = " Failed to move host to cluster ' %s ' due to : %s " %
( self . cluster_name , to_native ( task_error ) )
)
if reconnect or self . state == ' add_or_reconnect ' or self . state == ' reconnect ' :
result = " Host reconnected and moved to cluster ' %s ' " % self . cluster_name
else :
result = " Host moved to cluster ' %s ' " % self . cluster_name
def check_host_state ( self ) :
self . module . exit_json ( changed = changed , msg = str ( result ) )
if self . cluster_name :
self . host , self . cluster = find_host_by_cluster_datacenter ( self . module , self . content ,
self . datacenter_name , self . cluster_name ,
self . esxi_hostname )
elif self . folder_name :
si = self . content . searchIndex
folder_obj = si . FindByInventoryPath ( self . folder_name )
if folder_obj and isinstance ( folder_obj , vim . Folder ) :
self . folder = folder_obj
if self . folder is None :
self . module . fail_json ( msg = " Failed to get host system details from "
" the given folder %s " % self . folder_name ,
details = " This could either mean that the value of folder is "
" invalid or the provided folder does not exists. " )
for child in self . folder . childEntity :
if child and isinstance ( child , vim . HostSystem ) and child . name == self . esxi_hostname :
self . host = child
if self . host is None :
return ' absent '
else :
return ' present '
def main ( ) :
def main ( ) :
""" Main """
argument_spec = vmware_argument_spec ( )
argument_spec = vmware_argument_spec ( )
argument_spec . update (
argument_spec . update (
datacenter_name = dict ( type = ' str ' , required = True , aliases = [ ' datacenter ' ] ) ,
datacenter_name = dict ( type = ' str ' , required = True , aliases = [ ' datacenter ' ] ) ,
@ -385,12 +793,15 @@ def main():
esxi_hostname = dict ( type = ' str ' , required = True ) ,
esxi_hostname = dict ( type = ' str ' , required = True ) ,
esxi_username = dict ( type = ' str ' ) ,
esxi_username = dict ( type = ' str ' ) ,
esxi_password = dict ( type = ' str ' , no_log = True ) ,
esxi_password = dict ( type = ' str ' , no_log = True ) ,
esxi_ssl_thumbprint = dict ( type = ' str ' , default = ' ' ) ,
esxi_ssl_thumbprint = dict ( type = ' str ' , default = ' ' , aliases = [ ' ssl_thumbprint ' ] ) ,
fetch_ssl_thumbprint = dict ( type = ' bool ' , default = True ) ,
state = dict ( default = ' present ' ,
state = dict ( default = ' present ' ,
choices = [ ' present ' , ' absent ' , ' add_or_reconnect ' , ' reconnect ' ] ,
choices = [ ' present ' , ' absent ' , ' add_or_reconnect ' , ' reconnect ' ] ,
type = ' str ' ) ,
type = ' str ' ) ,
folder = dict ( type = ' str ' ),
folder = dict ( type = ' str ' , aliases = [ ' folder_name ' ] ),
add_connected = dict ( type = ' bool ' , default = True ) ,
add_connected = dict ( type = ' bool ' , default = True ) ,
force_connection = dict ( type = ' bool ' , default = True ) ,
reconnect_disconnected = dict ( type = ' bool ' , default = True ) ,
)
)
module = AnsibleModule (
module = AnsibleModule (