summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/cephadm/services/nfs.py
blob: ed751ec4ec0a8a8d2357c9bc53143beac70af172 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import logging

import rados
from typing import Dict, Optional, Tuple, Any, List, Set, cast

from ceph.deployment.service_spec import NFSServiceSpec

import orchestrator
from orchestrator import OrchestratorError
from orchestrator import DaemonDescription

import cephadm
from .. import utils

from .cephadmservice import CephadmService, CephadmDaemonSpec

logger = logging.getLogger(__name__)


class NFSService(CephadmService):
    TYPE = 'nfs'

    def generate_config(self, daemon_spec: CephadmDaemonSpec) -> Tuple[Dict[str, Any], List[str]]:
        assert self.TYPE == daemon_spec.daemon_type

        daemon_type = daemon_spec.daemon_type
        daemon_id = daemon_spec.daemon_id
        host = daemon_spec.host

        deps = []  # type: List[str]

        # find the matching NFSServiceSpec
        # TODO: find the spec and pass via _create_daemon instead ??
        dd = orchestrator.DaemonDescription()
        dd.daemon_type = daemon_type
        dd.daemon_id = daemon_id
        dd.hostname = host

        service_name = dd.service_name()
        specs = self.mgr.spec_store.find(service_name)

        if not specs:
            raise OrchestratorError('Cannot find service spec %s' % (service_name))
        elif len(specs) > 1:
            raise OrchestratorError('Found multiple service specs for %s' % (service_name))
        else:
            # cast to keep mypy happy
            spec = cast(NFSServiceSpec, specs[0])

        nfs = NFSGanesha(self.mgr, daemon_id, spec)

        # create the keyring
        entity = nfs.get_keyring_entity()
        keyring = nfs.get_or_create_keyring(entity=entity)

        # update the caps after get-or-create, the keyring might already exist!
        nfs.update_keyring_caps(entity=entity)

        # create the rados config object
        nfs.create_rados_config_obj()

        # generate the cephadm config
        cephadm_config = nfs.get_cephadm_config()
        cephadm_config.update(
                self.mgr._get_config_and_keyring(
                    daemon_type, daemon_id,
                    keyring=keyring))

        return cephadm_config, deps

    def config(self, spec: NFSServiceSpec) -> None:
        assert self.TYPE == spec.service_type
        self.mgr._check_pool_exists(spec.pool, spec.service_name())

        logger.info('Saving service %s spec with placement %s' % (
            spec.service_name(), spec.placement.pretty_str()))
        self.mgr.spec_store.save(spec)

    def create(self, daemon_spec: CephadmDaemonSpec[NFSServiceSpec]) -> str:
        assert self.TYPE == daemon_spec.daemon_type
        assert daemon_spec.spec

        daemon_id = daemon_spec.daemon_id
        host = daemon_spec.host
        spec = daemon_spec.spec

        logger.info('Create daemon %s on host %s with spec %s' % (
            daemon_id, host, spec))
        return self.mgr._create_daemon(daemon_spec)

    def config_dashboard(self, daemon_descrs: List[DaemonDescription]):
        
        def get_set_cmd_dicts(out: str) -> List[dict]:
            locations: Set[str] = set()
            for dd in daemon_descrs:
                spec = cast(NFSServiceSpec,
                            self.mgr.spec_store.specs.get(dd.service_name(), None))
                if not spec or not spec.service_id:
                    logger.warning('No ServiceSpec or service_id found for %s', dd)
                    continue
                location = '{}:{}'.format(spec.service_id, spec.pool)
                if spec.namespace:
                    location = '{}/{}'.format(location, spec.namespace)
                locations.add(location)
            new_value = ','.join(locations)
            if new_value and new_value != out:
                return [{'prefix': 'dashboard set-ganesha-clusters-rados-pool-namespace',
                         'value': new_value}]
            return []

        self._check_and_set_dashboard(
            service_name='Ganesha',
            get_cmd='dashboard get-ganesha-clusters-rados-pool-namespace',
            get_set_cmd_dicts=get_set_cmd_dicts
        )


class NFSGanesha(object):
    def __init__(self,
                 mgr,
                 daemon_id,
                 spec):
        # type: (cephadm.CephadmOrchestrator, str, NFSServiceSpec) -> None
        assert spec.service_id and daemon_id.startswith(spec.service_id)
        self.mgr = mgr
        self.daemon_id = daemon_id
        self.spec = spec

    def get_daemon_name(self) -> str:
        return '%s.%s' % (self.spec.service_type, self.daemon_id)

    def get_rados_user(self) -> str:
        return '%s.%s' % (self.spec.service_type, self.daemon_id)

    def get_keyring_entity(self) -> str:
        return utils.name_to_config_section(self.get_rados_user())

    def get_or_create_keyring(self, entity: Optional[str] = None) -> str:
        if not entity:
            entity = self.get_keyring_entity()

        logger.info('Create keyring: %s' % entity)
        ret, keyring, err = self.mgr.mon_command({
            'prefix': 'auth get-or-create',
            'entity': entity,
        })

        if ret != 0:
            raise OrchestratorError(
                    'Unable to create keyring %s: %s %s' \
                            % (entity, ret, err))
        return keyring

    def update_keyring_caps(self, entity: Optional[str] = None) -> None:
        if not entity:
            entity = self.get_keyring_entity()

        osd_caps='allow rw pool=%s' % (self.spec.pool)
        if self.spec.namespace:
            osd_caps='%s namespace=%s' % (osd_caps, self.spec.namespace)

        logger.info('Updating keyring caps: %s' % entity)
        ret, out, err = self.mgr.mon_command({
            'prefix': 'auth caps',
            'entity': entity,
            'caps': ['mon', 'allow r',
                     'osd', osd_caps],
        })

        if ret != 0:
            raise OrchestratorError(
                    'Unable to update keyring caps %s: %s %s' \
                            % (entity, ret, err))

    def create_rados_config_obj(self, clobber: Optional[bool] = False) -> None:
        obj = self.spec.rados_config_name()

        with self.mgr.rados.open_ioctx(self.spec.pool) as ioctx:
            if self.spec.namespace:
                ioctx.set_namespace(self.spec.namespace)

            exists = True
            try:
                ioctx.stat(obj)
            except rados.ObjectNotFound as e:
                exists = False

            if exists and not clobber:
                # Assume an existing config
                logger.info('Rados config object exists: %s' % obj)
            else:
                # Create an empty config object
                logger.info('Creating rados config object: %s' % obj)
                ioctx.write_full(obj, ''.encode('utf-8'))

    def get_ganesha_conf(self) -> str:
        context = dict(user=self.get_rados_user(),
                       nodeid=self.get_daemon_name(),
                       pool=self.spec.pool,
                       namespace=self.spec.namespace if self.spec.namespace else '',
                       url=self.spec.rados_config_location())
        return self.mgr.template.render('services/nfs/ganesha.conf.j2', context)

    def get_cephadm_config(self) -> Dict[str, Any]:
        config = {'pool' : self.spec.pool} # type: Dict
        if self.spec.namespace:
            config['namespace'] = self.spec.namespace
        config['userid'] = self.get_rados_user()
        config['extra_args'] = ['-N', 'NIV_EVENT']
        config['files'] = {
            'ganesha.conf' : self.get_ganesha_conf(),
        }
        logger.debug('Generated cephadm config-json: %s' % config)
        return config