commit 03e1952419153a6ce543773340e23f7c11db32e8 Author: Liam Young Date: Tue Feb 25 18:18:23 2020 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a65b417 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +lib diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c03af6a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "mod/operator"] + path = mod/operator + url = https://github.com/canonical/operator +[submodule "mod/interface-ceph-client"] + path = mod/interface-ceph-client + url = https://github.com/gnuoy/oper-interface-ceph-client.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..88ae53c --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +Ceph iSCSI Gateway charm +======================== + +To use, first pull in dependencies: + +```bash +./charm-prep.sh +``` + +To deploy with an example and test: + +```bash +cd test +./deploy.sh +./01-setup-client-apt.sh +./02-setup-gw.sh +./03-setup-client-iscsi.sh +``` diff --git a/charm-prep.sh b/charm-prep.sh new file mode 100755 index 0000000..4e57d26 --- /dev/null +++ b/charm-prep.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +rm -rf lib/* + +pip install -t lib/ git+https://github.com/juju/charm-helpers.git + +git submodule init +git submodule update +(cd lib; ln -s ../mod/operator/ops;) +(cd lib; ln -s ../mod/interface-ceph-client/interface_ceph_client.py;) diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..aa94446 --- /dev/null +++ b/config.yaml @@ -0,0 +1,49 @@ +options: + loglevel: + default: 1 + type: int + description: Mon and OSD debug level. Max is 20. + source: + type: string + default: + description: | + Optional configuration to support use of additional sources such as: + - ppa:myteam/ppa + - cloud:trusty-proposed/kilo + - http://my.archive.com/ubuntu main + The last option should be used in conjunction with the key configuration + option. + Note that a minimum ceph version of 0.48.2 is required for use with this + charm which is NOT provided by the packages in the main Ubuntu archive + for precise but is provided in the Ubuntu cloud archive. + key: + type: string + default: + description: | + Key ID to import to the apt keyring to support use with arbitary source + configuration from outside of Launchpad archives or PPA's. + use-syslog: + type: boolean + default: False + description: | + If set to True, supporting services will log to syslog. + ceph-public-network: + type: string + default: + description: | + The IP address and netmask of the public (front-side) network (e.g., + 192.168.0.0/24). + If multiple networks are to be used, a space-delimited list of a.b.c.d/x + can be provided. + prefer-ipv6: + type: boolean + default: False + description: | + If True enables IPv6 support. The charm will expect network interfaces + to be configured with an IPv6 address. If set to False (default) IPv4 + is expected. + + NOTE: these charms do not currently support IPv6 privacy extension. In + order for this charm to function correctly, the privacy extension must be + disabled and a non-temporary address must be configured/available on + your network interface. diff --git a/hooks/install b/hooks/install new file mode 120000 index 0000000..25b1f68 --- /dev/null +++ b/hooks/install @@ -0,0 +1 @@ +../src/charm.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..555ffc9 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,19 @@ +name: ceph-iscsi +summary: Gateway for provisioning iscsi devices backed by ceph. +maintainer: OpenStack Charmers +description: | + The iSCSI gateway is integrating Ceph Storage with the iSCSI standard to + provide a Highly Available (HA) iSCSI target that exports RADOS Block Device + (RBD) images as SCSI disks. +tags: + - openstack + - storage + - misc +series: + - focal +subordinate: false +requires: + ceph-client: + interface: ceph-client +extra-bindings: + public: diff --git a/mod/interface-ceph-client b/mod/interface-ceph-client new file mode 160000 index 0000000..93b4661 --- /dev/null +++ b/mod/interface-ceph-client @@ -0,0 +1 @@ +Subproject commit 93b4661be184753038da626d3a04d3855f948430 diff --git a/mod/operator b/mod/operator new file mode 160000 index 0000000..3a73427 --- /dev/null +++ b/mod/operator @@ -0,0 +1 @@ +Subproject commit 3a73427dee96f49acfe25880924528a3e57834cc diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..6b6e7a2 --- /dev/null +++ b/src/charm.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import sys + +sys.path.append('lib') + +from ops.charm import CharmBase +from ops.framework import ( + StoredState, +) +from ops.main import main +from charmhelpers.fetch import ( + apt_install, + apt_update, +) +import charmhelpers.core.host as ch_host +import charmhelpers.core.templating as ch_templating +import interface_ceph_client + + +class CephISCSIGatewayCharm(CharmBase): + state = StoredState() + + PACKAGES = ['ceph-iscsi', 'tcmu-runner', 'ceph-common'] + CEPH_CAPABILITIES = [ + "osd", "allow *", + "mon", "allow *", + "mgr", "allow r"] + + def __init__(self, framework, key): + super().__init__(framework, key) + self.framework.observe(self.on.install, self) + self.framework.observe(self.on.ceph_client_relation_joined, self) + self.ceph_client = interface_ceph_client.CephClientRequires( + self, + 'ceph-client') + self.framework.observe(self.ceph_client.on.pools_available, self) + + def on_install(self, event): + apt_update(fatal=True) + apt_install(self.PACKAGES, fatal=True) + + def on_ceph_client_relation_joined(self, event): + self.ceph_client.create_replicated_pool('rbd') + self.ceph_client.request_ceph_permissions( + 'ceph-iscsi', + self.CEPH_CAPABILITIES) + + def on_pools_available(self, event): + ceph_context = { + 'use_syslog': + str(self.framework.model.config['use-syslog']).lower(), + 'loglevel': self.framework.model.config['loglevel'] + } + ceph_context.update(self.ceph_client.get_pool_data()) + ceph_context['mon_hosts'] = ' '.join(ceph_context['mon_hosts']) + + restart_map = { + '/etc/ceph/ceph.conf': ['rbd-target-api'], + '/etc/ceph/iscsi-gateway.cfg': ['rbd-target-api'], + '/etc/ceph/ceph.client.ceph-iscsi.keyring': ['rbd-target-api']} + + def daemon_reload_and_restart(service_name): + subprocess.check_call(['systemctl', 'daemon-reload']) + subprocess.check_call(['systemctl', 'restart', service_name]) + + rfuncs = { + 'rbd-target-api': daemon_reload_and_restart} + + @ch_host.restart_on_change(restart_map, restart_functions=rfuncs) + def render_configs(): + for config_file in restart_map.keys(): + ch_templating.render( + os.path.basename(config_file), + config_file, + ceph_context) + render_configs() + + +if __name__ == '__main__': + main(CephISCSIGatewayCharm) diff --git a/templates/ceph.client.ceph-iscsi.keyring b/templates/ceph.client.ceph-iscsi.keyring new file mode 100644 index 0000000..fe13222 --- /dev/null +++ b/templates/ceph.client.ceph-iscsi.keyring @@ -0,0 +1,3 @@ +[client.ceph-iscsi] + key = {{ key }} + diff --git a/templates/ceph.conf b/templates/ceph.conf new file mode 100644 index 0000000..c8b3824 --- /dev/null +++ b/templates/ceph.conf @@ -0,0 +1,15 @@ +############################################################################### +# [ WARNING ] +# configuration file maintained by Juju +# local changes will be overwritten. +############################################################################### +[global] +auth_supported = {{ auth_supported }} +mon host = {{ mon_hosts }} +keyring = /etc/ceph/$cluster.$name.keyring + +[client.ceph-iscsi] +client mount uid = 0 +client mount gid = 0 +log file = /var/log/ceph/ceph-client.iscsi.log + diff --git a/templates/iscsi-gateway.cfg b/templates/iscsi-gateway.cfg new file mode 100644 index 0000000..26af98f --- /dev/null +++ b/templates/iscsi-gateway.cfg @@ -0,0 +1,27 @@ +[config] +# Name of the Ceph storage cluster. A suitable Ceph configuration file allowing +# # access to the Ceph storage cluster from the gateway node is required, if not +# # colocated on an OSD node. +logger_level = DEBUG +cluster_name = ceph +cluster_client_name = client.ceph-iscsi +# +# # Place a copy of the ceph cluster's admin keyring in the gateway's /etc/ceph +# # drectory and reference the filename here +#gateway_keyring = ceph.client.admin.keyring +gateway_keyring = ceph.client.ceph-iscsi.keyring +# +# +# # API settings. +# # The API supports a number of options that allow you to tailor it to your +# # local environment. If you want to run the API under https, you will need to +# # create cert/key files that are compatible for each iSCSI gateway node, that is +# # not locked to a specific node. SSL cert and key files *must* be called +# # 'iscsi-gateway.crt' and 'iscsi-gateway.key' and placed in the '/etc/ceph/' directory +# # on *each* gateway node. With the SSL files in place, you can use 'api_secure = true' +# # to switch to https mode. +# +# # To support the API, the bear minimum settings are: +api_secure = false +# +# diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..14b380e --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,13 @@ +# This file is managed centrally. If you find the need to modify this as a +# one-off, please don't. Intead, consult #openstack-charms and ask about +# requirements management in charms via bot-control. Thank you. +# +# Lint and unit test requirements +flake8>=2.2.4,<=2.4.1 +stestr>=2.2.0 +requests>=2.18.4 +charms.reactive +mock>=1.2 +nose>=1.3.7 +coverage>=3.6 +git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack diff --git a/tests/01-setup-client-apt.sh b/tests/01-setup-client-apt.sh new file mode 100755 index 0000000..65a9747 --- /dev/null +++ b/tests/01-setup-client-apt.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +client="ubuntu/0" + +juju run --unit $client "apt install --yes open-iscsi multipath-tools" +juju run --unit $client "systemctl start iscsi" +juju run --unit $client "systemctl start iscsid" diff --git a/tests/02-setup-gw.sh b/tests/02-setup-gw.sh new file mode 100755 index 0000000..25eeef7 --- /dev/null +++ b/tests/02-setup-gw.sh @@ -0,0 +1,26 @@ +#!/bin/bash -x + +gw1="ceph-iscsi/0" +gw2="ceph-iscsi/1" + +gw1_hostname=$(juju run --unit $gw1 "hostname -f") +gw2_hostname=$(juju run --unit $gw2 "hostname -f") +gw1_ip=$(juju status $gw1 --format=oneline | awk '{print $3}' | tr -d \\n ) +gw2_ip=$(juju status $gw2 --format=oneline | awk '{print $3}' | tr -d \\n ) +client_initiatorname=$(juju run --unit ubuntu/0 "grep -E '^InitiatorName' /etc/iscsi/initiatorname.iscsi") +client_initiatorname=$(echo $client_initiatorname | awk 'BEGIN {FS="="} {print $2}') +echo "!$gw1_hostname!" +echo "!$gw2_hostname!" +echo "!$gw1_ip!" +echo "!$gw2_ip!" +echo "!$client_initiatorname!" + +gw_iqn="iqn.2003-01.com.canonical.iscsi-gw:iscsi-igw" + +juju run --unit $gw1 "gwcli /iscsi-targets/ create $gw_iqn" +juju run --unit $gw1 "gwcli /iscsi-targets/${gw_iqn}/gateways create $gw1_hostname $gw1_ip skipchecks=true" +juju run --unit $gw1 "gwcli /iscsi-targets/${gw_iqn}/gateways create $gw2_hostname $gw2_ip skipchecks=true" +juju run --unit $gw1 "gwcli /disks create pool=rbd image=disk_1 size=1G" +juju run --unit $gw1 "gwcli /iscsi-targets/${gw_iqn}/hosts create ${client_initiatorname}" +juju run --unit $gw1 "gwcli /iscsi-targets/${gw_iqn}/hosts/${client_initiatorname} auth username=myiscsiusername password=myiscsipassword" +juju run --unit $gw1 "gwcli /iscsi-targets/${gw_iqn}/hosts/${client_initiatorname} disk add rbd/disk_1" diff --git a/tests/03-setup-client-iscsi.sh b/tests/03-setup-client-iscsi.sh new file mode 100755 index 0000000..df0913c --- /dev/null +++ b/tests/03-setup-client-iscsi.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +client="ubuntu/0" + +gw1="ceph-iscsi/0" + +gw1_ip=$(juju status $gw1 --format=oneline | awk '{print $3}' | tr -d \\n ) + +juju run --unit $client "iscsiadm -m discovery -t st -p $gw1_ip" + +target_name="iqn.2003-01.com.canonical.iscsi-gw:iscsi-igw" + +juju run --unit $client "iscsiadm --mode node --targetname ${target_name} --op=update --name node.session.auth.authmethod --value=CHAP" +juju run --unit $client "iscsiadm --mode node --targetname ${target_name} --op=update --name node.session.auth.username --value=myiscsiusername" +juju run --unit $client "iscsiadm --mode node --targetname ${target_name} --op=update --name node.session.auth.password --value=myiscsipassword" +juju run --unit $client "iscsiadm --mode node --targetname ${target_name} --login" +sleep 5 +juju ssh ubuntu/0 "ls -l /dev/dm-0" diff --git a/tests/deploy.sh b/tests/deploy.sh new file mode 100755 index 0000000..d8e831d --- /dev/null +++ b/tests/deploy.sh @@ -0,0 +1 @@ +juju deploy --force ./focal.yaml diff --git a/tests/focal.yaml b/tests/focal.yaml new file mode 100644 index 0000000..d045ad5 --- /dev/null +++ b/tests/focal.yaml @@ -0,0 +1,28 @@ +series: bionic +applications: + ubuntu: + charm: cs:ubuntu + num_units: 1 + ceph-iscsi: + charm: ../ + series: focal + num_units: 2 + ceph-osd: + charm: cs:~openstack-charmers-next/ceph-osd + num_units: 3 + storage: + osd-devices: 'cinder,10G' + options: + osd-devices: '/dev/test-non-existent' + source: cloud:bionic-train + ceph-mon: + charm: cs:~openstack-charmers-next/ceph-mon + num_units: 3 + options: + monitor-count: '3' + source: cloud:bionic-train +relations: +- - ceph-mon:client + - ceph-iscsi:ceph-client +- - ceph-osd:mon + - ceph-mon:osd diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f8f5092 --- /dev/null +++ b/tox.ini @@ -0,0 +1,92 @@ +# Source charm: ./tox.ini +# This file is managed centrally by release-tools and should not be modified +# within individual charm repos. See the 'global' dir contents for available +# choices of tox.ini for OpenStack Charms: +# https://github.com/openstack-charmers/release-tools + +[tox] +skipsdist = True +envlist = pep8,py3 +# NOTE: Avoid build/test env pollution by not enabling sitepackages. +sitepackages = False +# NOTE: Avoid false positives by not skipping missing interpreters. +skip_missing_interpreters = False + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 + TERM=linux + LAYER_PATH={toxinidir}/layers + INTERFACE_PATH={toxinidir}/interfaces + JUJU_REPOSITORY={toxinidir}/build +passenv = http_proxy https_proxy INTERFACE_PATH LAYER_PATH JUJU_REPOSITORY +install_command = + pip install {opts} {packages} +deps = + -r{toxinidir}/requirements.txt + +[testenv:build] +basepython = python3 +commands = + charm-build --log-level DEBUG -o {toxinidir}/build src {posargs} + +[testenv:py3] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py35] +basepython = python3.5 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py36] +basepython = python3.6 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py37] +basepython = python3.7 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} src unit_tests + +[testenv:cover] +# Technique based heavily upon +# https://github.com/openstack/nova/blob/master/tox.ini +basepython = python3 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +setenv = + {[testenv]setenv} + PYTHON=coverage run +commands = + coverage erase + stestr run --slowest {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report + +[coverage:run] +branch = True +concurrency = multiprocessing +parallel = True +source = + . +omit = + .tox/* + */charmhelpers/* + unit_tests/* + +[testenv:venv] +basepython = python3 +commands = {posargs} + +[flake8] +# E402 ignore necessary for path append before sys module import in actions +ignore = E402,W504 \ No newline at end of file