diff --git a/Vagrantfile b/Vagrantfile index 5b0962ed9..ae02f838c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -50,15 +50,15 @@ ansible_provision = proc do |ansible| # these aren't supported by Vagrant, see # https://github.com/mitchellh/vagrant/issues/3539 ansible.groups = { - 'mons' => (0..NMONS - 1).map { |j| "#{LABEL_PREFIX}mon#{j}" }, - 'osds' => (0..NOSDS - 1).map { |j| "#{LABEL_PREFIX}osd#{j}" }, - 'mdss' => (0..NMDSS - 1).map { |j| "#{LABEL_PREFIX}mds#{j}" }, - 'rgws' => (0..NRGWS - 1).map { |j| "#{LABEL_PREFIX}rgw#{j}" }, - 'nfss' => (0..NNFSS - 1).map { |j| "#{LABEL_PREFIX}nfs#{j}" }, - 'rbd_mirrors' => (0..NRBD_MIRRORS - 1).map { |j| "#{LABEL_PREFIX}rbd_mirror#{j}" }, - 'clients' => (0..CLIENTS - 1).map { |j| "#{LABEL_PREFIX}client#{j}" }, - 'iscsi_gw' => (0..NISCSI_GWS - 1).map { |j| "#{LABEL_PREFIX}iscsi_gw#{j}" }, - 'mgrs' => (0..MGRS - 1).map { |j| "#{LABEL_PREFIX}mgr#{j}" } + 'mons' => (0..NMONS - 1).map { |j| "#{LABEL_PREFIX}mon#{j}" }, + 'osds' => (0..NOSDS - 1).map { |j| "#{LABEL_PREFIX}osd#{j}" }, + 'mdss' => (0..NMDSS - 1).map { |j| "#{LABEL_PREFIX}mds#{j}" }, + 'rgws' => (0..NRGWS - 1).map { |j| "#{LABEL_PREFIX}rgw#{j}" }, + 'nfss' => (0..NNFSS - 1).map { |j| "#{LABEL_PREFIX}nfs#{j}" }, + 'rbd_mirrors' => (0..NRBD_MIRRORS - 1).map { |j| "#{LABEL_PREFIX}rbd_mirror#{j}" }, + 'clients' => (0..CLIENTS - 1).map { |j| "#{LABEL_PREFIX}client#{j}" }, + 'iscsi_gws' => (0..NISCSI_GWS - 1).map { |j| "#{LABEL_PREFIX}iscsi_gw#{j}" }, + 'mgrs' => (0..MGRS - 1).map { |j| "#{LABEL_PREFIX}mgr#{j}" } } if RESTAPI then @@ -397,7 +397,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| end (0..NISCSI_GWS - 1).each do |i| - config.vm.define "#{LABEL_PREFIX}iscsi_gw#{i}" do |iscsi_gw| + config.vm.define "#{LABEL_PREFIX}iscsi-gw#{i}" do |iscsi_gw| iscsi_gw.vm.hostname = "#{LABEL_PREFIX}iscsi-gw#{i}" if ASSIGN_STATIC_IP iscsi_gw.vm.network :private_network, @@ -420,7 +420,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| end # Parallels iscsi_gw.vm.provider "parallels" do |prl| - prl.name = "ceph-iscsi-gw#{i}" + prl.name = "iscsi-gw#{i}" prl.memory = "#{MEMORY}" end diff --git a/ceph-ansible.spec.in b/ceph-ansible.spec.in index ea10d1d43..0baee0390 100644 --- a/ceph-ansible.spec.in +++ b/ceph-ansible.spec.in @@ -46,13 +46,6 @@ pushd %{buildroot}%{_datarootdir}/ceph-ansible rm -r infrastructure-playbooks/untested-by-ci popd -# Strip iscsi files. -# These are just placeholders until ceph-iscsi-gw can merge into -# ceph-ansible. (See https://bugzilla.redhat.com/1454945). -pushd %{buildroot}%{_datarootdir}/ceph-ansible - rm -r roles/ceph-iscsi-gw -popd - %check # Borrowed from upstream's .travis.yml: ansible-playbook -i dummy-ansible-hosts test.yml --syntax-check diff --git a/group_vars/all.yml.sample b/group_vars/all.yml.sample index 22529125d..c648417c6 100644 --- a/group_vars/all.yml.sample +++ b/group_vars/all.yml.sample @@ -51,7 +51,7 @@ dummy: #restapi_group_name: restapis #rbdmirror_group_name: rbdmirrors #client_group_name: clients -#iscsi_group_name: iscsigws +#iscsi_gw_group_name: iscsi_gws #mgr_group_name: mgrs # If check_firewall is true, then ansible will try to determine if the @@ -205,6 +205,8 @@ dummy: # flavors so far include: ceph_master, ceph_jewel, ceph_kraken, ceph_luminous #nfs_ganesha_flavor: "ceph_master" +#ceph_iscsi_config_dev: true # special repo for deploying iSCSI gateways + # REPOSITORY: CUSTOM # diff --git a/group_vars/iscsi-gws.yml.sample b/group_vars/iscsi-gws.yml.sample index 1a03a2047..e196e39cd 100644 --- a/group_vars/iscsi-gws.yml.sample +++ b/group_vars/iscsi-gws.yml.sample @@ -7,4 +7,45 @@ # file as a good configuration file when no variable in it. dummy: +# You can override vars by using host or group vars + +# Specify the iqn for ALL gateways. This iqn is shared across the gateways, so an iscsi +# client sees the gateway group as a single storage subsystem. +#gateway_iqn: "iqn.2003-01.com.redhat.iscsi-gw:ceph-igw" + +# gateway_ip_list provides a list of the IP Addrresses - one per gateway - that will be used +# as an iscsi target portal ip. The list must be comma separated - and the order determines +# the sequence of TPG's within the iscsi target across each gateway. Once set, additional +# gateways can be added, but the order must *not* be changed. +#gateway_ip_list: "192.168.122.101,192.168.122.102,192.168.122.103" + +# rbd_devices defines the images that should be created and exported from the iscsi gateways. +# If the rbd does not exist, it will be created for you. In addition you may increase the +# size of rbd's by changing the size parameter and rerunning the playbook. A size value lower +# than the current size of the rbd is ignored. +# +# the 'host' parameter defines which of the gateway nodes should handle the physical +# allocation/expansion or removal of the rbd +# to remove an image, simply use a state of 'absent'. This will first check the rbd is not allocated +# to any client, and the remove it from LIO and then delete the rbd image +# +# NB. this variable definition can be commented out to bypass LUN management +#rbd_devices: +# - { pool: 'rbd', image: 'ansible1', size: '30G', host: 'ceph-1', state: 'present' } +# - { pool: 'rbd', image: 'ansible2', size: '15G', host: 'ceph-1', state: 'present' } +# - { pool: 'rbd', image: 'ansible3', size: '30G', host: 'ceph-1', state: 'present' } +# - { pool: 'rbd', image: 'ansible4', size: '50G', host: 'ceph-1', state: 'present' } + + +# client_connections defines the client ACL's to restrict client access to specific LUNs +# The settings are as follows; +# - image_list is a comma separated list of rbd images of the form . +# - chap supplies the user and password the client will use for authentication of the +# form / +# - status shows the intended state of this client definition - 'present' or 'absent' +# +# NB. this definition can be commented out to skip client (nodeACL) management +#client_connections: +# - { client: 'iqn.1994-05.com.redhat:rh7-iscsi-client', image_list: 'rbd.ansible1,rbd.ansible2', chap: 'rh7-iscsi-client/redhat', status: 'present' } +# - { client: 'iqn.1991-05.com.microsoft:w2k12r2', image_list: 'rbd.ansible4', chap: 'w2k12r2/microsoft_w2k12', status: 'absent' } diff --git a/group_vars/rhcs.yml.sample b/group_vars/rhcs.yml.sample index ee92c71df..1c255fac6 100644 --- a/group_vars/rhcs.yml.sample +++ b/group_vars/rhcs.yml.sample @@ -51,7 +51,7 @@ fetch_directory: ~/ceph-ansible-keys #restapi_group_name: restapis #rbdmirror_group_name: rbdmirrors #client_group_name: clients -#iscsi_group_name: iscsigws +#iscsi_gw_group_name: iscsi_gws #mgr_group_name: mgrs # If check_firewall is true, then ansible will try to determine if the @@ -205,6 +205,8 @@ ceph_repository: rhcs # flavors so far include: ceph_master, ceph_jewel, ceph_kraken, ceph_luminous #nfs_ganesha_flavor: "ceph_master" +#ceph_iscsi_config_dev: true # special repo for deploying iSCSI gateways + # REPOSITORY: CUSTOM # diff --git a/infrastructure-playbooks/purge-cluster.yml b/infrastructure-playbooks/purge-cluster.yml index 42c4acb46..759c1652b 100644 --- a/infrastructure-playbooks/purge-cluster.yml +++ b/infrastructure-playbooks/purge-cluster.yml @@ -426,7 +426,7 @@ hosts: - "{{ mon_group_name|default('mons') }}" - gather_facts: false # Already gathered previously + gather_facts: false # already gathered previously become: true @@ -453,6 +453,38 @@ path: /var/lib/ceph/ state: absent +- name: purge iscsi gateway(s) + + vars: + igw_purge_type: all + + hosts: + - "{{ iscsi_gw_group_name|default('iscsi-gw') }}" + + gather_facts: false # already gathered previously + + become: true + + tasks: + + - name: igw_purge | purging the gateway configuration + igw_purge: + mode: "gateway" + + - name: igw_purge | deleting configured rbd devices + igw_purge: + mode: "disks" + when: + - igw_purge_type == 'all' + + - name: restart rbd-target-gw daemons + service: + name: rbd-target-gw + state: restarted + when: + - ansible_service_mgr == 'systemd' + + - name: final cleanup - check any running ceph, purge ceph packages, purge config and remove data vars: diff --git a/library/igw_client.py b/library/igw_client.py new file mode 100644 index 000000000..cdd095bae --- /dev/null +++ b/library/igw_client.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +__author__ = 'pcuzner@redhat.com' + +DOCUMENTATION = """ +--- +module: igw_client +short_description: Manage iscsi gateway client definitions +description: + - This module calls the 'client' configuration management module installed + on the iscsi gateway node to handle the definition of iscsi clients on the + gateway(s). This definition will setup iscsi authentication (e.g. chap), + and mask the required rbd images to the client. + + The 'client' configuration module is provided by ceph-iscsi-config + rpm which is installed on the gateway nodes. + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on the target machine(s). + +option: + client_iqn: + description: + - iqn of the client machine which should be connected or removed from the + iscsi gateway environment + required: true + + image_list: + description: + - comma separated string providing the rbd images that this + client definition should have. The rbd images provided must use the + following format . + e.g. rbd.disk1,rbd.disk2 + required: true + + chap: + description: + - chap credentials for the client to authenticate to the gateways + to gain access to the exported rbds (LUNs). The credentials is a string + value of the form 'username/password'. The iscsi client must then use + these settings to gain access to any LUN resources. + required: true + + state: + description: + - desired state for this client - absent or present + required: true + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +from ceph_iscsi_config.client import GWClient +import ceph_iscsi_config.settings as settings + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + + fields = { + "client_iqn": {"required": True, "type": "str"}, + "image_list": {"required": True, "type": "str"}, + "chap": {"required": True, "type": "str"}, + "state": { + "required": True, + "choices": ['present', 'absent'], + "type": "str" + }, + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + client_iqn = module.params['client_iqn'] + + if module.params['image_list']: + image_list = module.params['image_list'].split(',') + else: + image_list = [] + + chap = module.params['chap'] + desired_state = module.params['state'] + + logger.info("START - Client configuration started : {}".format(client_iqn)) + + # The client is defined using the GWClient class. This class handles + # client attribute updates, rados configuration object updates and LIO + # settings. Since the logic is external to this custom module, clients + # can be created/deleted by other methods in the same manner. + client = GWClient(logger, client_iqn, image_list, chap) + if client.error: + module.fail_json(msg=client.error_msg) + + client.manage(desired_state) + if client.error: + module.fail_json(msg=client.error_msg) + + logger.info("END - Client configuration complete - {} " + "changes made".format(client.change_count)) + + changes_made = True if client.change_count > 0 else False + + module.exit_json(changed=changes_made, + meta={"msg": "Client definition completed {} " + "changes made".format(client.change_count)}) + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/library/igw_gateway.py b/library/igw_gateway.py new file mode 100644 index 000000000..9c3a1d035 --- /dev/null +++ b/library/igw_gateway.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +__author__ = 'pcuzner@redhat.com' + + +DOCUMENTATION = """ +--- +module: igw_gateway +short_description: Manage the iscsi gateway definition +description: + - This module calls the 'gateway' configuration management module installed + on the iscsi gateway node(s) to handle the definition of iscsi gateways. + The module will configure; + * the iscsi target and target portal group (TPG) + * rbd maps to the gateway and registration of those rbds as LUNs to the + kernels LIO subsystem + + The actual configuration modules are provided by ceph-iscsi-config rpm + which is installed on the gateway nodes. + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on the target machine(s). + +option: + gateway_iqn: + description: + - iqn that all gateway nodes will use to present a common system image + name to iscsi clients + required: true + + gateway_ip_list: + description: + - comma separated string providing the IP addresses that will be used + as iSCSI portal IPs to accept iscsi client connections. Each IP address + should equate to an IP on a gateway node - typically dedicated to iscsi + traffic. The order of the IP addresses determines the TPG sequence + within the target definition - so once defined, new gateways can be + added but *must* be added to the end of this list to preserve the tpg + sequence + + e.g. 192.168.122.101,192.168.122.103 + required: true + + mode: + description: + - mode in which to run the gateway module. Two modes are supported + target ... define the iscsi target iqn, tpg's and portals + map ...... map luns to the tpg's, and also define the ALUA path setting + for each LUN (activeOptimized/activenonoptimized) + required: true + + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging + +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +import ceph_iscsi_config.settings as settings + +from ceph_iscsi_config.gateway import GWTarget +from ceph_iscsi_config.utils import valid_ip + + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + # Configures the gateway on the host. All images defined are added to + # the default tpg for later allocation to clients + fields = {"gateway_iqn": {"required": True, "type": "str"}, + "gateway_ip_list": {"required": True}, # "type": "list"}, + "mode": { + "required": True, + "choices": ['target', 'map'] + } + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + gateway_iqn = module.params['gateway_iqn'] + gateway_ip_list = module.params['gateway_ip_list'].split(',') + mode = module.params['mode'] + + if not valid_ip(gateway_ip_list): + module.fail_json(msg="Invalid gateway IP address(es) provided - port " + "22 check failed ({})".format(gateway_ip_list)) + + logger.info("START - GATEWAY configuration started - mode {}".format(mode)) + + gateway = GWTarget(logger, gateway_iqn, gateway_ip_list) + if gateway.error: + logger.critical("(ansible_main) Gateway init failed - " + "{}".format(gateway.error_msg)) + module.fail_json(msg="iSCSI gateway initialisation failed " + "({})".format(gateway.error_msg)) + + gateway.manage(mode) + + if gateway.error: + logger.critical("(main) Gateway creation or load failed, " + "unable to continue") + module.fail_json(msg="iSCSI gateway creation/load failure " + "({})".format(gateway.error_msg)) + + + logger.info("END - GATEWAY configuration complete") + module.exit_json(changed=gateway.changes_made, + meta={"msg": "Gateway setup complete"}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/library/igw_lun.py b/library/igw_lun.py new file mode 100644 index 000000000..6554f3d2f --- /dev/null +++ b/library/igw_lun.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +__author__ = 'pcuzner@redhat.com' + +DOCUMENTATION = """ +--- +module: igw_lun +short_description: Manage ceph rbd images to present as iscsi LUNs to clients +description: + - This module calls the 'lun' configuration management module installed + on the iscsi gateway node(s). The lun module handles the creation and resize + of rbd images, and then maps these rbd devices to the gateway node(s) to be + exposed through the kernel's LIO target. + + To support module debugging, this module logs to /var/log/ansible-module-igw_config.log + on the target machine(s). + +option: + pool: + description: + - The ceph pool where the image should exist or be created in. + + NOTE - The pool *must* exist prior to the Ansible run. + + required: true + + image: + description: + - this is the rbd image name to create/resize - if the rbd does not exist it + is created for you with the settings optimised for exporting over iscsi. + required: true + + size: + description: + - The size of the rbd image to create/resize. The size is numeric suffixed by + G or T (GB or TB). Increasing the size of a LUN is supported, but if a size + is provided that is smaller that the current size, the request is simply ignored. + + e.g. 100G + required: true + + host: + description: + - the host variable defines the name of the gateway node that will be + the allocation host for this rbd image. RBD creation and resize can + only be performed by one gateway, the other gateways in the + configuration will wait for the operation to complete. + required: true + + features: + description: + - placeholder to potentially allow different rbd features to be set at + allocation time by Ansible. NOT CURRENTLY USED + required: false + + state: + description: + - desired state for this LUN - absent or present. For a state='absent' + request, the lun module will verify that the rbd image is not allocated to + a client. As long as the rbd image is not in use, the LUN definition will be + removed from LIO, unmapped from all gateways AND DELETED. + + USE WITH CARE! + required: true + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" +import os +import logging +from logging.handlers import RotatingFileHandler + +from ansible.module_utils.basic import * + +from ceph_iscsi_config.lun import LUN +from ceph_iscsi_config.utils import valid_size +import ceph_iscsi_config.settings as settings + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + + # Define the fields needs to create/map rbd's the the host(s) + # NB. features and state are reserved/unused + fields = { + "pool": {"required": False, "default": "rbd", "type": "str"}, + "image": {"required": True, "type": "str"}, + "size": {"required": True, "type": "str"}, + "host": {"required": True, "type": "str"}, + "features": {"required": False, "type": "str"}, + "state": { + "required": False, + "default": "present", + "choices": ['present', 'absent'], + "type": "str" + }, + } + + # not supporting check mode currently + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + pool = module.params["pool"] + image = module.params['image'] + size = module.params['size'] + allocating_host = module.params['host'] + desired_state = module.params['state'] + + ################################################ + # Validate the parameters passed from Ansible # + ################################################ + if not valid_size(size): + logger.critical("image '{}' has an invalid size specification '{}' " + "in the ansible configuration".format(image, + size)) + module.fail_json(msg="(main) Unable to use the size parameter '{}' " + "for image '{}' from the playbook - " + "must be a number suffixed by M,G " + "or T".format(size, + image)) + + # define a lun object and perform some initial parameter validation + lun = LUN(logger, pool, image, size, allocating_host) + if lun.error: + module.fail_json(msg=lun.error_msg) + + logger.info("START - LUN configuration started for {}/{}".format(pool, + image)) + + # attempt to create/allocate the LUN for LIO + lun.manage(desired_state) + if lun.error: + module.fail_json(msg=lun.error_msg) + + if lun.num_changes == 0: + logger.info("END - No changes needed") + else: + logger.info("END - {} configuration changes " + "made".format(lun.num_changes)) + + module.exit_json(changed=(lun.num_changes > 0), + meta={"msg": "Configuration updated"}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/library/igw_purge.py b/library/igw_purge.py new file mode 100644 index 000000000..cffc4f434 --- /dev/null +++ b/library/igw_purge.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python + +DOCUMENTATION = """ +--- +module: igw_purge +short_description: Provide a purge capability to remove an iSCSI gateway +environment +description: + - This module handles the removal of a gateway configuration from a ceph + environment. + The playbook that calls this module prompts the user for the type of purge + to perform. + The purge options are; + all ... purge all LIO configuration *and* delete all defined rbd images + lio ... purge only the LIO configuration (rbd's are left intact) + + USE WITH CAUTION + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on each target machine(s). + +option: + mode: + description: + - the mode defines the type of purge requested + gateway ... remove the LIO configuration only + disks ... remove the rbd disks defined to the gateway + required: true + +requirements: ['ceph-iscsi-config', 'python-rtslib'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging +import socket + +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +import ceph_iscsi_config.settings as settings +from ceph_iscsi_config.common import Config +from ceph_iscsi_config.lio import LIO, Gateway +from ceph_iscsi_config.utils import ipv4_addresses, get_ip + +__author__ = 'pcuzner@redhat.com' + + +def delete_group(module, image_list, cfg): + + logger.debug("RBD Images to delete are : {}".format(','.join(image_list))) + pending_list = list(image_list) + + for rbd_path in image_list: + if delete_rbd(module, rbd_path): + disk_key = rbd_path.replace('/', '.', 1) + cfg.del_item('disks', disk_key) + pending_list.remove(rbd_path) + cfg.changed = True + + if cfg.changed: + cfg.commit() + + return pending_list + + +def delete_rbd(module, rbd_path): + + logger.debug("issuing delete for {}".format(rbd_path)) + rm_cmd = 'rbd --no-progress rm {}'.format(rbd_path) + rc, rm_out, err = module.run_command(rm_cmd, use_unsafe_shell=True) + logger.debug("delete RC = {}, {}".format(rc, rm_out, err)) + + return True if rc == 0 else False + + +def is_cleanup_host(config): + """ + decide which gateway host should be responsible for any non-specific + updates to the config object + :param config: configuration dict from the rados pool + :return: boolean indicating whether the addition cleanup should be + performed by the running host + """ + cleanup = False + + if 'ip_list' in config.config["gateways"]: + + gw_1 = config.config["gateways"]["ip_list"][0] + + usable_ip = get_ip(gw_1) + if usable_ip != '0.0.0.0': + if usable_ip in ipv4_addresses(): + cleanup = True + + return cleanup + + +def ansible_main(): + + fields = {"mode": {"required": True, + "type": "str", + "choices": ["gateway", "disks"] + } + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + run_mode = module.params['mode'] + changes_made = False + + logger.info("START - GATEWAY configuration PURGE started, run mode " + "is {}".format(run_mode)) + cfg = Config(logger) + this_host = socket.gethostname().split('.')[0] + perform_cleanup_tasks = is_cleanup_host(cfg) + + # + # Purge gateway configuration, if the config has gateways + if run_mode == 'gateway' and len(cfg.config['gateways'].keys()) > 0: + + lio = LIO() + gateway = Gateway(cfg) + + if gateway.session_count() > 0: + module.fail_json(msg="Unable to purge - gateway still has active " + "sessions") + + gateway.drop_target(this_host) + if gateway.error: + module.fail_json(msg=gateway.error_msg) + + lio.drop_lun_maps(cfg, perform_cleanup_tasks) + if lio.error: + module.fail_json(msg=lio.error_msg) + + if gateway.changed or lio.changed: + + # each gateway removes it's own entry from the config + cfg.del_item("gateways", this_host) + + if perform_cleanup_tasks: + cfg.reset = True + + # drop all client definitions from the configuration object + client_names = cfg.config["clients"].keys() + for client in client_names: + cfg.del_item("clients", client) + + cfg.del_item("gateways", "iqn") + cfg.del_item("gateways", "created") + cfg.del_item("gateways", "ip_list") + + cfg.commit() + + changes_made = True + + elif run_mode == 'disks' and len(cfg.config['disks'].keys()) > 0: + # + # Remove the disks on this host, that have been registered in the + # config object + # + # if the owner field for a disk is set to this host, this host can + # safely delete it + # nb. owner gets set at rbd allocation and mapping time + images_left = [] + + # delete_list will contain a list of pool/image names where the owner + # is this host + delete_list = [key.replace('.', '/', 1) for key in cfg.config['disks'] + if cfg.config['disks'][key]['owner'] == this_host] + + if delete_list: + images_left = delete_group(module, delete_list, cfg) + + # if the delete list still has entries we had problems deleting the + # images + if images_left: + module.fail_json(msg="Problems deleting the following rbd's : " + "{}".format(','.join(images_left))) + + changes_made = cfg.changed + + logger.debug("ending lock state variable {}".format(cfg.config_locked)) + + logger.info("END - GATEWAY configuration PURGE complete") + + module.exit_json(changed=changes_made, + meta={"msg": "Purge of iSCSI settings ({}) " + "complete".format(run_mode)}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + settings.init() + + ansible_main() diff --git a/roles/ceph-common/tasks/checks/check_system.yml b/roles/ceph-common/tasks/checks/check_system.yml index ecc180e30..9d909a6fd 100644 --- a/roles/ceph-common/tasks/checks/check_system.yml +++ b/roles/ceph-common/tasks/checks/check_system.yml @@ -66,3 +66,18 @@ fail: msg: "Systemd must be present" when: ansible_service_mgr != 'systemd' + +- name: fail on unsupported distribution for iscsi gateways + fail: + msg: "iSCSI gateways can only be deployed on Red Hat Enterprise Linux or CentOS" + when: + - ansible_distribution not in ['Red Hat Enterprise Linux', 'CentOS'] + - iscsi_gw_group_name in group_names + +- name: fail on unsupported distribution version for iscsi gateways + fail: + msg: "iSCSI gateways can only be deployed on Red Hat Enterprise Linux or CentOS >= 7.4" + when: + - ansible_distribution == 'Red Hat Enterprise Linux' or ansible_distribution == 'CentOS' + - ansible_distribution_version < '7.4' + - iscsi_gw_group_name in group_names diff --git a/roles/ceph-common/tasks/installs/install_redhat_packages.yml b/roles/ceph-common/tasks/installs/install_redhat_packages.yml index 0688b3db4..9d7c6c0c6 100644 --- a/roles/ceph-common/tasks/installs/install_redhat_packages.yml +++ b/roles/ceph-common/tasks/installs/install_redhat_packages.yml @@ -97,3 +97,15 @@ when: - mgr_group_name in group_names - ceph_release_num.{{ ceph_release }} > ceph_release_num.jewel + +- name: install redhat ceph iscsi package + package: + name: "{{ item }}" + state: "{{ (upgrade_ceph_packages|bool) | ternary('latest','present') }}" + with_items: + - tcmu-runner + - ceph-iscsi-config + - targetcli + when: + - iscsi_gw_group_name in group_names + - ceph_release_num.{{ ceph_release }} >= ceph_release_num.luminous diff --git a/roles/ceph-common/tasks/installs/redhat_community_repository.yml b/roles/ceph-common/tasks/installs/redhat_community_repository.yml index 2928dc0d0..8d261e5dd 100644 --- a/roles/ceph-common/tasks/installs/redhat_community_repository.yml +++ b/roles/ceph-common/tasks/installs/redhat_community_repository.yml @@ -21,7 +21,7 @@ state: present gpgkey: "{{ ceph_stable_key }}" baseurl: "{{ ceph_mirror }}/nfs-ganesha/rpm-{{ nfs_ganesha_stable_branch }}/{{ ceph_stable_release }}/$basearch" - when: + when: - nfs_group_name in group_names - nfs_ganesha_stable diff --git a/roles/ceph-common/tasks/installs/redhat_dev_repository.yml b/roles/ceph-common/tasks/installs/redhat_dev_repository.yml index 989a604ac..7aa053715 100644 --- a/roles/ceph-common/tasks/installs/redhat_dev_repository.yml +++ b/roles/ceph-common/tasks/installs/redhat_dev_repository.yml @@ -33,3 +33,22 @@ - nfs_group_name in group_names - nfs_ganesha_dev +- name: fetch ceph-iscsi-config red hat development repository + uri: + url: https://shaman.ceph.com/api/repos/ceph-iscsi-config/{{ ceph_dev_branch }}/{{ ceph_dev_sha1 }}/{{ ansible_distribution | lower }}/{{ ansible_distribution_major_version }}/repo + return_content: yes + register: ceph_iscsi_config_dev_yum_repo + when: + - ceph_iscsi_config_dev + - iscsi_gw_group_name in group_names + +- name: configure ceph-iscsi-config red hat development repository + copy: + content: "{{ ceph_iscsi_config_dev_yum_repo.content }}" + dest: /etc/yum.repos.d/ceph-iscsi-config-dev.repo + owner: root + group: root + backup: yes + when: + - ceph_iscsi_config_dev + - iscsi_gw_group_name in group_names diff --git a/roles/ceph-defaults/defaults/main.yml b/roles/ceph-defaults/defaults/main.yml index 6d973677b..13c502568 100644 --- a/roles/ceph-defaults/defaults/main.yml +++ b/roles/ceph-defaults/defaults/main.yml @@ -43,7 +43,7 @@ nfs_group_name: nfss restapi_group_name: restapis rbdmirror_group_name: rbdmirrors client_group_name: clients -iscsi_group_name: iscsigws +iscsi_gw_group_name: iscsi_gws mgr_group_name: mgrs # If check_firewall is true, then ansible will try to determine if the @@ -197,6 +197,8 @@ nfs_ganesha_dev: false # use development repos for nfs-ganesha # flavors so far include: ceph_master, ceph_jewel, ceph_kraken, ceph_luminous nfs_ganesha_flavor: "ceph_master" +ceph_iscsi_config_dev: true # special repo for deploying iSCSI gateways + # REPOSITORY: CUSTOM # diff --git a/roles/ceph-iscsi-gw/LICENSE b/roles/ceph-iscsi-gw/LICENSE new file mode 100644 index 000000000..ff84f66fd --- /dev/null +++ b/roles/ceph-iscsi-gw/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016 Paul Cuzner pcuzner at redhat dot com + +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. diff --git a/roles/ceph-iscsi-gw/README b/roles/ceph-iscsi-gw/README new file mode 100644 index 000000000..e55a12489 --- /dev/null +++ b/roles/ceph-iscsi-gw/README @@ -0,0 +1,7 @@ +This package installs the playbooks/tasks necessary to configure LIO gateways that provide a front-end to a ceph cluster. + +The playbooks use custom modules that are just wrappers calling python modules installed on the gateways nodes. These +python modules handle the configuration interaction for; +- the iscsi target definition (target iqn, target portal groups and portal IP's) +- LUN's including RBD create and resize +- client definition diff --git a/roles/ceph-iscsi-gw/README.md b/roles/ceph-iscsi-gw/README.md new file mode 100644 index 000000000..3ba0e31ce --- /dev/null +++ b/roles/ceph-iscsi-gw/README.md @@ -0,0 +1,70 @@ +# ceph-iscsi-ansible +This project provides a mechanism to deploy iSCSI gateways in front of a ceph cluster using Ansible. The ansible playbooks +provided rely upon configuration logic from the "ceph-iscsi-config" project. This separation provides independence to the +configuration logic, potentially opening up the possibility for puppet/chef to create/manage ceph/iSCSI gateways in the +same way. + +## Introduction +At a high level, this project provides custom modules which are responsible for calling the configuration logic, together +with the relevant playbooks. The project defines a new ceph-ansible role; ceph-iscsi-gw, together with two playbooks; + +- **ceph-iscsi-gw-yml** ... to define our change the gateway configuration based on group_vars/ceph-iscsi-gw.yml +- **purge_gateways.yml** .. to destroy the LIO configuration, or the LIO and any associated rbd images. + +## Features +The combination of the playbooks and the configuration logic deliver the following features; + +- confirms RHEL7.3 and aborts if necessary +- ensures targetcli/device-mapper-multipath is installed (for rtslib support) +- configures multipath.conf +- creates rbd's if needed - at allocation time, each rbd is assigned an owner, which will become the preferred path +- checks the size of the rbds at run time and expands if necessary +- maps the rbd's to the host (gateway) +- enables the rbdmap service to start on boot, and reconfigures the target service to be dependent on rbdmap +- adds the rbd's to the /etc/ceph/rbdmap file ensuring the devices are automatically mapped following a gateway reboot +- maps these rbds to LIO +- once mapped, the alua preferred path state is set or cleared (supporting an active/passive topology) +- creates an iscsi target - common iqn, and multiple tpg's +- adds a portal ip based on a the provided IP addresses defined in the group vars to each tpg +- enables the local tpg, other gateways are defined as disabled +- adds all the mapped luns to ALL tpg's (ready for client assignment) +- add clients to the active/enabled tpg, with/without CHAP +- images mapped to clients can be added/removed by changing image_list and rerunning the playbook +- clients can be removed using the state=absent variable and rerunning the playbook. At this point the entry can be + removed from the group variables file +- configuration can be wiped with the purge_gateway playbook +- current state can be seen by looking at the configuration object (stored in the rbd pool) + +### Why RHEL 7.3? +There are several system dependencies that are required to ensure the correct (i.e. don't eat my data!) behaviors when OSD connectivity +or gateway nodes fail. RHEL 7.3 delivers the necessary kernel changes, and also provides an updated multipathd, enabling rbd images +to be managed by multipathd. + +## Prerequisites +* a working ceph cluster ( *rbd pool defined* ) +* a server/host with ceph-ansible installed and working +* nodes intended to be gateways should be at least ceph clients, with the ability to create and map rbd images + + +## Testing So far +The solution has been tested on a collocated cluster where the osd/mons and gateways all reside on the same node. + +## Quick Start +### Prepare the iSCSI Gateway Nodes +1. install the ceph-iscsi-config package on the nodes, intended to be gateways. NB. The playbook includes a check for the presence + of this rpm (https://github.com/pcuzner/ceph-iscsi-config) + +### Install the ansible playbooks +1. install the ceph-iscsi-ansible rpm from the packages directory on the node where you already have ceph-ansible installed. +2. update /etc/ansible/hosts to include a host group (ceph-iscsi-gw) for the nodes that you want to become iscsi gateways +3. make a copy of the group_vars/ceph-iscsi-gw.sample file called ceph-iscsi-gw, and update it to define the environment you want +4. run the playbook + ```> ansible-playbook ceph-iscsi-gw.yml``` + +## Purging the configuration +As mentioned above, the project provides a purge-gateways.yml playbook which can remove the LIO configuration alone, or remove +both LIO and all associated rbd images that have been declared in the group_vars/ceph-iscsi-gw file. The purge playbook will +check for any active iscsi sessions, and abort if any are found. + +## Known Issues and Considerations +1. the ceph cluster name is the default 'ceph', so the corresponding configuration file /etc/ceph/ceph.conf is assumed to be valid \ No newline at end of file diff --git a/roles/ceph-iscsi-gw/ceph-iscsi-ansible.spec b/roles/ceph-iscsi-gw/ceph-iscsi-ansible.spec new file mode 100644 index 000000000..c33d01865 --- /dev/null +++ b/roles/ceph-iscsi-gw/ceph-iscsi-ansible.spec @@ -0,0 +1,109 @@ +Name: ceph-iscsi-ansible +Version: 2.0 +Release: 1%{?dist} +Summary: Ansible playbooks for deploying LIO iscsi gateways in front of a Ceph cluster +License: ASL 2.0 +URL: https://github.com/pcuzner/ceph-iscsi-ansible +Source0: https://github.com/pcuzner/ceph-iscsi-ansible/archive/%{version}/%{name}-%{version}.tar.gz +BuildArch: noarch + +Requires: ansible >= 1.9 +Requires: ceph-ansible >= 1.0.5 + +%description +Ansible playbooks that define nodes as iSCSI gateways (LIO). Once complete, the +LIO instance on each node provides an ISCSI endpoint for clients to connect to. +The playbook defines the front-end iSCSI environment (target -> tpgN -> +NodeACLS/client), as well as the underlying rbd definition for the rbd images +to be exported over iSCSI. + +ceph-iscsi-gw.yml ......... defines the LIO configuration(defined by + group_vars/ceph-iscsi-gw.yml) +purge-iscsi-gateways.yml .. deletes the LIO configuration, and optionally rbd's + from the environment + +NB: The playbooks are dependent upon the ceph-iscsi-config package being +installed/available to the hosts that will become iSCSI gateways. + +%prep +%setup -q + +%build + +%install +mkdir -p %{buildroot}%{_datarootdir}/ceph-ansible + +for f in group_vars library roles ceph-iscsi-gw.yml purge-iscsi-gateways.yml; do + cp -a $f %{buildroot}%{_datarootdir}/ceph-ansible +done + +%files +%doc LICENSE +%doc README +%{_datarootdir}/ceph-ansible/ceph-iscsi-gw.yml +%{_datarootdir}/ceph-ansible/purge-iscsi-gateways.yml +%{_datarootdir}/ceph-ansible/group_vars/ceph-iscsi-gw.sample +%{_datarootdir}/ceph-ansible/roles/ceph-iscsi-gw +%{_datarootdir}/ceph-ansible/library/igw* +%exclude %{_datarootdir}/ceph-ansible/library/igw*.pyo +%exclude %{_datarootdir}/ceph-ansible/library/igw*.pyc + +%changelog +* Fri Jan 13 2017 Paul Cuzner - 2.0-1 +- converted from device-mapper/krbd to TCMU based rbd configurations +- renamed iscsi-gateway config file to use .cfg extension +- renamed purge playbook to match naming in ceph-ansible + +* Fri Nov 04 2016 Paul Cuzner - 1.5-1 +- playbook now seeds the configuration directory on ansible host (rhbz 1390026) +- resolve a 1.4 regression affecting the igw_purge module +- fail gracefully if bogus client name is given (rhbz 1390023) + +* Thu Oct 27 2016 Paul Cuzner - 1.4-1 +- clients can now be added without images or chap defined using null strings +- changed parameters for client definition to position for other auth mechanisms +- adapt purge module to use revised disk naming scheme within config object +- updated group_vars sample to reflect name changes +- added state= setting to LUN definitions (enabling disks to be add/removed) +- fix syntax issue during ceph.conf seed process +- documentation added to the Ansible modules + +* Fri Oct 21 2016 Paul Cuzner - 1.3-1 +- removed rsync rpm dependency (BZ 1386090) +- ceph.conf pulled from seed monitor, then pushed to gateways using copy task +- add a template based config file to each gateway for runtime info +- add additional variables allowing non-default ceph cluster names/keyrings (BZ 1386617) + +* Sat Oct 15 2016 Paul Cuzner - 1.2-1 +- documented the passwordless ssh requirement for the seed_monitor node +- fix BZ 1384505 mask the target service preventing manual start/stop +- fix BZ 1384858 when the admin updates ansible hosts but not the gateway_ip_list variable + +* Wed Oct 12 2016 Paul Cuzner - 1.1-1 +- updated playbook to modify lvm.conf to exclude the dm devices created for mapped rbds + +* Mon Oct 10 2016 Paul Cuzner - 1.0-1 +- fix : allow client_connections and rbd_devices to be be empty to skip those steps +- add usage guidelines to the group_vars/ceph-iscsi-gw.sample file +- added variable to allow pre-req checks to be bypassed during a run +- updated list of rpm pre-req that ansible checks for +- add synchronize task to the playbook to copy admin keyring to gateway node(s) +- updated igw_purge module to allow for the deletion of the alua port groups + +* Thu Oct 06 2016 Paul Cuzner - 0.8-1 +- fix : purge_gateways.yml was missing +- removed packages directory to clean up the source archive +- spec file updates (dependencies) + +* Wed Oct 05 2016 Paul Cuzner - 0.7-1 +- removed service dependencies for rbdmap/target (replaced by rbd-target-gw form ceph-iscsi-config rpm) +- removed target overrides files +- updated playbook to add skip_partx yes to multipath.conf + +* Mon Oct 03 2016 Paul Cuzner - 0.6-1 +- changed the main function to have an ansible prefix to allow the code to know where it is invoked from +- updated the purge module to support image names being prefixed by a pool i.e. pool/image + +* Tue Sep 27 2016 Paul Cuzner - 0.5-1 +- initial rpm package + diff --git a/roles/ceph-iscsi-gw/defaults/main.yml b/roles/ceph-iscsi-gw/defaults/main.yml index ed97d539c..a67d864dd 100644 --- a/roles/ceph-iscsi-gw/defaults/main.yml +++ b/roles/ceph-iscsi-gw/defaults/main.yml @@ -1 +1,42 @@ --- +# You can override vars by using host or group vars + +# Specify the iqn for ALL gateways. This iqn is shared across the gateways, so an iscsi +# client sees the gateway group as a single storage subsystem. +gateway_iqn: "iqn.2003-01.com.redhat.iscsi-gw:ceph-igw" + +# gateway_ip_list provides a list of the IP Addrresses - one per gateway - that will be used +# as an iscsi target portal ip. The list must be comma separated - and the order determines +# the sequence of TPG's within the iscsi target across each gateway. Once set, additional +# gateways can be added, but the order must *not* be changed. +gateway_ip_list: "192.168.122.101,192.168.122.102,192.168.122.103" + +# rbd_devices defines the images that should be created and exported from the iscsi gateways. +# If the rbd does not exist, it will be created for you. In addition you may increase the +# size of rbd's by changing the size parameter and rerunning the playbook. A size value lower +# than the current size of the rbd is ignored. +# +# the 'host' parameter defines which of the gateway nodes should handle the physical +# allocation/expansion or removal of the rbd +# to remove an image, simply use a state of 'absent'. This will first check the rbd is not allocated +# to any client, and the remove it from LIO and then delete the rbd image +# +# NB. this variable definition can be commented out to bypass LUN management +rbd_devices: + - { pool: 'rbd', image: 'ansible1', size: '30G', host: 'ceph-1', state: 'present' } + - { pool: 'rbd', image: 'ansible2', size: '15G', host: 'ceph-1', state: 'present' } + - { pool: 'rbd', image: 'ansible3', size: '30G', host: 'ceph-1', state: 'present' } + - { pool: 'rbd', image: 'ansible4', size: '50G', host: 'ceph-1', state: 'present' } + + +# client_connections defines the client ACL's to restrict client access to specific LUNs +# The settings are as follows; +# - image_list is a comma separated list of rbd images of the form . +# - chap supplies the user and password the client will use for authentication of the +# form / +# - status shows the intended state of this client definition - 'present' or 'absent' +# +# NB. this definition can be commented out to skip client (nodeACL) management +client_connections: + - { client: 'iqn.1994-05.com.redhat:rh7-iscsi-client', image_list: 'rbd.ansible1,rbd.ansible2', chap: 'rh7-iscsi-client/redhat', status: 'present' } + - { client: 'iqn.1991-05.com.microsoft:w2k12r2', image_list: 'rbd.ansible4', chap: 'w2k12r2/microsoft_w2k12', status: 'absent' } diff --git a/roles/ceph-iscsi-gw/library/igw_client.py b/roles/ceph-iscsi-gw/library/igw_client.py new file mode 100644 index 000000000..cdd095bae --- /dev/null +++ b/roles/ceph-iscsi-gw/library/igw_client.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +__author__ = 'pcuzner@redhat.com' + +DOCUMENTATION = """ +--- +module: igw_client +short_description: Manage iscsi gateway client definitions +description: + - This module calls the 'client' configuration management module installed + on the iscsi gateway node to handle the definition of iscsi clients on the + gateway(s). This definition will setup iscsi authentication (e.g. chap), + and mask the required rbd images to the client. + + The 'client' configuration module is provided by ceph-iscsi-config + rpm which is installed on the gateway nodes. + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on the target machine(s). + +option: + client_iqn: + description: + - iqn of the client machine which should be connected or removed from the + iscsi gateway environment + required: true + + image_list: + description: + - comma separated string providing the rbd images that this + client definition should have. The rbd images provided must use the + following format . + e.g. rbd.disk1,rbd.disk2 + required: true + + chap: + description: + - chap credentials for the client to authenticate to the gateways + to gain access to the exported rbds (LUNs). The credentials is a string + value of the form 'username/password'. The iscsi client must then use + these settings to gain access to any LUN resources. + required: true + + state: + description: + - desired state for this client - absent or present + required: true + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +from ceph_iscsi_config.client import GWClient +import ceph_iscsi_config.settings as settings + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + + fields = { + "client_iqn": {"required": True, "type": "str"}, + "image_list": {"required": True, "type": "str"}, + "chap": {"required": True, "type": "str"}, + "state": { + "required": True, + "choices": ['present', 'absent'], + "type": "str" + }, + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + client_iqn = module.params['client_iqn'] + + if module.params['image_list']: + image_list = module.params['image_list'].split(',') + else: + image_list = [] + + chap = module.params['chap'] + desired_state = module.params['state'] + + logger.info("START - Client configuration started : {}".format(client_iqn)) + + # The client is defined using the GWClient class. This class handles + # client attribute updates, rados configuration object updates and LIO + # settings. Since the logic is external to this custom module, clients + # can be created/deleted by other methods in the same manner. + client = GWClient(logger, client_iqn, image_list, chap) + if client.error: + module.fail_json(msg=client.error_msg) + + client.manage(desired_state) + if client.error: + module.fail_json(msg=client.error_msg) + + logger.info("END - Client configuration complete - {} " + "changes made".format(client.change_count)) + + changes_made = True if client.change_count > 0 else False + + module.exit_json(changed=changes_made, + meta={"msg": "Client definition completed {} " + "changes made".format(client.change_count)}) + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/roles/ceph-iscsi-gw/library/igw_gateway.py b/roles/ceph-iscsi-gw/library/igw_gateway.py new file mode 100644 index 000000000..9c3a1d035 --- /dev/null +++ b/roles/ceph-iscsi-gw/library/igw_gateway.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +__author__ = 'pcuzner@redhat.com' + + +DOCUMENTATION = """ +--- +module: igw_gateway +short_description: Manage the iscsi gateway definition +description: + - This module calls the 'gateway' configuration management module installed + on the iscsi gateway node(s) to handle the definition of iscsi gateways. + The module will configure; + * the iscsi target and target portal group (TPG) + * rbd maps to the gateway and registration of those rbds as LUNs to the + kernels LIO subsystem + + The actual configuration modules are provided by ceph-iscsi-config rpm + which is installed on the gateway nodes. + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on the target machine(s). + +option: + gateway_iqn: + description: + - iqn that all gateway nodes will use to present a common system image + name to iscsi clients + required: true + + gateway_ip_list: + description: + - comma separated string providing the IP addresses that will be used + as iSCSI portal IPs to accept iscsi client connections. Each IP address + should equate to an IP on a gateway node - typically dedicated to iscsi + traffic. The order of the IP addresses determines the TPG sequence + within the target definition - so once defined, new gateways can be + added but *must* be added to the end of this list to preserve the tpg + sequence + + e.g. 192.168.122.101,192.168.122.103 + required: true + + mode: + description: + - mode in which to run the gateway module. Two modes are supported + target ... define the iscsi target iqn, tpg's and portals + map ...... map luns to the tpg's, and also define the ALUA path setting + for each LUN (activeOptimized/activenonoptimized) + required: true + + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging + +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +import ceph_iscsi_config.settings as settings + +from ceph_iscsi_config.gateway import GWTarget +from ceph_iscsi_config.utils import valid_ip + + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + # Configures the gateway on the host. All images defined are added to + # the default tpg for later allocation to clients + fields = {"gateway_iqn": {"required": True, "type": "str"}, + "gateway_ip_list": {"required": True}, # "type": "list"}, + "mode": { + "required": True, + "choices": ['target', 'map'] + } + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + gateway_iqn = module.params['gateway_iqn'] + gateway_ip_list = module.params['gateway_ip_list'].split(',') + mode = module.params['mode'] + + if not valid_ip(gateway_ip_list): + module.fail_json(msg="Invalid gateway IP address(es) provided - port " + "22 check failed ({})".format(gateway_ip_list)) + + logger.info("START - GATEWAY configuration started - mode {}".format(mode)) + + gateway = GWTarget(logger, gateway_iqn, gateway_ip_list) + if gateway.error: + logger.critical("(ansible_main) Gateway init failed - " + "{}".format(gateway.error_msg)) + module.fail_json(msg="iSCSI gateway initialisation failed " + "({})".format(gateway.error_msg)) + + gateway.manage(mode) + + if gateway.error: + logger.critical("(main) Gateway creation or load failed, " + "unable to continue") + module.fail_json(msg="iSCSI gateway creation/load failure " + "({})".format(gateway.error_msg)) + + + logger.info("END - GATEWAY configuration complete") + module.exit_json(changed=gateway.changes_made, + meta={"msg": "Gateway setup complete"}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/roles/ceph-iscsi-gw/library/igw_lun.py b/roles/ceph-iscsi-gw/library/igw_lun.py new file mode 100644 index 000000000..6554f3d2f --- /dev/null +++ b/roles/ceph-iscsi-gw/library/igw_lun.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +__author__ = 'pcuzner@redhat.com' + +DOCUMENTATION = """ +--- +module: igw_lun +short_description: Manage ceph rbd images to present as iscsi LUNs to clients +description: + - This module calls the 'lun' configuration management module installed + on the iscsi gateway node(s). The lun module handles the creation and resize + of rbd images, and then maps these rbd devices to the gateway node(s) to be + exposed through the kernel's LIO target. + + To support module debugging, this module logs to /var/log/ansible-module-igw_config.log + on the target machine(s). + +option: + pool: + description: + - The ceph pool where the image should exist or be created in. + + NOTE - The pool *must* exist prior to the Ansible run. + + required: true + + image: + description: + - this is the rbd image name to create/resize - if the rbd does not exist it + is created for you with the settings optimised for exporting over iscsi. + required: true + + size: + description: + - The size of the rbd image to create/resize. The size is numeric suffixed by + G or T (GB or TB). Increasing the size of a LUN is supported, but if a size + is provided that is smaller that the current size, the request is simply ignored. + + e.g. 100G + required: true + + host: + description: + - the host variable defines the name of the gateway node that will be + the allocation host for this rbd image. RBD creation and resize can + only be performed by one gateway, the other gateways in the + configuration will wait for the operation to complete. + required: true + + features: + description: + - placeholder to potentially allow different rbd features to be set at + allocation time by Ansible. NOT CURRENTLY USED + required: false + + state: + description: + - desired state for this LUN - absent or present. For a state='absent' + request, the lun module will verify that the rbd image is not allocated to + a client. As long as the rbd image is not in use, the LUN definition will be + removed from LIO, unmapped from all gateways AND DELETED. + + USE WITH CARE! + required: true + +requirements: ['ceph-iscsi-config'] + +author: + - 'Paul Cuzner' + +""" +import os +import logging +from logging.handlers import RotatingFileHandler + +from ansible.module_utils.basic import * + +from ceph_iscsi_config.lun import LUN +from ceph_iscsi_config.utils import valid_size +import ceph_iscsi_config.settings as settings + +# the main function is called ansible_main to allow the call stack +# to be checked to determine whether the call to the ceph_iscsi_config +# modules is from ansible or not +def ansible_main(): + + # Define the fields needs to create/map rbd's the the host(s) + # NB. features and state are reserved/unused + fields = { + "pool": {"required": False, "default": "rbd", "type": "str"}, + "image": {"required": True, "type": "str"}, + "size": {"required": True, "type": "str"}, + "host": {"required": True, "type": "str"}, + "features": {"required": False, "type": "str"}, + "state": { + "required": False, + "default": "present", + "choices": ['present', 'absent'], + "type": "str" + }, + } + + # not supporting check mode currently + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + pool = module.params["pool"] + image = module.params['image'] + size = module.params['size'] + allocating_host = module.params['host'] + desired_state = module.params['state'] + + ################################################ + # Validate the parameters passed from Ansible # + ################################################ + if not valid_size(size): + logger.critical("image '{}' has an invalid size specification '{}' " + "in the ansible configuration".format(image, + size)) + module.fail_json(msg="(main) Unable to use the size parameter '{}' " + "for image '{}' from the playbook - " + "must be a number suffixed by M,G " + "or T".format(size, + image)) + + # define a lun object and perform some initial parameter validation + lun = LUN(logger, pool, image, size, allocating_host) + if lun.error: + module.fail_json(msg=lun.error_msg) + + logger.info("START - LUN configuration started for {}/{}".format(pool, + image)) + + # attempt to create/allocate the LUN for LIO + lun.manage(desired_state) + if lun.error: + module.fail_json(msg=lun.error_msg) + + if lun.num_changes == 0: + logger.info("END - No changes needed") + else: + logger.info("END - {} configuration changes " + "made".format(lun.num_changes)) + + module.exit_json(changed=(lun.num_changes > 0), + meta={"msg": "Configuration updated"}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + # initialise global variables used by all called modules + # e.g. ceph conffile, keyring etc + settings.init() + + ansible_main() diff --git a/roles/ceph-iscsi-gw/library/igw_purge.py b/roles/ceph-iscsi-gw/library/igw_purge.py new file mode 100644 index 000000000..cffc4f434 --- /dev/null +++ b/roles/ceph-iscsi-gw/library/igw_purge.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python + +DOCUMENTATION = """ +--- +module: igw_purge +short_description: Provide a purge capability to remove an iSCSI gateway +environment +description: + - This module handles the removal of a gateway configuration from a ceph + environment. + The playbook that calls this module prompts the user for the type of purge + to perform. + The purge options are; + all ... purge all LIO configuration *and* delete all defined rbd images + lio ... purge only the LIO configuration (rbd's are left intact) + + USE WITH CAUTION + + To support module debugging, this module logs to + /var/log/ansible-module-igw_config.log on each target machine(s). + +option: + mode: + description: + - the mode defines the type of purge requested + gateway ... remove the LIO configuration only + disks ... remove the rbd disks defined to the gateway + required: true + +requirements: ['ceph-iscsi-config', 'python-rtslib'] + +author: + - 'Paul Cuzner' + +""" + +import os +import logging +import socket + +from logging.handlers import RotatingFileHandler +from ansible.module_utils.basic import * + +import ceph_iscsi_config.settings as settings +from ceph_iscsi_config.common import Config +from ceph_iscsi_config.lio import LIO, Gateway +from ceph_iscsi_config.utils import ipv4_addresses, get_ip + +__author__ = 'pcuzner@redhat.com' + + +def delete_group(module, image_list, cfg): + + logger.debug("RBD Images to delete are : {}".format(','.join(image_list))) + pending_list = list(image_list) + + for rbd_path in image_list: + if delete_rbd(module, rbd_path): + disk_key = rbd_path.replace('/', '.', 1) + cfg.del_item('disks', disk_key) + pending_list.remove(rbd_path) + cfg.changed = True + + if cfg.changed: + cfg.commit() + + return pending_list + + +def delete_rbd(module, rbd_path): + + logger.debug("issuing delete for {}".format(rbd_path)) + rm_cmd = 'rbd --no-progress rm {}'.format(rbd_path) + rc, rm_out, err = module.run_command(rm_cmd, use_unsafe_shell=True) + logger.debug("delete RC = {}, {}".format(rc, rm_out, err)) + + return True if rc == 0 else False + + +def is_cleanup_host(config): + """ + decide which gateway host should be responsible for any non-specific + updates to the config object + :param config: configuration dict from the rados pool + :return: boolean indicating whether the addition cleanup should be + performed by the running host + """ + cleanup = False + + if 'ip_list' in config.config["gateways"]: + + gw_1 = config.config["gateways"]["ip_list"][0] + + usable_ip = get_ip(gw_1) + if usable_ip != '0.0.0.0': + if usable_ip in ipv4_addresses(): + cleanup = True + + return cleanup + + +def ansible_main(): + + fields = {"mode": {"required": True, + "type": "str", + "choices": ["gateway", "disks"] + } + } + + module = AnsibleModule(argument_spec=fields, + supports_check_mode=False) + + run_mode = module.params['mode'] + changes_made = False + + logger.info("START - GATEWAY configuration PURGE started, run mode " + "is {}".format(run_mode)) + cfg = Config(logger) + this_host = socket.gethostname().split('.')[0] + perform_cleanup_tasks = is_cleanup_host(cfg) + + # + # Purge gateway configuration, if the config has gateways + if run_mode == 'gateway' and len(cfg.config['gateways'].keys()) > 0: + + lio = LIO() + gateway = Gateway(cfg) + + if gateway.session_count() > 0: + module.fail_json(msg="Unable to purge - gateway still has active " + "sessions") + + gateway.drop_target(this_host) + if gateway.error: + module.fail_json(msg=gateway.error_msg) + + lio.drop_lun_maps(cfg, perform_cleanup_tasks) + if lio.error: + module.fail_json(msg=lio.error_msg) + + if gateway.changed or lio.changed: + + # each gateway removes it's own entry from the config + cfg.del_item("gateways", this_host) + + if perform_cleanup_tasks: + cfg.reset = True + + # drop all client definitions from the configuration object + client_names = cfg.config["clients"].keys() + for client in client_names: + cfg.del_item("clients", client) + + cfg.del_item("gateways", "iqn") + cfg.del_item("gateways", "created") + cfg.del_item("gateways", "ip_list") + + cfg.commit() + + changes_made = True + + elif run_mode == 'disks' and len(cfg.config['disks'].keys()) > 0: + # + # Remove the disks on this host, that have been registered in the + # config object + # + # if the owner field for a disk is set to this host, this host can + # safely delete it + # nb. owner gets set at rbd allocation and mapping time + images_left = [] + + # delete_list will contain a list of pool/image names where the owner + # is this host + delete_list = [key.replace('.', '/', 1) for key in cfg.config['disks'] + if cfg.config['disks'][key]['owner'] == this_host] + + if delete_list: + images_left = delete_group(module, delete_list, cfg) + + # if the delete list still has entries we had problems deleting the + # images + if images_left: + module.fail_json(msg="Problems deleting the following rbd's : " + "{}".format(','.join(images_left))) + + changes_made = cfg.changed + + logger.debug("ending lock state variable {}".format(cfg.config_locked)) + + logger.info("END - GATEWAY configuration PURGE complete") + + module.exit_json(changed=changes_made, + meta={"msg": "Purge of iSCSI settings ({}) " + "complete".format(run_mode)}) + + +if __name__ == '__main__': + + module_name = os.path.basename(__file__).replace('ansible_module_', '') + logger = logging.getLogger(os.path.basename(module_name)) + logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler('/var/log/ansible-module-igw_config.log', + maxBytes=5242880, + backupCount=7) + log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s : ' + '%(message)s') + handler.setFormatter(log_fmt) + logger.addHandler(handler) + + settings.init() + + ansible_main() diff --git a/roles/ceph-iscsi-gw/meta/main.yml b/roles/ceph-iscsi-gw/meta/main.yml index ed97d539c..b96b9cfe8 100644 --- a/roles/ceph-iscsi-gw/meta/main.yml +++ b/roles/ceph-iscsi-gw/meta/main.yml @@ -1 +1,13 @@ --- +galaxy_info: + author: Paul Cuzner + description: Installs Ceph iSCSI Gateways + license: Apache + min_ansible_version: 2.3 + platforms: + - name: EL + versions: + - 7 + categories: + - system +dependencies: [] diff --git a/roles/ceph-iscsi-gw/tasks/configure_iscsi.yml b/roles/ceph-iscsi-gw/tasks/configure_iscsi.yml new file mode 100644 index 000000000..d270facb5 --- /dev/null +++ b/roles/ceph-iscsi-gw/tasks/configure_iscsi.yml @@ -0,0 +1,33 @@ +--- +- name: igw_gateway (tgt) | configure iscsi target (gateway) + igw_gateway: + mode: "target" + gateway_iqn: "{{ gateway_iqn }}" + gateway_ip_list: "{{ gateway_ip_list }}" + register: target + +- name: igw_lun | configure luns (create/map rbds and add to lio) + igw_lun: + pool: "{{ item.pool }}" + image: "{{ item.image }}" + size: "{{ item.size }}" + host: "{{ item.host }}" + state: "{{ item.state }}" + with_items: "{{ rbd_devices|default([]) }}" + register: images + +- name: igw_gateway (map) | map luns to the iscsi target + igw_gateway: + mode: "map" + gateway_iqn: "{{ gateway_iqn }}" + gateway_ip_list: "{{ gateway_ip_list }}" + register: luns + +- name: igw_client | configure client connectivity + igw_client: + client_iqn: "{{ item.client }}" + image_list: "{{ item.image_list }}" + chap: "{{ item.chap }}" + state: "{{ item.status }}" + with_items: "{{ client_connections|default([]) }}" + register: clients diff --git a/roles/ceph-iscsi-gw/tasks/deploy_ssl_keys.yml b/roles/ceph-iscsi-gw/tasks/deploy_ssl_keys.yml new file mode 100644 index 000000000..0665f0a7f --- /dev/null +++ b/roles/ceph-iscsi-gw/tasks/deploy_ssl_keys.yml @@ -0,0 +1,35 @@ +--- +- name: set crt path(s) + set_fact: + crt_files: + - "/etc/ceph/iscsi-gateway.crt" + - "/etc/ceph/iscsi-gateway.key" + - "/etc/ceph/iscsi-gateway.pem" + - "/etc/ceph/iscsi-gateway-pub.key" + +- name: stat for crt file(s) + local_action: stat path={{ fetch_directory }}/{{ fsid }}/{{ item }} + with_items: "{{ crt_files }}" + changed_when: false + failed_when: false + always_run: true + register: crt_files_exist + +- name: try to fetch crt file(s) + copy: + src: "{{ fetch_directory }}/{{ fsid }}/{{ item.0 }}" + dest: "{{ item.0 }}" + owner: root + group: root + mode: 0400 + changed_when: false + with_together: + - "{{ crt_files }}" + - "{{ crt_files_exist.results }}" + when: item.1.stat.exists == true + +- include: generate_crt.yml + with_together: + - "{{ crt_files }}" + - "{{ crt_files_exist.results }}" + when: item.1.stat.exists == false diff --git a/roles/ceph-iscsi-gw/tasks/generate_crt.yml b/roles/ceph-iscsi-gw/tasks/generate_crt.yml new file mode 100644 index 000000000..db53acbaa --- /dev/null +++ b/roles/ceph-iscsi-gw/tasks/generate_crt.yml @@ -0,0 +1,33 @@ +--- +- name: (local) create ssl crt/key files + shell: | + openssl req -newkey rsa:2048 -nodes -keyout /etc/ceph/iscsi-gateway.key -x509 -days 365 -out /etc/ceph/iscsi-gateway.crt -subj "/C=US/ST=./L=./O=RedHat/OU=Linux/CN={{ ansible_hostname }}" + run_once: True + +- name: (local) create pem + shell: | + cat /etc/ceph/iscsi-gateway.crt /etc/ceph/iscsi-gateway.key > /etc/ceph/iscsi-gateway.pem + run_once: True + register: pem + +- name: (local) create public key from pem + shell: | + openssl x509 -inform pem -in /etc/ceph/iscsi-gateway.pem -pubkey -noout > /etc/ceph/iscsi-gateway-pub.key + run_once: True + when: + - pem.changed + +- name: lock ssl file access to root only + file: + path: "{{ item }}" + mode: 0400 + owner: root + group: root + with_items: "{{ crt_files }}" + +- name: copy crt(s) to the ansible server + fetch: + src: "{{ item }}" + dest: "{{ fetch_directory }}/{{ fsid }}/{{ item }}" + flat: yes + with_items: "{{ crt_files }}" diff --git a/roles/ceph-iscsi-gw/tasks/main.yml b/roles/ceph-iscsi-gw/tasks/main.yml index ed97d539c..87102c523 100644 --- a/roles/ceph-iscsi-gw/tasks/main.yml +++ b/roles/ceph-iscsi-gw/tasks/main.yml @@ -1 +1,8 @@ --- +- include: prerequisites.yml + +# deploy_ssl_keys used the ansible controller to create self-signed crt/key/pub files +# and transfers them to /etc/ceph directory on each controller. SSL certs are used by +# the API for https support. +- include: deploy_ssl_keys.yml +- include: configure_iscsi.yml diff --git a/roles/ceph-iscsi-gw/tasks/prerequisites.yml b/roles/ceph-iscsi-gw/tasks/prerequisites.yml new file mode 100644 index 000000000..3cc4670bf --- /dev/null +++ b/roles/ceph-iscsi-gw/tasks/prerequisites.yml @@ -0,0 +1,34 @@ +--- +- name: check the status of the target.service override + stat: + path: /etc/systemd/system/target.service + register: target + +- name: mask the target service - preventing manual start + systemd: + name: target + masked: yes + enabled: no + when: + - target.stat.exists == False or (target.stat.exists and target.stat.islnk == False) + +- name: enable the rbd-target-gw service and make sure it is running + service: + name: rbd-target-gw + enabled: yes + state: started + +- name: copy admin key + copy: + src: "{{ fetch_directory }}/{{ fsid }}/etc/ceph/{{ cluster }}.client.admin.keyring" + dest: "/etc/ceph/{{ cluster }}.client.admin.keyring" + owner: "ceph" + group: "ceph" + mode: "0600" + when: + - cephx + +- name: deploy gateway settings, used by the ceph_iscsi_config modules + template: + src: "{{ role_path }}/templates/iscsi-gateway.cfg.j2" + dest: /etc/ceph/iscsi-gateway.cfg diff --git a/roles/ceph-iscsi-gw/templates/iscsi-gateway.cfg.j2 b/roles/ceph-iscsi-gw/templates/iscsi-gateway.cfg.j2 new file mode 100644 index 000000000..ce0d859f7 --- /dev/null +++ b/roles/ceph-iscsi-gw/templates/iscsi-gateway.cfg.j2 @@ -0,0 +1,17 @@ +# This is seed configuration used by the ceph_iscsi_config modules +# when handling configuration tasks for iscsi gateway(s) +# +# {{ ansible_managed }} + +[config] +cluster_name = {{ cluster }} +gateway_keyring = /etc/ceph/{{ cluster }}.client.admin.keyring + + +# Optional settings related to the CLI/API service +#api_user = admin +#api_password = admin +#api_port = 5001 +#api_secure = true +#loop_delay = .5 +#trusted_ip_list = 192.168.122.1 diff --git a/site.yml.sample b/site.yml.sample index f32621250..113e9bb03 100644 --- a/site.yml.sample +++ b/site.yml.sample @@ -12,6 +12,7 @@ - rbdmirrors - clients - mgrs + - iscsi_gws gather_facts: false @@ -140,3 +141,13 @@ - { role: ceph-common, when: "ceph_release_num.{{ ceph_stable_release }} > ceph_release_num.jewel" } - { role: ceph-config, when: "ceph_release_num.{{ ceph_stable_release }} > ceph_release_num.jewel" } - { role: ceph-mgr, when: "ceph_release_num.{{ ceph_stable_release }} > ceph_release_num.jewel" } + +- hosts: iscsi_gws + gather_facts: false + become: True + roles: + - { role: ceph-defaults, when: "ceph_release_num.{{ ceph_stable_release }} >= ceph_release_num.luminous" } + - { role: ceph-common, when: "ceph_release_num.{{ ceph_stable_release }} >= ceph_release_num.luminous" } + - { role: ceph-config, when: "ceph_release_num.{{ ceph_stable_release }} >= ceph_release_num.luminous" } + - { role: ceph-iscsi-gw, when: "ceph_release_num.{{ ceph_stable_release }} >= ceph_release_num.luminous" } + diff --git a/tests/functional/centos/7/cluster/hosts b/tests/functional/centos/7/cluster/hosts index 71408cdef..058639646 100644 --- a/tests/functional/centos/7/cluster/hosts +++ b/tests/functional/centos/7/cluster/hosts @@ -23,3 +23,6 @@ ceph-nfs0 [rbdmirrors] ceph-rbd-mirror0 + +#[iscsi_gws] +#ceph-iscsi-gw0 ceph_repository="dev"