Browse Source

Update rabbit driver config options

The stein version of python-oslo.messaging (9.0.0+) has removed
the following config options from the [oslo_messaging_rabbit]
section:

rabbit_host, rabbit_port, rabbit_hosts, rabbit_userid,
rabbit_password, rabbit_virtual_host rabbit_max_retries, and
rabbit_durable_queues.

The above change requires a sync from charm-helpers.

Additionally the transport_url directive has been moved to the
[DEFAULT] section.

These have been deprecated since Ocata, therefore this change
will be provided to pre-Stein templates in order to drop
deprecation warnings.

See release notes at:
https://docs.openstack.org/releasenotes/oslo.messaging/index.html

Change-Id: I77e3437a5241c9ded2ef5000639f984222bc4803
Closes-Bug: #1817672
changes/75/639275/2
Corey Bryant 3 months ago
parent
commit
5e3ffa90e6

+ 134
- 0
hooks/charmhelpers/contrib/openstack/audits/__init__.py View File

@@ -0,0 +1,134 @@
1
+# Copyright 2019 Canonical Limited.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#  http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+"""OpenStack Security Audit code"""
16
+
17
+import collections
18
+from enum import Enum
19
+import traceback
20
+
21
+from charmhelpers.core.host import cmp_pkgrevno
22
+
23
+import charmhelpers.core.hookenv as hookenv
24
+
25
+
26
+class AuditType(Enum):
27
+    OpenStackSecurityGuide = 1
28
+
29
+
30
+_audits = {}
31
+
32
+Audit = collections.namedtuple('Audit', 'func filters')
33
+
34
+
35
+def audit(*args):
36
+    """Decorator to register an audit.
37
+
38
+    These are used to generate audits that can be run on a
39
+    deployed system that matches the given configuration
40
+
41
+    :param args: List of functions to filter tests against
42
+    :type args: List[Callable(Config)]
43
+    """
44
+    def wrapper(f):
45
+        test_name = f.__name__
46
+        if _audits.get(test_name):
47
+            raise RuntimeError(
48
+                "Test name '{}' used more than once"
49
+                .format(test_name))
50
+        non_callables = [fn for fn in args if not callable(fn)]
51
+        if non_callables:
52
+            raise RuntimeError(
53
+                "Configuration includes non-callable filters: {}"
54
+                .format(non_callables))
55
+        _audits[test_name] = Audit(func=f, filters=args)
56
+        return f
57
+    return wrapper
58
+
59
+
60
+def is_audit_type(*args):
61
+    """This audit is included in the specified kinds of audits."""
62
+    def should_run(audit_options):
63
+        if audit_options.get('audit_type') in args:
64
+            return True
65
+        else:
66
+            return False
67
+    return should_run
68
+
69
+
70
+def since_package(pkg, pkg_version):
71
+    """This audit should be run after the specified package version (incl)."""
72
+    return lambda audit_options=None: cmp_pkgrevno(pkg, pkg_version) >= 0
73
+
74
+
75
+def before_package(pkg, pkg_version):
76
+    """This audit should be run before the specified package version (excl)."""
77
+    return lambda audit_options=None: not since_package(pkg, pkg_version)()
78
+
79
+
80
+def it_has_config(config_key):
81
+    """This audit should be run based on specified config keys."""
82
+    return lambda audit_options: audit_options.get(config_key) is not None
83
+
84
+
85
+def run(audit_options):
86
+    """Run the configured audits with the specified audit_options.
87
+
88
+    :param audit_options: Configuration for the audit
89
+    :type audit_options: Config
90
+    """
91
+    errors = {}
92
+    results = {}
93
+    for name, audit in sorted(_audits.items()):
94
+        result_name = name.replace('_', '-')
95
+        if all(p(audit_options) for p in audit.filters):
96
+            try:
97
+                audit.func(audit_options)
98
+                print("{}: PASS".format(name))
99
+                results[result_name] = {
100
+                    'success': True,
101
+                }
102
+            except AssertionError as e:
103
+                print("{}: FAIL ({})".format(name, e))
104
+                results[result_name] = {
105
+                    'success': False,
106
+                    'message': e,
107
+                }
108
+            except Exception as e:
109
+                print("{}: ERROR ({})".format(name, e))
110
+                errors[name] = e
111
+                results[result_name] = {
112
+                    'success': False,
113
+                    'message': e,
114
+                }
115
+    for name, error in errors.items():
116
+        print("=" * 20)
117
+        print("Error in {}: ".format(name))
118
+        traceback.print_tb(error.__traceback__)
119
+        print()
120
+    return results
121
+
122
+
123
+def action_parse_results(result):
124
+    """Parse the result of `run` in the context of an action."""
125
+    passed = True
126
+    for test, result in result.items():
127
+        if result['success']:
128
+            hookenv.action_set({test: 'PASS'})
129
+        else:
130
+            hookenv.action_set({test: 'FAIL - {}'.format(result['message'])})
131
+            passed = False
132
+    if not passed:
133
+        hookenv.action_fail("One or more tests failed")
134
+    return 0 if passed else 1

+ 303
- 0
hooks/charmhelpers/contrib/openstack/audits/openstack_security_guide.py View File

@@ -0,0 +1,303 @@
1
+# Copyright 2019 Canonical Limited.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#  http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import collections
16
+import configparser
17
+import glob
18
+import os.path
19
+import subprocess
20
+
21
+from charmhelpers.contrib.openstack.audits import (
22
+    audit,
23
+    AuditType,
24
+    # filters
25
+    is_audit_type,
26
+    it_has_config,
27
+)
28
+
29
+from charmhelpers.core.hookenv import (
30
+    cached,
31
+)
32
+
33
+
34
+FILE_ASSERTIONS = {
35
+    'barbican': {
36
+        # From security guide
37
+        '/etc/barbican/barbican.conf': {'group': 'barbican', 'mode': '640'},
38
+        '/etc/barbican/barbican-api-paste.ini':
39
+            {'group': 'barbican', 'mode': '640'},
40
+        '/etc/barbican/policy.json': {'group': 'barbican', 'mode': '640'},
41
+    },
42
+    'ceph-mon': {
43
+        '/var/lib/charm/ceph-mon/ceph.conf':
44
+            {'owner': 'root', 'group': 'root', 'mode': '644'},
45
+        '/etc/ceph/ceph.client.admin.keyring':
46
+            {'owner': 'ceph', 'group': 'ceph'},
47
+        '/etc/ceph/rbdmap': {'mode': '644'},
48
+        '/var/lib/ceph': {'owner': 'ceph', 'group': 'ceph', 'mode': '750'},
49
+        '/var/lib/ceph/bootstrap-*/ceph.keyring':
50
+            {'owner': 'ceph', 'group': 'ceph', 'mode': '600'}
51
+    },
52
+    'ceph-osd': {
53
+        '/var/lib/charm/ceph-osd/ceph.conf':
54
+            {'owner': 'ceph', 'group': 'ceph', 'mode': '644'},
55
+        '/var/lib/ceph': {'owner': 'ceph', 'group': 'ceph', 'mode': '750'},
56
+        '/var/lib/ceph/*': {'owner': 'ceph', 'group': 'ceph', 'mode': '755'},
57
+        '/var/lib/ceph/bootstrap-*/ceph.keyring':
58
+            {'owner': 'ceph', 'group': 'ceph', 'mode': '600'},
59
+        '/var/lib/ceph/radosgw':
60
+            {'owner': 'ceph', 'group': 'ceph', 'mode': '755'},
61
+    },
62
+    'cinder': {
63
+        # From security guide
64
+        '/etc/cinder/cinder.conf': {'group': 'cinder', 'mode': '640'},
65
+        '/etc/cinder/api-paste.conf': {'group': 'cinder', 'mode': '640'},
66
+        '/etc/cinder/rootwrap.conf': {'group': 'cinder', 'mode': '640'},
67
+    },
68
+    'glance': {
69
+        # From security guide
70
+        '/etc/glance/glance-api-paste.ini': {'group': 'glance', 'mode': '640'},
71
+        '/etc/glance/glance-api.conf': {'group': 'glance', 'mode': '640'},
72
+        '/etc/glance/glance-cache.conf': {'group': 'glance', 'mode': '640'},
73
+        '/etc/glance/glance-manage.conf': {'group': 'glance', 'mode': '640'},
74
+        '/etc/glance/glance-registry-paste.ini':
75
+            {'group': 'glance', 'mode': '640'},
76
+        '/etc/glance/glance-registry.conf': {'group': 'glance', 'mode': '640'},
77
+        '/etc/glance/glance-scrubber.conf': {'group': 'glance', 'mode': '640'},
78
+        '/etc/glance/glance-swift-store.conf':
79
+            {'group': 'glance', 'mode': '640'},
80
+        '/etc/glance/policy.json': {'group': 'glance', 'mode': '640'},
81
+        '/etc/glance/schema-image.json': {'group': 'glance', 'mode': '640'},
82
+        '/etc/glance/schema.json': {'group': 'glance', 'mode': '640'},
83
+    },
84
+    'keystone': {
85
+        # From security guide
86
+        '/etc/keystone/keystone.conf': {'group': 'keystone', 'mode': '640'},
87
+        '/etc/keystone/keystone-paste.ini':
88
+            {'group': 'keystone', 'mode': '640'},
89
+        '/etc/keystone/policy.json': {'group': 'keystone', 'mode': '640'},
90
+        '/etc/keystone/logging.conf': {'group': 'keystone', 'mode': '640'},
91
+        '/etc/keystone/ssl/certs/signing_cert.pem':
92
+            {'group': 'keystone', 'mode': '640'},
93
+        '/etc/keystone/ssl/private/signing_key.pem':
94
+            {'group': 'keystone', 'mode': '640'},
95
+        '/etc/keystone/ssl/certs/ca.pem': {'group': 'keystone', 'mode': '640'},
96
+    },
97
+    'manilla': {
98
+        # From security guide
99
+        '/etc/manila/manila.conf': {'group': 'manilla', 'mode': '640'},
100
+        '/etc/manila/api-paste.ini': {'group': 'manilla', 'mode': '640'},
101
+        '/etc/manila/policy.json': {'group': 'manilla', 'mode': '640'},
102
+        '/etc/manila/rootwrap.conf': {'group': 'manilla', 'mode': '640'},
103
+    },
104
+    'neutron-gateway': {
105
+        '/etc/neutron/neutron.conf': {'group': 'neutron', 'mode': '640'},
106
+        '/etc/neutron/rootwrap.conf': {'mode': '640'},
107
+        '/etc/neutron/rootwrap.d': {'mode': '755'},
108
+        '/etc/neutron/*': {'group': 'neutron', 'mode': '644'},
109
+    },
110
+    'neutron-api': {
111
+        # From security guide
112
+        '/etc/neutron/neutron.conf': {'group': 'neutron', 'mode': '640'},
113
+        '/etc/nova/api-paste.ini': {'group': 'neutron', 'mode': '640'},
114
+        '/etc/neutron/rootwrap.conf': {'group': 'neutron', 'mode': '640'},
115
+        # Additional validations
116
+        '/etc/neutron/rootwrap.d': {'mode': '755'},
117
+        '/etc/neutron/neutron_lbaas.conf': {'mode': '644'},
118
+        '/etc/neutron/neutron_vpnaas.conf': {'mode': '644'},
119
+        '/etc/neutron/*': {'group': 'neutron', 'mode': '644'},
120
+    },
121
+    'nova-cloud-controller': {
122
+        # From security guide
123
+        '/etc/nova/api-paste.ini': {'group': 'nova', 'mode': '640'},
124
+        '/etc/nova/nova.conf': {'group': 'nova', 'mode': '750'},
125
+        '/etc/nova/*': {'group': 'nova', 'mode': '640'},
126
+        # Additional validations
127
+        '/etc/nova/logging.conf': {'group': 'nova', 'mode': '640'},
128
+    },
129
+    'nova-compute': {
130
+        # From security guide
131
+        '/etc/nova/nova.conf': {'group': 'nova', 'mode': '640'},
132
+        '/etc/nova/api-paste.ini': {'group': 'nova', 'mode': '640'},
133
+        '/etc/nova/rootwrap.conf': {'group': 'nova', 'mode': '640'},
134
+        # Additional Validations
135
+        '/etc/nova/nova-compute.conf': {'group': 'nova', 'mode': '640'},
136
+        '/etc/nova/logging.conf': {'group': 'nova', 'mode': '640'},
137
+        '/etc/nova/nm.conf': {'mode': '644'},
138
+        '/etc/nova/*': {'group': 'nova', 'mode': '640'},
139
+    },
140
+    'openstack-dashboard': {
141
+        # From security guide
142
+        '/etc/openstack-dashboard/local_settings.py':
143
+            {'group': 'horizon', 'mode': '640'},
144
+    },
145
+}
146
+
147
+Ownership = collections.namedtuple('Ownership', 'owner group mode')
148
+
149
+
150
+@cached
151
+def _stat(file):
152
+    """
153
+    Get the Ownership information from a file.
154
+
155
+    :param file: The path to a file to stat
156
+    :type file: str
157
+    :returns: owner, group, and mode of the specified file
158
+    :rtype: Ownership
159
+    :raises subprocess.CalledProcessError: If the underlying stat fails
160
+    """
161
+    out = subprocess.check_output(
162
+        ['stat', '-c', '%U %G %a', file]).decode('utf-8')
163
+    return Ownership(*out.strip().split(' '))
164
+
165
+
166
+@cached
167
+def _config_ini(path):
168
+    """
169
+    Parse an ini file
170
+
171
+    :param path: The path to a file to parse
172
+    :type file: str
173
+    :returns: Configuration contained in path
174
+    :rtype: Dict
175
+    """
176
+    conf = configparser.ConfigParser()
177
+    conf.read(path)
178
+    return dict(conf)
179
+
180
+
181
+def _validate_file_ownership(owner, group, file_name):
182
+    """
183
+    Validate that a specified file is owned by `owner:group`.
184
+
185
+    :param owner: Name of the owner
186
+    :type owner: str
187
+    :param group: Name of the group
188
+    :type group: str
189
+    :param file_name: Path to the file to verify
190
+    :type file_name: str
191
+    """
192
+    try:
193
+        ownership = _stat(file_name)
194
+    except subprocess.CalledProcessError as e:
195
+        print("Error reading file: {}".format(e))
196
+        assert False, "Specified file does not exist: {}".format(file_name)
197
+    assert owner == ownership.owner, \
198
+        "{} has an incorrect owner: {} should be {}".format(
199
+            file_name, ownership.owner, owner)
200
+    assert group == ownership.group, \
201
+        "{} has an incorrect group: {} should be {}".format(
202
+            file_name, ownership.group, group)
203
+    print("Validate ownership of {}: PASS".format(file_name))
204
+
205
+
206
+def _validate_file_mode(mode, file_name):
207
+    """
208
+    Validate that a specified file has the specified permissions.
209
+
210
+    :param mode: file mode that is desires
211
+    :type owner: str
212
+    :param file_name: Path to the file to verify
213
+    :type file_name: str
214
+    """
215
+    try:
216
+        ownership = _stat(file_name)
217
+    except subprocess.CalledProcessError as e:
218
+        print("Error reading file: {}".format(e))
219
+        assert False, "Specified file does not exist: {}".format(file_name)
220
+    assert mode == ownership.mode, \
221
+        "{} has an incorrect mode: {} should be {}".format(
222
+            file_name, ownership.mode, mode)
223
+    print("Validate mode of {}: PASS".format(file_name))
224
+
225
+
226
+@cached
227
+def _config_section(config, section):
228
+    """Read the configuration file and return a section."""
229
+    path = os.path.join(config.get('config_path'), config.get('config_file'))
230
+    conf = _config_ini(path)
231
+    return conf.get(section)
232
+
233
+
234
+@audit(is_audit_type(AuditType.OpenStackSecurityGuide),
235
+       it_has_config('files'))
236
+def validate_file_ownership(config):
237
+    """Verify that configuration files are owned by the correct user/group."""
238
+    files = config.get('files', {})
239
+    for file_name, options in files.items():
240
+        for key in options.keys():
241
+            if key not in ["owner", "group", "mode"]:
242
+                raise RuntimeError(
243
+                    "Invalid ownership configuration: {}".format(key))
244
+        owner = options.get('owner', config.get('owner', 'root'))
245
+        group = options.get('group', config.get('group', 'root'))
246
+        if '*' in file_name:
247
+            for file in glob.glob(file_name):
248
+                if file not in files.keys():
249
+                    if os.path.isfile(file):
250
+                        _validate_file_ownership(owner, group, file)
251
+        else:
252
+            if os.path.isfile(file_name):
253
+                _validate_file_ownership(owner, group, file_name)
254
+
255
+
256
+@audit(is_audit_type(AuditType.OpenStackSecurityGuide),
257
+       it_has_config('files'))
258
+def validate_file_permissions(config):
259
+    """Verify that permissions on configuration files are secure enough."""
260
+    files = config.get('files', {})
261
+    for file_name, options in files.items():
262
+        for key in options.keys():
263
+            if key not in ["owner", "group", "mode"]:
264
+                raise RuntimeError(
265
+                    "Invalid ownership configuration: {}".format(key))
266
+        mode = options.get('mode', config.get('permissions', '600'))
267
+        if '*' in file_name:
268
+            for file in glob.glob(file_name):
269
+                if file not in files.keys():
270
+                    if os.path.isfile(file):
271
+                        _validate_file_mode(mode, file)
272
+        else:
273
+            if os.path.isfile(file_name):
274
+                _validate_file_mode(mode, file_name)
275
+
276
+
277
+@audit(is_audit_type(AuditType.OpenStackSecurityGuide))
278
+def validate_uses_keystone(audit_options):
279
+    """Validate that the service uses Keystone for authentication."""
280
+    section = _config_section(audit_options, 'DEFAULT')
281
+    assert section is not None, "Missing section 'DEFAULT'"
282
+    assert section.get('auth_strategy') == "keystone", \
283
+        "Application is not using Keystone"
284
+
285
+
286
+@audit(is_audit_type(AuditType.OpenStackSecurityGuide))
287
+def validate_uses_tls_for_keystone(audit_options):
288
+    """Verify that TLS is used to communicate with Keystone."""
289
+    section = _config_section(audit_options, 'keystone_authtoken')
290
+    assert section is not None, "Missing section 'keystone_authtoken'"
291
+    assert not section.get('insecure') and \
292
+        "https://" in section.get("auth_uri"), \
293
+        "TLS is not used for Keystone"
294
+
295
+
296
+@audit(is_audit_type(AuditType.OpenStackSecurityGuide))
297
+def validate_uses_tls_for_glance(audit_options):
298
+    """Verify that TLS is used to communicate with Glance."""
299
+    section = _config_section(audit_options, 'glance')
300
+    assert section is not None, "Missing section 'glance'"
301
+    assert not section.get('insecure') and \
302
+        "https://" in section.get("api_servers"), \
303
+        "TLS is not used for Glance"

+ 2
- 1
hooks/charmhelpers/contrib/openstack/context.py View File

@@ -29,6 +29,7 @@ from charmhelpers.fetch import (
29 29
     filter_installed_packages,
30 30
 )
31 31
 from charmhelpers.core.hookenv import (
32
+    NoNetworkBinding,
32 33
     config,
33 34
     is_relation_made,
34 35
     local_unit,
@@ -868,7 +869,7 @@ class ApacheSSLContext(OSContextGenerator):
868 869
                     addr = network_get_primary_address(
869 870
                         ADDRESS_MAP[net_type]['binding']
870 871
                     )
871
-                except NotImplementedError:
872
+                except (NotImplementedError, NoNetworkBinding):
872 873
                     addr = fallback
873 874
 
874 875
             endpoint = resolve_address(net_type)

+ 2
- 1
hooks/charmhelpers/contrib/openstack/ip.py View File

@@ -13,6 +13,7 @@
13 13
 # limitations under the License.
14 14
 
15 15
 from charmhelpers.core.hookenv import (
16
+    NoNetworkBinding,
16 17
     config,
17 18
     unit_get,
18 19
     service_name,
@@ -175,7 +176,7 @@ def resolve_address(endpoint_type=PUBLIC, override=True):
175 176
             #       configuration is not in use
176 177
             try:
177 178
                 resolved_address = network_get_primary_address(binding)
178
-            except NotImplementedError:
179
+            except (NotImplementedError, NoNetworkBinding):
179 180
                 resolved_address = fallback_addr
180 181
 
181 182
     if resolved_address is None:

+ 10
- 0
hooks/charmhelpers/contrib/openstack/templates/section-oslo-messaging-rabbit View File

@@ -0,0 +1,10 @@
1
+[oslo_messaging_rabbit]
2
+{% if rabbitmq_ha_queues -%}
3
+rabbit_ha_queues = True
4
+{% endif -%}
5
+{% if rabbit_ssl_port -%}
6
+ssl = True
7
+{% endif -%}
8
+{% if rabbit_ssl_ca -%}
9
+ssl_ca_file = {{ rabbit_ssl_ca }}
10
+{% endif -%}

+ 49
- 38
hooks/charmhelpers/contrib/storage/linux/ceph.py View File

@@ -59,6 +59,7 @@ from charmhelpers.core.host import (
59 59
     service_stop,
60 60
     service_running,
61 61
     umount,
62
+    cmp_pkgrevno,
62 63
 )
63 64
 from charmhelpers.fetch import (
64 65
     apt_install,
@@ -178,7 +179,6 @@ class Pool(object):
178 179
         """
179 180
         # read-only is easy, writeback is much harder
180 181
         mode = get_cache_mode(self.service, cache_pool)
181
-        version = ceph_version()
182 182
         if mode == 'readonly':
183 183
             check_call(['ceph', '--id', self.service, 'osd', 'tier', 'cache-mode', cache_pool, 'none'])
184 184
             check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove', self.name, cache_pool])
@@ -186,7 +186,7 @@ class Pool(object):
186 186
         elif mode == 'writeback':
187 187
             pool_forward_cmd = ['ceph', '--id', self.service, 'osd', 'tier',
188 188
                                 'cache-mode', cache_pool, 'forward']
189
-            if version >= '10.1':
189
+            if cmp_pkgrevno('ceph', '10.1') >= 0:
190 190
                 # Jewel added a mandatory flag
191 191
                 pool_forward_cmd.append('--yes-i-really-mean-it')
192 192
 
@@ -196,7 +196,8 @@ class Pool(object):
196 196
             check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove-overlay', self.name])
197 197
             check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove', self.name, cache_pool])
198 198
 
199
-    def get_pgs(self, pool_size, percent_data=DEFAULT_POOL_WEIGHT):
199
+    def get_pgs(self, pool_size, percent_data=DEFAULT_POOL_WEIGHT,
200
+                device_class=None):
200 201
         """Return the number of placement groups to use when creating the pool.
201 202
 
202 203
         Returns the number of placement groups which should be specified when
@@ -229,6 +230,9 @@ class Pool(object):
229 230
             increased. NOTE: the default is primarily to handle the scenario
230 231
             where related charms requiring pools has not been upgraded to
231 232
             include an update to indicate their relative usage of the pools.
233
+        :param device_class: str. class of storage to use for basis of pgs
234
+            calculation; ceph supports nvme, ssd and hdd by default based
235
+            on presence of devices of each type in the deployment.
232 236
         :return: int.  The number of pgs to use.
233 237
         """
234 238
 
@@ -243,17 +247,20 @@ class Pool(object):
243 247
 
244 248
         # If the expected-osd-count is specified, then use the max between
245 249
         # the expected-osd-count and the actual osd_count
246
-        osd_list = get_osds(self.service)
250
+        osd_list = get_osds(self.service, device_class)
247 251
         expected = config('expected-osd-count') or 0
248 252
 
249 253
         if osd_list:
250
-            osd_count = max(expected, len(osd_list))
254
+            if device_class:
255
+                osd_count = len(osd_list)
256
+            else:
257
+                osd_count = max(expected, len(osd_list))
251 258
 
252 259
             # Log a message to provide some insight if the calculations claim
253 260
             # to be off because someone is setting the expected count and
254 261
             # there are more OSDs in reality. Try to make a proper guess
255 262
             # based upon the cluster itself.
256
-            if expected and osd_count != expected:
263
+            if not device_class and expected and osd_count != expected:
257 264
                 log("Found more OSDs than provided expected count. "
258 265
                     "Using the actual count instead", INFO)
259 266
         elif expected:
@@ -626,7 +633,8 @@ def remove_erasure_profile(service, profile_name):
626 633
 def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure',
627 634
                            failure_domain='host',
628 635
                            data_chunks=2, coding_chunks=1,
629
-                           locality=None, durability_estimator=None):
636
+                           locality=None, durability_estimator=None,
637
+                           device_class=None):
630 638
     """
631 639
     Create a new erasure code profile if one does not already exist for it.  Updates
632 640
     the profile if it exists. Please see http://docs.ceph.com/docs/master/rados/operations/erasure-code-profile/
@@ -640,10 +648,9 @@ def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure'
640 648
     :param coding_chunks: int
641 649
     :param locality: int
642 650
     :param durability_estimator: int
651
+    :param device_class: six.string_types
643 652
     :return: None.  Can raise CalledProcessError
644 653
     """
645
-    version = ceph_version()
646
-
647 654
     # Ensure this failure_domain is allowed by Ceph
648 655
     validator(failure_domain, six.string_types,
649 656
               ['chassis', 'datacenter', 'host', 'osd', 'pdu', 'pod', 'rack', 'region', 'room', 'root', 'row'])
@@ -654,12 +661,20 @@ def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure'
654 661
     if locality is not None and durability_estimator is not None:
655 662
         raise ValueError("create_erasure_profile should be called with k, m and one of l or c but not both.")
656 663
 
664
+    luminous_or_later = cmp_pkgrevno('ceph', '12.0.0') >= 0
657 665
     # failure_domain changed in luminous
658
-    if version and version >= '12.0.0':
666
+    if luminous_or_later:
659 667
         cmd.append('crush-failure-domain=' + failure_domain)
660 668
     else:
661 669
         cmd.append('ruleset-failure-domain=' + failure_domain)
662 670
 
671
+    # device class new in luminous
672
+    if luminous_or_later and device_class:
673
+        cmd.append('crush-device-class={}'.format(device_class))
674
+    else:
675
+        log('Skipping device class configuration (ceph < 12.0.0)',
676
+            level=DEBUG)
677
+
663 678
     # Add plugin specific information
664 679
     if locality is not None:
665 680
         # For local erasure codes
@@ -744,20 +759,26 @@ def pool_exists(service, name):
744 759
     return name in out.split()
745 760
 
746 761
 
747
-def get_osds(service):
762
+def get_osds(service, device_class=None):
748 763
     """Return a list of all Ceph Object Storage Daemons currently in the
749
-    cluster.
764
+    cluster (optionally filtered by storage device class).
765
+
766
+    :param device_class: Class of storage device for OSD's
767
+    :type device_class: str
750 768
     """
751
-    version = ceph_version()
752
-    if version and version >= '0.56':
769
+    luminous_or_later = cmp_pkgrevno('ceph', '12.0.0') >= 0
770
+    if luminous_or_later and device_class:
771
+        out = check_output(['ceph', '--id', service,
772
+                            'osd', 'crush', 'class',
773
+                            'ls-osd', device_class,
774
+                            '--format=json'])
775
+    else:
753 776
         out = check_output(['ceph', '--id', service,
754 777
                             'osd', 'ls',
755 778
                             '--format=json'])
756
-        if six.PY3:
757
-            out = out.decode('UTF-8')
758
-        return json.loads(out)
759
-
760
-    return None
779
+    if six.PY3:
780
+        out = out.decode('UTF-8')
781
+    return json.loads(out)
761 782
 
762 783
 
763 784
 def install():
@@ -811,7 +832,7 @@ def set_app_name_for_pool(client, pool, name):
811 832
 
812 833
     :raises: CalledProcessError if ceph call fails
813 834
     """
814
-    if ceph_version() >= '12.0.0':
835
+    if cmp_pkgrevno('ceph', '12.0.0') >= 0:
815 836
         cmd = ['ceph', '--id', client, 'osd', 'pool',
816 837
                'application', 'enable', pool, name]
817 838
         check_call(cmd)
@@ -1091,22 +1112,6 @@ def ensure_ceph_keyring(service, user=None, group=None,
1091 1112
     return True
1092 1113
 
1093 1114
 
1094
-def ceph_version():
1095
-    """Retrieve the local version of ceph."""
1096
-    if os.path.exists('/usr/bin/ceph'):
1097
-        cmd = ['ceph', '-v']
1098
-        output = check_output(cmd)
1099
-        if six.PY3:
1100
-            output = output.decode('UTF-8')
1101
-        output = output.split()
1102
-        if len(output) > 3:
1103
-            return output[2]
1104
-        else:
1105
-            return None
1106
-    else:
1107
-        return None
1108
-
1109
-
1110 1115
 class CephBrokerRq(object):
1111 1116
     """Ceph broker request.
1112 1117
 
@@ -1147,7 +1152,8 @@ class CephBrokerRq(object):
1147 1152
             'object-prefix-permissions': object_prefix_permissions})
1148 1153
 
1149 1154
     def add_op_create_pool(self, name, replica_count=3, pg_num=None,
1150
-                           weight=None, group=None, namespace=None):
1155
+                           weight=None, group=None, namespace=None,
1156
+                           app_name=None):
1151 1157
         """Adds an operation to create a pool.
1152 1158
 
1153 1159
         @param pg_num setting:  optional setting. If not provided, this value
@@ -1155,6 +1161,11 @@ class CephBrokerRq(object):
1155 1161
         cluster at the time of creation. Note that, if provided, this value
1156 1162
         will be capped at the current available maximum.
1157 1163
         @param weight: the percentage of data the pool makes up
1164
+        :param app_name: (Optional) Tag pool with application name.  Note that
1165
+                         there is certain protocols emerging upstream with
1166
+                         regard to meaningful application names to use.
1167
+                         Examples are ``rbd`` and ``rgw``.
1168
+        :type app_name: str
1158 1169
         """
1159 1170
         if pg_num and weight:
1160 1171
             raise ValueError('pg_num and weight are mutually exclusive')
@@ -1162,7 +1173,7 @@ class CephBrokerRq(object):
1162 1173
         self.ops.append({'op': 'create-pool', 'name': name,
1163 1174
                          'replicas': replica_count, 'pg_num': pg_num,
1164 1175
                          'weight': weight, 'group': group,
1165
-                         'group-namespace': namespace})
1176
+                         'group-namespace': namespace, 'app-name': app_name})
1166 1177
 
1167 1178
     def set_ops(self, ops):
1168 1179
         """Set request ops to provided value.

+ 48
- 0
templates/ocata/neutron.conf View File

@@ -0,0 +1,48 @@
1
+# kilo
2
+###############################################################################
3
+# [ WARNING ]
4
+# Configuration file maintained by Juju. Local changes may be overwritten.
5
+# Config managed by neutron-openvswitch charm
6
+# Service restart triggered by remote application: {{ restart_trigger }}
7
+#                                                  {{ restart_trigger_neutron }}
8
+###############################################################################
9
+[DEFAULT]
10
+verbose = {{ verbose }}
11
+debug = {{ debug }}
12
+use_syslog = {{ use_syslog }}
13
+state_path = /var/lib/neutron
14
+bind_host = 0.0.0.0
15
+bind_port = 9696
16
+{% if network_device_mtu -%}
17
+network_device_mtu = {{ network_device_mtu }}
18
+{% endif -%}
19
+{% if core_plugin -%}
20
+core_plugin =  {{ core_plugin }}
21
+{% endif -%}
22
+{% if transport_url %}
23
+transport_url = {{ transport_url }}
24
+{% endif %}
25
+
26
+api_paste_config = /etc/neutron/api-paste.ini
27
+auth_strategy = keystone
28
+rpc_response_timeout = {{ rpc_response_timeout }}
29
+
30
+{% include "section-zeromq" %}
31
+
32
+{% include "section-oslo-messaging-rabbit" %}
33
+
34
+{% include "section-oslo-notifications" %}
35
+
36
+[QUOTAS]
37
+
38
+[DEFAULT_SERVICETYPE]
39
+
40
+[AGENT]
41
+root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
42
+report_interval = {{ report_interval }}
43
+
44
+[keystone_authtoken]
45
+signing_dir = /var/lib/neutron/keystone-signing
46
+
47
+[oslo_concurrency]
48
+lock_path = $state_path/lock

Loading…
Cancel
Save