From 84541f5bacbb873c24bad31f67d260442dda1481 Mon Sep 17 00:00:00 2001 From: Prerna Date: Fri, 8 Oct 2021 15:14:30 +0530 Subject: [PATCH 1/2] Added_function_to_manage_SDDC_host --- docs/ref/modules/all.rst | 1 + .../saltext.vmware.modules.vmc_sddc_host.rst | 6 + src/saltext/vmware/modules/vmc_sddc_host.py | 201 ++++++++++++++++++ src/saltext/vmware/utils/vmc_templates.py | 9 + tests/unit/modules/test_vmc_sddc_host.py | 159 ++++++++++++++ 5 files changed, 376 insertions(+) create mode 100644 docs/ref/modules/saltext.vmware.modules.vmc_sddc_host.rst create mode 100644 src/saltext/vmware/modules/vmc_sddc_host.py create mode 100644 tests/unit/modules/test_vmc_sddc_host.py diff --git a/docs/ref/modules/all.rst b/docs/ref/modules/all.rst index 1f5479ba..77738b10 100644 --- a/docs/ref/modules/all.rst +++ b/docs/ref/modules/all.rst @@ -34,6 +34,7 @@ Execution Modules saltext.vmware.modules.vmc_nat_rules saltext.vmware.modules.vmc_networks saltext.vmware.modules.vmc_sddc + saltext.vmware.modules.vmc_sddc_host saltext.vmware.modules.vmc_security_groups saltext.vmware.modules.vmc_security_rules saltext.vmware.modules.vmc_vpn_statistics diff --git a/docs/ref/modules/saltext.vmware.modules.vmc_sddc_host.rst b/docs/ref/modules/saltext.vmware.modules.vmc_sddc_host.rst new file mode 100644 index 00000000..e1fd5626 --- /dev/null +++ b/docs/ref/modules/saltext.vmware.modules.vmc_sddc_host.rst @@ -0,0 +1,6 @@ + +saltext.vmware.modules.vmc_sddc_host +==================================== + +.. automodule:: saltext.vmware.modules.vmc_sddc_host + :members: diff --git a/src/saltext/vmware/modules/vmc_sddc_host.py b/src/saltext/vmware/modules/vmc_sddc_host.py new file mode 100644 index 00000000..cc864d11 --- /dev/null +++ b/src/saltext/vmware/modules/vmc_sddc_host.py @@ -0,0 +1,201 @@ +""" +Salt execution module for ESX hosts +Provides methods to Add/Remove one or more ESX hosts in the target SDDC + +""" +import logging + +from saltext.vmware.modules import vmc_sddc +from saltext.vmware.utils import vmc_constants +from saltext.vmware.utils import vmc_request +from saltext.vmware.utils import vmc_templates + +log = logging.getLogger(__name__) + +__virtualname__ = "vmc_sddc_host" + + +def __virtual__(): + return __virtualname__ + + +def manage( + hostname, + refresh_key, + authorization_host, + org_id, + sddc_id, + num_hosts, + availability_zone=None, + cluster_id=None, + esxs=None, + strict_placement=False, + action=None, + verify_ssl=True, + cert=None, +): + """ + Add/remove host for a given SDDC + + Please refer the `VMC Create ESXs documentation `_ to get insight of functionality and input parameters + + CLI Example: + + .. code-block:: bash + + salt vmc_sddc_host.manage hostname=vmc.vmware.com ... + + hostname + The host name of VMC + + refresh_key + API Token of the user which is used to get the Access Token required for VMC operations + + authorization_host + Hostname of the Cloud Services Platform (CSP) + + org_id + The Id of organization to which the SDDC belongs to + + sddc_id + The Id of SDDC for which the host would be added/removed + + num_hosts: Integer + (Required) Count of Hosts that would be added/removed for the given SDDC + + availability_zone: String + (Optional) Availability zone where the hosts should be provisioned. + (Can be specified only for privileged host operations). + + cluster_id: String + (Optional) An optional cluster id if the esxs operation has to be on a specific cluster. + + esxs: Array Of String As UUID + (Optional) An optional list of ESX IDs to remove. + + strict_placement: Boolean + (Optional) An option to indicate if the host needs to be strictly placed in a placement group. + Fail the operation otherwise. + + action: String + (Optional) + If action = 'add', will add the esx. + + If action = 'remove', will delete the esx/esxs bound to a single cluster(Cluster Id is mandatory for non cluster 1 esx remove). + + If action = 'force-remove', will delete the esx even if it can lead to data loss (This is an privileged operation). + + If action = 'addToAll', will add esxs to all clusters in the SDDC (This is an privileged operation). + + If action = 'removeFromAll', will delete the esxs from all clusters in the SDDC (This is an privileged operation). + + If action = 'attach-diskgroup', will attach the provided diskgroups to a given host (privileged). + + If action = 'detach-diskgroup', will detach the diskgroups of a given host (privileged). + + Default behaviour is 'add' + + verify_ssl + (Optional) Option to enable/disable SSL verification. Enabled by default. + If set to False, the certificate validation is skipped. + + cert + (Optional) Path to the SSL client certificate file to connect to VMC Cloud Console. + The certificate can be retrieved from browser. + + For example: + .. code:: + + { + "availability_zone": "us-west-2a", + "cluster_id": "e97920ae-1410-4269-9caa-29584eb8cf6d", + "esxs": [ + "94ce40e1-8619-45b5-9817-0f3466d0dc78" + ], + "num_hosts": 1, + "strict_placement": false + } + + """ + + log.info("Managing host for the SDDC %s", sddc_id) + api_base_url = vmc_request.set_base_url(hostname) + api_url = "{base_url}vmc/api/orgs/{org_id}/sddcs/{sddc_id}/esxs".format( + base_url=api_base_url, org_id=org_id, sddc_id=sddc_id + ) + + allowed_dict = { + "num_hosts": num_hosts, + "availability_zone": availability_zone, + "cluster_id": cluster_id, + "esxs": esxs, + "strict_placement": strict_placement, + } + req_data = vmc_request._filter_kwargs(allowed_kwargs=allowed_dict.keys(), **allowed_dict) + params = vmc_request._filter_kwargs(allowed_kwargs=["action"], action=action) + request_data = vmc_request.create_payload_for_request(vmc_templates.manage_sddc_host, req_data) + return vmc_request.call_api( + method=vmc_constants.POST_REQUEST_METHOD, + url=api_url, + refresh_key=refresh_key, + authorization_host=authorization_host, + description="vmc_sddc_host.manage", + data=request_data, + params=params, + verify_ssl=verify_ssl, + cert=cert, + ) + + +def get(hostname, refresh_key, authorization_host, org_id, sddc_id, verify_ssl=True, cert=None): + """ + Retrieves ESX hosts for the given SDDC + + Please refer the `VMC Get SDDC documentation `_ to get insight of functionality and input parameters + + CLI Example: + + .. code-block:: bash + + salt vmc_sddc_host.get hostname=vmc.vmware.com ... + + hostname + The host name of VMC + + refresh_key + API Token of the user which is used to get the Access Token required for VMC operations + + authorization_host + Hostname of the Cloud Services Platform (CSP) + + org_id + The Id of organization to which the SDDC belongs to + + sddc_id + The Id of SDDC for which the hosts would be retrieved + + verify_ssl + (Optional) Option to enable/disable SSL verification. Enabled by default. + If set to False, the certificate validation is skipped. + + cert + (Optional) Path to the SSL client certificate file to connect to VMC Cloud Console. + The certificate can be retrieved from browser. + + """ + + log.info("Retrieving hosts for SDDC {} %s", sddc_id) + sddc_detail = vmc_sddc.get_by_id( + hostname=hostname, + refresh_key=refresh_key, + authorization_host=authorization_host, + org_id=org_id, + sddc_id=sddc_id, + verify_ssl=verify_ssl, + cert=cert, + ) + if "error" in sddc_detail: + return sddc_detail + esx_hosts_details = sddc_detail["resource_config"]["esx_hosts"] + result = {"description": "vmc_sddc_host.get", "esx_hosts_details": esx_hosts_details} + return result diff --git a/src/saltext/vmware/utils/vmc_templates.py b/src/saltext/vmware/utils/vmc_templates.py index 306b9fd2..eef83833 100644 --- a/src/saltext/vmware/utils/vmc_templates.py +++ b/src/saltext/vmware/utils/vmc_templates.py @@ -24,6 +24,15 @@ } +manage_sddc_host = { + "availability_zone": None, + "cluster_id": None, + "esxs": None, + "num_hosts": 1, + "strict_placement": False, +} + + create_security_rules_mgw = { "sequence_number": 0, "source_groups": ["ANY"], diff --git a/tests/unit/modules/test_vmc_sddc_host.py b/tests/unit/modules/test_vmc_sddc_host.py new file mode 100644 index 00000000..0aba447e --- /dev/null +++ b/tests/unit/modules/test_vmc_sddc_host.py @@ -0,0 +1,159 @@ +""" + Unit tests for vmc_sddc_host execution module +""" +import logging +from unittest.mock import patch + +import pytest +import saltext.vmware.modules.vmc_sddc_host as vmc_sddc_host +from saltext.vmware.utils import vmc_constants +from saltext.vmware.utils import vmc_request + +log = logging.getLogger(__name__) + + +@pytest.fixture +def host_data(): + data = [ + { + "esx_id": "f5437b89-3360-444c-ab14-3332e32e03cd", + "name": "Not Really ESX3ddaf75e-a570-40d3-a470-52b17eff8eb7", + "hostname": "zerocloud.esx.localff020345-3ce0-4fe6-b15a-c54d33df5ad3", + "mac_address": "01-23-45-67-89-ab", + "provider": "ZEROCLOUD", + "esx_state": "READY", + "custom_properties": None, + "esx_credential": None, + "availability_zone": "us-west-2a", + "state_last_updated": "2021-04-19T16:34:55.157Z", + "ssh_check_failure_retry_time": 0, + "instance_type": "i3.metal", + "finger_print": None, + "instance_id": "ddae0fbb-257f-4fea-b9d5-a02b609db708", + "durable_host_name": None, + }, + { + "esx_id": "8bba6fee-0db7-441e-a7fd-86cc4ecce480", + "name": "Not Really ESX1d2259b4-0758-4dbc-8bf5-bd96e398e77a", + "hostname": "zerocloud.esx.localb1d2325b-8dad-4e41-a7b1-c735d7d62c51", + "mac_address": "01-23-45-67-89-ab", + "provider": "ZEROCLOUD", + "esx_state": "READY", + "custom_properties": None, + "esx_credential": None, + "availability_zone": None, + "state_last_updated": "2021-04-19T16:38:27.686Z", + "ssh_check_failure_retry_time": 0, + "instance_type": "i3.metal", + "finger_print": None, + "instance_id": "25817091-8325-4948-8b5d-a373bbf1e67f", + "durable_host_name": None, + }, + ] + yield data + + +@pytest.fixture +def hosts_data(host_data): + data = {"description": "vmc_sddc_host.get", "esx_hosts_details": host_data} + yield data + + +@pytest.fixture +def sddc_data_by_id(mock_vmc_request_call_api, host_data): + data = { + "user_id": "f0880137-ae17-3a09-a8e1-2bd1da0d4b5d", + "user_name": "test@mvmware.com", + "created": "2021-04-05T18:29:35.000705Z", + "id": "f46eda60-c67c-4613-a7fe-526b87948cb7", + "resource_config": { + "vc_url": "https://vcenter.sddc-10-182-155-238.vmwarevmc.com/", + "cloud_username": "cloudadmin@vmc.local", + "cloud_password": "Z8OYrQhD-T3v-cw", + "esx_hosts": host_data, + }, + "updated_by_user_id": "e05378ed-7c3d-3bfb-8129-9b5a554b8e50", + "updated_by_user_name": "Internal-Operator", + "updated": "2021-04-08T02:17:04.000000Z", + "name": "CMBU-STG-NSXT-M12v6-04-05-21", + } + mock_vmc_request_call_api.return_value = data + yield data + + +def test_get_sddc_hosts_should_return_api_response(sddc_data_by_id, hosts_data): + result = vmc_sddc_host.get( + hostname="hostname", + refresh_key="refresh_key", + authorization_host="authorization_host", + org_id="org_id", + sddc_id="sddc_id", + verify_ssl=False, + ) + assert result == hosts_data + + +def test_get_sddc_hosts_fail_with_error(mock_vmc_request_call_api): + expected_response = {"error": "Given SDDC does not exist"} + mock_vmc_request_call_api.return_value = expected_response + result = vmc_sddc_host.get( + hostname="hostname", + refresh_key="refresh_key", + authorization_host="authorization_host", + org_id="org_id", + sddc_id="sddc_d", + verify_ssl=False, + ) + assert "error" in result + + +def test_get_sddc_called_with_url(): + expected_url = "https://hostname/vmc/api/orgs/org_id/sddcs/sddc_id" + with patch("saltext.vmware.utils.vmc_request.call_api", autospec=True) as vmc_call_api: + vmc_sddc_host.get( + hostname="hostname", + refresh_key="refresh_key", + authorization_host="authorization_host", + org_id="org_id", + sddc_id="sddc_id", + verify_ssl=False, + ) + call_kwargs = vmc_call_api.mock_calls[0][-1] + assert call_kwargs["url"] == expected_url + assert call_kwargs["method"] == vmc_constants.GET_REQUEST_METHOD + + +def test_manage_esx_host_when_api_should_return_api_response( + mock_vmc_request_call_api, +): + expected_response = {"message": "Esx Host Added successfully"} + mock_vmc_request_call_api.return_value = expected_response + assert ( + vmc_sddc_host.manage( + hostname="hostname", + refresh_key="refresh_key", + authorization_host="authorization_host", + org_id="org_id", + sddc_id="sddc_id", + num_hosts=1, + verify_ssl=False, + ) + == expected_response + ) + + +def test_manage_esx_host_called_with_url(): + expected_url = "https://hostname/vmc/api/orgs/org_id/sddcs/sddc_id/esxs" + with patch("saltext.vmware.utils.vmc_request.call_api", autospec=True) as vmc_call_api: + vmc_sddc_host.manage( + hostname="hostname", + refresh_key="refresh_key", + authorization_host="authorization_host", + org_id="org_id", + sddc_id="sddc_id", + num_hosts=1, + verify_ssl=False, + ) + call_kwargs = vmc_call_api.mock_calls[0][-1] + assert call_kwargs["url"] == expected_url + assert call_kwargs["method"] == vmc_constants.POST_REQUEST_METHOD From 83179adeae40fa54298b8c005fce1746a54da61e Mon Sep 17 00:00:00 2001 From: Prerna Date: Wed, 8 Dec 2021 17:39:37 +0530 Subject: [PATCH 2/2] unit test improvised for sddc_host module --- tests/unit/modules/test_vmc_sddc_host.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/modules/test_vmc_sddc_host.py b/tests/unit/modules/test_vmc_sddc_host.py index 0aba447e..b6f07587 100644 --- a/tests/unit/modules/test_vmc_sddc_host.py +++ b/tests/unit/modules/test_vmc_sddc_host.py @@ -157,3 +157,36 @@ def test_manage_esx_host_called_with_url(): call_kwargs = vmc_call_api.mock_calls[0][-1] assert call_kwargs["url"] == expected_url assert call_kwargs["method"] == vmc_constants.POST_REQUEST_METHOD + + +@pytest.mark.parametrize( + "actual_args, expected_params", + [ + # all actual args are None + ( + {}, + {}, + ), + # all actual args have param values + ( + {"action": "add"}, + {"action": "add"}, + ), + ], +) +def test_assert_manage_esx_host_should_correctly_filter_args(actual_args, expected_params): + common_actual_args = { + "hostname": "hostname", + "refresh_key": "refresh_key", + "authorization_host": "authorization_host", + "org_id": "org_id", + "sddc_id": "sddc_id", + "num_hosts": 1, + "verify_ssl": False, + } + with patch("saltext.vmware.utils.vmc_request.call_api", autospec=True) as vmc_call_api: + actual_args.update(common_actual_args) + vmc_sddc_host.manage(**actual_args) + + call_kwargs = vmc_call_api.mock_calls[0][-1] + assert call_kwargs["params"] == expected_params