From ec8b8cc59b473dee65f7dcfddcfb031011f34d8d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Juan=20Miguel=20Olmo=20Mart=C3=ADnez?= Date: Fri, 18 Jan 2019 11:07:21 +0100 Subject: [PATCH] mgr/ansible: Replace Inventory Ansible playbook MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit A new Ansible playbook allows now to retrieve the storage devices information produced by ceph-volume. Signed-off-by: Juan Miguel Olmo Martínez --- src/pybind/mgr/ansible/module.py | 67 ++++++++++++----------- src/pybind/mgr/orchestrator.py | 29 +++++++++- src/pybind/mgr/orchestrator_cli/module.py | 33 ++++------- 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/pybind/mgr/ansible/module.py b/src/pybind/mgr/ansible/module.py index d22f1bb6c63..76cd0b2bd35 100644 --- a/src/pybind/mgr/ansible/module.py +++ b/src/pybind/mgr/ansible/module.py @@ -21,7 +21,7 @@ WAIT_PERIOD = 10 # Name of the playbook used in the "get_inventory" method. # This playbook is expected to provide a list of storage devices in the host # where the playbook is executed. -GET_STORAGE_DEVICES_CATALOG_PLAYBOOK = "host-disks.yml" +GET_STORAGE_DEVICES_CATALOG_PLAYBOOK = "storage-inventory.yml" # Used in the create_osd method ADD_OSD_PLAYBOOK = "add-osd.yml" @@ -119,19 +119,18 @@ class AnsibleReadOperation(orchestrator.ReadCompletion): processed_result = [] - raw_result = self.pb_execution.get_result(self.event_filter) - if self.process_output: - processed_result = self.process_output( - raw_result, - self.ar_client, - self.pb_execution.play_uuid) - else: - processed_result = raw_result + if self._is_complete: + raw_result = self.pb_execution.get_result(self.event_filter) - #Clean objects to avoid problems between interpreters - self.pb_execution = None - self.ar_client = None + if self.process_output: + processed_result = self.process_output( + raw_result, + self.ar_client, + self.pb_execution.play_uuid, + self.log) + else: + processed_result = raw_result self._result = processed_result @@ -196,6 +195,7 @@ class AnsibleChangeOperation(orchestrator.WriteCompletion): class Module(MgrModule, orchestrator.Orchestrator): """An Orchestrator that uses to perform operations """ + MODULE_OPTIONS = [ {'name': 'server_url'}, {'name': 'username'}, @@ -278,7 +278,7 @@ class Module(MgrModule, orchestrator.Orchestrator): ansible_operation = AnsibleReadOperation(client = self.ar_client, playbook = GET_STORAGE_DEVICES_CATALOG_PLAYBOOK, logger = self.log, - result_pattern = "RESULTS", + result_pattern = "list storage inventory", params = {}) # Assign the process_output function @@ -390,7 +390,7 @@ class Module(MgrModule, orchestrator.Orchestrator): # Auxiliary functions #============================================================================== -def process_inventory_json(inventory_events, ar_client, playbook_uuid): +def process_inventory_json(inventory_events, ar_client, playbook_uuid, logger): """ Adapt the output of the playbook used in 'get_inventory' to the Orchestrator expected output (list of InventoryNode) @@ -399,10 +399,10 @@ def process_inventory_json(inventory_events, ar_client, playbook_uuid): Example: inventory_events = {'37-100564f1-9fed-48c2-bd62-4ae8636dfcdb': {'host': '192.168.121.254', - 'task': 'RESULTS', + 'task': 'list storage inventory', 'event': 'runner_on_ok'}, '36-2016b900-e38f-7dcd-a2e7-00000000000e': {'host': '192.168.121.252' - 'task': 'RESULTS', + 'task': 'list storage inventory', 'event': 'runner_on_ok'}} :param ar_client: Ansible Runner Service client :param playbook_uuid: Playbook identifier @@ -414,7 +414,8 @@ def process_inventory_json(inventory_events, ar_client, playbook_uuid): inventory_nodes = [] # Loop over the result events and request the event data - for event_key, data in inventory_events.items(): + for event_key, dummy_data in inventory_events.items(): + event_response = ar_client.http_get(EVENT_DATA_URL % (playbook_uuid, event_key)) @@ -422,21 +423,23 @@ def process_inventory_json(inventory_events, ar_client, playbook_uuid): if event_response: event_data = json.loads(event_response.text)["data"]["event_data"] - free_disks = event_data["res"]["disks_catalog"] - for item, data in free_disks.items(): - if item not in [host.name for host in inventory_nodes]: - - devs = [] - for dev_key, dev_data in data.items(): - if dev_key not in [device.id for device in devs]: - dev = orchestrator.InventoryDevice() - dev.id = dev_key - dev.type = 'hdd' if dev_data["rotational"] else "sdd/nvme" - dev.size = dev_data["sectorsize"] * dev_data["sectors"] - devs.append(dev) - - inventory_nodes.append( - orchestrator.InventoryNode(item, devs)) + host = event_data["host"] + devices = json.loads(event_data["res"]["stdout"]) + + devs = [] + for storage_device in devices: + dev = orchestrator.InventoryDevice() + dev.id = storage_device["path"] + dev.type = 'hdd' if storage_device["sys_api"]["rotational"] == "1" else 'sdd/nvme' + dev.size = storage_device["sys_api"]["human_readable_size"] + dev.rotates = storage_device["sys_api"]["rotational"] == "1" + dev.available = storage_device["available"] + dev.dev_id = "%s/%s" % (storage_device["sys_api"]["vendor"], + storage_device["sys_api"]["model"]) + + devs.append(dev) + + inventory_nodes.append(orchestrator.InventoryNode(host, devs)) return inventory_nodes diff --git a/src/pybind/mgr/orchestrator.py b/src/pybind/mgr/orchestrator.py index bfa71e66a1c..648a8941953 100644 --- a/src/pybind/mgr/orchestrator.py +++ b/src/pybind/mgr/orchestrator.py @@ -647,7 +647,10 @@ class InventoryDevice(object): self.type = None # 'ssd', 'hdd', 'nvme' self.id = None # unique within a node (or globally if you like). self.size = None # byte integer. - self.extended = None # arbitrary JSON-serializable object + self.rotates = False # indicates if it is a spinning disk + self.available = False # can be used to create a new OSD? + self.dev_id = None # vendor/model + self.extended = {} # arbitrary JSON-serializable object # If this drive is not empty, but is suitable for appending # additional journals, wals, or bluestore dbs, then report @@ -655,7 +658,29 @@ class InventoryDevice(object): self.metadata_space_free = None def to_json(self): - return dict(type=self.type, blank=self.blank, id=self.id, size=self.size, **self.extended) + return dict(type=self.type, blank=self.blank, id=self.id, + size=self.size, rotates=self.rotates, + available=self.available, dev_id=self.dev_id, + **self.extended) + + def pretty_print(self, only_header=False): + """Print a human friendly line with the information of the device + + :param only_header: Print only the name of the device attributes + + Ex: + > Device Path Type Size Rotates Available Model + > /dev/sdc hdd 50.00 GB True True ATA/QEMU + + """ + row_format = " {0:<15} {1:>10} {2:>10} {3:>10} {4:>10} {5:<15}\n" + if only_header: + return row_format.format("Device Path", "Type", "Size", "Rotates", + "Available", "Model") + else: + return row_format.format(self.id, self.type, self.size, + str(self.rotates), str(self.available), + self.dev_id) class InventoryNode(object): diff --git a/src/pybind/mgr/orchestrator_cli/module.py b/src/pybind/mgr/orchestrator_cli/module.py index 916b4d0fcd9..cf00786a88e 100644 --- a/src/pybind/mgr/orchestrator_cli/module.py +++ b/src/pybind/mgr/orchestrator_cli/module.py @@ -13,10 +13,11 @@ class OrchestratorCli(orchestrator.OrchestratorClientMixin, MgrModule): MODULE_OPTIONS = [ {'name': 'orchestrator'} ] + COMMANDS = [ { 'cmd': "orchestrator device ls " - "name=host,type=CephString,req=false" + "name=host,type=CephString,req=false " "name=format,type=CephChoices,strings=json|plain,req=false ", "desc": "List devices on a node", "perm": "r" @@ -141,23 +142,7 @@ class OrchestratorCli(orchestrator.OrchestratorClientMixin, MgrModule): def _list_devices(self, cmd): """ - This (all lines starting with ">") is how it is supposed to work. As of - now, it's not yet implemented: - > :returns: Either JSON: - > [ - > { - > "name": "sda", - > "host": "foo", - > ... lots of stuff from ceph-volume ... - > "stamp": when this state was refreshed - > }, - > ] - > - > or human readable: - > - > HOST DEV SIZE DEVID(vendor\\_model\\_serial) IN-USE TIMESTAMP - > - > Note: needs ceph-volume on the host. + Provide information about storage devices present in cluster hosts Note: this does not have to be completely synchronous. Slightly out of date hardware inventory is fine as long as hardware ultimately appears @@ -181,11 +166,17 @@ class OrchestratorCli(orchestrator.OrchestratorClientMixin, MgrModule): else: # Return a human readable version result = "" + for inventory_node in completion.result: - result += "{0}:\n".format(inventory_node.name) + result += "Host {0}:\n".format(inventory_node.name) + + if inventory_node.devices: + result += inventory_node.devices[0].pretty_print(only_header=True) + else: + result += "No storage devices found" + for d in inventory_node.devices: - result += " {0} ({1}, {2}b)\n".format( - d.id, d.type, d.size) + result += d.pretty_print() result += "\n" return HandleCommandResult(stdout=result) -- 2.39.5