summaryrefslogtreecommitdiffstats
path: root/ansible_collections/dellemc/unity/plugins/modules/cifsserver.py
blob: 0225eb381b7242d07fb1f38f58eeb4a67ca0ee1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
#!/usr/bin/python
# 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) <ansible.team@dell.com>

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.7.1"


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()