File: //lib/python3.9/site-packages/ansible_collections/dellemc/unity/plugins/modules/cifsserver.py
# Copyright: (c) 2022, Dell Technologies
# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt)
"""Ansible module for managing CIFS server on Unity"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
module: cifsserver
version_added: '1.4.0'
short_description: Manage CIFS server on Unity storage system
description:
- Managing the CIFS server on the Unity storage system includes creating CIFS server, getting CIFS server details
and deleting CIFS server.
extends_documentation_fragment:
- dellemc.unity.unity
author:
- Akash Shendge (@shenda1) <[email protected]>
options:
nas_server_name:
description:
- Name of the NAS server on which CIFS server will be hosted.
type: str
nas_server_id:
description:
- ID of the NAS server on which CIFS server will be hosted.
type: str
netbios_name:
description:
- The computer name of the SMB server in Windows network.
type: str
workgroup:
description:
- Standalone SMB server workgroup.
type: str
local_password:
description:
- Standalone SMB server administrator password.
type: str
domain:
description:
- The domain name where the SMB server is registered in Active Directory.
type: str
domain_username:
description:
- Active Directory domain user name.
type: str
domain_password:
description:
- Active Directory domain password.
type: str
cifs_server_name:
description:
- The name of the CIFS server.
type: str
cifs_server_id:
description:
- The ID of the CIFS server.
type: str
interfaces:
description:
- List of file IP interfaces that service CIFS protocol of SMB server.
type: list
elements: str
unjoin_cifs_server_account:
description:
- Keep SMB server account unjoined in Active Directory after deletion.
- C(false) specifies keep SMB server account joined after deletion.
- C(true) specifies unjoin SMB server account from Active Directory before deletion.
type: bool
state:
description:
- Define whether the CIFS server should exist or not.
choices: [absent, present]
required: true
type: str
notes:
- The I(check_mode) is supported.
'''
EXAMPLES = r'''
- name: Create CIFS server belonging to Active Directory
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
nas_server_name: "test_nas1"
cifs_server_name: "test_cifs"
domain: "ad_domain"
domain_username: "domain_username"
domain_password: "domain_password"
state: "present"
- name: Get CIFS server details using CIFS server ID
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cifs_server_id: "cifs_37"
state: "present"
- name: Get CIFS server details using NAS server name
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
nas_server_name: "test_nas1"
state: "present"
- name: Delete CIFS server
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cifs_server_id: "cifs_37"
unjoin_cifs_server_account: True
domain_username: "domain_username"
domain_password: "domain_password"
state: "absent"
- name: Create standalone CIFS server
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
netbios_name: "ANSIBLE_CIFS"
workgroup: "ansible"
local_password: "Password123!"
nas_server_name: "test_nas1"
state: "present"
- name: Get CIFS server details using netbios name
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
netbios_name: "ANSIBLE_CIFS"
state: "present"
- name: Delete standalone CIFS server
dellemc.unity.cifsserver:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cifs_server_id: "cifs_40"
state: "absent"
'''
RETURN = r'''
changed:
description: Whether or not the resource has changed.
returned: always
type: bool
sample: true
cifs_server_details:
description: Details of the CIFS server.
returned: When CIFS server exists
type: dict
contains:
id:
description: Unique identifier of the CIFS server instance.
type: str
name:
description: User-specified name for the SMB server.
type: str
netbios_name:
description: Computer Name of the SMB server in windows network.
type: str
description:
description: Description of the SMB server.
type: str
domain:
description: Domain name where SMB server is registered in Active Directory.
type: str
workgroup:
description: Windows network workgroup for the SMB server.
type: str
is_standalone:
description: Indicates whether the SMB server is standalone.
type: bool
nasServer:
description: Information about the NAS server in the storage system.
type: dict
contains:
UnityNasServer:
description: Information about the NAS server in the storage system.
type: dict
contains:
id:
description: Unique identifier of the NAS server instance.
type: str
file_interfaces:
description: The file interfaces associated with the NAS server.
type: dict
contains:
UnityFileInterfaceList:
description: List of file interfaces associated with the NAS server.
type: list
contains:
UnityFileInterface:
description: Details of file interface associated with the NAS server.
type: dict
contains:
id:
description: Unique identifier of the file interface.
type: str
smb_multi_channel_supported:
description: Indicates whether the SMB 3.0+ multichannel feature is supported.
type: bool
smb_protocol_versions:
description: Supported SMB protocols, such as 1.0, 2.0, 2.1, 3.0, and so on.
type: list
smbca_supported:
description: Indicates whether the SMB server supports continuous availability.
type: bool
sample: {
"description": null,
"domain": "xxx.xxx.xxx.com",
"existed": true,
"file_interfaces": {
"UnityFileInterfaceList": [
{
"UnityFileInterface": {
"hash": -9223363258905013637,
"id": "if_43"
}
}
]
},
"hash": -9223363258905010379,
"health": {
"UnityHealth": {
"hash": 8777949765559
}
},
"id": "cifs_40",
"is_standalone": false,
"last_used_organizational_unit": "ou=Computers,ou=Dell NAS servers",
"name": "ansible_cifs",
"nas_server": {
"UnityNasServer": {
"hash": 8777949765531,
"id": "nas_18"
}
},
"netbios_name": "ANSIBLE_CIFS",
"smb_multi_channel_supported": true,
"smb_protocol_versions": [
"1.0",
"2.0",
"2.1",
"3.0"
],
"smbca_supported": true,
"workgroup": null
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.unity.plugins.module_utils.storage.dell import utils
LOG = utils.get_logger('cifsserver')
application_type = "Ansible/1.6.0"
class CIFSServer(object):
"""Class with CIFS server operations"""
def __init__(self):
"""Define all parameters required by this module"""
self.module_params = utils.get_unity_management_host_parameters()
self.module_params.update(get_cifs_server_parameters())
mutually_exclusive = [['nas_server_name', 'nas_server_id'], ['cifs_server_id', 'cifs_server_name'],
['cifs_server_id', 'netbios_name']]
required_one_of = [['cifs_server_id', 'cifs_server_name', 'netbios_name', 'nas_server_name', 'nas_server_id']]
# initialize the Ansible module
self.module = AnsibleModule(
argument_spec=self.module_params,
supports_check_mode=True,
mutually_exclusive=mutually_exclusive,
required_one_of=required_one_of
)
utils.ensure_required_libs(self.module)
self.unity_conn = utils.get_unity_unisphere_connection(
self.module.params, application_type)
LOG.info('Check Mode Flag %s', self.module.check_mode)
def get_details(self, cifs_server_id=None, cifs_server_name=None, netbios_name=None, nas_server_id=None):
"""Get CIFS server details.
:param: cifs_server_id: The ID of the CIFS server
:param: cifs_server_name: The name of the CIFS server
:param: netbios_name: Name of the SMB server in windows network
:param: nas_server_id: The ID of the NAS server
:return: Dict containing CIFS server details if exists
"""
LOG.info("Getting CIFS server details")
id_or_name = get_id_name(cifs_server_id, cifs_server_name, netbios_name, nas_server_id)
try:
if cifs_server_id:
cifs_server_details = self.unity_conn.get_cifs_server(_id=cifs_server_id)
return process_response(cifs_server_details)
if cifs_server_name:
cifs_server_details = self.unity_conn.get_cifs_server(name=cifs_server_name)
return process_response(cifs_server_details)
if netbios_name:
cifs_server_details = self.unity_conn.get_cifs_server(netbios_name=netbios_name)
if len(cifs_server_details) > 0:
return process_dict(cifs_server_details._get_properties())
if nas_server_id:
cifs_server_details = self.unity_conn.get_cifs_server(nas_server=nas_server_id)
if len(cifs_server_details) > 0:
return process_dict(cifs_server_details._get_properties())
return None
except utils.HttpError as e:
if e.http_status == 401:
msg = "Failed to get CIFS server: %s due to incorrect " \
"username/password error: %s" % (id_or_name, str(e))
else:
msg = "Failed to get CIFS server: %s with error: %s" % (id_or_name, str(e))
except utils.UnityResourceNotFoundError:
msg = "CIFS server with ID %s not found" % cifs_server_id
LOG.info(msg)
return None
except utils.StoropsConnectTimeoutError as e:
msg = "Failed to get CIFS server: %s with error: %s. Please check unispherehost IP: %s" % (
id_or_name, str(e), self.module.params['unispherehost'])
except Exception as e:
msg = "Failed to get details of CIFS server: %s with error: %s" % (id_or_name, str(e))
LOG.error(msg)
self.module.fail_json(msg=msg)
def get_cifs_server_instance(self, cifs_server_id):
"""Get CIFS server instance.
:param: cifs_server_id: The ID of the CIFS server
:return: Return CIFS server instance if exists
"""
try:
cifs_server_obj = utils.UnityCifsServer.get(cli=self.unity_conn._cli, _id=cifs_server_id)
return cifs_server_obj
except Exception as e:
error_msg = "Failed to get the CIFS server %s instance" \
" with error %s" % (cifs_server_id, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def delete_cifs_server(self, cifs_server_id, skip_unjoin=None, domain_username=None, domain_password=None):
"""Delete CIFS server.
:param: cifs_server_id: The ID of the CIFS server
:param: skip_unjoin: Flag indicating whether to unjoin SMB server account from AD before deletion
:param: domain_username: The domain username
:param: domain_password: The domain password
:return: Return True if CIFS server is deleted
"""
LOG.info("Deleting CIFS server")
try:
if not self.module.check_mode:
cifs_obj = self.get_cifs_server_instance(cifs_server_id=cifs_server_id)
cifs_obj.delete(skip_domain_unjoin=skip_unjoin, username=domain_username, password=domain_password)
return True
except Exception as e:
msg = "Failed to delete CIFS server: %s with error: %s" % (cifs_server_id, str(e))
LOG.error(msg)
self.module.fail_json(msg=msg)
def get_nas_server_id(self, nas_server_name):
"""Get NAS server ID.
:param: nas_server_name: The name of NAS server
:return: Return NAS server ID if exists
"""
LOG.info("Getting NAS server ID")
try:
obj_nas = self.unity_conn.get_nas_server(name=nas_server_name)
return obj_nas.get_id()
except Exception as e:
msg = "Failed to get details of NAS server: %s with error: %s" % (nas_server_name, str(e))
LOG.error(msg)
self.module.fail_json(msg=msg)
def is_modify_interfaces(self, cifs_server_details):
"""Check if modification is required in existing interfaces
:param: cifs_server_details: CIFS server details
:return: Flag indicating if modification is required
"""
existing_interfaces = []
if cifs_server_details['file_interfaces']['UnityFileInterfaceList']:
for interface in cifs_server_details['file_interfaces']['UnityFileInterfaceList']:
existing_interfaces.append(interface['UnityFileInterface']['id'])
for interface in self.module.params['interfaces']:
if interface not in existing_interfaces:
return True
return False
def is_modification_required(self, cifs_server_details):
"""Check if modification is required in existing CIFS server
:param: cifs_server_details: CIFS server details
:return: Flag indicating if modification is required
"""
LOG.info("Checking if any modification is required")
param_list = ['netbios_name', 'workgroup']
for param in param_list:
if self.module.params[param] is not None and cifs_server_details[param] is not None and \
self.module.params[param].upper() != cifs_server_details[param]:
return True
# Check for domain
if self.module.params['domain'] is not None and cifs_server_details['domain'] is not None and \
self.module.params['domain'] != cifs_server_details['domain']:
return True
# Check file interfaces
if self.module.params['interfaces'] is not None:
return self.is_modify_interfaces(cifs_server_details)
return False
def create_cifs_server(self, nas_server_id, interfaces=None, netbios_name=None, cifs_server_name=None, domain=None,
domain_username=None, domain_password=None, workgroup=None, local_password=None):
"""Create CIFS server.
:param: nas_server_id: The ID of NAS server
:param: interfaces: List of file interfaces
:param: netbios_name: Name of the SMB server in windows network
:param: cifs_server_name: Name of the CIFS server
:param: domain: The domain name where the SMB server is registered in Active Directory
:param: domain_username: The domain username
:param: domain_password: The domain password
:param: workgroup: Standalone SMB server workgroup
:param: local_password: Standalone SMB server admin password
:return: Return True if CIFS server is created
"""
LOG.info("Creating CIFS server")
try:
if not self.module.check_mode:
utils.UnityCifsServer.create(cli=self.unity_conn._cli, nas_server=nas_server_id, interfaces=interfaces,
netbios_name=netbios_name, name=cifs_server_name, domain=domain,
domain_username=domain_username, domain_password=domain_password,
workgroup=workgroup, local_password=local_password)
return True
except Exception as e:
msg = "Failed to create CIFS server with error: %s" % (str(e))
LOG.error(msg)
self.module.fail_json(msg=msg)
def validate_params(self):
"""Validate the parameters
"""
param_list = ['nas_server_id', 'nas_server_name', 'domain', 'cifs_server_id', 'cifs_server_name',
'local_password', 'netbios_name', 'workgroup', 'domain_username', 'domain_password']
msg = "Please provide valid {0}"
for param in param_list:
if self.module.params[param] is not None and len(self.module.params[param].strip()) == 0:
errmsg = msg.format(param)
self.module.fail_json(msg=errmsg)
def perform_module_operation(self):
"""
Perform different actions on CIFS server module based on parameters
passed in the playbook
"""
cifs_server_id = self.module.params['cifs_server_id']
cifs_server_name = self.module.params['cifs_server_name']
nas_server_id = self.module.params['nas_server_id']
nas_server_name = self.module.params['nas_server_name']
netbios_name = self.module.params['netbios_name']
workgroup = self.module.params['workgroup']
local_password = self.module.params['local_password']
domain = self.module.params['domain']
domain_username = self.module.params['domain_username']
domain_password = self.module.params['domain_password']
interfaces = self.module.params['interfaces']
unjoin_cifs_server_account = self.module.params['unjoin_cifs_server_account']
state = self.module.params['state']
# result is a dictionary that contains changed status and CIFS server details
result = dict(
changed=False,
cifs_server_details={}
)
# Validate the parameters
self.validate_params()
if nas_server_name is not None:
nas_server_id = self.get_nas_server_id(nas_server_name)
cifs_server_details = self.get_details(cifs_server_id=cifs_server_id,
cifs_server_name=cifs_server_name,
netbios_name=netbios_name,
nas_server_id=nas_server_id)
# Check if modification is required
if cifs_server_details:
if cifs_server_id is None:
cifs_server_id = cifs_server_details['id']
modify_flag = self.is_modification_required(cifs_server_details)
if modify_flag:
self.module.fail_json(msg="Modification is not supported through Ansible module")
if not cifs_server_details and state == 'present':
if not nas_server_id:
self.module.fail_json(msg="Please provide nas server id/name to create CIFS server.")
if any([netbios_name, workgroup, local_password]) and not all([netbios_name, workgroup, local_password]):
msg = "netbios_name, workgroup and local_password " \
"are required to create standalone CIFS server."
LOG.error(msg)
self.module.fail_json(msg=msg)
result['changed'] = self.create_cifs_server(nas_server_id, interfaces, netbios_name,
cifs_server_name, domain, domain_username, domain_password,
workgroup, local_password)
if state == 'absent' and cifs_server_details:
skip_unjoin = None
if unjoin_cifs_server_account is not None:
skip_unjoin = not unjoin_cifs_server_account
result['changed'] = self.delete_cifs_server(cifs_server_id, skip_unjoin, domain_username,
domain_password)
if state == 'present':
result['cifs_server_details'] = self.get_details(cifs_server_id=cifs_server_id,
cifs_server_name=cifs_server_name,
netbios_name=netbios_name,
nas_server_id=nas_server_id)
LOG.info("Process Dict: %s", result['cifs_server_details'])
self.module.exit_json(**result)
def get_id_name(cifs_server_id=None, cifs_server_name=None, netbios_name=None, nas_server_id=None):
"""Get the id_or_name.
:param: cifs_server_id: The ID of CIFS server
:param: cifs_server_name: The name of CIFS server
:param: netbios_name: Name of the SMB server in windows network
:param: nas_server_id: The ID of NAS server
:return: Return id_or_name
"""
if cifs_server_id:
id_or_name = cifs_server_id
elif cifs_server_name:
id_or_name = cifs_server_name
elif netbios_name:
id_or_name = netbios_name
else:
id_or_name = nas_server_id
return id_or_name
def process_response(cifs_server_details):
"""Process CIFS server details.
:param: cifs_server_details: Dict containing CIFS server details
:return: Processed dict containing CIFS server details
"""
if cifs_server_details.existed:
return cifs_server_details._get_properties()
def process_dict(cifs_server_details):
"""Process CIFS server details.
:param: cifs_server_details: Dict containing CIFS server details
:return: Processed dict containing CIFS server details
"""
param_list = ['description', 'domain', 'file_interfaces', 'health', 'id', 'is_standalone', 'name', 'nas_server'
'netbios_name', 'smb_multi_channel_supported', 'smb_protocol_versions', 'smbca_supported',
'workgroup', 'netbios_name']
for param in param_list:
if param in cifs_server_details:
cifs_server_details[param] = cifs_server_details[param][0]
return cifs_server_details
def get_cifs_server_parameters():
"""This method provide parameters required for the ansible
CIFS server module on Unity"""
return dict(
cifs_server_id=dict(), cifs_server_name=dict(),
netbios_name=dict(), workgroup=dict(),
local_password=dict(no_log=True), domain=dict(),
domain_username=dict(), domain_password=dict(no_log=True),
nas_server_name=dict(), nas_server_id=dict(),
interfaces=dict(type='list', elements='str'),
unjoin_cifs_server_account=dict(type='bool'),
state=dict(required=True, type='str', choices=['present', 'absent']),
)
def main():
"""Create Unity CIFS server object and perform action on it
based on user input from playbook"""
obj = CIFSServer()
obj.perform_module_operation()
if __name__ == '__main__':
main()