Host dev upstream (#57086)
* Improve netapp_e_host module Add host type strings for windows, windows cluster, linux and vmware to netapp_e_host module Make host port information case-insensitive in netapp_e_host module Fix port removal and default group. Fix port reassignment in netapp_e_host module. Fix port label or address change within existing host object in module netapp_e_host Add unit and integration tests * Created new unit test for netapp_e_host module
This commit is contained in:
parent
80e5b2ade5
commit
acbffce079
4 changed files with 639 additions and 369 deletions
|
@ -17,7 +17,9 @@ module: netapp_e_host
|
||||||
short_description: NetApp E-Series manage eseries hosts
|
short_description: NetApp E-Series manage eseries hosts
|
||||||
description: Create, update, remove hosts on NetApp E-series storage arrays
|
description: Create, update, remove hosts on NetApp E-series storage arrays
|
||||||
version_added: '2.2'
|
version_added: '2.2'
|
||||||
author: Kevin Hulquest (@hulquest)
|
author:
|
||||||
|
- Kevin Hulquest (@hulquest)
|
||||||
|
- Nathan Swartz (@ndswartz)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- netapp.eseries
|
- netapp.eseries
|
||||||
options:
|
options:
|
||||||
|
@ -37,13 +39,15 @@ options:
|
||||||
- present
|
- present
|
||||||
default: present
|
default: present
|
||||||
version_added: 2.7
|
version_added: 2.7
|
||||||
host_type_index:
|
host_type:
|
||||||
description:
|
description:
|
||||||
- The index that maps to host type you wish to create. It is recommended to use the M(netapp_e_facts) module to gather this information.
|
- This is the type of host to be mapped
|
||||||
Alternatively you can use the WSP portal to retrieve the information.
|
|
||||||
- Required when C(state=present)
|
- Required when C(state=present)
|
||||||
|
- Either one of the following names can be specified, Linux DM-MP, VMWare, Windows, Windows Clustered, or a
|
||||||
|
host type index which can be found in M(netapp_e_facts)
|
||||||
|
type: str
|
||||||
aliases:
|
aliases:
|
||||||
- host_type
|
- host_type_index
|
||||||
ports:
|
ports:
|
||||||
description:
|
description:
|
||||||
- A list of host ports you wish to associate with the host.
|
- A list of host ports you wish to associate with the host.
|
||||||
|
@ -88,7 +92,6 @@ options:
|
||||||
- A local path to a file to be used for debug logging
|
- A local path to a file to be used for debug logging
|
||||||
required: False
|
required: False
|
||||||
version_added: 2.7
|
version_added: 2.7
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
|
@ -96,11 +99,11 @@ EXAMPLES = """
|
||||||
netapp_e_host:
|
netapp_e_host:
|
||||||
ssid: "1"
|
ssid: "1"
|
||||||
api_url: "10.113.1.101:8443"
|
api_url: "10.113.1.101:8443"
|
||||||
api_username: "admin"
|
api_username: admin
|
||||||
api_password: "myPassword"
|
api_password: myPassword
|
||||||
name: "Host1"
|
name: "Host1"
|
||||||
state: present
|
state: present
|
||||||
host_type_index: 28
|
host_type_index: Linux DM-MP
|
||||||
ports:
|
ports:
|
||||||
- type: 'iscsi'
|
- type: 'iscsi'
|
||||||
label: 'PORT_1'
|
label: 'PORT_1'
|
||||||
|
@ -116,11 +119,10 @@ EXAMPLES = """
|
||||||
netapp_e_host:
|
netapp_e_host:
|
||||||
ssid: "1"
|
ssid: "1"
|
||||||
api_url: "10.113.1.101:8443"
|
api_url: "10.113.1.101:8443"
|
||||||
api_username: "admin"
|
api_username: admin
|
||||||
api_password: "myPassword"
|
api_password: myPassword
|
||||||
name: "Host2"
|
name: "Host2"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
|
@ -153,10 +155,10 @@ api_url:
|
||||||
type: str
|
type: str
|
||||||
sample: https://webservices.example.com:8443
|
sample: https://webservices.example.com:8443
|
||||||
version_added: "2.6"
|
version_added: "2.6"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
@ -170,6 +172,8 @@ HEADERS = {
|
||||||
|
|
||||||
|
|
||||||
class Host(object):
|
class Host(object):
|
||||||
|
HOST_TYPE_INDEXES = {"linux dm-mp": 28, "vmware": 10, "windows": 1, "windows clustered": 8}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
argument_spec = eseries_host_argument_spec()
|
argument_spec = eseries_host_argument_spec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update(dict(
|
||||||
|
@ -178,57 +182,70 @@ class Host(object):
|
||||||
ports=dict(type='list', required=False),
|
ports=dict(type='list', required=False),
|
||||||
force_port=dict(type='bool', default=False),
|
force_port=dict(type='bool', default=False),
|
||||||
name=dict(type='str', required=True, aliases=['label']),
|
name=dict(type='str', required=True, aliases=['label']),
|
||||||
host_type_index=dict(type='int', aliases=['host_type']),
|
host_type_index=dict(type='str', aliases=['host_type']),
|
||||||
log_path=dict(type='str', required=False),
|
log_path=dict(type='str', required=False),
|
||||||
))
|
))
|
||||||
|
|
||||||
required_if = [
|
self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||||
["state", "absent", ["name"]],
|
|
||||||
["state", "present", ["name", "host_type"]]
|
|
||||||
]
|
|
||||||
|
|
||||||
self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if)
|
|
||||||
self.check_mode = self.module.check_mode
|
self.check_mode = self.module.check_mode
|
||||||
args = self.module.params
|
args = self.module.params
|
||||||
self.group = args['group']
|
self.group = args['group']
|
||||||
self.ports = args['ports']
|
self.ports = args['ports']
|
||||||
self.force_port = args['force_port']
|
self.force_port = args['force_port']
|
||||||
self.name = args['name']
|
self.name = args['name']
|
||||||
self.host_type_index = args['host_type_index']
|
|
||||||
self.state = args['state']
|
self.state = args['state']
|
||||||
self.ssid = args['ssid']
|
self.ssid = args['ssid']
|
||||||
self.url = args['api_url']
|
self.url = args['api_url']
|
||||||
self.user = args['api_username']
|
self.user = args['api_username']
|
||||||
self.pwd = args['api_password']
|
self.pwd = args['api_password']
|
||||||
self.certs = args['validate_certs']
|
self.certs = args['validate_certs']
|
||||||
self.post_body = dict()
|
|
||||||
|
|
||||||
|
self.post_body = dict()
|
||||||
self.all_hosts = list()
|
self.all_hosts = list()
|
||||||
|
self.host_obj = dict()
|
||||||
self.newPorts = list()
|
self.newPorts = list()
|
||||||
self.portsForUpdate = list()
|
self.portsForUpdate = list()
|
||||||
self.force_port_update = False
|
self.portsForRemoval = list()
|
||||||
|
|
||||||
log_path = args['log_path']
|
# Update host type with the corresponding index
|
||||||
|
host_type = args['host_type_index']
|
||||||
|
if host_type:
|
||||||
|
host_type = host_type.lower()
|
||||||
|
if host_type in [key.lower() for key in list(self.HOST_TYPE_INDEXES.keys())]:
|
||||||
|
self.host_type_index = self.HOST_TYPE_INDEXES[host_type]
|
||||||
|
elif host_type.isdigit():
|
||||||
|
self.host_type_index = int(args['host_type_index'])
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg="host_type must be either a host type name or host type index found integer"
|
||||||
|
" the documentation.")
|
||||||
|
|
||||||
# logging setup
|
# logging setup
|
||||||
self._logger = logging.getLogger(self.__class__.__name__)
|
self._logger = logging.getLogger(self.__class__.__name__)
|
||||||
|
if args['log_path']:
|
||||||
if log_path:
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG, filename=log_path, filemode='w',
|
level=logging.DEBUG, filename=args['log_path'], filemode='w',
|
||||||
format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
|
format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
|
||||||
|
|
||||||
if not self.url.endswith('/'):
|
if not self.url.endswith('/'):
|
||||||
self.url += '/'
|
self.url += '/'
|
||||||
|
|
||||||
|
# Ensure when state==present then host_type_index is defined
|
||||||
|
if self.state == "present" and self.host_type_index is None:
|
||||||
|
self.module.fail_json(msg="Host_type_index is required when state=='present'. Array Id: [%s]" % self.ssid)
|
||||||
|
|
||||||
# Fix port representation if they are provided with colons
|
# Fix port representation if they are provided with colons
|
||||||
if self.ports is not None:
|
if self.ports is not None:
|
||||||
for port in self.ports:
|
for port in self.ports:
|
||||||
if port['type'] != 'iscsi':
|
port['label'] = port['label'].lower()
|
||||||
port['port'] = port['port'].replace(':', '')
|
port['type'] = port['type'].lower()
|
||||||
|
port['port'] = port['port'].lower()
|
||||||
|
|
||||||
|
# Determine whether address is 16-byte WWPN and, if so, remove
|
||||||
|
if re.match(r'^(0x)?[0-9a-f]{16}$', port['port'].replace(':', '')):
|
||||||
|
port['port'] = port['port'].replace(':', '').replace('0x', '')
|
||||||
|
|
||||||
@property
|
|
||||||
def valid_host_type(self):
|
def valid_host_type(self):
|
||||||
|
host_types = None
|
||||||
try:
|
try:
|
||||||
(rc, host_types) = request(self.url + 'storage-systems/%s/host-types' % self.ssid, url_password=self.pwd,
|
(rc, host_types) = request(self.url + 'storage-systems/%s/host-types' % self.ssid, url_password=self.pwd,
|
||||||
url_username=self.user, validate_certs=self.certs, headers=HEADERS)
|
url_username=self.user, validate_certs=self.certs, headers=HEADERS)
|
||||||
|
@ -242,22 +259,65 @@ class Host(object):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.module.fail_json(msg="There is no host type with index %s" % self.host_type_index)
|
self.module.fail_json(msg="There is no host type with index %s" % self.host_type_index)
|
||||||
|
|
||||||
@property
|
def assigned_host_ports(self, apply_unassigning=False):
|
||||||
def host_ports_available(self):
|
"""Determine if the hostPorts requested have already been assigned and return list of required used ports."""
|
||||||
"""Determine if the hostPorts requested have already been assigned"""
|
used_host_ports = {}
|
||||||
for host in self.all_hosts:
|
for host in self.all_hosts:
|
||||||
if host['label'] != self.name:
|
if host['label'] != self.name:
|
||||||
for host_port in host['hostSidePorts']:
|
for host_port in host['hostSidePorts']:
|
||||||
for port in self.ports:
|
for port in self.ports:
|
||||||
if (port['port'] == host_port['address'] or port['label'] == host_port['label']):
|
if port['port'] == host_port["address"] or port['label'] == host_port['label']:
|
||||||
if not self.force_port:
|
if not self.force_port:
|
||||||
self.module.fail_json(
|
self.module.fail_json(msg="There are no host ports available OR there are not enough"
|
||||||
msg="There are no host ports available OR there are not enough unassigned host ports")
|
" unassigned host ports")
|
||||||
else:
|
else:
|
||||||
return False
|
# Determine port reference
|
||||||
return True
|
port_ref = [port["hostPortRef"] for port in host["ports"]
|
||||||
|
if port["hostPortName"] == host_port["address"]]
|
||||||
|
port_ref.extend([port["initiatorRef"] for port in host["initiators"]
|
||||||
|
if port["nodeName"]["iscsiNodeName"] == host_port["address"]])
|
||||||
|
|
||||||
|
# Create dictionary of hosts containing list of port references
|
||||||
|
if host["hostRef"] not in used_host_ports.keys():
|
||||||
|
used_host_ports.update({host["hostRef"]: port_ref})
|
||||||
|
else:
|
||||||
|
used_host_ports[host["hostRef"]].extend(port_ref)
|
||||||
|
else:
|
||||||
|
for host_port in host['hostSidePorts']:
|
||||||
|
for port in self.ports:
|
||||||
|
if ((host_port['label'] == port['label'] and host_port['address'] != port['port']) or
|
||||||
|
(host_port['label'] != port['label'] and host_port['address'] == port['port'])):
|
||||||
|
if not self.force_port:
|
||||||
|
self.module.fail_json(msg="There are no host ports available OR there are not enough"
|
||||||
|
" unassigned host ports")
|
||||||
|
else:
|
||||||
|
# Determine port reference
|
||||||
|
port_ref = [port["hostPortRef"] for port in host["ports"]
|
||||||
|
if port["hostPortName"] == host_port["address"]]
|
||||||
|
port_ref.extend([port["initiatorRef"] for port in host["initiators"]
|
||||||
|
if port["nodeName"]["iscsiNodeName"] == host_port["address"]])
|
||||||
|
|
||||||
|
# Create dictionary of hosts containing list of port references
|
||||||
|
if host["hostRef"] not in used_host_ports.keys():
|
||||||
|
used_host_ports.update({host["hostRef"]: port_ref})
|
||||||
|
else:
|
||||||
|
used_host_ports[host["hostRef"]].extend(port_ref)
|
||||||
|
|
||||||
|
# Unassign assigned ports
|
||||||
|
if apply_unassigning:
|
||||||
|
for host_ref in used_host_ports.keys():
|
||||||
|
try:
|
||||||
|
rc, resp = request(self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, host_ref),
|
||||||
|
url_username=self.user, url_password=self.pwd, headers=HEADERS,
|
||||||
|
validate_certs=self.certs, method='POST',
|
||||||
|
data=json.dumps({"portsToRemove": used_host_ports[host_ref]}))
|
||||||
|
except Exception as err:
|
||||||
|
self.module.fail_json(msg="Failed to unassign host port. Host Id [%s]. Array Id [%s]. Ports [%s]."
|
||||||
|
" Error [%s]." % (self.host_obj['id'], self.ssid,
|
||||||
|
used_host_ports[host_ref], to_native(err)))
|
||||||
|
|
||||||
|
return used_host_ports
|
||||||
|
|
||||||
@property
|
|
||||||
def group_id(self):
|
def group_id(self):
|
||||||
if self.group:
|
if self.group:
|
||||||
try:
|
try:
|
||||||
|
@ -277,12 +337,13 @@ class Host(object):
|
||||||
# Return the value equivalent of no group
|
# Return the value equivalent of no group
|
||||||
return "0000000000000000000000000000000000000000"
|
return "0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
@property
|
|
||||||
def host_exists(self):
|
def host_exists(self):
|
||||||
"""Determine if the requested host exists
|
"""Determine if the requested host exists
|
||||||
As a side effect, set the full list of defined hosts in 'all_hosts', and the target host in 'host_obj'.
|
As a side effect, set the full list of defined hosts in 'all_hosts', and the target host in 'host_obj'.
|
||||||
"""
|
"""
|
||||||
|
match = False
|
||||||
all_hosts = list()
|
all_hosts = list()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd,
|
(rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd,
|
||||||
url_username=self.user, validate_certs=self.certs, headers=HEADERS)
|
url_username=self.user, validate_certs=self.certs, headers=HEADERS)
|
||||||
|
@ -290,104 +351,65 @@ class Host(object):
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
|
msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
|
||||||
|
|
||||||
self.all_hosts = all_hosts
|
|
||||||
|
|
||||||
# Augment the host objects
|
# Augment the host objects
|
||||||
for host in all_hosts:
|
for host in all_hosts:
|
||||||
|
for port in host['hostSidePorts']:
|
||||||
|
port['type'] = port['type'].lower()
|
||||||
|
port['address'] = port['address'].lower()
|
||||||
|
port['label'] = port['label'].lower()
|
||||||
|
|
||||||
# Augment hostSidePorts with their ID (this is an omission in the API)
|
# Augment hostSidePorts with their ID (this is an omission in the API)
|
||||||
host_side_ports = host['hostSidePorts']
|
|
||||||
initiators = dict((port['label'], port['id']) for port in host['initiators'])
|
|
||||||
ports = dict((port['label'], port['id']) for port in host['ports'])
|
ports = dict((port['label'], port['id']) for port in host['ports'])
|
||||||
ports.update(initiators)
|
ports.update((port['label'], port['id']) for port in host['initiators'])
|
||||||
for port in host_side_ports:
|
|
||||||
if port['label'] in ports:
|
|
||||||
port['id'] = ports[port['label']]
|
|
||||||
|
|
||||||
try: # Try to grab the host object
|
for host_side_port in host['hostSidePorts']:
|
||||||
self.host_obj = list(filter(lambda host: host['label'] == self.name, all_hosts))[0]
|
if host_side_port['label'] in ports:
|
||||||
return True
|
host_side_port['id'] = ports[host_side_port['label']]
|
||||||
except IndexError:
|
|
||||||
# Host with the name passed in does not exist
|
if host['label'] == self.name:
|
||||||
return False
|
self.host_obj = host
|
||||||
|
match = True
|
||||||
|
|
||||||
|
self.all_hosts = all_hosts
|
||||||
|
return match
|
||||||
|
|
||||||
@property
|
|
||||||
def needs_update(self):
|
def needs_update(self):
|
||||||
"""Determine whether we need to update the Host object
|
"""Determine whether we need to update the Host object
|
||||||
As a side effect, we will set the ports that we need to update (portsForUpdate), and the ports we need to add
|
As a side effect, we will set the ports that we need to update (portsForUpdate), and the ports we need to add
|
||||||
(newPorts), on self.
|
(newPorts), on self.
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
needs_update = False
|
changed = False
|
||||||
|
if (self.host_obj["clusterRef"].lower() != self.group_id().lower() or
|
||||||
if self.host_obj['clusterRef'] != self.group_id or self.host_obj['hostTypeIndex'] != self.host_type_index:
|
self.host_obj["hostTypeIndex"] != self.host_type_index):
|
||||||
self._logger.info("Either hostType or the clusterRef doesn't match, an update is required.")
|
self._logger.info("Either hostType or the clusterRef doesn't match, an update is required.")
|
||||||
needs_update = True
|
changed = True
|
||||||
|
current_host_ports = dict((port["id"], {"type": port["type"], "port": port["address"], "label": port["label"]})
|
||||||
|
for port in self.host_obj["hostSidePorts"])
|
||||||
|
|
||||||
if self.ports:
|
if self.ports:
|
||||||
self._logger.debug("Determining if ports need to be updated.")
|
for port in self.ports:
|
||||||
# Find the names of all defined ports
|
for current_host_port_id in current_host_ports.keys():
|
||||||
port_names = set(port['label'] for port in self.host_obj['hostSidePorts'])
|
if port == current_host_ports[current_host_port_id]:
|
||||||
port_addresses = set(port['address'] for port in self.host_obj['hostSidePorts'])
|
current_host_ports.pop(current_host_port_id)
|
||||||
|
break
|
||||||
|
elif port["port"] == current_host_ports[current_host_port_id]["port"]:
|
||||||
|
if self.port_on_diff_host(port) and not self.force_port:
|
||||||
|
self.module.fail_json(msg="The port you specified [%s] is associated with a different host."
|
||||||
|
" Specify force_port as True or try a different port spec" % port)
|
||||||
|
|
||||||
# If we have ports defined and there are no ports on the host object, we need an update
|
if (port["label"] != current_host_ports[current_host_port_id]["label"] or
|
||||||
if not self.host_obj['hostSidePorts']:
|
port["type"] != current_host_ports[current_host_port_id]["type"]):
|
||||||
needs_update = True
|
current_host_ports.pop(current_host_port_id)
|
||||||
for arg_port in self.ports:
|
self.portsForUpdate.append({"portRef": current_host_port_id, "port": port["port"],
|
||||||
# First a quick check to see if the port is mapped to a different host
|
"label": port["label"], "hostRef": self.host_obj["hostRef"]})
|
||||||
if not self.port_on_diff_host(arg_port):
|
break
|
||||||
# The port (as defined), currently does not exist
|
|
||||||
if arg_port['label'] not in port_names:
|
|
||||||
needs_update = True
|
|
||||||
# This port has not been defined on the host at all
|
|
||||||
if arg_port['port'] not in port_addresses:
|
|
||||||
self.newPorts.append(arg_port)
|
|
||||||
# A port label update has been requested
|
|
||||||
else:
|
|
||||||
self.portsForUpdate.append(arg_port)
|
|
||||||
# The port does exist, does it need to be updated?
|
|
||||||
else:
|
|
||||||
for obj_port in self.host_obj['hostSidePorts']:
|
|
||||||
if arg_port['label'] == obj_port['label']:
|
|
||||||
# Confirmed that port arg passed in exists on the host
|
|
||||||
# port_id = self.get_port_id(obj_port['label'])
|
|
||||||
if arg_port['type'] != obj_port['type']:
|
|
||||||
needs_update = True
|
|
||||||
self.portsForUpdate.append(arg_port)
|
|
||||||
if 'iscsiChapSecret' in arg_port:
|
|
||||||
# No way to know the current secret attr, so always return True just in case
|
|
||||||
needs_update = True
|
|
||||||
self.portsForUpdate.append(arg_port)
|
|
||||||
else:
|
else:
|
||||||
# If the user wants the ports to be reassigned, do it
|
self.newPorts.append(port)
|
||||||
if self.force_port:
|
|
||||||
self.force_port_update = True
|
|
||||||
needs_update = True
|
|
||||||
else:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg="The port you specified:\n%s\n is associated with a different host. Specify force_port"
|
|
||||||
" as True or try a different port spec" % arg_port
|
|
||||||
)
|
|
||||||
self._logger.debug("Is an update required ?=%s", needs_update)
|
|
||||||
return needs_update
|
|
||||||
|
|
||||||
def get_ports_on_host(self):
|
self.portsForRemoval = list(current_host_ports.keys())
|
||||||
"""Retrieve the hostPorts that are defined on the target host
|
changed = any([self.newPorts, self.portsForUpdate, self.portsForRemoval, changed])
|
||||||
:return: a list of hostPorts with their labels and ids
|
|
||||||
Example:
|
return changed
|
||||||
[
|
|
||||||
{
|
|
||||||
'name': 'hostPort1',
|
|
||||||
'id': '0000000000000000000000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
ret = dict()
|
|
||||||
for host in self.all_hosts:
|
|
||||||
if host['name'] == self.name:
|
|
||||||
ports = host['hostSidePorts']
|
|
||||||
for port in ports:
|
|
||||||
ret[port['address']] = {'label': port['label'], 'id': port['id'], 'address': port['address']}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def port_on_diff_host(self, arg_port):
|
def port_on_diff_host(self, arg_port):
|
||||||
""" Checks to see if a passed in port arg is present on a different host """
|
""" Checks to see if a passed in port arg is present on a different host """
|
||||||
|
@ -401,68 +423,24 @@ class Host(object):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_port(self, label, address):
|
|
||||||
for host in self.all_hosts:
|
|
||||||
for port in host['hostSidePorts']:
|
|
||||||
if port['label'] == label or port['address'] == address:
|
|
||||||
return port
|
|
||||||
|
|
||||||
def reassign_ports(self, apply=True):
|
|
||||||
post_body = dict(
|
|
||||||
portsToUpdate=dict()
|
|
||||||
)
|
|
||||||
|
|
||||||
for port in self.ports:
|
|
||||||
if self.port_on_diff_host(port):
|
|
||||||
host_port = self.get_port(port['label'], port['port'])
|
|
||||||
post_body['portsToUpdate'].update(dict(
|
|
||||||
portRef=host_port['id'],
|
|
||||||
hostRef=self.host_obj['id'],
|
|
||||||
label=port['label']
|
|
||||||
# Doesn't yet address port identifier or chap secret
|
|
||||||
))
|
|
||||||
|
|
||||||
self._logger.info("reassign_ports: %s", pformat(post_body))
|
|
||||||
|
|
||||||
if apply:
|
|
||||||
try:
|
|
||||||
(rc, self.host_obj) = request(
|
|
||||||
self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id']),
|
|
||||||
url_username=self.user, url_password=self.pwd, headers=HEADERS,
|
|
||||||
validate_certs=self.certs, method='POST', data=json.dumps(post_body))
|
|
||||||
except Exception as err:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg="Failed to reassign host port. Host Id [%s]. Array Id [%s]. Error [%s]." % (
|
|
||||||
self.host_obj['id'], self.ssid, to_native(err)))
|
|
||||||
|
|
||||||
return post_body
|
|
||||||
|
|
||||||
def update_host(self):
|
def update_host(self):
|
||||||
self._logger.debug("Beginning the update for host=%s.", self.name)
|
self._logger.info("Beginning the update for host=%s.", self.name)
|
||||||
|
|
||||||
if self.ports:
|
if self.ports:
|
||||||
|
|
||||||
|
# Remove ports that need reassigning from their current host.
|
||||||
|
self.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
self.post_body["portsToUpdate"] = self.portsForUpdate
|
||||||
|
self.post_body["ports"] = self.newPorts
|
||||||
self._logger.info("Requested ports: %s", pformat(self.ports))
|
self._logger.info("Requested ports: %s", pformat(self.ports))
|
||||||
if self.host_ports_available or self.force_port:
|
|
||||||
self.reassign_ports(apply=True)
|
|
||||||
# Make sure that only ports that aren't being reassigned are passed into the ports attr
|
|
||||||
host_ports = self.get_ports_on_host()
|
|
||||||
ports_for_update = list()
|
|
||||||
self._logger.info("Ports on host: %s", pformat(host_ports))
|
|
||||||
for port in self.portsForUpdate:
|
|
||||||
if port['port'] in host_ports:
|
|
||||||
defined_port = host_ports.get(port['port'])
|
|
||||||
defined_port.update(port)
|
|
||||||
defined_port['portRef'] = defined_port['id']
|
|
||||||
ports_for_update.append(defined_port)
|
|
||||||
self._logger.info("Ports to update: %s", pformat(ports_for_update))
|
|
||||||
self._logger.info("Ports to define: %s", pformat(self.newPorts))
|
|
||||||
self.post_body['portsToUpdate'] = ports_for_update
|
|
||||||
self.post_body['ports'] = self.newPorts
|
|
||||||
else:
|
else:
|
||||||
self._logger.debug("No host ports were defined.")
|
self._logger.info("No host ports were defined.")
|
||||||
|
|
||||||
if self.group:
|
if self.group:
|
||||||
self.post_body['groupId'] = self.group_id
|
self.post_body['groupId'] = self.group_id()
|
||||||
|
else:
|
||||||
|
self.post_body['groupId'] = "0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
self.post_body['hostType'] = dict(index=self.host_type_index)
|
self.post_body['hostType'] = dict(index=self.host_type_index)
|
||||||
|
|
||||||
|
@ -482,46 +460,36 @@ class Host(object):
|
||||||
|
|
||||||
def create_host(self):
|
def create_host(self):
|
||||||
self._logger.info("Creating host definition.")
|
self._logger.info("Creating host definition.")
|
||||||
needs_reassignment = False
|
|
||||||
|
# Remove ports that need reassigning from their current host.
|
||||||
|
self.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
# needs_reassignment = False
|
||||||
post_body = dict(
|
post_body = dict(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
hostType=dict(index=self.host_type_index),
|
hostType=dict(index=self.host_type_index),
|
||||||
groupId=self.group_id,
|
groupId=self.group_id(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.ports:
|
if self.ports:
|
||||||
# Check that all supplied port args are valid
|
post_body.update(ports=self.ports)
|
||||||
if self.host_ports_available:
|
|
||||||
self._logger.info("The host-ports requested are available.")
|
|
||||||
post_body.update(ports=self.ports)
|
|
||||||
elif not self.force_port:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg="You supplied ports that are already in use."
|
|
||||||
" Supply force_port to True if you wish to reassign the ports")
|
|
||||||
else:
|
|
||||||
needs_reassignment = True
|
|
||||||
|
|
||||||
api = self.url + "storage-systems/%s/hosts" % self.ssid
|
api = self.url + "storage-systems/%s/hosts" % self.ssid
|
||||||
self._logger.info('POST => url=%s, body=%s', api, pformat(post_body))
|
self._logger.info('POST => url=%s, body=%s', api, pformat(post_body))
|
||||||
|
|
||||||
if not (self.host_exists and self.check_mode):
|
if not self.check_mode:
|
||||||
try:
|
if not self.host_exists():
|
||||||
(rc, self.host_obj) = request(api, method='POST',
|
try:
|
||||||
url_username=self.user, url_password=self.pwd, validate_certs=self.certs,
|
(rc, self.host_obj) = request(api, method='POST', url_username=self.user, url_password=self.pwd, validate_certs=self.certs,
|
||||||
data=json.dumps(post_body), headers=HEADERS)
|
data=json.dumps(post_body), headers=HEADERS)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
|
msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
|
||||||
else:
|
else:
|
||||||
payload = self.build_success_payload(self.host_obj)
|
payload = self.build_success_payload(self.host_obj)
|
||||||
self.module.exit_json(changed=False,
|
self.module.exit_json(changed=False, msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name), **payload)
|
||||||
msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name), **payload)
|
|
||||||
|
|
||||||
self._logger.info("Created host, beginning port re-assignment.")
|
|
||||||
if needs_reassignment:
|
|
||||||
self.reassign_ports()
|
|
||||||
|
|
||||||
payload = self.build_success_payload(self.host_obj)
|
payload = self.build_success_payload(self.host_obj)
|
||||||
|
|
||||||
self.module.exit_json(changed=True, msg='Host created.', **payload)
|
self.module.exit_json(changed=True, msg='Host created.', **payload)
|
||||||
|
|
||||||
def remove_host(self):
|
def remove_host(self):
|
||||||
|
@ -547,17 +515,17 @@ class Host(object):
|
||||||
|
|
||||||
def apply(self):
|
def apply(self):
|
||||||
if self.state == 'present':
|
if self.state == 'present':
|
||||||
if self.host_exists:
|
if self.host_exists():
|
||||||
if self.needs_update and self.valid_host_type:
|
if self.needs_update() and self.valid_host_type():
|
||||||
self.update_host()
|
self.update_host()
|
||||||
else:
|
else:
|
||||||
payload = self.build_success_payload(self.host_obj)
|
payload = self.build_success_payload(self.host_obj)
|
||||||
self.module.exit_json(changed=False, msg="Host already present; no changes required.", **payload)
|
self.module.exit_json(changed=False, msg="Host already present; no changes required.", **payload)
|
||||||
elif self.valid_host_type:
|
elif self.valid_host_type():
|
||||||
self.create_host()
|
self.create_host()
|
||||||
else:
|
else:
|
||||||
payload = self.build_success_payload()
|
payload = self.build_success_payload()
|
||||||
if self.host_exists:
|
if self.host_exists():
|
||||||
self.remove_host()
|
self.remove_host()
|
||||||
self.module.exit_json(changed=True, msg="Host removed.", **payload)
|
self.module.exit_json(changed=True, msg="Host removed.", **payload)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# This test is not enabled by default, but can be utilized by defining required variables in integration_config.yml
|
# This test is not enabled by default, but can be utilized by defining required variables in integration_config.yml
|
||||||
# Example integration_config.yml:
|
# Example integration_config.yml:
|
||||||
# ---
|
# ---
|
||||||
#netapp_e_api_host: 10.113.1.111:8443
|
#netapp_e_api_host: 192.168.1.1
|
||||||
#netapp_e_api_username: admin
|
#netapp_e_api_username: admin
|
||||||
#netapp_e_api_password: myPass
|
#netapp_e_api_password: myPass
|
||||||
#netapp_e_ssid: 1
|
#netapp_e_ssid: 1
|
||||||
|
|
|
@ -22,38 +22,38 @@
|
||||||
update_host_type: 28
|
update_host_type: 28
|
||||||
ports:
|
ports:
|
||||||
- type: 'iscsi'
|
- type: 'iscsi'
|
||||||
label: 'I_1'
|
label: 'I1_1'
|
||||||
port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe'
|
port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe-PORT1'
|
||||||
- type: 'iscsi'
|
- type: 'iscsi'
|
||||||
label: 'I_2'
|
label: 'I1_2'
|
||||||
port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff'
|
port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff-port1'
|
||||||
ports2:
|
ports2:
|
||||||
- type: 'iscsi'
|
- type: 'iscsi'
|
||||||
label: 'I_1'
|
label: 'I1_1'
|
||||||
port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe'
|
port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe-port2'
|
||||||
- type: 'iscsi'
|
- type: 'iscsi'
|
||||||
label: 'I_2'
|
label: 'I1_2'
|
||||||
port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff'
|
port: 'iqn.1996-04.de.suse:01:56f86f9bd1ff-port2'
|
||||||
- type: 'fc'
|
- type: 'iscsi'
|
||||||
label: 'FC_3'
|
label: 'I1_3'
|
||||||
port: '10:00:8C:7C:FF:1A:B9:01'
|
port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-PORT1'
|
||||||
2:
|
2:
|
||||||
host_type: 27
|
host_type: 27
|
||||||
update_host_type: 28
|
update_host_type: 28
|
||||||
ports:
|
ports:
|
||||||
- type: 'fc'
|
- type: 'iscsi'
|
||||||
label: 'FC_1'
|
label: 'I2_1'
|
||||||
port: '10:00:8C:7C:FF:1A:B9:01'
|
port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-port1'
|
||||||
- type: 'fc'
|
- type: 'iscsi'
|
||||||
label: 'FC_2'
|
label: 'I2_2'
|
||||||
port: '10:00:8C:7C:FF:1A:B9:00'
|
port: 'iqn.1996-04.redhat:01:56f86f9bd1ff-port1'
|
||||||
ports2:
|
ports2:
|
||||||
- type: 'fc'
|
- type: 'iscsi'
|
||||||
label: 'FC_6'
|
label: 'I2_1'
|
||||||
port: '10:00:8C:7C:FF:1A:B9:01'
|
port: 'iqn.1996-04.redhat:01:56f86f9bd1fe-port2'
|
||||||
- type: 'fc'
|
- type: 'iscsi'
|
||||||
label: 'FC_4'
|
label: 'I2_2'
|
||||||
port: '10:00:8C:7C:FF:1A:B9:00'
|
port: 'iqn.1996-04.redhat:01:56f86f9bd1ff-PORT2'
|
||||||
|
|
||||||
|
|
||||||
# ********************************************
|
# ********************************************
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
# (c) 2018, NetApp Inc.
|
# (c) 2018, NetApp Inc.
|
||||||
# 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 mock import MagicMock
|
|
||||||
|
|
||||||
from ansible.module_utils import netapp
|
|
||||||
from ansible.modules.storage.netapp import netapp_e_host
|
|
||||||
from ansible.modules.storage.netapp.netapp_e_host import Host
|
from ansible.modules.storage.netapp.netapp_e_host import Host
|
||||||
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
import mock
|
|
||||||
from units.compat.mock import patch
|
try:
|
||||||
from ansible.module_utils._text import to_bytes
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
|
||||||
|
|
||||||
class HostTest(ModuleTestCase):
|
class HostTest(ModuleTestCase):
|
||||||
|
@ -24,6 +22,7 @@ class HostTest(ModuleTestCase):
|
||||||
}
|
}
|
||||||
HOST = {
|
HOST = {
|
||||||
'name': '1',
|
'name': '1',
|
||||||
|
'hostRef': '123',
|
||||||
'label': '1',
|
'label': '1',
|
||||||
'id': '0' * 30,
|
'id': '0' * 30,
|
||||||
'clusterRef': 40 * '0',
|
'clusterRef': 40 * '0',
|
||||||
|
@ -41,6 +40,48 @@ class HostTest(ModuleTestCase):
|
||||||
'initiators': [],
|
'initiators': [],
|
||||||
'ports': [],
|
'ports': [],
|
||||||
}
|
}
|
||||||
|
EXISTING_HOSTS = [
|
||||||
|
{"hostRef": "84000000600A098000A4B28D00303D065D430118", "clusterRef": "0000000000000000000000000000000000000000", "label": "beegfs_storage1",
|
||||||
|
"hostTypeIndex": 28, "ports": [], "initiators": [{"initiatorRef": "89000000600A098000A4B28D00303CF55D4300E3",
|
||||||
|
"nodeName": {"ioInterfaceType": "iscsi",
|
||||||
|
"iscsiNodeName": "iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818",
|
||||||
|
"remoteNodeWWN": None, "nvmeNodeName": None},
|
||||||
|
"alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "beegfs_storage1_iscsi_0",
|
||||||
|
"hostRef": "84000000600A098000A4B28D00303D065D430118",
|
||||||
|
"id": "89000000600A098000A4B28D00303CF55D4300E3"}],
|
||||||
|
"hostSidePorts": [{"type": "iscsi", "address": "iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818", "label": "beegfs_storage1_iscsi_0"}],
|
||||||
|
"id": "84000000600A098000A4B28D00303D065D430118", "name": "beegfs_storage1"},
|
||||||
|
{"hostRef": "84000000600A098000A4B9D10030370B5D430109", "clusterRef": "0000000000000000000000000000000000000000", "label": "beegfs_metadata1",
|
||||||
|
"hostTypeIndex": 28, "ports": [], "initiators": [{"initiatorRef": "89000000600A098000A4B28D00303CFC5D4300F7",
|
||||||
|
"nodeName": {"ioInterfaceType": "iscsi",
|
||||||
|
"iscsiNodeName": "iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8",
|
||||||
|
"remoteNodeWWN": None, "nvmeNodeName": None},
|
||||||
|
"alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "beegfs_metadata1_iscsi_0",
|
||||||
|
"hostRef": "84000000600A098000A4B9D10030370B5D430109",
|
||||||
|
"id": "89000000600A098000A4B28D00303CFC5D4300F7"}],
|
||||||
|
"hostSidePorts": [{"type": "iscsi", "address": "iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8", "label": "beegfs_metadata1_iscsi_0"}],
|
||||||
|
"id": "84000000600A098000A4B9D10030370B5D430109", "name": "beegfs_metadata1"},
|
||||||
|
{"hostRef": "84000000600A098000A4B9D10030370B5D430109", "clusterRef": "85000000600A098000A4B9D1003637135D483DEB", "label": "beegfs_metadata2",
|
||||||
|
"hostTypeIndex": 28, "ports": [], "initiators": [{"initiatorRef": "89000000600A098000A4B28D00303CFC5D4300F7",
|
||||||
|
"nodeName": {"ioInterfaceType": "iscsi",
|
||||||
|
"iscsiNodeName": "iqn.used_elsewhere",
|
||||||
|
"remoteNodeWWN": None, "nvmeNodeName": None},
|
||||||
|
"alias": {"ioInterfaceType": "iscsi", "iscsiAlias": ""}, "label": "beegfs_metadata2_iscsi_0",
|
||||||
|
"hostRef": "84000000600A098000A4B9D10030370B5D430109",
|
||||||
|
"id": "89000000600A098000A4B28D00303CFC5D4300F7"}],
|
||||||
|
"hostSidePorts": [{"type": "iscsi", "address": "iqn.used_elsewhere", "label": "beegfs_metadata2_iscsi_0"}],
|
||||||
|
"id": "84000000600A098000A4B9D10030370B5D430120", "name": "beegfs_metadata2"}]
|
||||||
|
HOST_GROUPS = [{"clusterRef": "85000000600A098000A4B9D1003637135D483DEB", "label": "test_group", "isSAControlled": False,
|
||||||
|
"confirmLUNMappingCreation": False, "protectionInformationCapableAccessMethod": True, "isLun0Restricted": False,
|
||||||
|
"id": "85000000600A098000A4B9D1003637135D483DEB", "name": "test_group"}]
|
||||||
|
HOST_TYPES = [{"name": "FactoryDefault", "index": 0, "code": "FactoryDefault"},
|
||||||
|
{"name": "Windows 2000/Server 2003/Server 2008 Non-Clustered", "index": 1, "code": "W2KNETNCL"},
|
||||||
|
{"name": "Solaris", "index": 2, "code": "SOL"},
|
||||||
|
{"name": "Linux", "index": 6, "code": "LNX"},
|
||||||
|
{"name": "LnxALUA", "index": 7, "code": "LnxALUA"},
|
||||||
|
{"name": "Windows 2000/Server 2003/Server 2008 Clustered", "index": 8, "code": "W2KNETCL"},
|
||||||
|
{"name": "LnxTPGSALUA_SF", "index": 27, "code": "LnxTPGSALUA_SF"},
|
||||||
|
{"name": "LnxDHALUA", "index": 28, "code": "LnxDHALUA"}]
|
||||||
REQ_FUNC = 'ansible.modules.storage.netapp.netapp_e_host.request'
|
REQ_FUNC = 'ansible.modules.storage.netapp.netapp_e_host.request'
|
||||||
|
|
||||||
def _set_args(self, args):
|
def _set_args(self, args):
|
||||||
|
@ -48,140 +89,401 @@ class HostTest(ModuleTestCase):
|
||||||
module_args.update(args)
|
module_args.update(args)
|
||||||
set_module_args(module_args)
|
set_module_args(module_args)
|
||||||
|
|
||||||
def test_delete_host(self):
|
def test_host_exists_pass(self):
|
||||||
"""Validate removing a host object"""
|
"""Verify host_exists produces expected results."""
|
||||||
self._set_args({
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
'state': 'absent'
|
self._set_args({'state': 'present', 'name': 'new_host', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
})
|
'ports': [{'label': 'new_host_port_1', 'type': 'fc', 'port': '0x08ef08ef08ef08ef'}]})
|
||||||
host = Host()
|
host = Host()
|
||||||
with self.assertRaises(AnsibleExitJson) as result:
|
self.assertFalse(host.host_exists())
|
||||||
# We expect 2 calls to the API, the first to retrieve the host objects defined,
|
|
||||||
# the second to remove the host definition.
|
|
||||||
with mock.patch(self.REQ_FUNC, side_effect=[(200, [self.HOST]), (204, {})]) as request:
|
|
||||||
host.apply()
|
|
||||||
self.assertEquals(request.call_count, 2)
|
|
||||||
# We expect the module to make changes
|
|
||||||
self.assertEquals(result.exception.args[0]['changed'], True)
|
|
||||||
|
|
||||||
def test_delete_host_no_changes(self):
|
self._set_args({'state': 'present', 'name': 'does_not_exist', 'host_type': 'linux dm-mp',
|
||||||
"""Ensure that removing a host that doesn't exist works correctly."""
|
'ports': [{'label': 'beegfs_storage1_iscsi_0', 'type': 'iscsi',
|
||||||
self._set_args({
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
'state': 'absent'
|
host = Host()
|
||||||
})
|
self.assertFalse(host.host_exists())
|
||||||
host = Host()
|
|
||||||
with self.assertRaises(AnsibleExitJson) as result:
|
|
||||||
# We expect a single call to the API: retrieve the defined hosts.
|
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [])):
|
|
||||||
host.apply()
|
|
||||||
# We should not mark changed=True
|
|
||||||
self.assertEquals(result.exception.args[0]['changed'], False)
|
|
||||||
|
|
||||||
def test_host_exists(self):
|
self._set_args({'state': 'present', 'name': 'beegfs_storage1', 'host_type': 'linux dm-mp',
|
||||||
"""Test host_exists method"""
|
'ports': [{'label': 'beegfs_storage1_iscsi_0', 'type': 'iscsi', 'port': 'iqn.differentiqn.org'}]})
|
||||||
self._set_args({
|
host = Host()
|
||||||
'state': 'absent'
|
self.assertTrue(host.host_exists())
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [self.HOST])) as request:
|
|
||||||
host_exists = host.host_exists
|
|
||||||
self.assertTrue(host_exists, msg="This host should exist!")
|
|
||||||
|
|
||||||
def test_host_exists_negative(self):
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
"""Test host_exists method with no matching hosts to return"""
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
self._set_args({
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
'state': 'absent'
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
})
|
host = Host()
|
||||||
host = Host()
|
self.assertTrue(host.host_exists())
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [self.HOST_ALT])) as request:
|
|
||||||
host_exists = host.host_exists
|
|
||||||
self.assertFalse(host_exists, msg="This host should exist!")
|
|
||||||
|
|
||||||
def test_host_exists_fail(self):
|
def test_host_exists_fail(self):
|
||||||
"""Ensure we do not dump a stack trace if we fail to make the request"""
|
"""Verify host_exists produces expected exceptions."""
|
||||||
self._set_args({
|
self._set_args({'state': 'present', 'host_type': 'linux dm-mp', 'ports': [{'label': 'abc', 'type': 'iscsi', 'port': 'iqn:0'}]})
|
||||||
'state': 'absent'
|
|
||||||
})
|
|
||||||
host = Host()
|
host = Host()
|
||||||
with self.assertRaises(AnsibleFailJson):
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to determine host existence."):
|
||||||
with mock.patch(self.REQ_FUNC, side_effect=Exception("http_error")) as request:
|
with mock.patch(self.REQ_FUNC, return_value=Exception()):
|
||||||
host_exists = host.host_exists
|
host.host_exists()
|
||||||
|
|
||||||
def test_needs_update_host_type(self):
|
def test_needs_update_pass(self):
|
||||||
"""Ensure a changed host_type triggers an update"""
|
"""Verify needs_update produces expected results."""
|
||||||
self._set_args({
|
# No changes
|
||||||
'state': 'present',
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
'host_type': 27
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp',
|
||||||
})
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertFalse(host.needs_update())
|
||||||
|
|
||||||
|
# Change host type
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi', 'port': 'iqn.not_used'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
|
||||||
|
# Add port to host
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi', 'port': 'iqn.not_used'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
|
||||||
|
# Change port name
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_2', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
|
||||||
|
# take port from another host by force
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
|
||||||
|
def test_needs_update_fail(self):
|
||||||
|
"""Verify needs_update produces expected exceptions."""
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "is associated with a different host."):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
host.needs_update()
|
||||||
|
|
||||||
|
def test_valid_host_type_pass(self):
|
||||||
|
"""Validate the available host types."""
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.HOST_TYPES)):
|
||||||
|
self._set_args({'state': 'present', 'host_type': '0'})
|
||||||
|
host = Host()
|
||||||
|
self.assertTrue(host.valid_host_type())
|
||||||
|
self._set_args({'state': 'present', 'host_type': '28'})
|
||||||
|
host = Host()
|
||||||
|
self.assertTrue(host.valid_host_type())
|
||||||
|
self._set_args({'state': 'present', 'host_type': 'windows'})
|
||||||
|
host = Host()
|
||||||
|
self.assertTrue(host.valid_host_type())
|
||||||
|
self._set_args({'state': 'present', 'host_type': 'linux dm-mp'})
|
||||||
|
host = Host()
|
||||||
|
self.assertTrue(host.valid_host_type())
|
||||||
|
|
||||||
|
def test_valid_host_type_fail(self):
|
||||||
|
"""Validate the available host types."""
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "host_type must be either a host type name or host type index found integer the documentation"):
|
||||||
|
self._set_args({'state': 'present', 'host_type': 'non-host-type'})
|
||||||
|
host = Host()
|
||||||
|
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.HOST_TYPES)):
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "There is no host type with index"):
|
||||||
|
self._set_args({'state': 'present', 'host_type': '4'})
|
||||||
|
host = Host()
|
||||||
|
host.valid_host_type()
|
||||||
|
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=Exception()):
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to get host types."):
|
||||||
|
self._set_args({'state': 'present', 'host_type': '4'})
|
||||||
|
host = Host()
|
||||||
|
host.valid_host_type()
|
||||||
|
|
||||||
|
def test_group_id_pass(self):
|
||||||
|
"""Verify group_id produces expected results."""
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.HOST_GROUPS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
self.assertEqual(host.group_id(), "0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata2', 'host_type': 'linux dm-mp', 'force_port': False, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
self.assertEqual(host.group_id(), "85000000600A098000A4B9D1003637135D483DEB")
|
||||||
|
|
||||||
|
def test_group_id_fail(self):
|
||||||
|
"""Verify group_id produces expected exceptions."""
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to get host groups."):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=Exception()):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata2', 'host_type': 'linux dm-mp', 'force_port': False, 'group': 'test_group2',
|
||||||
|
'ports': [
|
||||||
|
{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.group_id()
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "No group with the name:"):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.HOST_GROUPS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata2', 'host_type': 'linux dm-mp', 'force_port': False, 'group': 'test_group2',
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.group_id()
|
||||||
|
|
||||||
|
def test_assigned_host_ports_pass(self):
|
||||||
|
"""Verify assigned_host_ports gives expected results."""
|
||||||
|
|
||||||
|
# Add an unused port to host
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi', 'port': 'iqn.not_used'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
self.assertEquals(host.assigned_host_ports(), {})
|
||||||
|
|
||||||
|
# Change port name (force)
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_2', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
self.assertEquals(host.assigned_host_ports(), {'84000000600A098000A4B9D10030370B5D430109': ['89000000600A098000A4B28D00303CFC5D4300F7']})
|
||||||
|
|
||||||
|
# Change port type
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'fc', 'port': '08:ef:7e:24:52:a0'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
self.assertEquals(host.assigned_host_ports(), {})
|
||||||
|
|
||||||
|
# take port from another host by force
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.used_elsewhere'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
self.assertEquals(host.assigned_host_ports(), {'84000000600A098000A4B9D10030370B5D430109': ['89000000600A098000A4B28D00303CFC5D4300F7']})
|
||||||
|
|
||||||
|
# take port from another host by force
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS), (200, {})]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.used_elsewhere'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
self.assertEquals(host.assigned_host_ports(apply_unassigning=True),
|
||||||
|
{'84000000600A098000A4B9D10030370B5D430109': ['89000000600A098000A4B28D00303CFC5D4300F7']})
|
||||||
|
|
||||||
|
def test_assigned_host_ports_fail(self):
|
||||||
|
"""Verify assigned_host_ports gives expected exceptions."""
|
||||||
|
# take port from another
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "There are no host ports available OR there are not enough unassigned host ports"):
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS)]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_2', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
# take port from another host and fail because force == False
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "There are no host ports available OR there are not enough unassigned host ports"):
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS)]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.used_elsewhere'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
# take port from another host and fail because force == False
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "There are no host ports available OR there are not enough unassigned host ports"):
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS)]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata3', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.used_elsewhere'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
host.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to unassign host port."):
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS), Exception()]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata2_iscsi_0', 'type': 'iscsi', 'port': 'iqn.used_elsewhere'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.assigned_host_ports(apply_unassigning=True)
|
||||||
|
|
||||||
|
def test_update_host_pass(self):
|
||||||
|
"""Verify update_host produces expected results."""
|
||||||
|
# Change host type
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
# Change port iqn
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi', 'port': 'iqn.not_used'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
# Change port type to fc
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'fc', 'port': '0x08ef08ef08ef08ef'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
# Change port name
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True,
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_12', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
# Change group
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, self.EXISTING_HOSTS)):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': False, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.group_id = lambda: "85000000600A098000A4B9D1003637135D483DEB"
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
def test_update_host_fail(self):
|
||||||
|
"""Verify update_host produces expected exceptions."""
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to update host."):
|
||||||
|
with mock.patch(self.REQ_FUNC, side_effect=[(200, self.EXISTING_HOSTS), Exception()]):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': False, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.group_id = lambda: "85000000600A098000A4B9D1003637135D483DEB"
|
||||||
|
host.host_exists()
|
||||||
|
self.assertTrue(host.needs_update())
|
||||||
|
host.update_host()
|
||||||
|
|
||||||
|
def test_create_host_pass(self):
|
||||||
|
"""Verify create_host produces expected results."""
|
||||||
|
def _assigned_host_ports(apply_unassigning=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
with self.assertRaises(AnsibleExitJson):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, {'id': '84000000600A098000A4B9D10030370B5D430109'})):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists = lambda: False
|
||||||
|
host.assigned_host_ports = _assigned_host_ports
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.group_id = lambda: "85000000600A098000A4B9D1003637135D483DEB"
|
||||||
|
host.create_host()
|
||||||
|
|
||||||
|
def test_create_host_fail(self):
|
||||||
|
"""Verify create_host produces expected exceptions."""
|
||||||
|
def _assigned_host_ports(apply_unassigning=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to create host."):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=Exception()):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists = lambda: False
|
||||||
|
host.assigned_host_ports = _assigned_host_ports
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.group_id = lambda: "85000000600A098000A4B9D1003637135D483DEB"
|
||||||
|
host.create_host()
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(AnsibleExitJson, "Host already exists."):
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_exists = lambda: True
|
||||||
|
host.assigned_host_ports = _assigned_host_ports
|
||||||
|
host.build_success_payload = lambda x: {}
|
||||||
|
host.group_id = lambda: "85000000600A098000A4B9D1003637135D483DEB"
|
||||||
|
host.create_host()
|
||||||
|
|
||||||
|
def test_remove_host_pass(self):
|
||||||
|
"""Verify remove_host produces expected results."""
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=(200, None)):
|
||||||
|
self._set_args({'state': 'absent', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_obj = {"id": "84000000600A098000A4B9D10030370B5D430109"}
|
||||||
|
host.remove_host()
|
||||||
|
|
||||||
|
def test_remove_host_fail(self):
|
||||||
|
"""Verify remove_host produces expected exceptions."""
|
||||||
|
with self.assertRaisesRegexp(AnsibleFailJson, "Failed to remove host."):
|
||||||
|
with mock.patch(self.REQ_FUNC, return_value=Exception()):
|
||||||
|
self._set_args({'state': 'absent', 'name': 'beegfs_metadata1', 'host_type': 'linux dm-mp', 'force_port': False, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_0', 'type': 'iscsi',
|
||||||
|
'port': 'iqn.1993-08.org.debian.beegfs-metadata:01:69e4efdf30b8'}]})
|
||||||
|
host = Host()
|
||||||
|
host.host_obj = {"id": "84000000600A098000A4B9D10030370B5D430109"}
|
||||||
|
host.remove_host()
|
||||||
|
|
||||||
|
def test_build_success_payload(self):
|
||||||
|
"""Validate success payload."""
|
||||||
|
def _assigned_host_ports(apply_unassigning=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
self._set_args({'state': 'present', 'name': 'beegfs_metadata1', 'host_type': 'windows', 'force_port': True, 'group': 'test_group',
|
||||||
|
'ports': [{'label': 'beegfs_metadata1_iscsi_1', 'type': 'iscsi', 'port': 'iqn.1993-08.org.debian.beegfs-storage1:01:b0621126818'}]})
|
||||||
host = Host()
|
host = Host()
|
||||||
host.host_obj = self.HOST
|
self.assertEquals(host.build_success_payload(), {'api_url': 'http://localhost/', 'ssid': '1'})
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [self.HOST])) as request:
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertTrue(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
||||||
def test_needs_update_cluster(self):
|
|
||||||
"""Ensure a changed group_id triggers an update"""
|
|
||||||
self._set_args({
|
|
||||||
'state': 'present',
|
|
||||||
'host_type': self.HOST['hostTypeIndex'],
|
|
||||||
'group': '1',
|
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
host.host_obj = self.HOST
|
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [self.HOST])) as request:
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertTrue(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
||||||
def test_needs_update_no_change(self):
|
|
||||||
"""Ensure no changes do not trigger an update"""
|
|
||||||
self._set_args({
|
|
||||||
'state': 'present',
|
|
||||||
'host_type': self.HOST['hostTypeIndex'],
|
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
host.host_obj = self.HOST
|
|
||||||
with mock.patch(self.REQ_FUNC, return_value=(200, [self.HOST])) as request:
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertFalse(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
||||||
def test_needs_update_ports(self):
|
|
||||||
"""Ensure added ports trigger an update"""
|
|
||||||
self._set_args({
|
|
||||||
'state': 'present',
|
|
||||||
'host_type': self.HOST['hostTypeIndex'],
|
|
||||||
'ports': [{'label': 'abc', 'type': 'iscsi', 'port': '0'}],
|
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
host.host_obj = self.HOST
|
|
||||||
with mock.patch.object(host, 'all_hosts', [self.HOST]):
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertTrue(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
||||||
def test_needs_update_changed_ports(self):
|
|
||||||
"""Ensure changed ports trigger an update"""
|
|
||||||
self._set_args({
|
|
||||||
'state': 'present',
|
|
||||||
'host_type': self.HOST['hostTypeIndex'],
|
|
||||||
'ports': [{'label': 'abc', 'type': 'iscsi', 'port': '0'}],
|
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
host.host_obj = self.HOST.copy()
|
|
||||||
host.host_obj['hostSidePorts'] = [{'label': 'xyz', 'type': 'iscsi', 'port': '0', 'address': 'iqn:0'}]
|
|
||||||
|
|
||||||
with mock.patch.object(host, 'all_hosts', [self.HOST]):
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertTrue(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
||||||
def test_needs_update_changed_negative(self):
|
|
||||||
"""Ensure a ports update with no changes does not trigger an update"""
|
|
||||||
self._set_args({
|
|
||||||
'state': 'present',
|
|
||||||
'host_type': self.HOST['hostTypeIndex'],
|
|
||||||
'ports': [{'label': 'abc', 'type': 'iscsi', 'port': '0'}],
|
|
||||||
})
|
|
||||||
host = Host()
|
|
||||||
host.host_obj = self.HOST.copy()
|
|
||||||
host.host_obj['hostSidePorts'] = [{'label': 'xyz', 'type': 'iscsi', 'port': '0', 'address': 'iqn:0'}]
|
|
||||||
|
|
||||||
with mock.patch.object(host, 'all_hosts', [self.HOST]):
|
|
||||||
needs_update = host.needs_update
|
|
||||||
self.assertTrue(needs_update, msg="An update to the host should be required!")
|
|
||||||
|
|
Loading…
Reference in a new issue