Update to get ganesha setup.
This change also includes a new helper class to actually create CephFS shares that are then provided by Ganesha.
This commit is contained in:
parent
b8e7c454ae
commit
3b5c303aa7
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,5 +4,4 @@ __pycache__
|
|||||||
.stestr/
|
.stestr/
|
||||||
lib/*
|
lib/*
|
||||||
!lib/README.txt
|
!lib/README.txt
|
||||||
build
|
*.charm
|
||||||
ceph-iscsi.charm
|
|
||||||
|
25
config.yaml
25
config.yaml
@ -8,10 +8,25 @@
|
|||||||
# Learn more about config at: https://juju.is/docs/sdk/config
|
# Learn more about config at: https://juju.is/docs/sdk/config
|
||||||
|
|
||||||
options:
|
options:
|
||||||
thing:
|
source:
|
||||||
default: 🎁
|
|
||||||
description: A thing used by the charm.
|
|
||||||
type: string
|
type: string
|
||||||
|
default: ppa:chris.macnaughton/focal-ussuri
|
||||||
|
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.
|
||||||
ceph-osd-replication-count:
|
ceph-osd-replication-count:
|
||||||
type: int
|
type: int
|
||||||
default: 3
|
default: 3
|
||||||
@ -41,5 +56,5 @@ options:
|
|||||||
default:
|
default:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
Optionally specify an existing pool that shares should map to. Defaults
|
Optionally specify an existing pool that Ganesha should store recovery
|
||||||
to the application's name.
|
data into. Defaults to the application's name.
|
||||||
|
@ -5,7 +5,6 @@ description: |
|
|||||||
The NFS gateway is provided by NFS-Ganesha and provides NFS shares
|
The NFS gateway is provided by NFS-Ganesha and provides NFS shares
|
||||||
that are backed by CephFS.
|
that are backed by CephFS.
|
||||||
tags:
|
tags:
|
||||||
- openstack
|
|
||||||
- storage
|
- storage
|
||||||
- misc
|
- misc
|
||||||
series:
|
series:
|
||||||
@ -20,3 +19,6 @@ extra-bindings:
|
|||||||
requires:
|
requires:
|
||||||
ceph-client:
|
ceph-client:
|
||||||
interface: ceph-client
|
interface: ceph-client
|
||||||
|
peers:
|
||||||
|
cluster:
|
||||||
|
interface: ceph-nfs-peer
|
@ -2,4 +2,4 @@
|
|||||||
git+https://github.com/juju/charm-helpers.git#egg=charmhelpers
|
git+https://github.com/juju/charm-helpers.git#egg=charmhelpers
|
||||||
git+https://github.com/canonical/operator.git#egg=ops
|
git+https://github.com/canonical/operator.git#egg=ops
|
||||||
git+https://opendev.org/openstack/charm-ops-interface-ceph-client#egg=interface_ceph_client
|
git+https://opendev.org/openstack/charm-ops-interface-ceph-client#egg=interface_ceph_client
|
||||||
git+https://opendev.org/openstack/charm-ops-openstack#egg=ops_openstack
|
git+https://github.com/ChrisMacNaughton/charm-ops-openstack.git@feature/charm-instance-to-relation-adapter#egg=ops_openstack
|
||||||
|
109
src/charm.py
109
src/charm.py
@ -15,9 +15,9 @@ develop a new k8s charm using the Operator Framework:
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from ops.charm import CharmBase
|
|
||||||
from ops.framework import StoredState
|
from ops.framework import StoredState
|
||||||
from ops.main import main
|
from ops.main import main
|
||||||
# from ops.model import ActiveStatus
|
# from ops.model import ActiveStatus
|
||||||
@ -25,6 +25,9 @@ from ops.main import main
|
|||||||
import charmhelpers.core.host as ch_host
|
import charmhelpers.core.host as ch_host
|
||||||
import charmhelpers.core.templating as ch_templating
|
import charmhelpers.core.templating as ch_templating
|
||||||
import interface_ceph_client.ceph_client as ceph_client
|
import interface_ceph_client.ceph_client as ceph_client
|
||||||
|
import interface_ceph_nfs_peer
|
||||||
|
# TODO: Add the below class functionaity to action / relations
|
||||||
|
# from ganesha import GaneshaNfs
|
||||||
|
|
||||||
import ops_openstack.adapters
|
import ops_openstack.adapters
|
||||||
import ops_openstack.core
|
import ops_openstack.core
|
||||||
@ -65,6 +68,32 @@ class CephClientAdapter(ops_openstack.adapters.OpenStackOperRelationAdapter):
|
|||||||
return self.relation.get_relation_data()['key']
|
return self.relation.get_relation_data()['key']
|
||||||
|
|
||||||
|
|
||||||
|
class CephNFSContext(object):
|
||||||
|
"""Adapter for ceph NFS config."""
|
||||||
|
|
||||||
|
name = 'ceph_nfs'
|
||||||
|
|
||||||
|
def __init__(self, charm_instance):
|
||||||
|
self.charm_instance = charm_instance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pool_name(self):
|
||||||
|
"""The name of the default rbd data pool to be used for shares.
|
||||||
|
|
||||||
|
:returns: Data pool name.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self.charm_instance.config_get('rbd-pool-name', self.charm_instance.app.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_name(self):
|
||||||
|
return self.charm_instance.app.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hostname(self):
|
||||||
|
return socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
class CephNFSAdapters(
|
class CephNFSAdapters(
|
||||||
ops_openstack.adapters.OpenStackRelationAdapters):
|
ops_openstack.adapters.OpenStackRelationAdapters):
|
||||||
"""Collection of relation adapters."""
|
"""Collection of relation adapters."""
|
||||||
@ -74,13 +103,14 @@ class CephNFSAdapters(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CephNfsCharm(CharmBase):
|
class CephNfsCharm(
|
||||||
|
ops_openstack.plugins.classes.BaseCephClientCharm):
|
||||||
"""Ceph NFS Base Charm."""
|
"""Ceph NFS Base Charm."""
|
||||||
|
|
||||||
_stored = StoredState()
|
PACKAGES = ['nfs-ganesha-ceph', 'nfs-ganesha-rados-grace', 'ceph-common']
|
||||||
PACKAGES = ['nfs-ganesha', 'ceph-common']
|
|
||||||
|
|
||||||
CEPH_CAPABILITIES = [
|
CEPH_CAPABILITIES = [
|
||||||
|
"mgr", "allow rw",
|
||||||
"mds", "allow *",
|
"mds", "allow *",
|
||||||
"osd", "allow rw",
|
"osd", "allow rw",
|
||||||
"mon", "allow r, "
|
"mon", "allow r, "
|
||||||
@ -89,14 +119,14 @@ class CephNfsCharm(CharmBase):
|
|||||||
"allow command \"auth get\", "
|
"allow command \"auth get\", "
|
||||||
"allow command \"auth get-or-create\""]
|
"allow command \"auth get-or-create\""]
|
||||||
|
|
||||||
REQUIRED_RELATIONS = ['ceph-client', 'cluster']
|
REQUIRED_RELATIONS = ['ceph-client']
|
||||||
|
|
||||||
CEPH_CONFIG_PATH = Path('/etc/ceph')
|
CEPH_CONFIG_PATH = Path('/etc/ceph')
|
||||||
GANESHA_CONFIG_PATH = Path('/etc/ganesha')
|
GANESHA_CONFIG_PATH = Path('/etc/ganesha')
|
||||||
|
|
||||||
CEPH_GANESHA_CONFIG_PATH = CEPH_CONFIG_PATH / 'ganesha'
|
CEPH_GANESHA_CONFIG_PATH = CEPH_CONFIG_PATH / 'ganesha'
|
||||||
CEPH_CONF = CEPH_GANESHA_CONFIG_PATH / 'ceph.conf'
|
CEPH_CONF = CEPH_CONFIG_PATH / 'ceph.conf'
|
||||||
GANESHA_KEYRING = CEPH_GANESHA_CONFIG_PATH / 'ceph.client.ceph-ganesha.keyring'
|
GANESHA_KEYRING = CEPH_GANESHA_CONFIG_PATH / 'ceph.keyring'
|
||||||
GANESHA_CONF = GANESHA_CONFIG_PATH / 'ganesha.conf'
|
GANESHA_CONF = GANESHA_CONFIG_PATH / 'ganesha.conf'
|
||||||
|
|
||||||
SERVICES = ['nfs-ganesha']
|
SERVICES = ['nfs-ganesha']
|
||||||
@ -114,13 +144,18 @@ class CephNfsCharm(CharmBase):
|
|||||||
logging.info("Using %s class", self.release)
|
logging.info("Using %s class", self.release)
|
||||||
self._stored.set_default(
|
self._stored.set_default(
|
||||||
is_started=False,
|
is_started=False,
|
||||||
|
is_cluster_setup=False
|
||||||
)
|
)
|
||||||
self.ceph_client = ceph_client.CephClientRequires(
|
self.ceph_client = ceph_client.CephClientRequires(
|
||||||
self,
|
self,
|
||||||
'ceph-client')
|
'ceph-client')
|
||||||
|
self.peers = interface_ceph_nfs_peer.CephNfsPeers(
|
||||||
|
self,
|
||||||
|
'cluster')
|
||||||
self.adapters = CephNFSAdapters(
|
self.adapters = CephNFSAdapters(
|
||||||
(self.ceph_client,),
|
(self.ceph_client, self.peers),
|
||||||
self)
|
contexts=(CephNFSContext(self),),
|
||||||
|
charm_instance=self)
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.ceph_client.on.broker_available,
|
self.ceph_client.on.broker_available,
|
||||||
self.request_ceph_pool)
|
self.request_ceph_pool)
|
||||||
@ -133,14 +168,20 @@ class CephNfsCharm(CharmBase):
|
|||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.on.upgrade_charm,
|
self.on.upgrade_charm,
|
||||||
self.render_config)
|
self.render_config)
|
||||||
|
self.framework.observe(
|
||||||
|
self.ceph_client.on.pools_available,
|
||||||
|
self.setup_ganesha),
|
||||||
|
self.framework.observe(
|
||||||
|
self.peers.on.pool_initialised,
|
||||||
|
self.on_pool_initialised)
|
||||||
|
|
||||||
def config_get(self, key):
|
def config_get(self, key, default=None):
|
||||||
"""Retrieve config option.
|
"""Retrieve config option.
|
||||||
|
|
||||||
:returns: Value of the corresponding config option or None.
|
:returns: Value of the corresponding config option or None.
|
||||||
:rtype: Any
|
:rtype: Any
|
||||||
"""
|
"""
|
||||||
return self.model.config.get(key)
|
return self.model.config.get(key, default)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pool_name(self):
|
def pool_name(self):
|
||||||
@ -149,11 +190,7 @@ class CephNfsCharm(CharmBase):
|
|||||||
:returns: Data pool name.
|
:returns: Data pool name.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
if self.config_get('rbd-pool-name'):
|
return self.config_get('rbd-pool-name', self.app.name)
|
||||||
pool_name = self.config_get('rbd-pool-name')
|
|
||||||
else:
|
|
||||||
pool_name = self.app.name
|
|
||||||
return pool_name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client_name(self):
|
def client_name(self):
|
||||||
@ -180,6 +217,7 @@ class CephNfsCharm(CharmBase):
|
|||||||
logging.info("Requesting replicated pool")
|
logging.info("Requesting replicated pool")
|
||||||
self.ceph_client.create_replicated_pool(
|
self.ceph_client.create_replicated_pool(
|
||||||
name=self.pool_name,
|
name=self.pool_name,
|
||||||
|
app_name='ganesha',
|
||||||
replicas=replicas,
|
replicas=replicas,
|
||||||
weight=weight,
|
weight=weight,
|
||||||
**bcomp_kwargs)
|
**bcomp_kwargs)
|
||||||
@ -200,7 +238,7 @@ class CephNfsCharm(CharmBase):
|
|||||||
event.defer()
|
event.defer()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.CEPH_GANESHA_PATH.mkdir(
|
self.CEPH_GANESHA_CONFIG_PATH.mkdir(
|
||||||
exist_ok=True,
|
exist_ok=True,
|
||||||
mode=0o750)
|
mode=0o750)
|
||||||
|
|
||||||
@ -223,16 +261,35 @@ class CephNfsCharm(CharmBase):
|
|||||||
self._stored.is_started = True
|
self._stored.is_started = True
|
||||||
self.update_status()
|
self.update_status()
|
||||||
logging.info("on_pools_available: status updated")
|
logging.info("on_pools_available: status updated")
|
||||||
|
if not self._stored.is_cluster_setup:
|
||||||
|
subprocess.check_call([
|
||||||
|
'ganesha-rados-grace', '--userid', self.client_name,
|
||||||
|
'--cephconf', '/etc/ceph/ganesha/ceph.conf', '--pool', self.pool_name,
|
||||||
|
'add', socket.gethostname()])
|
||||||
|
self._stored.is_cluster_setup = True
|
||||||
|
|
||||||
# def custom_status_check(self):
|
def setup_ganesha(self, event):
|
||||||
# """Custom update status checks."""
|
if not self.model.unit.is_leader():
|
||||||
# if ch_host.is_container():
|
return
|
||||||
# return ops.model.BlockedStatus(
|
cmd = [
|
||||||
# 'Charm cannot be deployed into a container')
|
'rados', '-p', self.pool_name,
|
||||||
# if self.peers.unit_count not in self.ALLOWED_UNIT_COUNTS:
|
'-c', '/etc/ceph/ganesha/ceph.conf',
|
||||||
# return ops.model.BlockedStatus(
|
'--id', self.client_name,
|
||||||
# '{} is an invalid unit count'.format(self.peers.unit_count))
|
'put', 'ganesha-export-index', '/dev/null'
|
||||||
# return ops.model.ActiveStatus()
|
]
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
self.peers.pool_initialised()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("Failed to setup ganesha index object")
|
||||||
|
event.defer()
|
||||||
|
|
||||||
|
def on_pool_initialised(self, event):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(['systemctl', 'restart', 'nfs-ganesha'])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("Failed torestart nfs-ganesha")
|
||||||
|
event.defer()
|
||||||
|
|
||||||
|
|
||||||
@ops_openstack.core.charm_class
|
@ops_openstack.core.charm_class
|
||||||
|
153
src/ganesha.py
Normal file
153
src/ganesha.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 OpenStack Charmers
|
||||||
|
# See LICENSE file for licensing details.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Add ACL with client IPs
|
||||||
|
# TODO: Add ACL with kerberos
|
||||||
|
GANESHA_EXPORT_TEMPLATE = """EXPORT {{
|
||||||
|
# Each EXPORT must have a unique Export_Id.
|
||||||
|
Export_Id = {id};
|
||||||
|
|
||||||
|
# The directory in the exported file system this export
|
||||||
|
# is rooted on.
|
||||||
|
Path = '{path}';
|
||||||
|
|
||||||
|
# FSAL, Ganesha's module component
|
||||||
|
FSAL {{
|
||||||
|
# FSAL name
|
||||||
|
Name = "Ceph";
|
||||||
|
User_Id = "{user_id}";
|
||||||
|
Secret_Access_Key = "{secret_key}";
|
||||||
|
}}
|
||||||
|
|
||||||
|
# Path of export in the NFSv4 pseudo filesystem
|
||||||
|
Pseudo = '{path}';
|
||||||
|
|
||||||
|
SecType = "sys";
|
||||||
|
CLIENT {{
|
||||||
|
Access_Type = "rw";
|
||||||
|
Clients = 0.0.0.0;
|
||||||
|
}}
|
||||||
|
# User id squashing, one of None, Root, All
|
||||||
|
Squash = "None";
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class GaneshaNfs(object):
|
||||||
|
|
||||||
|
export_index = "ganesha-export-index"
|
||||||
|
export_counter = "ganesha-export-counter"
|
||||||
|
|
||||||
|
def __init__(self, client_name, ceph_pool):
|
||||||
|
self.client_name = client_name
|
||||||
|
self.name = str(uuid.uuid4())
|
||||||
|
self.ceph_pool = ceph_pool
|
||||||
|
self.access_id = 'ganesha-{}'.format(self.name)
|
||||||
|
|
||||||
|
def create_share(self):
|
||||||
|
self.export_path = self._create_cephfs_share()
|
||||||
|
export_id = self._get_next_export_id()
|
||||||
|
export_template = GANESHA_EXPORT_TEMPLATE.format(
|
||||||
|
id=export_id,
|
||||||
|
path=self.export_path,
|
||||||
|
user_id=self.access_id,
|
||||||
|
secret_key=self._ceph_auth_key(),
|
||||||
|
)
|
||||||
|
logging.debug("Export template:: \n{}".format(export_template))
|
||||||
|
tmp_file = self._tmpfile(export_template)
|
||||||
|
self.rados_put('ganesha-export-{}'.format(export_id), tmp_file.name)
|
||||||
|
self._ganesha_add_export(self.export_path, tmp_file.name)
|
||||||
|
|
||||||
|
def _ganesha_add_export(self, export_path, tmp_path):
|
||||||
|
return self._dbus_send(
|
||||||
|
'ExportMgr', 'AddExport',
|
||||||
|
'string:{}'.format(tmp_path), 'string:EXPORT(Path={})'.format(export_path))
|
||||||
|
|
||||||
|
def _dbus_send(self, section, action, *args):
|
||||||
|
cmd = [
|
||||||
|
'dbus-send', '--print-reply', '--system', '--dest=org.ganesha.nfsd',
|
||||||
|
'/org/ganesha/nfsd/{}'.format(section),
|
||||||
|
'org.ganesha.nfsd.exportmgr.{}'.format(action)] + [*args]
|
||||||
|
logging.debug("About to call: {}".format(cmd))
|
||||||
|
return subprocess.check_output(cmd)
|
||||||
|
|
||||||
|
def _create_cephfs_share(self):
|
||||||
|
"""Create an authorise a CephFS share.
|
||||||
|
|
||||||
|
:returns: export path
|
||||||
|
:rtype: union[str, bool]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._ceph_subvolume_command('create', 'ceph-fs', self.name)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("failed to create subvolume")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._ceph_subvolume_command(
|
||||||
|
'authorize', 'ceph-fs', self.name,
|
||||||
|
'ganesha-{name}'.format(name=self.name))
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("failed to authorize subvolume")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
output = self._ceph_subvolume_command('getpath', 'ceph-fs', self.name)
|
||||||
|
return output.decode('utf-8').strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("failed to get path")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _ceph_subvolume_command(self, *cmd):
|
||||||
|
return self._ceph_fs_command('subvolume', *cmd)
|
||||||
|
|
||||||
|
def _ceph_fs_command(self, *cmd):
|
||||||
|
return self._ceph_command('fs', *cmd)
|
||||||
|
|
||||||
|
def _ceph_auth_key(self):
|
||||||
|
output = self._ceph_command(
|
||||||
|
'auth', 'get', 'client.{}'.format(self.access_id), '--format=json')
|
||||||
|
return json.loads(output.decode('UTF-8'))[0]['key']
|
||||||
|
|
||||||
|
def _ceph_command(self, *cmd):
|
||||||
|
cmd = ["ceph", "--id", self.client_name, "--conf=/etc/ceph/ganesha/ceph.conf"] + [*cmd]
|
||||||
|
return subprocess.check_output(cmd)
|
||||||
|
|
||||||
|
def _get_next_export_id(self):
|
||||||
|
next_id = int(self.rados_get(self.export_counter))
|
||||||
|
file = self._tmpfile(next_id + 1)
|
||||||
|
self.rados_put(self.export_counter, file.name)
|
||||||
|
return next_id
|
||||||
|
|
||||||
|
def _tmpfile(self, value):
|
||||||
|
file = tempfile.NamedTemporaryFile(mode='w+')
|
||||||
|
file.write(str(value))
|
||||||
|
file.seek(0)
|
||||||
|
return file
|
||||||
|
|
||||||
|
def rados_get(self, name):
|
||||||
|
cmd = [
|
||||||
|
'rados', '-p', self.ceph_pool, '--id', self.client_name,
|
||||||
|
'get', name, '/dev/stdout'
|
||||||
|
]
|
||||||
|
logging.debug("About to call: {}".format(cmd))
|
||||||
|
output = subprocess.check_output(cmd)
|
||||||
|
return output.decode('utf-8')
|
||||||
|
|
||||||
|
def rados_put(self, name, source):
|
||||||
|
cmd = [
|
||||||
|
'rados', '-p', self.ceph_pool, '--id', self.client_name,
|
||||||
|
'put', name, source
|
||||||
|
]
|
||||||
|
logging.debug("About to call: {}".format(cmd))
|
||||||
|
subprocess.check_call(cmd)
|
51
src/interface_ceph_nfs_peer.py
Normal file
51
src/interface_ceph_nfs_peer.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# import json
|
||||||
|
import logging
|
||||||
|
# import socket
|
||||||
|
|
||||||
|
from ops.framework import (
|
||||||
|
StoredState,
|
||||||
|
EventBase,
|
||||||
|
ObjectEvents,
|
||||||
|
EventSource,
|
||||||
|
Object)
|
||||||
|
|
||||||
|
|
||||||
|
class PoolInitialisedEvent(EventBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CephNfsPeerEvents(ObjectEvents):
|
||||||
|
pool_initialised = EventSource(PoolInitialisedEvent)
|
||||||
|
|
||||||
|
|
||||||
|
class CephNfsPeers(Object):
|
||||||
|
|
||||||
|
on = CephNfsPeerEvents()
|
||||||
|
_stored = StoredState()
|
||||||
|
|
||||||
|
def __init__(self, charm, relation_name):
|
||||||
|
super().__init__(charm, relation_name)
|
||||||
|
self.relation_name = relation_name
|
||||||
|
self.this_unit = self.framework.model.unit
|
||||||
|
self._stored.set_default(
|
||||||
|
pool_initialised=False)
|
||||||
|
self.framework.observe(
|
||||||
|
charm.on[relation_name].relation_changed,
|
||||||
|
self.on_changed)
|
||||||
|
|
||||||
|
def on_changed(self, event):
|
||||||
|
logging.info("CephNfsPeers on_changed")
|
||||||
|
if self.pool_initialised == 'True' and not self._stored.pool_initialised:
|
||||||
|
self.on.pool_initialised.emit()
|
||||||
|
self._stored.pool_initialised = True
|
||||||
|
|
||||||
|
def pool_initialised(self):
|
||||||
|
logging.info("Setting pool initialised")
|
||||||
|
self.peer_rel.data[self.peer_rel.app]['pool_initialised'] = 'True'
|
||||||
|
self.on.pool_initialised.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def peer_rel(self):
|
||||||
|
return self.framework.model.get_relation(self.relation_name)
|
@ -1,3 +0,0 @@
|
|||||||
[client.ceph-nfs]
|
|
||||||
key = {{ ceph_client.key }}
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
|||||||
[global]
|
[global]
|
||||||
auth supported = {{ ceph_client.auth_supported }}
|
auth supported = {{ ceph_client.auth_supported }}
|
||||||
mon host = {{ ceph_client.mon_hosts }}
|
mon host = {{ ceph_client.mon_hosts }}
|
||||||
keyring = /etc/ceph/{{ options.application_name }}/$cluster.$name.keyring
|
keyring = /etc/ceph/ganesha/$cluster.keyring
|
||||||
|
|
||||||
[client.{{ options.application_name }}]
|
[client.{{ ceph_nfs.client_name }}]
|
||||||
client mount uid = 0
|
client mount uid = 0
|
||||||
client mount gid = 0
|
client mount gid = 0
|
||||||
log file = /var/log/ceph/ceph-client.{{ options.application_name }}.log
|
log file = /var/log/ceph/ceph-client.{{ ceph_nfs.client_name }}.log
|
||||||
{% endif -%}
|
|
||||||
|
3
templates/ceph.keyring
Normal file
3
templates/ceph.keyring
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[client.{{ ceph_nfs.client_name }}]
|
||||||
|
key = {{ ceph_client.key }}
|
||||||
|
|
@ -66,112 +66,20 @@ MDCACHE {
|
|||||||
Dir_Chunk = 0;
|
Dir_Chunk = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT
|
|
||||||
{
|
|
||||||
# Unique export ID number for this export
|
|
||||||
Export_ID=100;
|
|
||||||
|
|
||||||
# We're only interested in NFSv4 in this configuration
|
|
||||||
Protocols = 4;
|
|
||||||
|
|
||||||
# NFSv4 does not allow UDP transport
|
|
||||||
Transports = TCP;
|
|
||||||
|
|
||||||
#
|
|
||||||
# Path into the cephfs tree.
|
|
||||||
#
|
|
||||||
# Note that FSAL_CEPH does not support subtree checking, so there is
|
|
||||||
# no way to validate that a filehandle presented by a client is
|
|
||||||
# reachable via an exported subtree.
|
|
||||||
#
|
|
||||||
# For that reason, we just export "/" here.
|
|
||||||
Path = /;
|
|
||||||
|
|
||||||
#
|
|
||||||
# The pseudoroot path. This is where the export will appear in the
|
|
||||||
# NFS pseudoroot namespace.
|
|
||||||
#
|
|
||||||
Pseudo = /cephfs_a/;
|
|
||||||
|
|
||||||
# We want to be able to read and write
|
|
||||||
Access_Type = RW;
|
|
||||||
|
|
||||||
# Time out attribute cache entries immediately
|
|
||||||
Attr_Expiration_Time = 0;
|
|
||||||
|
|
||||||
# Enable read delegations? libcephfs v13.0.1 and later allow the
|
|
||||||
# ceph client to set a delegation. While it's possible to allow RW
|
|
||||||
# delegations it's not recommended to enable them until ganesha
|
|
||||||
# acquires CB_GETATTR support.
|
|
||||||
#
|
|
||||||
# Note too that delegations may not be safe in clustered
|
|
||||||
# configurations, so it's probably best to just disable them until
|
|
||||||
# this problem is resolved:
|
|
||||||
#
|
|
||||||
# http://tracker.ceph.com/issues/24802
|
|
||||||
#
|
|
||||||
# Delegations = R;
|
|
||||||
|
|
||||||
# NFS servers usually decide to "squash" incoming requests from the
|
|
||||||
# root user to a "nobody" user. It's possible to disable that, but for
|
|
||||||
# now, we leave it enabled.
|
|
||||||
# Squash = root;
|
|
||||||
|
|
||||||
FSAL {
|
|
||||||
# FSAL_CEPH export
|
|
||||||
Name = CEPH;
|
|
||||||
|
|
||||||
#
|
|
||||||
# Ceph filesystems have a name string associated with them, and
|
|
||||||
# modern versions of libcephfs can mount them based on the
|
|
||||||
# name. The default is to mount the default filesystem in the
|
|
||||||
# cluster (usually the first one created).
|
|
||||||
#
|
|
||||||
# Filesystem = "cephfs_a";
|
|
||||||
|
|
||||||
#
|
|
||||||
# Ceph clusters have their own authentication scheme (cephx).
|
|
||||||
# Ganesha acts as a cephfs client. This is the client username
|
|
||||||
# to use. This user will need to be created before running
|
|
||||||
# ganesha.
|
|
||||||
#
|
|
||||||
# Typically ceph clients have a name like "client.foo". This
|
|
||||||
# setting should not contain the "client." prefix.
|
|
||||||
#
|
|
||||||
# See:
|
|
||||||
#
|
|
||||||
# http://docs.ceph.com/docs/jewel/rados/operations/user-management/
|
|
||||||
#
|
|
||||||
# The default is to set this to NULL, which means that the
|
|
||||||
# userid is set to the default in libcephfs (which is
|
|
||||||
# typically "admin").
|
|
||||||
#
|
|
||||||
User_Id = "{{ client_name }}";
|
|
||||||
|
|
||||||
#
|
|
||||||
# Key to use for the session (if any). If not set, it uses the
|
|
||||||
# normal search path for cephx keyring files to find a key:
|
|
||||||
#
|
|
||||||
# Secret_Access_Key = "YOUR SECRET KEY HERE";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# The below were taken from the Manila docs at
|
|
||||||
# https://docs.openstack.org/manila/queens/contributor/ganesha.html
|
|
||||||
|
|
||||||
# To read exports from RADOS objects
|
# To read exports from RADOS objects
|
||||||
RADOS_URLS {
|
RADOS_URLS {
|
||||||
ceph_conf = "/etc/ceph/ganesha/ceph.conf";
|
ceph_conf = "/etc/ceph/ganesha/ceph.conf";
|
||||||
userid = "{{ client_name }}";
|
userid = "{{ ceph_nfs.client_name }}";
|
||||||
}
|
}
|
||||||
|
|
||||||
%url rados://{{ pool_name }}/ganesha-export-index
|
%url rados://{{ ceph_nfs.pool_name }}/ganesha-export-index
|
||||||
# To store client recovery data in the same RADOS pool
|
# To store client recovery data in the same RADOS pool
|
||||||
|
|
||||||
RADOS_KV {
|
RADOS_KV {
|
||||||
ceph_conf = "/etc/ceph/ganesha/ceph.conf";
|
ceph_conf = "/etc/ceph/ganesha/ceph.conf";
|
||||||
userid = "{{ client_name }}";
|
userid = "{{ ceph_nfs.client_name }}";
|
||||||
pool = {{ pool_name }};
|
pool = "{{ ceph_nfs.pool_name }}";
|
||||||
|
nodeid = "{{ ceph_nfs.hostname }}";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Config block for FSAL_CEPH
|
# Config block for FSAL_CEPH
|
||||||
|
@ -20,8 +20,14 @@ applications:
|
|||||||
options:
|
options:
|
||||||
monitor-count: '3'
|
monitor-count: '3'
|
||||||
expected-osd-count: 6
|
expected-osd-count: 6
|
||||||
|
ceph-fs:
|
||||||
|
charm: cs:~openstack-charmers-next/ceph-fs
|
||||||
|
num_units: 1
|
||||||
|
|
||||||
relations:
|
relations:
|
||||||
- - 'ceph-mon:client'
|
- - 'ceph-mon:client'
|
||||||
- 'ceph-nfs:ceph-client'
|
- 'ceph-nfs:ceph-client'
|
||||||
- - 'ceph-osd:mon'
|
- - 'ceph-osd:mon'
|
||||||
- 'ceph-mon:osd'
|
- 'ceph-mon:osd'
|
||||||
|
- - 'ceph-fs'
|
||||||
|
- 'ceph-mon'
|
||||||
|
@ -9,4 +9,4 @@ tests: []
|
|||||||
target_deploy_status:
|
target_deploy_status:
|
||||||
ubuntu:
|
ubuntu:
|
||||||
workload-status: active
|
workload-status: active
|
||||||
workload-status-message: ''
|
workload-status-message-prefix: ''
|
||||||
|
Loading…
x
Reference in New Issue
Block a user