summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/pmem.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/pmem.py')
-rw-r--r--ansible_collections/community/general/plugins/modules/pmem.py637
1 files changed, 637 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/pmem.py b/ansible_collections/community/general/plugins/modules/pmem.py
new file mode 100644
index 000000000..d7fcb8e01
--- /dev/null
+++ b/ansible_collections/community/general/plugins/modules/pmem.py
@@ -0,0 +1,637 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2022, Masayoshi Mizuma <msys.mizuma@gmail.com>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+author:
+ - Masayoshi Mizuma (@mizumm)
+module: pmem
+short_description: Configure Intel Optane Persistent Memory modules
+version_added: 4.5.0
+description:
+ - This module allows Configuring Intel Optane Persistent Memory modules
+ (PMem) using ipmctl and ndctl command line tools.
+requirements:
+ - ipmctl and ndctl command line tools
+ - xmltodict
+extends_documentation_fragment:
+ - community.general.attributes
+attributes:
+ check_mode:
+ support: none
+ diff_mode:
+ support: none
+options:
+ appdirect:
+ description:
+ - Percentage of the total capacity to use in AppDirect Mode (C(0)-C(100)).
+ - Create AppDirect capacity utilizing hardware interleaving across the
+ requested PMem modules if applicable given the specified target.
+ - Total of I(appdirect), I(memorymode) and I(reserved) must be C(100)
+ type: int
+ appdirect_interleaved:
+ description:
+ - Create AppDirect capacity that is interleaved any other PMem modules.
+ type: bool
+ required: false
+ default: true
+ memorymode:
+ description:
+ - Percentage of the total capacity to use in Memory Mode (C(0)-C(100)).
+ type: int
+ reserved:
+ description:
+ - Percentage of the capacity to reserve (C(0)-C(100)). I(reserved) will not be mapped
+ into the system physical address space and will be presented as reserved
+ capacity with Show Device and Show Memory Resources Commands.
+ - I(reserved) will be set automatically if this is not configured.
+ type: int
+ required: false
+ socket:
+ description:
+ - This enables to set the configuration for each socket by using the socket ID.
+ - Total of I(appdirect), I(memorymode) and I(reserved) must be C(100) within one socket.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description: The socket ID of the PMem module.
+ type: int
+ required: true
+ appdirect:
+ description:
+ - Percentage of the total capacity to use in AppDirect Mode (C(0)-C(100)) within the socket ID.
+ type: int
+ required: true
+ appdirect_interleaved:
+ description:
+ - Create AppDirect capacity that is interleaved any other PMem modules within the socket ID.
+ type: bool
+ required: false
+ default: true
+ memorymode:
+ description:
+ - Percentage of the total capacity to use in Memory Mode (C(0)-C(100)) within the socket ID.
+ type: int
+ required: true
+ reserved:
+ description:
+ - Percentage of the capacity to reserve (C(0)-C(100)) within the socket ID.
+ type: int
+ namespace:
+ description:
+ - This enables to set the configuration for the namespace of the PMem.
+ type: list
+ elements: dict
+ suboptions:
+ mode:
+ description:
+ - The mode of namespace. The detail of the mode is in the man page of ndctl-create-namespace.
+ type: str
+ required: true
+ choices: ['raw', 'sector', 'fsdax', 'devdax']
+ type:
+ description:
+ - The type of namespace. The detail of the type is in the man page of ndctl-create-namespace.
+ type: str
+ required: false
+ choices: ['pmem', 'blk']
+ size:
+ description:
+ - The size of namespace. This option supports the suffixes C(k) or C(K) or C(KB) for KiB,
+ C(m) or C(M) or C(MB) for MiB, C(g) or C(G) or C(GB) for GiB and C(t) or C(T) or C(TB) for TiB.
+ - This option is required if multiple namespaces are configured.
+ - If this option is not set, all of the available space of a region is configured.
+ type: str
+ required: false
+ namespace_append:
+ description:
+ - Enable to append the new namespaces to the system.
+ - The default is C(false) so the all existing namespaces not listed in I(namespace) are removed.
+ type: bool
+ default: false
+ required: false
+'''
+
+RETURN = r'''
+reboot_required:
+ description: Indicates that the system reboot is required to complete the PMem configuration.
+ returned: success
+ type: bool
+ sample: true
+result:
+ description:
+ - Shows the value of AppDirect, Memory Mode and Reserved size in bytes.
+ - If I(socket) argument is provided, shows the values in each socket with C(socket) which contains the socket ID.
+ - If I(namespace) argument is provided, shows the detail of each namespace.
+ returned: success
+ type: list
+ elements: dict
+ contains:
+ appdirect:
+ description: AppDirect size in bytes.
+ type: int
+ memorymode:
+ description: Memory Mode size in bytes.
+ type: int
+ reserved:
+ description: Reserved size in bytes.
+ type: int
+ socket:
+ description: The socket ID to be configured.
+ type: int
+ namespace:
+ description: The list of the detail of namespace.
+ type: list
+ sample: [
+ {
+ "appdirect": 111669149696,
+ "memorymode": 970662608896,
+ "reserved": 3626500096,
+ "socket": 0
+ },
+ {
+ "appdirect": 111669149696,
+ "memorymode": 970662608896,
+ "reserved": 3626500096,
+ "socket": 1
+ }
+ ]
+'''
+
+EXAMPLES = r'''
+- name: Configure the Pmem as AppDirect 10, Memory Mode 70, and the Reserved 20 percent.
+ community.general.pmem:
+ appdirect: 10
+ memorymode: 70
+
+- name: Configure the Pmem as AppDirect 10, Memory Mode 80, and the Reserved 10 percent.
+ community.general.pmem:
+ appdirect: 10
+ memorymode: 80
+ reserved: 10
+
+- name: Configure the Pmem as AppDirect with not interleaved 10, Memory Mode 70, and the Reserved 20 percent.
+ community.general.pmem:
+ appdirect: 10
+ appdirect_interleaved: false
+ memorymode: 70
+
+- name: Configure the Pmem each socket.
+ community.general.pmem:
+ socket:
+ - id: 0
+ appdirect: 10
+ appdirect_interleaved: false
+ memorymode: 70
+ reserved: 20
+ - id: 1
+ appdirect: 10
+ memorymode: 80
+ reserved: 10
+
+- name: Configure the two namespaces.
+ community.general.pmem:
+ namespace:
+ - size: 1GB
+ type: pmem
+ mode: raw
+ - size: 320MB
+ type: pmem
+ mode: sector
+'''
+
+import json
+import re
+import traceback
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib, human_to_bytes
+
+try:
+ import xmltodict
+except ImportError:
+ HAS_XMLTODICT_LIBRARY = False
+ XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
+else:
+ HAS_XMLTODICT_LIBRARY = True
+ XMLTODICT_LIBRARY_IMPORT_ERROR = None
+
+
+class PersistentMemory(object):
+ def __init__(self):
+ module = AnsibleModule(
+ argument_spec=dict(
+ appdirect=dict(type='int'),
+ appdirect_interleaved=dict(type='bool', default=True),
+ memorymode=dict(type='int'),
+ reserved=dict(type='int'),
+ socket=dict(
+ type='list', elements='dict',
+ options=dict(
+ id=dict(required=True, type='int'),
+ appdirect=dict(required=True, type='int'),
+ appdirect_interleaved=dict(type='bool', default=True),
+ memorymode=dict(required=True, type='int'),
+ reserved=dict(type='int'),
+ ),
+ ),
+ namespace=dict(
+ type='list', elements='dict',
+ options=dict(
+ mode=dict(required=True, type='str', choices=['raw', 'sector', 'fsdax', 'devdax']),
+ type=dict(type='str', choices=['pmem', 'blk']),
+ size=dict(type='str'),
+ ),
+ ),
+ namespace_append=dict(type='bool', default=False),
+ ),
+ required_together=(
+ ['appdirect', 'memorymode'],
+ ),
+ required_one_of=(
+ ['appdirect', 'memorymode', 'socket', 'namespace'],
+ ),
+ mutually_exclusive=(
+ ['appdirect', 'socket'],
+ ['memorymode', 'socket'],
+ ['appdirect', 'namespace'],
+ ['memorymode', 'namespace'],
+ ['socket', 'namespace'],
+ ['appdirect', 'namespace_append'],
+ ['memorymode', 'namespace_append'],
+ ['socket', 'namespace_append'],
+ ),
+ )
+
+ if not HAS_XMLTODICT_LIBRARY:
+ module.fail_json(
+ msg=missing_required_lib('xmltodict'),
+ exception=XMLTODICT_LIBRARY_IMPORT_ERROR)
+
+ self.ipmctl_exec = module.get_bin_path('ipmctl', True)
+ self.ndctl_exec = module.get_bin_path('ndctl', True)
+
+ self.appdirect = module.params['appdirect']
+ self.interleaved = module.params['appdirect_interleaved']
+ self.memmode = module.params['memorymode']
+ self.reserved = module.params['reserved']
+ self.socket = module.params['socket']
+ self.namespace = module.params['namespace']
+ self.namespace_append = module.params['namespace_append']
+
+ self.module = module
+ self.changed = False
+ self.result = []
+
+ def pmem_run_command(self, command, returnCheck=True):
+ # in case command[] has number
+ cmd = [str(part) for part in command]
+
+ self.module.log(msg='pmem_run_command: execute: %s' % cmd)
+
+ rc, out, err = self.module.run_command(cmd)
+
+ self.module.log(msg='pmem_run_command: result: %s' % out)
+
+ if returnCheck and rc != 0:
+ self.module.fail_json(msg='Error while running: %s' %
+ cmd, rc=rc, out=out, err=err)
+
+ return out
+
+ def pmem_run_ipmctl(self, command, returnCheck=True):
+
+ command = [self.ipmctl_exec] + command
+
+ return self.pmem_run_command(command, returnCheck)
+
+ def pmem_run_ndctl(self, command, returnCheck=True):
+
+ command = [self.ndctl_exec] + command
+
+ return self.pmem_run_command(command, returnCheck)
+
+ def pmem_is_dcpmm_installed(self):
+ # To check this system has dcpmm
+ command = ['show', '-system', '-capabilities']
+ return self.pmem_run_ipmctl(command)
+
+ def pmem_get_region_align_size(self, region):
+ aligns = []
+ for rg in region:
+ if rg['align'] not in aligns:
+ aligns.append(rg['align'])
+
+ return aligns
+
+ def pmem_get_available_region_size(self, region):
+ available_size = []
+ for rg in region:
+ available_size.append(rg['available_size'])
+
+ return available_size
+
+ def pmem_get_available_region_type(self, region):
+ types = []
+ for rg in region:
+ if rg['type'] not in types:
+ types.append(rg['type'])
+
+ return types
+
+ def pmem_argument_check(self):
+ def namespace_check(self):
+ command = ['list', '-R']
+ out = self.pmem_run_ndctl(command)
+ if not out:
+ return 'Available region(s) is not in this system.'
+ region = json.loads(out)
+
+ aligns = self.pmem_get_region_align_size(region)
+ if len(aligns) != 1:
+ return 'Not supported the regions whose alignment size is different.'
+
+ available_size = self.pmem_get_available_region_size(region)
+ types = self.pmem_get_available_region_type(region)
+ for ns in self.namespace:
+ if ns['size']:
+ try:
+ size_byte = human_to_bytes(ns['size'])
+ except ValueError:
+ return 'The format of size: NNN TB|GB|MB|KB|T|G|M|K|B'
+
+ if size_byte % aligns[0] != 0:
+ return 'size: %s should be align with %d' % (ns['size'], aligns[0])
+
+ is_space_enough = False
+ for i, avail in enumerate(available_size):
+ if avail > size_byte:
+ available_size[i] -= size_byte
+ is_space_enough = True
+ break
+
+ if is_space_enough is False:
+ return 'There is not available region for size: %s' % ns['size']
+
+ ns['size_byte'] = size_byte
+
+ elif len(self.namespace) != 1:
+ return 'size option is required to configure multiple namespaces'
+
+ if ns['type'] not in types:
+ return 'type %s is not supported in this system. Supported type: %s' % (ns['type'], types)
+
+ return None
+
+ def percent_check(self, appdirect, memmode, reserved=None):
+ if appdirect is None or (appdirect < 0 or appdirect > 100):
+ return 'appdirect percent should be from 0 to 100.'
+ if memmode is None or (memmode < 0 or memmode > 100):
+ return 'memorymode percent should be from 0 to 100.'
+
+ if reserved is None:
+ if appdirect + memmode > 100:
+ return 'Total percent should be less equal 100.'
+ else:
+ if reserved < 0 or reserved > 100:
+ return 'reserved percent should be from 0 to 100.'
+ if appdirect + memmode + reserved != 100:
+ return 'Total percent should be 100.'
+
+ def socket_id_check(self):
+ command = ['show', '-o', 'nvmxml', '-socket']
+ out = self.pmem_run_ipmctl(command)
+ sockets_dict = xmltodict.parse(out, dict_constructor=dict)['SocketList']['Socket']
+ socket_ids = []
+ for sl in sockets_dict:
+ socket_ids.append(int(sl['SocketID'], 16))
+
+ for skt in self.socket:
+ if skt['id'] not in socket_ids:
+ return 'Invalid socket number: %d' % skt['id']
+
+ return None
+
+ if self.namespace:
+ return namespace_check(self)
+ elif self.socket is None:
+ return percent_check(self, self.appdirect, self.memmode, self.reserved)
+ else:
+ ret = socket_id_check(self)
+ if ret is not None:
+ return ret
+
+ for skt in self.socket:
+ ret = percent_check(
+ self, skt['appdirect'], skt['memorymode'], skt['reserved'])
+ if ret is not None:
+ return ret
+
+ return None
+
+ def pmem_remove_namespaces(self):
+ command = ['list', '-N']
+ out = self.pmem_run_ndctl(command)
+
+ # There's nothing namespaces in this system. Nothing to do.
+ if not out:
+ return
+
+ namespaces = json.loads(out)
+
+ # Disable and destroy all namespaces
+ for ns in namespaces:
+ command = ['disable-namespace', ns['dev']]
+ self.pmem_run_ndctl(command)
+
+ command = ['destroy-namespace', ns['dev']]
+ self.pmem_run_ndctl(command)
+
+ return
+
+ def pmem_delete_goal(self):
+ # delete the goal request
+ command = ['delete', '-goal']
+ self.pmem_run_ipmctl(command)
+
+ def pmem_init_env(self):
+ if self.namespace is None or (self.namespace and self.namespace_append is False):
+ self.pmem_remove_namespaces()
+ if self.namespace is None:
+ self.pmem_delete_goal()
+
+ def pmem_get_capacity(self, skt=None):
+ command = ['show', '-d', 'Capacity', '-u', 'B', '-o', 'nvmxml', '-dimm']
+ if skt:
+ command += ['-socket', skt['id']]
+ out = self.pmem_run_ipmctl(command)
+
+ dimm_list = xmltodict.parse(out, dict_constructor=dict)['DimmList']['Dimm']
+ capacity = 0
+ for entry in dimm_list:
+ for key, v in entry.items():
+ if key == 'Capacity':
+ capacity += int(v.split()[0])
+
+ return capacity
+
+ def pmem_create_memory_allocation(self, skt=None):
+ def build_ipmctl_creation_opts(self, skt=None):
+ ipmctl_opts = []
+
+ if skt:
+ appdirect = skt['appdirect']
+ memmode = skt['memorymode']
+ reserved = skt['reserved']
+ socket_id = skt['id']
+ ipmctl_opts += ['-socket', socket_id]
+ else:
+ appdirect = self.appdirect
+ memmode = self.memmode
+ reserved = self.reserved
+
+ if reserved is None:
+ res = 100 - memmode - appdirect
+ ipmctl_opts += ['memorymode=%d' % memmode, 'reserved=%d' % res]
+ else:
+ ipmctl_opts += ['memorymode=%d' % memmode, 'reserved=%d' % reserved]
+
+ if self.interleaved:
+ ipmctl_opts += ['PersistentMemoryType=AppDirect']
+ else:
+ ipmctl_opts += ['PersistentMemoryType=AppDirectNotInterleaved']
+
+ return ipmctl_opts
+
+ def is_allocation_good(self, ipmctl_out, command):
+ warning = re.compile('WARNING')
+ error = re.compile('.*Error.*')
+ ignore_error = re.compile(
+ 'Do you want to continue? [y/n] Error: Invalid data input.')
+
+ errmsg = ''
+ rc = True
+ for line in ipmctl_out.splitlines():
+ if warning.match(line):
+ errmsg = '%s (command: %s)' % (line, command)
+ rc = False
+ break
+ elif error.match(line):
+ if not ignore_error:
+ errmsg = '%s (command: %s)' % (line, command)
+ rc = False
+ break
+
+ return rc, errmsg
+
+ def get_allocation_result(self, goal, skt=None):
+ ret = {'appdirect': 0, 'memorymode': 0}
+
+ if skt:
+ ret['socket'] = skt['id']
+
+ out = xmltodict.parse(goal, dict_constructor=dict)['ConfigGoalList']['ConfigGoal']
+ for entry in out:
+
+ # Probably it's a bug of ipmctl to show the socket goal
+ # which isn't specified by the -socket option.
+ # Anyway, filter the noise out here:
+ if skt and skt['id'] != int(entry['SocketID'], 16):
+ continue
+
+ for key, v in entry.items():
+ if key == 'MemorySize':
+ ret['memorymode'] += int(v.split()[0])
+ elif key == 'AppDirect1Size' or key == 'AapDirect2Size':
+ ret['appdirect'] += int(v.split()[0])
+
+ capacity = self.pmem_get_capacity(skt)
+ ret['reserved'] = capacity - ret['appdirect'] - ret['memorymode']
+
+ return ret
+
+ reboot_required = False
+
+ ipmctl_opts = build_ipmctl_creation_opts(self, skt)
+
+ # First, do dry run ipmctl create command to check the error and warning.
+ command = ['create', '-goal'] + ipmctl_opts
+ out = self.pmem_run_ipmctl(command, returnCheck=False)
+ rc, errmsg = is_allocation_good(self, out, command)
+ if rc is False:
+ return reboot_required, {}, errmsg
+
+ # Run actual creation here
+ command = ['create', '-u', 'B', '-o', 'nvmxml', '-force', '-goal'] + ipmctl_opts
+ goal = self.pmem_run_ipmctl(command)
+ ret = get_allocation_result(self, goal, skt)
+ reboot_required = True
+
+ return reboot_required, ret, ''
+
+ def pmem_config_namespaces(self, namespace):
+ command = ['create-namespace', '-m', namespace['mode']]
+ if namespace['type']:
+ command += ['-t', namespace['type']]
+ if 'size_byte' in namespace:
+ command += ['-s', namespace['size_byte']]
+
+ self.pmem_run_ndctl(command)
+
+ return None
+
+
+def main():
+
+ pmem = PersistentMemory()
+
+ pmem.pmem_is_dcpmm_installed()
+
+ error = pmem.pmem_argument_check()
+ if error:
+ pmem.module.fail_json(msg=error)
+
+ pmem.pmem_init_env()
+ pmem.changed = True
+
+ if pmem.namespace:
+ for ns in pmem.namespace:
+ pmem.pmem_config_namespaces(ns)
+
+ command = ['list', '-N']
+ out = pmem.pmem_run_ndctl(command)
+ all_ns = json.loads(out)
+
+ pmem.result = all_ns
+ reboot_required = False
+ elif pmem.socket is None:
+ reboot_required, ret, errmsg = pmem.pmem_create_memory_allocation()
+ if errmsg:
+ pmem.module.fail_json(msg=errmsg)
+ pmem.result.append(ret)
+ else:
+ for skt in pmem.socket:
+ skt_reboot_required, skt_ret, skt_errmsg = pmem.pmem_create_memory_allocation(skt)
+
+ if skt_errmsg:
+ pmem.module.fail_json(msg=skt_errmsg)
+
+ if skt_reboot_required:
+ reboot_required = True
+
+ pmem.result.append(skt_ret)
+
+ pmem.module.exit_json(
+ changed=pmem.changed,
+ reboot_required=reboot_required,
+ result=pmem.result
+ )
+
+
+if __name__ == '__main__':
+ main()