diff --git a/plugins/modules/dcs_instance.py b/plugins/modules/dcs_instance.py new file mode 100644 index 00000000..82a01d12 --- /dev/null +++ b/plugins/modules/dcs_instance.py @@ -0,0 +1,538 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance +short_description: Manage DCS Instances on Open Telekom Cloud +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Manage DCS Instances on Open Telekom Cloud +options: + name: + description: + - Specifies the name of the instance + type: str + description: + description: + - Specifies the description of the instance + type: str + id: + description: + - ID of the instance. Needed when modifying an instance + type: str + engine: + description: + - Cache engine + type: str + default: Redis + engine_version: + description: + - Cache Engine Version + type: str + default: 3.0 + capacity: + description: + - Cache capacity in GB + type: int + password: + description: + - Password of a DCS instance + - Password must meet following criteria + - String 8 to 32 chars, different than the old one, at least 3 of the types (Lowercase, Uppercase, Digits, Special) + type: str + vpc_id: + description: + - VPC ID + type: str + security_group_id: + description: + - ID of the Security Group where the instance belongs to + type: str + subnet_id: + description: + - Network ID of the Subnet + type: str + available_zones: + description: + - ID of the AZ where the cache node resides and which has available ressources. + type: list + elements: str + product_id: + description: + - ID of the product that can be created + type: str + instance_backup_policy: + description: + - Backup policy + type: dict + maintain_begin: + description: + - Time at which the maintenance time window starts. + - Format HH mm ss + - Must be set in pairs with maintain_end + type: str + maintain_end: + description: + - Time at which the maintenance time window ends. + - Format HH mm ss + - Must be set in pairs with maintain_begin + type: str + redis_config: + description: + - If this param is set for modification, only redis_config will be changed + type: dict + restart_instance: + description: + - Restart the instance. Do nothing else if true + type: bool + default: false + state: + choices: [present, absent] + default: present + description: Instance state + type: str +requirements: ["openstacksdk", "otcextensions"] +''' + +RETURN = ''' +dcs_instance: + description: Dictionary of DCS instance + returned: changed + type: dict + sample: { + "dcs_instance": { + "available_zones": [ + "123456786ce4e948da0b97d9a7d62fb" + ], + "backup_policy": { + "backup_policy_id": "12345678-6d67-4b79-a9b8-0ba20b365070", + "created_at": "2021-03-01T12:43:05.233Z", + "policy": { + "backup_type": "manual", + "periodical_backup_plan": { + "backup_at": [ + 1, + 2, + 3, + 4 + ], + "begin_at": "00:00-01:00", + "period_type": "weekly", + "timezone_offset": null + }, + "save_days": 2 + }, + "tenant_id": "12345678a13b49529d2e2c3646691288", + "updated_at": "2021-03-02T08:59:16.069Z" + }, + "capacity": 8, + "charging_mode": 0, + "created_at": "2021-03-01T12:43:05.245Z", + "description": "", + "domain_name": "", + "engine": "Redis", + "engine_version": "3.0", + "error_code": null, + "id": "12345678-20fb-441b-a0cd-46369a9f7db0", + "internal_version": null, + "ip": "192.168.10.177", + "location": { + "cloud": "otc", + "project": { + "domain_id": null, + "domain_name": null, + "id": "12345678a13b49529d2e2c3646691288", + "name": "eu-de" + }, + "region_name": "eu-de", + "zone": null + }, + "lock_time": null, + "lock_time_left": null, + "maintain_begin": "22:00:00", + "maintain_end": "02:00:00", + "max_memory": 6554, + "message": null, + "name": "test_dcs", + "order_id": null, + "password": null, + "port": 6379, + "product_id": "OTC_DCS_MS", + "resource_spec_code": "dcs.master_standby", + "result": null, + "retry_times_left": null, + "security_group_id": "12345678-b782-4aff-8311-19896597fd4e", + "security_group_name": "sg-test", + "status": "RUNNING", + "subnet_cidr": "192.168.10.0/24", + "subnet_id": "12345678-ca80-4b49-bbbb-85ea9b96f8b3", + "subnet_name": "subnet-sebastian", + "used_memory": 4, + "user_id": "1234567890bb4c6f81bc358d54693962", + "user_name": "sgode", + "vpc_id": "12345678-dc40-4e3a-95b1-5a0756441e12", + "vpc_name": "vpc-test" + } + } +''' + +EXAMPLES = ''' +# Create DCS Instance +- opentelekomcloud.cloud.dcs_instance: + name: "dcs_test" + password: "Thatson3v3rysecureP4ssw0rd" + capacity: "4" + vpc_id: 12345678-dc40-4e3a-95b1-5a0756441e12 + security_group_id: 12345678-b782-4aff-8311-19896597fd4e + subnet_id: 12345678-ca80-4b49-bbbb-85ea9b96f8b3 + available_zones: + - bf84aba586ce4e948da0b97d9a7d62fb + product_id: OTC_DCS_MS + instance_backup_policy: + save_days: "2" + backup_type: "manual" + periodical_backup_plan: + begin_at: "00:00-01:00" + period_type: "weekly" + backup_at: + - 1 + - 2 + - 3 + - 4 + maintain_begin: "22:00:00" + maintain_end: "02:00:00" + +# Modify DCS Instance +- opentelekomcloud.cloud.dcs_instance: + name: "dcs_test_2" + id: 12345678-dc40-4e3a-95b1-5a0756441e12 + description: "Test-description" + instance_backup_policy: + save_days: "2" + backup_type: "manual" + periodical_backup_plan: + begin_at: "00:00-01:00" + period_type: "weekly" + backup_at: + - 1 + - 2 + - 3 + maintain_begin: "23:00:00" + maintain_end: "03:00:00" + +# Delete DCS Instance +- opentelekomcloud.cloud.dcs_instance: + name: "dcs_test_2" + state: absent +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceModule(OTCModule): + argument_spec = dict( + name=dict(required=False), + id=dict(required=False), + description=dict(required=False), + engine=dict(required=False, default='Redis'), + engine_version=dict(required=False, default='3.0'), + capacity=dict(required=False, type='int'), + password=dict(required=False), + vpc_id=dict(required=False), + security_group_id=dict(required=False), + subnet_id=dict(required=False), + available_zones=dict(required=False, type='list', elements='str'), + product_id=dict(required=False), + instance_backup_policy=dict(required=False, type='dict'), + maintain_begin=dict(required=False), + maintain_end=dict(required=False), + redis_config=dict(required=False, type='dict'), + restart_instance=dict(required=False, type='bool', default='false'), + state=dict(type='str', choices=['present', 'absent'], default='present') + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + changed = False + + if self.params['id']: + nameid = self.params['id'] + elif self.params['name']: + nameid = self.params['name'] + else: + self.exit( + changed=False, + message=('No Name or ID provided, but required!'), + failed=True + ) + + instance = self.conn.dcs.find_instance( + name_or_id=nameid, + ignore_missing=True + ) + + # Instance Deletion + if self.params['state'] == 'absent': + if instance: + if self.ansible.check_mode: + self.exit(changed=True) + dcs_instance = self.conn.dcs.delete_instance(instance.id) + self.exit(changed=True, dcs_instance=dcs_instance) + + if self.params['state'] == 'present': + attrs = {} + # Modifying an Instance + if instance: + if not self.params['capacity']: + capacity_var = -1 # This is done to prevent an error if no capacity has been given + else: + capacity_var = self.params['capacity'] + if self.params['restart_instance'] is True: + if self.ansible.check_mode: + self.exit(changed=True) + dcs_instance = self.conn.dcs.restart_instance(instance.id) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + # elif self.params['redis_config']: + # attrs['redis_config'] = { + # 'param_id': self.params['redis_config']['param_id'], + # 'param_name': self.params['redis_config']['param_name'], + # 'param_value': self.params['redis_config']['param_value'] + # } + # if self.ansible.check_mode: + # self.exit(changed=True) + # dcs_instance = self.conn.dcs.update_instance_params(instance.id, **attrs) + # self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + # Scaling up + elif instance.to_dict()['capacity'] < capacity_var and capacity_var != -1: + if self.ansible.check_mode: + self.exit(changed=True) + dcs_instance = self.conn.dcs.extend_instance(instance.id, self.params['capacity']) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict(), message='Scaling instance up, ignoring other params') + elif instance.to_dict()['capacity'] > capacity_var and capacity_var != -1: + self.exit( + changed=False, + message=('''When extending an DCS Instance the capacity needs to be larger! + The Instance has a capacity of %s and new provided capacity was %s''' + % (instance.to_dict()['capacity'], self.params['capacity'])), + failed=True + ) + # Changing other params + elif instance.to_dict()['capacity'] == capacity_var or capacity_var == -1: + changed = False + if instance.to_dict()['name'] != self.params['name']: + changed = True + attrs['name'] = self.params['name'] + if instance.to_dict()['description'] != self.params['description']: + changed = True + attrs['description'] = self.params['description'] + if instance.to_dict()['security_group_id'] != self.params['security_group_id']: + changed = True + attrs['security_group_id'] = self.params['security_group_id'] + if (instance.to_dict()['maintain_begin'] != self.params['maintain_begin']) and \ + (instance.to_dict()['maintain_end'] != self.params['maintain_end']): + changed = True + attrs['maintain_begin'] = self.params['maintain_begin'] + attrs['maintain_end'] = self.params['maintain_end'] + elif (instance.to_dict()['maintain_begin'] != self.params['maintain_begin']) or \ + (instance.to_dict()['maintain_end'] != self.params['maintain_end']): + self.exit( + changed=False, + message=('''You need to specify maintain_begin and maintain_end when trying to change one of them'''), + failed=True + ) + # The Query API currently will always give a NULL response for backup_policy so this will always result in "changed" + # There's no way to go around this + if instance.to_dict()['backup_policy'] != self.params['instance_backup_policy']: + changed = True + instance_backup_policy_var = self.params['instance_backup_policy'] + # In case the user didn't specify timezone_offset or something we need to give the API an null type so it won't throw an error + try: + instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] = \ + (instance_backup_policy_var['periodical_backup_plan']['timezone_offset']) + except Exception: + instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] = None + try: + instance_backup_policy_var['save_days'] = (instance_backup_policy_var['save_days']) + except Exception: + instance_backup_policy_var['save_days'] = None + try: + instance_backup_policy_var['backup_type'] = (instance_backup_policy_var['backup_type']) + except Exception: + instance_backup_policy_var['backup_type'] = None + + attrs['instance_backup_policy'] = { + "save_days": instance_backup_policy_var['save_days'], + "backup_type": instance_backup_policy_var['backup_type'], + "periodical_backup_plan": { + "begin_at": instance_backup_policy_var['periodical_backup_plan']['begin_at'], + "period_type": instance_backup_policy_var['periodical_backup_plan']['period_type'], + "backup_at": instance_backup_policy_var['periodical_backup_plan']['backup_at'], + "timezone_offset": instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] + } + } + + if self.ansible.check_mode: + self.exit(changed=True) + dcs_instance = self.conn.dcs.update_instance(instance.id, **attrs) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + + # Creating a new Instance + if not instance: + if not self.params['name']: + self.exit( + changed=False, + message=('No name param provided, but required!'), + failed=True + ) + if self.params['engine']: + engine_var = self.params['engine'] + else: + self.exit( + changed=False, + message=('No engine param provided, but required!'), + failed=True + ) + if self.params['engine_version']: + engine_version_var = self.params['engine_version'] + else: + self.exit( + changed=False, + message=('No engine_version param provided, but required!'), + failed=True + ) + if self.params['capacity']: + capacity_var = self.params['capacity'] + else: + self.exit( + changed=False, + message=('No capacity param provided, but required!'), + failed=True + ) + if self.params['password']: + password_var = self.params['password'] + else: + self.exit( + changed=False, + message=('No password param provided, but required!'), + failed=True + ) + if self.params['vpc_id']: + vpc_id_var = self.params['vpc_id'] + else: + self.exit( + changed=False, + message=('No vpc_id param provided, but required!'), + failed=True + ) + if self.params['security_group_id']: + security_group_id_var = self.params['security_group_id'] + else: + self.exit( + changed=False, + message=('No security_group_id param provided, but required!'), + failed=True + ) + if self.params['subnet_id']: + subnet_id_var = self.params['subnet_id'] + else: + self.exit( + changed=False, + message=('No subnet_id param provided, but required!'), + failed=True + ) + if self.params['available_zones']: + available_zones_var = self.params['available_zones'] + else: + self.exit( + changed=False, + message=('No available_zones param provided, but required!'), + failed=True + ) + if self.params['product_id']: + product_id_var = self.params['product_id'] + else: + self.exit( + changed=False, + message=('No product_id param provided, but required!'), + failed=True + ) + if self.params['maintain_begin'] and self.params['maintain_end']: + maintain_begin_var = self.params['maintain_begin'] + maintain_end_var = self.params['maintain_end'] + else: + maintain_begin_var = None + maintain_end_var = None + + attrs = { + "name": self.params['name'], + "description": self.params['description'], + "engine": engine_var, + "engine_version": engine_version_var, + "capacity": capacity_var, + "password": password_var, + "vpc_id": vpc_id_var, + "security_group_id": security_group_id_var, + "subnet_id": subnet_id_var, + "available_zones": available_zones_var, + "product_id": product_id_var, + "maintain_begin": maintain_begin_var, + "maintain_end": maintain_end_var, + } + if self.params['instance_backup_policy']: + instance_backup_policy_var = self.params['instance_backup_policy'] + # In case the user didn't specify timezone_offset or something we need to give the API an null type so it won't throw an error + try: + instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] = \ + (instance_backup_policy_var['periodical_backup_plan']['timezone_offset']) + except Exception: + instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] = None + try: + instance_backup_policy_var['save_days'] = (instance_backup_policy_var['save_days']) + except Exception: + instance_backup_policy_var['save_days'] = None + try: + instance_backup_policy_var['backup_type'] = (instance_backup_policy_var['backup_type']) + except Exception: + instance_backup_policy_var['backup_type'] = None + + attrs['instance_backup_policy'] = { + "save_days": instance_backup_policy_var['save_days'], + "backup_type": instance_backup_policy_var['backup_type'], + "periodical_backup_plan": { + "begin_at": instance_backup_policy_var['periodical_backup_plan']['begin_at'], + "period_type": instance_backup_policy_var['periodical_backup_plan']['period_type'], + "backup_at": instance_backup_policy_var['periodical_backup_plan']['backup_at'], + "timezone_offset": instance_backup_policy_var['periodical_backup_plan']['timezone_offset'] + } + } + if self.ansible.check_mode: + self.exit(changed=True) + dcs_instance = self.conn.dcs.create_instance(**attrs) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + + self.exit( + changed=changed + ) + + +def main(): + module = DcsInstanceModule() + module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/dcs_instance_backup.py b/plugins/modules/dcs_instance_backup.py new file mode 100644 index 00000000..63b73e84 --- /dev/null +++ b/plugins/modules/dcs_instance_backup.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_backup +short_description: Manage DCS Instance-Backups on Open Telekom Cloud +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Manage DCS Instance-Backups on Open Telekom Cloud +options: + instance: + description: + - Specifies the name or ID of the instance + type: str + required: true + description: + description: + - Specifies the description of the backup + type: str + backup_id: + description: + - Specifies the backup ID which is required for restoring or deletion + type: str + state: + choices: [present, absent] + default: present + description: Instance state + type: str +requirements: ["openstacksdk", "otcextensions"] +''' + +RETURN = ''' +dcs_instance: + description: Dictionary of DCS instance + returned: changed + type: dict + sample: { + "dcs_instance": { + "created_at": null, + "description": null, + "error_code": null, + "id": "12345678-4b3a-4dfa-9f13-33c732a8f497", + "is_restorable": null, + "location": { + "cloud": "otc", + "project": { + "domain_id": null, + "domain_name": null, + "id": "123456768a13b49529d2e2c3646691288", + "name": "eu-de" + }, + "region_name": "eu-de", + "zone": null + }, + "name": null, + "period": null, + "progress": null, + "size": null, + "status": null, + "type": null, + "updated_at": null + } + } +''' + +EXAMPLES = ''' +# Create a Backup +- opentelekomcloud.cloud.dcs_instance_backup: + instance: 12345678-20fb-441b-a0cd-46369a9f7db0 + description: "This is a test" + +# Restore a backup +- opentelekomcloud.cloud.dcs_instance_backup: + instance: 12345678-20fb-441b-a0cd-46369a9f7db0 + backup_id: 12345678-f021-417f-b019-dc02182926a9 + +# Delete a backup +- opentelekomcloud.cloud.dcs_instance_backup: + instance: 12345678-20fb-441b-a0cd-46369a9f7db0 + backup_id: 12345678-f021-417f-b019-dc02182926a9 + state: absent +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceModule(OTCModule): + argument_spec = dict( + instance=dict(required=True), + description=dict(required=False), + backup_id=dict(required=False), + state=dict(type='str', choices=['present', 'absent'], default='present') + ) + module_kwargs = dict( + supports_check_mode=True, + required_if=[ + ('state', 'absent', + ['backup_id']) + ] + ) + + def run(self): + changed = False + attrs = {} + + instance = self.conn.dcs.find_instance( + name_or_id=self.params['instance'], + ignore_missing=True + ) + if instance: + if self.params['state'] == 'present': + if self.params['description']: + attrs['description'] = self.params['description'] + # Restore Backup + if self.params['backup_id']: + attrs['backup_id'] = self.params['backup_id'] + if not self.ansible.check_mode: + dcs_instance = self.conn.dcs.restore_instance(instance.id, **attrs) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + self.exit_json(True) + + # Create Backup + else: + if not self.ansible.check_mode: + dcs_instance = self.conn.dcs.backup_instance(instance.id, **attrs) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + self.exit_json(True) + + elif self.params['state'] == 'absent': + if not self.ansible.check_mode: + dcs_instance = self.conn.dcs.delete_instance_backup(self.params['backup_id'], instance.id) + self.exit(changed=True, dcs_instance=dcs_instance) + self.exit_json(True) + + else: + self.exit( + changed=False, + message=('No Instance with name or id %s found!', self.params['instance']), + failed=True + ) + + self.exit( + changed=changed + ) + + +def main(): + module = DcsInstanceModule() + module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/dcs_instance_backup_info.py b/plugins/modules/dcs_instance_backup_info.py new file mode 100644 index 00000000..aa156598 --- /dev/null +++ b/plugins/modules/dcs_instance_backup_info.py @@ -0,0 +1,91 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_backup_info +short_description: Get Instance backup informations +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Get Instance backup informations +requirements: ["openstacksdk", "otcextensions"] +options: + id: + description: + - Instance ID or name of the chosen DCS Instance + type: str + required: true +''' + +RETURN = ''' +instances: + description: Dictionary of Metrics + returned: changed + type: list + sample: [ + { + "created_at": "2021-03-02T13:23:42.968Z", + "description": null, + "error_code": null, + "id": "c417bd7d-f021-417f-b019-dc02182926a9", + "name": "backup_20210302142342", + "period": null, + "progress": "100.00", + "size": 124, + "status": "succeed", + "type": "manual", + "updated_at": "2021-03-02T13:25:30.723Z" + } + ] +''' + +EXAMPLES = ''' +# Query Params +- opentelekomcloud.cloud.dcs_instance_backup_info: + id: 12345678-20fb-441b-a0cd-46369a9f7db0 +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceBackupInfoModule(OTCModule): + argument_spec = dict( + id=dict(required=True), + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + data = [] + + for raw in self.conn.dcs.backups(self.params['id']): + dt = raw.to_dict() + dt.pop('location') + dt.pop('is_restorable') + data = dt + + self.exit( + changed=False, + instances=data + ) + + +def main(): + module = DcsInstanceBackupInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dcs_instance_info.py b/plugins/modules/dcs_instance_info.py new file mode 100644 index 00000000..cff6deb8 --- /dev/null +++ b/plugins/modules/dcs_instance_info.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_info +short_description: Get Instance Informations +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Get Instance Informations +requirements: ["openstacksdk", "otcextensions"] +''' + +RETURN = ''' +instances: + description: Dictionary of Metrics + returned: changed + type: list + sample: [ + { + "available_zones": null, + "backup_policy": null, + "capacity": 2, + "charging_mode": 0, + "created_at": "2021-02-26T08:21:49.137Z", + "description": null, + "domain_name": null, + "engine": "Redis", + "engine_version": "3.0", + "error_code": null, + "id": "6543212f-6bd4-45df-9c4e-fadb35d6e0d0", + "internal_version": null, + "ip": "192.168.10.12", + "lock_time": null, + "lock_time_left": null, + "maintain_begin": "02:00:00", + "maintain_end": "06:00:00", + "max_memory": 1536, + "message": null, + "name": "dcs-qrt6", + "order_id": null, + "password": null, + "port": 6379, + "product_id": null, + "resource_spec_code": "dcs.master_standby", + "result": null, + "retry_times_left": null, + "security_group_id": "6543212f-b782-4aff-8311-19896597fd4e", + "security_group_name": null, + "status": "RUNNING", + "subnet_cidr": null, + "subnet_id": null, + "subnet_name": null, + "used_memory": 4, + "user_id": "1234567890bb4c6f81bc358d54693962", + "user_name": "user", + "vpc_id": "12345678-dc40-4e3a-95b1-5a0756441e12", + "vpc_name": null + } + ] +''' + +EXAMPLES = ''' +# Query Instance Informations from DCS Instance with ID +- opentelekomcloud.cloud.dcs_instance_info: + instance_id: 6543212f-6bd4-45df-9c4e-fadb35d6e0d0 + +# Query all existing Instances +- opentelekomcloud.cloud.dcs_instance_info: +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceInfoModule(OTCModule): + argument_spec = dict( + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + + data = [] + query = {} + + for raw in self.conn.dcs.instances(**query): + dt = raw.to_dict() + dt.pop('location') + data.append(dt) + + self.exit( + changed=False, + instances=data + ) + + +def main(): + module = DcsInstanceInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dcs_instance_params_info.py b/plugins/modules/dcs_instance_params_info.py new file mode 100644 index 00000000..7116c58f --- /dev/null +++ b/plugins/modules/dcs_instance_params_info.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_params_info +short_description: Get Instance Params +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Get Instance Statistics +requirements: ["openstacksdk", "otcextensions"] +options: + instance: + description: + - Instance ID or name of the chosen DCS Instance + type: str + required: true +''' + +RETURN = ''' +instances: + description: Dictionary of Metrics + returned: changed + type: list + sample: [ + { + "status": "RUNNING", + "instance_id": "c08fdc6e-5c25-4185-ab57-c0a5529b727f", + "redis_config": [ + { + "description": "How Redis will select what to remove when maxmemory is reached, You can select among five behaviors...", + "param_id": 2, + "param_name": "maxmemory-policy", + "param_value": "noeviction", + "default_value": "noeviction", + "value_type": "Enum", + "value_range": "volatile-lru,allkeys-lru,volatile-random,allkeys-random,volatile-ttl,noeviction" + }, + { + "description": "Hashes are encoded using a memory efficient data structure when they have a small number of entries", + "param_id": 3, + "param_name": "hash-max-ziplist-entries", + "param_value": "512", + "default_value": "512", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Hashes are encoded using a memory efficient data structure when the biggest entry does not exceed a given threshold", + "param_id": 4, + "param_name": "hash-max-ziplist-value", + "param_value": "64", + "default_value": "64", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Lists are encoded using a memory efficient data structure when they have a small number of entries", + "param_id": 5, + "param_name": "list-max-ziplist-entries", + "param_value": "512", + "default_value": "512", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Lists are encoded using a memory efficient data structure when the biggest entry does not exceed a given threshold", + "param_id": 6, + "param_name": "list-max-ziplist-value", + "param_value": "64", + "default_value": "64", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "When a set is composed of just strings that happen to be integers in radix 10 in the range of 64 bit signed integers.", + "param_id": 7, + "param_name": "set-max-intset-entries", + "param_value": "512", + "default_value": "512", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Sorted sets are encoded using a memory efficient data structure when they have a small number of entries", + "param_id": 8, + "param_name": "zset-max-ziplist-entries", + "param_value": "128", + "default_value": "128", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Sorted sets are encoded using a memory efficient data structure when the biggest entry does not exceed a given threshold", + "param_id": 9, + "param_name": "zset-max-ziplist-value", + "param_value": "64", + "default_value": "64", + "value_type": "Interger", + "value_range": "1-10000" + }, + { + "description": "Close the connection after a client is idle for N seconds (0 to disable)", + "param_id": 1, + "param_name": "timeout", + "param_value": "0", + "default_value": "0", + "value_type": "Interger", + "value_range": "0-7200" + }, + { + "description": "Only events that run in more time than the configured latency-monitor-threshold will be logged as latency spikes...", + "param_id": 10, + "param_name": "latency-monitor-threshold", + "param_value": "0", + "default_value": "0", + "value_type": "Interger", + "value_range": "0-86400000" + }, + { + "description": "The total memory, in bytes, reserved for non-data usage.", + "param_id": 12, + "param_name": "reserved-memory", + "param_value": "0", + "default_value": "0", + "value_type": "Interger", + "value_range": "0-6553" + }, + { + "description": "Redis can notify Pub or Sub clients about events happening in the key space", + "param_id": 13, + "param_name": "notify-keyspace-events", + "param_value": null, + "default_value": null, + "value_type": "regular", + "value_range": "([KE]+([A]|[g$lshzxe]+)){0,11}" + } + ], + "config_status": "SUCCESS", + "config_time": "" + } + ] +''' + +EXAMPLES = ''' +# Query Params +- opentelekomcloud.cloud.dcs_instance_params_info: + id: 12345678-20fb-441b-a0cd-46369a9f7db0 +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceParamsInfoModule(OTCModule): + argument_spec = dict( + instance=dict(required=True) + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + data = [] + instance = self.conn.dcs.find_instance( + name_or_id=self.params['instance'], + ignore_missing=True + ) + + if instance: + for raw in self.conn.dcs.instance_params(instance.id): + dt = raw.to_dict() + dt.pop('location') + data.append(dt) + + self.exit( + changed=False, + instances=data + ) + + else: + self.exit( + changed=False, + message=('No Instance with name or id %s found!', self.params['instance']), + failed=True + ) + + +def main(): + module = DcsInstanceParamsInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dcs_instance_password.py b/plugins/modules/dcs_instance_password.py new file mode 100644 index 00000000..6db82d47 --- /dev/null +++ b/plugins/modules/dcs_instance_password.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_password +short_description: Manage DCS Instance Passwords on Open Telekom Cloud +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Manage DCS Instance Passwords on Open Telekom Cloud +options: + new_password: + description: + - New password of the instance + type: str + required: true + old_password: + description: + - Old Password of the instance + type: str + required: true + instance: + description: + - ID or name of the instance + type: str + required: true +requirements: ["openstacksdk", "otcextensions"] +''' + +RETURN = ''' +dcs_instance: + description: Dictionary of DCS instance + returned: changed + type: dict + sample: { + + } +''' + +EXAMPLES = ''' +# Modify Password +- opentelekomcloud.cloud.dcs_instance: + id: dcs_test_name + old_password: "This1st0t4llys4f3!" + new_password: "Th!7173v3NS4f3r!s1t" + +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstancePasswordModule(OTCModule): + argument_spec = dict( + instance=dict(required=True), + old_password=dict(required=True, no_log=True), + new_password=dict(required=True, no_log=True) + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + instance = self.conn.dcs.find_instance( + name_or_id=self.params['instance'], + ignore_missing=True + ) + if instance: + if not self.ansible.check_mode: + dcs_instance = self.conn.dcs.change_instance_password(instance.id, self.params['old_password'], self.params['new_password']) + self.exit(changed=True, dcs_instance=dcs_instance.to_dict()) + self.exit(True) + else: + self.exit( + changed=False, + message=('No Instance with name or id %s found!', self.params['id']), + failed=True + ) + + +def main(): + module = DcsInstancePasswordModule() + module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/dcs_instance_restore_info.py b/plugins/modules/dcs_instance_restore_info.py new file mode 100644 index 00000000..5de22938 --- /dev/null +++ b/plugins/modules/dcs_instance_restore_info.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_restore_info +short_description: Get Instance Restore infos +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Get Instance Restore infos +requirements: ["openstacksdk", "otcextensions"] +options: + instance: + description: + - Instance ID of the chosen DCS Instance + type: str + required: true + beginTime: + description: + - Beginning Time of the query search + type: str + required: false + endTime: + description: + - End Time of the query search + type: str + required: false +''' + +RETURN = ''' +instances: + description: Dictionary of Metrics + returned: changed + type: list + sample: [ + { + "backup_description": null, + "backup_id": "12345678-f021-417f-b019-dc02182926a9", + "backup_name": "backup_20210302142342", + "created_at": "2021-03-02T13:57:56.194Z", + "description": null, + "error_code": null, + "id": "12345678-05c3-42c9-ac09-c2da452372cc", + "name": null, + "progress": "100.00", + "restore_description": null, + "restore_name": "restore_20210302145756", + "status": "succeed", + "updated_at": "2021-03-02T13:58:20.382Z" + } + + ] +''' + +EXAMPLES = ''' +# Query Params +- opentelekomcloud.cloud.dcs_instance_restore_info: + id: 12345678-20fb-441b-a0cd-46369a9f7db0 +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceRestoreInfoModule(OTCModule): + argument_spec = dict( + instance=dict(required=True), + beginTime=dict(required=False), + endTime=dict(required=False) + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + data = [] + query = {} + + instance = self.conn.dcs.find_instance( + name_or_id=self.params['instance'], + ignore_missing=True + ) + + if instance: + query['instance'] = instance.id + if self.params['beginTime']: + query['beginTime'] = self.params['beginTime'] + if self.params['endTime']: + query['endTime'] = self.params['endTime'] + + for raw in self.conn.dcs.restore_records(**query): + dt = raw.to_dict() + dt.pop('location') + data.append(dt) + + self.exit( + changed=False, + instances=data + ) + + else: + self.exit( + changed=False, + message=('No Instance with name or id %s found!', self.params['instance']), + failed=True + ) + + +def main(): + module = DcsInstanceRestoreInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dcs_instance_statistics_info.py b/plugins/modules/dcs_instance_statistics_info.py new file mode 100644 index 00000000..da40c9e3 --- /dev/null +++ b/plugins/modules/dcs_instance_statistics_info.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +module: dcs_instance_statistics_info +short_description: Get Instance Statistics +extends_documentation_fragment: opentelekomcloud.cloud.otc +version_added: "0.8.2" +author: "Sebastian Gode (@SebastianGode)" +description: + - Get Instance Statistics +options: + instance_id: + description: + - Instance ID to filter results for that instance + type: str + required: false +requirements: ["openstacksdk", "otcextensions"] +''' + +RETURN = ''' +instances: + description: Dictionary of Metrics + returned: changed + type: list + sample: [ + "instances": [ + { + "cmd_get_count": 0, + "cmd_set_count": 0, + "id": "12345678-20fb-441b-a0cd-46369a9f7db0", + "input_kbps": "0.06", + "instance_id": "12345678-20fb-441b-a0cd-46369a9f7db0", + "keys": 0, + "max_memory": 6554, + "name": null, + "output_kbps": "1.44", + "used_cpu": "0.0", + "used_memory": 4 + } + ] + ] +''' + +EXAMPLES = ''' +# Query statistics about all instances +- opentelekomcloud.cloud.dcs_instance_statistics_info: + +# Query statistics about a specific instance +- opentelekomcloud.cloud.dcs_instance_statistics_info: + instance_id: "12345678-b17b-434d-b6a1-4cc5abb1f2ca" +''' + +from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModule + + +class DcsInstanceStatisticsInfoModule(OTCModule): + argument_spec = dict( + instance_id=dict(required=False), + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def run(self): + final_data = [] + + for raw in self.conn.dcs.statistics(): + dt = raw.to_dict() + dt.pop('location') + if self.params['instance_id']: + if raw.instance_id == self.params['instance_id']: + final_data = dt + break + else: + final_data.append(dt) + + self.exit( + changed=False, + instances=final_data + ) + + +def main(): + module = DcsInstanceStatisticsInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/dcs/aliases b/tests/integration/targets/dcs/aliases new file mode 100644 index 00000000..7a68b11d --- /dev/null +++ b/tests/integration/targets/dcs/aliases @@ -0,0 +1 @@ +disabled diff --git a/tests/integration/targets/dcs/tasks/main.yaml b/tests/integration/targets/dcs/tasks/main.yaml new file mode 100644 index 00000000..0fecf7d3 --- /dev/null +++ b/tests/integration/targets/dcs/tasks/main.yaml @@ -0,0 +1,164 @@ +--- +- name: Doing Integration test + block: + - name: Set random prefix + set_fact: + prefix: "{{ (99999999 | random | to_uuid | hash('md5')) }}" + + - name: Set facts + set_fact: + network_name: "{{ prefix + 'test-dcs-network' }}" + subnet_name: "{{ prefix + 'test-dcs-subnet' }}" + router_name: "{{ prefix + 'test-dcs-router' }}" + sg_name: "{{ prefix + 'test-dcs-sg' }}" + dcs_name: "{{ ( 'dcs' + prefix + '-dcs-name' ) }}" + dcs_password: "ThisIsas4f3p4a77w0rd" + dcs_password_new: "ThisIsnotans4f3password" + dcs_description: "{{ ( prefix + '-dcs-description' ) }}" + dcs_capacity: 16 + dcs_available_zones: + - bf84aba586ce4e948da0b97d9a7d62fb + dcs_ibp_save_days: 2 + dcs_ibp_backup_type: manual + dcs_ibp_pbp_begin_at: "00:00-01:00" + dcs_ibp_pbp_period_type: weekly + dcs_ibp_pbp_backup_at: + - 1 + - 2 + - 3 + - 4 + - name: Create network for DCS + openstack.cloud.network: + name: "{{ network_name }}" + state: present + register: dcs_net + + - name: Create subnet for DCS + openstack.cloud.subnet: + name: "{{ subnet_name }}" + state: present + network_name: "{{ dcs_net.network.name }}" + cidr: "192.168.110.0/24" + dns_nameservers: "{{ ['100.125.4.25', '8.8.8.8'] }}" + register: dcs_subnet + + - name: Create Router for DCS + openstack.cloud.router: + name: "{{ router_name }}" + state: present + network: admin_external_net + enable_snat: false + interfaces: + - net: "{{ dcs_net.network.name }}" + subnet: "{{ dcs_subnet.subnet.name }}" + register: dcs_router + + - name: Create Security Group for DCS + openstack.cloud.security_group: + name: "{{ sg_name }}" + register: dcs_sg + + - name: Creating DCS Instance + opentelekomcloud.cloud.dcs_instance: + name: "{{ dcs_name }}" + description: "{{ dcs_description }}" + password: "{{ dcs_password }}" + capacity: "{{ dcs_capacity }}" + vpc_id: "{{ dcs_router.id }}" + security_group_id: "{{ dcs_sg.id }}" + subnet_id: "{{ dcs_subnet.subnet.network_id }}" + available_zones: "{{ dcs_available_zones }}" + product_id: OTC_DCS_MS + instance_backup_policy: + backup_type: "{{ dcs_ibp_backup_type }}" + periodical_backup_plan: + begin_at: "{{ dcs_ibp_pbp_begin_at }}" + period_type: "{{ dcs_ibp_pbp_period_type }}" + backup_at: "{{ dcs_ibp_pbp_backup_at }}" + maintain_begin: "22:00:00" + maintain_end: "02:00:00" + register: dcs_ins + check_mode: false + + - name: Getting Instance Informations + opentelekomcloud.cloud.dcs_instance_info: + + - name: Getting Statistics + opentelekomcloud.cloud.dcs_instance_statistics_info: + + - name: Getting Params + opentelekomcloud.cloud.dcs_instance_params_info: + instance: "{{ dcs_ins.dcs_instance.id }}" + + - name: Getting Backups + opentelekomcloud.cloud.dcs_instance_backup_info: + id: "{{ dcs_ins.dcs_instance.id }}" + + - name: Getting Restore Data + opentelekomcloud.cloud.dcs_instance_restore_info: + id: "{{ dcs_ins.dcs_instance.id }}" + + - name: Updating DCS Instance params + opentelekomcloud.cloud.dcs_instance: + name: "{{ dcs_name }}" + redis_config: + param_id: "1" + param_name: "timeout" + param_value: "100" + register: dcs_ins_up_par + check_mode: false + + - name: Restart Instance + opentelekomcloud.cloud.dcs_instance: + name: "{{ dcs_name }}" + restart_instance: true + register: dcs_ins_up_par + check_mode: true + + - name: Change Password + opentelekomcloud.cloud.dcs_instance_password: + id: "{{ dcs_name }}" + old_password: "{{ dcs_password }}" + new_password: "{{ dcs_password_new}}" + register: dcs_ins_up_par + check_mode: false + + - name: Backup Instance + opentelekomcloud.cloud.dcs_instance_backup: + instance: "{{ dcs_ins.dcs_instance.id }}" + register: dcs_ins_back + check_mode: false + + always: + - block: + - name: Drop DCS Instance + opentelekomcloud.cloud.dcs_instance: + name: "{{ dcs_name }}" + state: absent + register: dcs_ins_dr + check_mode: false + + - name: Delete Security Group + openstack.cloud.security_group: + name: "{{ sg_name }}" + state: absent + register: dcs_sg + + - name: Drop existing Router + openstack.cloud.router: + name: "{{ router_name }}" + state: absent + register: dcs_router_dr + + - name: Drop existing subnet + openstack.cloud.subnet: + name: "{{ subnet_name }}" + state: absent + register: dcs_subnet_dr + + - name: Drop existing network + openstack.cloud.network: + name: "{{ network_name }}" + state: absent + register: dcs_net_dr + ignore_errors: yes \ No newline at end of file diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 6d8a3418..e38cd2d7 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -25,6 +25,14 @@ plugins/modules/ces_metrics_info.py validate-modules:missing-gplv3-license plugins/modules/ces_quotas_info.py validate-modules:missing-gplv3-license plugins/modules/cce_cluster_node_info.py validate-modules:missing-gplv3-license plugins/modules/cce_cluster_node.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_backup_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_backup.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_params_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_password.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_restore_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_statistics_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance.py validate-modules:missing-gplv3-license plugins/modules/css_cluster.py validate-modules:missing-gplv3-license plugins/modules/css_cluster_info.py validate-modules:missing-gplv3-license plugins/modules/css_snapshot_info.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index fcdb0ea4..51a40199 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -25,6 +25,15 @@ plugins/modules/ces_metric_data_info.py validate-modules:missing-gplv3-license plugins/modules/ces_metrics_info.py validate-modules:missing-gplv3-license plugins/modules/ces_quotas_info.py validate-modules:missing-gplv3-license plugins/modules/cce_cluster_node_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_backup_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_backup.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_maintain_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_params_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_password.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_restore_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance_statistics_info.py validate-modules:missing-gplv3-license +plugins/modules/dcs_instance.py validate-modules:missing-gplv3-license plugins/modules/css_cluster.py validate-modules:missing-gplv3-license plugins/modules/css_cluster_info.py validate-modules:missing-gplv3-license plugins/modules/css_snapshot.py validate-modules:missing-gplv3-license