=== modified file '.gitreview'
--- a/.gitreview	2020-03-26 13:13:25 +0000
+++ b/.gitreview	2020-03-27 12:59:54 +0000
@@ -2,4 +2,3 @@
 host=review.opendev.org
 port=29418
 project=openstack/oslo.config.git
-defaultbranch=stable/train

=== modified file '.zuul.d/project.yaml'
--- a/.zuul.d/project.yaml	2019-09-04 09:39:50 +0000
+++ b/.zuul.d/project.yaml	2020-03-27 12:59:54 +0000
@@ -1,11 +1,9 @@
 - project:
     templates:
       - check-requirements
-      - lib-forward-testing
       - lib-forward-testing-python3
       - openstack-lower-constraints-jobs
-      - openstack-python-jobs
-      - openstack-python3-train-jobs
+      - openstack-python3-ussuri-jobs
       - periodic-stable-jobs
       - publish-openstack-docs-pti
       - release-notes-jobs-python3

=== modified file 'README.rst'
--- a/README.rst	2018-08-19 12:10:29 +0000
+++ b/README.rst	2020-03-27 12:59:54 +0000
@@ -24,6 +24,6 @@
 
 * License: Apache License, Version 2.0
 * Documentation: https://docs.openstack.org/oslo.config/latest/
-* Source: https://git.openstack.org/cgit/openstack/oslo.config
+* Source: https://opendev.org/openstack/oslo.config/
 * Bugs: https://bugs.launchpad.net/oslo.config
 * Release notes:  https://docs.openstack.org/releasenotes/oslo.config/

=== modified file 'bindep.txt'
--- a/bindep.txt	2018-03-20 15:26:47 +0000
+++ b/bindep.txt	2020-03-27 12:59:54 +0000
@@ -2,8 +2,6 @@
 # see http://docs.openstack.org/infra/bindep/ for additional information.
 
 locales [platform:debian]
-python-dev [platform:dpkg]
-python-devel [platform:rpm]
 python3-all-dev [platform:ubuntu !platform:ubuntu-precise]
 python3-dev [platform:dpkg]
 python3-devel [platform:fedora]

=== modified file 'debian/changelog'
--- a/debian/changelog	2020-03-26 13:13:51 +0000
+++ b/debian/changelog	2020-03-27 12:59:54 +0000
@@ -1,3 +1,9 @@
+python-oslo.config (1:8.0.1-1) UNRELEASED; urgency=medium
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 27 Mar 2020 12:59:38 +0000
+
 python-oslo.config (1:6.11.2-1) unstable; urgency=medium
 
   * New upstream point release.

=== modified file 'doc/requirements.txt'
--- a/doc/requirements.txt	2019-09-04 09:39:50 +0000
+++ b/doc/requirements.txt	2020-03-27 12:59:54 +0000
@@ -2,8 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
-sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
-sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD
+sphinx>=1.8.0,!=2.1.0 # BSD
 sphinxcontrib-apidoc>=0.2.0  # BSD
 openstackdocstheme>=1.20.0 # Apache-2.0
 reno>=2.5.0 # Apache-2.0

=== modified file 'doc/source/reference/command-line.rst'
--- a/doc/source/reference/command-line.rst	2018-07-19 21:26:38 +0000
+++ b/doc/source/reference/command-line.rst	2020-03-27 12:59:54 +0000
@@ -17,6 +17,10 @@
     >>> conf.bar
     ['a', 'b']
 
+By default, positional arguments are also required. You may opt-out of this
+behavior by setting ``required=False``, to have an optional positional
+argument.
+
 Sub-Parsers
 -----------
 

=== modified file 'lower-constraints.txt'
--- a/lower-constraints.txt	2019-09-04 09:39:50 +0000
+++ b/lower-constraints.txt	2020-03-27 12:59:54 +0000
@@ -6,7 +6,6 @@
 debtcollector==1.2.0
 docutils==0.11
 dulwich==0.15.0
-enum34==1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
 extras==1.0.0
 fixtures==3.0.0
 flake8==2.5.5
@@ -44,7 +43,7 @@
 six==1.10.0
 smmap==0.9.0
 snowballstemmer==1.2.1
-Sphinx==1.6.2
+Sphinx==1.8.0
 sphinxcontrib-websupport==1.0.1
 stestr==2.1.0
 stevedore==1.20.0

=== modified file 'oslo_config/cfg.py'
--- a/oslo_config/cfg.py	2020-03-26 13:13:25 +0000
+++ b/oslo_config/cfg.py	2020-03-27 12:59:54 +0000
@@ -540,7 +540,7 @@
 
     def __init__(self, name, type=None, dest=None, short=None,
                  default=None, positional=False, metavar=None, help=None,
-                 secret=False, required=False,
+                 secret=False, required=None,
                  deprecated_name=None, deprecated_group=None,
                  deprecated_opts=None, sample_default=None,
                  deprecated_for_removal=False, deprecated_reason=None,
@@ -556,6 +556,11 @@
             raise TypeError('type must be callable')
         self.type = type
 
+        # By default, non-positional options are *optional*, and positional
+        # options are *required*.
+        if required is None:
+            required = True if positional else False
+
         if dest is None:
             self.dest = self.name.replace('-', '_')
         else:
@@ -716,6 +721,13 @@
         def hyphen(arg):
             return arg if not positional else ''
 
+        # Because we must omit the dest parameter when using a positional
+        # argument, the name supplied for the positional argument must not
+        # include hyphens.
+        if positional:
+            prefix = prefix.replace('-', '_')
+            name = name.replace('-', '_')
+
         args = [hyphen('--') + prefix + name]
         if short:
             args.append(hyphen('-') + short)
@@ -751,7 +763,7 @@
             if group is not None:
                 dest = group.name + '_' + dest
             kwargs['dest'] = dest
-        else:
+        elif not self.required:
             kwargs['nargs'] = '?'
         kwargs.update({'default': None,
                        'metavar': self.metavar,

=== removed file 'oslo_config/cfgfilter.py'
--- a/oslo_config/cfgfilter.py	2019-09-04 09:39:50 +0000
+++ b/oslo_config/cfgfilter.py	1970-01-01 00:00:00 +0000
@@ -1,390 +0,0 @@
-# Copyright 2014 Red Hat, Inc.
-#
-#    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.
-
-r"""
-DEPRECATED: This module is deprecated and scheduled for removal in the
-U cycle.
-
-There are three use cases for the ConfigFilter class:
-
-1. Help enforce that a given module does not access options registered
-   by another module, without first declaring those cross-module
-   dependencies using import_opt().
-
-2. Prevent private configuration opts from being visible to modules
-   other than the one which registered it.
-
-3. Limit the options on a Cfg object that can be accessed.
-
-.. versionadded:: 1.4
-
-Cross-Module Option Dependencies
---------------------------------
-
-When using the global cfg.CONF object, it is quite common for a module
-to require the existence of configuration options registered by other
-modules.
-
-For example, if module 'foo' registers the 'blaa' option and the module
-'bar' uses the 'blaa' option then 'bar' might do::
-
-  import foo
-
-  print(CONF.blaa)
-
-However, it's completely non-obvious why foo is being imported (is it
-unused, can we remove the import) and where the 'blaa' option comes from.
-
-The CONF.import_opt() method allows such a dependency to be explicitly
-declared::
-
-  CONF.import_opt('blaa', 'foo')
-  print(CONF.blaa)
-
-However, import_opt() has a weakness - if 'bar' imports 'foo' using the
-import builtin and doesn't use import_opt() to import 'blaa', then 'blaa'
-can still be used without problems. Similarly, where multiple options
-are registered a module imported via import_opt(), a lazy programmer can
-get away with only declaring a dependency on a single option.
-
-The ConfigFilter class provides a way to ensure that options are not
-available unless they have been registered in the module or imported using
-import_opt() for example with::
-
-  CONF = ConfigFilter(cfg.CONF)
-  CONF.import_opt('blaa', 'foo')
-  print(CONF.blaa)
-
-no other options other than 'blaa' are available via CONF.
-
-Private Configuration Options
------------------------------
-
-Libraries which register configuration options typically do not want
-users of the library API to access those configuration options. If
-API users do access private configuration options, those users will
-be disrupted if and when a configuration option is renamed. In other
-words, one does not typically wish for the name of the private config
-options to be part of the public API.
-
-The ConfigFilter class provides a way for a library to register
-options such that they are not visible via the ConfigOpts instance
-which the API user supplies to the library. For example::
-
-  from __future__ import print_function
-
-  from oslo_config.cfg import *
-  from oslo_config.cfgfilter import *
-
-  class Widget(object):
-
-      def __init__(self, conf):
-          self.conf = conf
-          self._private_conf = ConfigFilter(self.conf)
-          self._private_conf.register_opt(StrOpt('foo'))
-
-      @property
-      def foo(self):
-          return self._private_conf.foo
-
-  conf = ConfigOpts()
-  widget = Widget(conf)
-  print(widget.foo)
-  print(conf.foo)  # raises NoSuchOptError
-
-
-Limited Configuration Options
------------------------------
-
-It may be required that when passing a CONF object to other functions we want
-to filter that the receiving code is only able to access a restricted subset
-of the options that are available on the CONF object. This is essentially a
-more general case of the Private Configuration Options and Cross-Module Options
-whereby we expose an option that is already present on the underlying CONF
-object without providing any means to load it if not present.
-
-So given a CONF object with options defined::
-
-  CONF.register_opt(StrOpt('foo'))
-  CONF.register_opt(StrOpt('bar'))
-
-we can expose options such that only those options are present::
-
-  restricted_conf = CfgFilter(CONF)
-  restricted_conf.expose_opt('foo')
-
-  print(restricted_conf.foo)
-  print(restricted_conf.bar)  # raises NoSuchOptError
-"""
-
-import collections
-import itertools
-
-from debtcollector import removals
-
-from oslo_config import cfg
-
-
-@removals.removed_class('CliOptRegisteredError')
-class CliOptRegisteredError(cfg.Error):
-    """Raised when registering cli opt not in original ConfigOpts.
-
-    .. versionadded:: 1.12
-    """
-
-    def __str__(self):
-        ret = "Cannot register a cli option that was not present in the" \
-              " original ConfigOpts."
-
-        if self.msg:
-            ret += ": " + self.msg
-        return ret
-
-
-@removals.removed_class('ConfigFilter')
-class ConfigFilter(collections.Mapping):
-    """A helper class which wraps a ConfigOpts object.
-
-    ConfigFilter enforces the explicit declaration of dependencies on external
-    options and allows private options which are not registered with the
-    wrapped Configopts object.
-    """
-
-    def __init__(self, conf):
-        """Construct a ConfigFilter object.
-
-        :param conf: a ConfigOpts object
-        """
-        self._conf = conf
-        self._fconf = cfg.ConfigOpts()
-        self._sync()
-
-        self._imported_opts = set()
-        self._imported_groups = dict()
-
-    def _sync(self):
-        if self._fconf._namespace is not self._conf._namespace:
-            self._fconf.clear()
-            self._fconf._namespace = self._conf._namespace
-            self._fconf._args = self._conf._args
-
-    def __getattr__(self, name):
-        """Look up an option value.
-
-        :param name: the opt name (or 'dest', more precisely)
-        :returns: the option value (after string subsititution) or a GroupAttr
-        :raises: NoSuchOptError,ConfigFileValueError,TemplateSubstitutionError
-        """
-        if name in self._imported_groups:
-            return self._imported_groups[name]
-        elif name in self._imported_opts:
-            return getattr(self._conf, name)
-        else:
-            self._sync()
-            return getattr(self._fconf, name)
-
-    def __getitem__(self, key):
-        """Look up an option value."""
-        return getattr(self, key)
-
-    def __contains__(self, key):
-        """Return True if key is the name of a registered opt or group."""
-        return (key in self._fconf or
-                key in self._imported_opts or
-                key in self._imported_groups)
-
-    def __iter__(self):
-        """Iterate over all registered opt and group names."""
-        return itertools.chain(self._fconf.keys(),
-                               self._imported_opts,
-                               self._imported_groups.keys())
-
-    def __len__(self):
-        """Return the number of options and option groups."""
-        return (len(self._fconf) +
-                len(self._imported_opts) +
-                len(self._imported_groups))
-
-    @staticmethod
-    def _already_registered(conf, opt, group=None):
-        group_name = group.name if isinstance(group, cfg.OptGroup) else group
-        return ((group_name is None and
-                 opt.dest in conf) or
-                (group_name is not None and
-                 group_name in conf and
-                 opt.dest in conf[group_name]))
-
-    def register_opt(self, opt, group=None):
-        """Register an option schema.
-
-        :param opt: an instance of an Opt sub-class
-        :param group: an optional OptGroup object or group name
-        :return: False if the opt was already registered, True otherwise
-        :raises: DuplicateOptError
-        """
-        if self._already_registered(self._conf, opt, group):
-            # Raises DuplicateError if there is another opt with the same name
-            ret = self._conf.register_opt(opt, group)
-            self._import_opt(opt.dest, group)
-            return ret
-        else:
-            return self._fconf.register_opt(opt, group)
-
-    def register_opts(self, opts, group=None):
-        """Register multiple option schemas at once."""
-        for opt in opts:
-            self.register_opt(opt, group)
-
-    def register_cli_opt(self, opt, group=None):
-        """Register a CLI option schema.
-
-        :param opt: an instance of an Opt sub-class
-        :param group: an optional OptGroup object or group name
-        :return: False if the opt was already register, True otherwise
-        :raises: DuplicateOptError, ArgsAlreadyParsedError
-        """
-        if self._already_registered(self._conf, opt, group):
-            # Raises DuplicateError if there is another opt with the same name
-            ret = self._conf.register_opt(
-                opt, group, cli=True, clear_cache=False)
-            self._import_opt(opt.dest, group)
-            return ret
-        else:
-            raise CliOptRegisteredError(
-                "Opt '{0}' cannot be registered.".format(opt.dest))
-
-    def register_cli_opts(self, opts, group=None):
-        """Register multiple CLI option schemas at once."""
-        for opt in opts:
-            self.register_cli_opt(opt, group)
-
-    def register_group(self, group):
-        """Register an option group.
-
-        :param group: an OptGroup object
-        """
-        self._fconf.register_group(group)
-
-    def import_opt(self, opt_name, module_str, group=None):
-        """Import an option definition from a module.
-
-        :param name: the name/dest of the opt
-        :param module_str: the name of a module to import
-        :param group: an option OptGroup object or group name
-        :raises: NoSuchOptError, NoSuchGroupError
-        """
-        self._conf.import_opt(opt_name, module_str, group)
-        self._import_opt(opt_name, group)
-
-    def import_group(self, group, module_str):
-        """Import an option group from a module.
-
-        Note that this allows access to all options registered with
-        the group whether or not those options were registered by
-        the given module.
-
-        :param group: an option OptGroup object or group name
-        :param module_str: the name of a module to import
-        :raises: ImportError, NoSuchGroupError
-        """
-        self._conf.import_group(group, module_str)
-        group = self._import_group(group)
-        group._all_opts = True
-
-    def _import_opt(self, opt_name, group):
-        if group is None:
-            self._imported_opts.add(opt_name)
-            return True
-        else:
-            group = self._import_group(group)
-            return group._import_opt(opt_name)
-
-    def _import_group(self, group_or_name):
-        if isinstance(group_or_name, cfg.OptGroup):
-            group_name = group_or_name.name
-        else:
-            group_name = group_or_name
-
-        if group_name in self._imported_groups:
-            return self._imported_groups[group_name]
-        else:
-            group = self.GroupAttr(self._conf, group_name)
-            self._imported_groups[group_name] = group
-            return group
-
-    def expose_opt(self, opt_name, group=None):
-        """Expose an option from the underlying conf object.
-
-        This allows an object that has already been imported or used from the
-        base conf object to be seen from the filter object.
-
-        :param opt_name: the name/dest of the opt
-        :param group: an option OptGroup object or group name
-        """
-        self._import_opt(opt_name, group)
-
-    def expose_group(self, group):
-        """Expose all option from a group in the underlying conf object.
-
-        This allows an object that has already been imported or used from the
-        base conf object to be seen from the filter object.
-
-        :param group: an option OptGroup object or group name
-        """
-        group = self._import_group(group)
-        group._all_opts = True
-
-    class GroupAttr(collections.Mapping):
-
-        """Helper class to wrap a group object.
-
-        Represents the option values of a group as a mapping and attributes.
-        """
-
-        def __init__(self, conf, group):
-            """Construct a GroupAttr object.
-
-            :param conf: a ConfigOpts object
-            :param group: an OptGroup object
-            """
-            self._conf = conf
-            self._group = group
-            self._imported_opts = set()
-            self._all_opts = False
-
-        def __getattr__(self, name):
-            """Look up an option value."""
-            if not self._all_opts and name not in self._imported_opts:
-                raise cfg.NoSuchOptError(name)
-            return getattr(self._conf[self._group], name)
-
-        def __getitem__(self, key):
-            """Look up an option value."""
-            return getattr(self, key)
-
-        def __contains__(self, key):
-            """Return True if key is the name of a registered opt or group."""
-            return key in self._imported_opts
-
-        def __iter__(self):
-            """Iterate over all registered opt and group names."""
-            for key in self._imported_opts:
-                yield key
-
-        def __len__(self):
-            """Return the number of options and option groups."""
-            return len(self._imported_opts)
-
-        def _import_opt(self, opt_name):
-            self._imported_opts.add(opt_name)

=== modified file 'oslo_config/generator.py'
--- a/oslo_config/generator.py	2019-03-20 19:34:02 +0000
+++ b/oslo_config/generator.py	2020-03-27 12:59:54 +0000
@@ -255,10 +255,10 @@
         lines = self._format_help(help_text)
 
         if getattr(opt.type, 'min', None) is not None:
-            lines.append('# Minimum value: %d\n' % opt.type.min)
+            lines.append('# Minimum value: {}\n'.format(opt.type.min))
 
         if getattr(opt.type, 'max', None) is not None:
-            lines.append('# Maximum value: %d\n' % opt.type.max)
+            lines.append('# Maximum value: {}\n'.format(opt.type.max))
 
         if getattr(opt.type, 'choices', None):
             lines.append('# Possible values:\n')

=== modified file 'oslo_config/tests/test_cfg.py'
--- a/oslo_config/tests/test_cfg.py	2019-03-20 19:34:02 +0000
+++ b/oslo_config/tests/test_cfg.py	2020-03-27 12:59:54 +0000
@@ -94,13 +94,13 @@
 
     class TestConfigOpts(cfg.ConfigOpts):
         def __call__(self, args=None, default_config_files=[],
-                     default_config_dirs=[]):
+                     default_config_dirs=[], usage=None):
             return cfg.ConfigOpts.__call__(
                 self,
                 args=args,
                 prog='test',
                 version='1.0',
-                usage='%(prog)s FOO BAR',
+                usage=usage,
                 description='somedesc',
                 epilog='tepilog',
                 default_config_files=default_config_files,
@@ -142,6 +142,21 @@
         f = moves.StringIO()
         self.conf([])
         self.conf.print_usage(file=f)
+        self.assertIn(
+            'usage: test [-h] [--config-dir DIR] [--config-file PATH] '
+            '[--version]',
+            f.getvalue())
+        self.assertNotIn('somedesc', f.getvalue())
+        self.assertNotIn('tepilog', f.getvalue())
+        self.assertNotIn('optional:', f.getvalue())
+
+    def test_print_custom_usage(self):
+        conf = self.TestConfigOpts()
+
+        self.tempdirs = []
+        f = moves.StringIO()
+        conf([], usage='%(prog)s FOO BAR')
+        conf.print_usage(file=f)
         self.assertIn('usage: test FOO BAR', f.getvalue())
         self.assertNotIn('somedesc', f.getvalue())
         self.assertNotIn('tepilog', f.getvalue())
@@ -151,7 +166,10 @@
         f = moves.StringIO()
         self.conf([])
         self.conf.print_help(file=f)
-        self.assertIn('usage: test FOO BAR', f.getvalue())
+        self.assertIn(
+            'usage: test [-h] [--config-dir DIR] [--config-file PATH] '
+            '[--version]',
+            f.getvalue())
         self.assertIn('somedesc', f.getvalue())
         self.assertIn('tepilog', f.getvalue())
         self.assertNotIn('optional:', f.getvalue())
@@ -163,7 +181,10 @@
         f = moves.StringIO()
         self.conf([])
         self.conf.print_help(file=f)
-        self.assertIn('usage: test FOO BAR', f.getvalue())
+        self.assertIn(
+            'usage: test [-h] [--config-dir DIR] [--config-file PATH] '
+            '[--version]',
+            f.getvalue())
         self.assertIn('optional', f.getvalue())
         self.assertIn('-h, --help', f.getvalue())
 
@@ -183,7 +204,10 @@
         self.conf.register_cli_opts(cli_opts)
         self.conf([])
         self.conf.print_help(file=f)
-        self.assertIn('usage: test FOO BAR', f.getvalue())
+        self.assertIn(
+            'usage: test [-h] [--aa AA] [--bb BB] [--cc CC] [--config-dir DIR]'
+            '\n            [--config-file PATH] [--version]',
+            f.getvalue())
         self.assertIn('optional', f.getvalue())
         self.assertIn('-h, --help', f.getvalue())
         self.assertIn('StrOpt with choices. Allowed values: xx, yy, zz',
@@ -210,7 +234,8 @@
 
     def test_print_sorted_help_with_positionals(self):
         f = moves.StringIO()
-        self.conf.register_cli_opt(cfg.StrOpt('pst', positional=True))
+        self.conf.register_cli_opt(
+            cfg.StrOpt('pst', positional=True, required=False))
         self.conf.register_cli_opt(cfg.StrOpt('abc'))
         self.conf.register_cli_opt(cfg.StrOpt('zba'))
         self.conf.register_cli_opt(cfg.StrOpt('ghi'))
@@ -756,10 +781,12 @@
     def test_help(self):
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        self.assertIn('FOO BAR', sys.stdout.getvalue())
-        self.assertIn('--version', sys.stdout.getvalue())
+        self.assertIn('usage: test', sys.stdout.getvalue())
+        self.assertIn('[--version]', sys.stdout.getvalue())
+        self.assertIn('[-h]', sys.stdout.getvalue())
         self.assertIn('--help', sys.stdout.getvalue())
-        self.assertIn('--config-file', sys.stdout.getvalue())
+        self.assertIn('[--config-dir DIR]', sys.stdout.getvalue())
+        self.assertIn('[--config-file PATH]', sys.stdout.getvalue())
 
     def test_version(self):
         # In Python 3.4+, argparse prints the version on stdout; before 3.4, it
@@ -787,7 +814,8 @@
     def _do_pos_test(self, opt_class, default, cli_args, value):
         self.conf.register_cli_opt(opt_class('foo',
                                              default=default,
-                                             positional=True))
+                                             positional=True,
+                                             required=False))
 
         self.conf(cli_args)
 
@@ -914,7 +942,7 @@
         self.assertRaises(SystemExit, self.conf, ['--help'])
         self.assertIn(' foo\n', sys.stdout.getvalue())
 
-        self.assertRaises(cfg.RequiredOptError, self.conf, [])
+        self.assertRaises(SystemExit, self.conf, [])
 
     def test_optional_positional_opt_defined(self):
         self.conf.register_cli_opt(
@@ -922,11 +950,7 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        # FIXME(dolphm): Due to bug 1676989, this argument appears as a
-        # required argument in the CLI help. Instead, the following
-        # commented-out code should work:
-        # self.assertIn(' [foo]\n', sys.stdout.getvalue())
-        self.assertIn(' foo\n', sys.stdout.getvalue())
+        self.assertIn(' [foo]\n', sys.stdout.getvalue())
 
         self.conf(['bar'])
 
@@ -939,11 +963,7 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        # FIXME(dolphm): Due to bug 1676989, this argument appears as a
-        # required argument in the CLI help. Instead, the following
-        # commented-out code should work:
-        # self.assertIn(' [foo]\n', sys.stdout.getvalue())
-        self.assertIn(' foo\n', sys.stdout.getvalue())
+        self.assertIn(' [foo]\n', sys.stdout.getvalue())
 
         self.conf([])
 
@@ -956,19 +976,11 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        # FIXME(dolphm): Due to bug 1676989, this argument appears as a
-        # required argument in the CLI help. Instead, the following
-        # commented-out code should work:
-        # self.assertIn(' [foo-bar]\n', sys.stdout.getvalue())
-        self.assertIn(' foo-bar\n', sys.stdout.getvalue())
+        self.assertIn(' [foo_bar]\n', sys.stdout.getvalue())
 
         self.conf(['baz'])
         self.assertTrue(hasattr(self.conf, 'foo_bar'))
-        # FIXME(dolphm): Due to bug 1676989, this argument cannot be retrieved
-        # by oslo_config.cfg. Instead, the following commented-out code should
-        # work:
-        # self.assertEqual('baz', self.conf.foo_bar)
-        self.assertIsNone(self.conf.foo_bar)
+        self.assertEqual('baz', self.conf.foo_bar)
 
     def test_optional_positional_hyphenated_opt_undefined(self):
         self.conf.register_cli_opt(
@@ -976,11 +988,7 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        # FIXME(dolphm): Due to bug 1676989, this argument appears as a
-        # required argument in the CLI help. Instead, the following
-        # commented-out code should work:
-        # self.assertIn(' [foo-bar]\n', sys.stdout.getvalue())
-        self.assertIn(' foo-bar\n', sys.stdout.getvalue())
+        self.assertIn(' [foo_bar]\n', sys.stdout.getvalue())
 
         self.conf([])
         self.assertTrue(hasattr(self.conf, 'foo_bar'))
@@ -992,15 +1000,11 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        self.assertIn(' foo-bar\n', sys.stdout.getvalue())
+        self.assertIn(' foo_bar\n', sys.stdout.getvalue())
 
-        # FIXME(dolphm): Due to bug 1676989, this mistakenly raises an
-        # exception, even though the option is clearly defined. Instead, the
-        # following commented out lines should work:
-        # self.conf(['baz'])
-        # self.assertTrue(hasattr(self.conf, 'foo_bar'))
-        # self.assertEqual('baz', self.conf.foo_bar)
-        self.assertRaises(cfg.RequiredOptError, self.conf, ['baz'])
+        self.conf(['baz'])
+        self.assertTrue(hasattr(self.conf, 'foo_bar'))
+        self.assertEqual('baz', self.conf.foo_bar)
 
     def test_required_positional_hyphenated_opt_undefined(self):
         self.conf.register_cli_opt(
@@ -1008,14 +1012,14 @@
 
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO()))
         self.assertRaises(SystemExit, self.conf, ['--help'])
-        self.assertIn(' foo-bar\n', sys.stdout.getvalue())
+        self.assertIn(' foo_bar\n', sys.stdout.getvalue())
 
-        self.assertRaises(cfg.RequiredOptError, self.conf, [])
+        self.assertRaises(SystemExit, self.conf, [])
 
     def test_missing_required_cli_opt(self):
         self.conf.register_cli_opt(
             cfg.StrOpt('foo', required=True, positional=True))
-        self.assertRaises(cfg.RequiredOptError, self.conf, [])
+        self.assertRaises(SystemExit, self.conf, [])
 
     def test_positional_opts_order(self):
         self.conf.register_cli_opts((

=== removed file 'oslo_config/tests/test_cfgfilter.py'
--- a/oslo_config/tests/test_cfgfilter.py	2018-08-19 12:10:29 +0000
+++ b/oslo_config/tests/test_cfgfilter.py	1970-01-01 00:00:00 +0000
@@ -1,362 +0,0 @@
-# Copyright 2014 Red Hat, Inc.
-#
-#    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.
-
-from oslotest import base as test_base
-import testtools
-
-from oslo_config import cfg
-from oslo_config import cfgfilter
-
-
-class BaseTestCase(test_base.BaseTestCase):
-
-    def setUp(self, conf=None):
-        super(BaseTestCase, self).setUp()
-        if conf is None:
-            self.conf = cfg.ConfigOpts()
-        else:
-            self.conf = conf
-        self.fconf = cfgfilter.ConfigFilter(self.conf)
-
-
-class RegisterTestCase(BaseTestCase):
-
-    def test_register_opt_default(self):
-        self.fconf.register_opt(cfg.StrOpt('foo', default='bar'))
-
-        self.assertEqual('bar', self.fconf.foo)
-        self.assertEqual('bar', self.fconf['foo'])
-        self.assertIn('foo', self.fconf)
-        self.assertEqual(set(['config_source', 'foo']), set(self.fconf))
-        self.assertEqual(2, len(self.fconf))
-
-        self.assertNotIn('foo', self.conf)
-        self.assertEqual(1, len(self.conf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
-
-    def test_register_opt_none_default(self):
-        self.fconf.register_opt(cfg.StrOpt('foo'))
-
-        self.assertIsNone(self.fconf.foo)
-        self.assertIsNone(self.fconf['foo'])
-        self.assertIn('foo', self.fconf)
-        self.assertEqual(set(['config_source', 'foo']), set(self.fconf))
-        self.assertEqual(2, len(self.fconf))
-
-        self.assertNotIn('foo', self.conf)
-        self.assertEqual(1, len(self.conf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
-
-    def test_register_grouped_opt_default(self):
-        self.fconf.register_opt(cfg.StrOpt('foo', default='bar'),
-                                group='blaa')
-
-        self.assertEqual('bar', self.fconf.blaa.foo)
-        self.assertEqual('bar', self.fconf['blaa']['foo'])
-        self.assertIn('blaa', self.fconf)
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
-        self.assertEqual(['foo'], list(self.fconf.blaa))
-        self.assertEqual(2, len(self.fconf))
-        self.assertEqual(1, len(self.fconf.blaa))
-
-        self.assertNotIn('blaa', self.conf)
-        self.assertEqual(1, len(self.conf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
-
-    def test_register_grouped_opt_none_default(self):
-        self.fconf.register_opt(cfg.StrOpt('foo'), group='blaa')
-
-        self.assertIsNone(self.fconf.blaa.foo)
-        self.assertIsNone(self.fconf['blaa']['foo'])
-        self.assertIn('blaa', self.fconf)
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
-        self.assertEqual(['foo'], list(self.fconf.blaa))
-        self.assertEqual(2, len(self.fconf))
-        self.assertEqual(1, len(self.fconf.blaa))
-
-        self.assertNotIn('blaa', self.conf)
-        self.assertEqual(1, len(self.conf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
-
-    def test_register_group(self):
-        group = cfg.OptGroup('blaa')
-        self.fconf.register_group(group)
-        self.fconf.register_opt(cfg.StrOpt('foo'), group=group)
-
-        self.assertIsNone(self.fconf.blaa.foo)
-        self.assertIsNone(self.fconf['blaa']['foo'])
-        self.assertIn('blaa', self.fconf)
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
-        self.assertEqual(['foo'], list(self.fconf.blaa))
-        self.assertEqual(2, len(self.fconf))
-        self.assertEqual(1, len(self.fconf.blaa))
-
-        self.assertNotIn('blaa', self.conf)
-        self.assertEqual(1, len(self.conf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
-
-    def test_register_opts(self):
-        self.fconf.register_opts([cfg.StrOpt('foo'),
-                                  cfg.StrOpt('bar')])
-        self.assertIn('foo', self.fconf)
-        self.assertIn('bar', self.fconf)
-        self.assertNotIn('foo', self.conf)
-        self.assertNotIn('bar', self.conf)
-
-    def test_register_known_cli_opt(self):
-        self.conf.register_opt(cfg.StrOpt('foo'))
-        self.fconf.register_cli_opt(cfg.StrOpt('foo'))
-        self.assertIn('foo', self.fconf)
-        self.assertIn('foo', self.conf)
-
-    def test_register_unknown_cli_opt(self):
-        with testtools.ExpectedException(cfgfilter.CliOptRegisteredError):
-            self.fconf.register_cli_opt(cfg.StrOpt('foo'))
-
-    def test_register_known_cli_opts(self):
-        self.conf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')])
-        self.fconf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')])
-        self.assertIn('foo', self.fconf)
-        self.assertIn('bar', self.fconf)
-        self.assertIn('foo', self.conf)
-        self.assertIn('bar', self.conf)
-
-    def test_register_unknown_cli_opts(self):
-        self.conf.register_cli_opt(cfg.StrOpt('foo'))
-        with testtools.ExpectedException(cfgfilter.CliOptRegisteredError):
-            self.fconf.register_cli_opts([
-                cfg.StrOpt('foo'),
-                cfg.StrOpt('bar')
-            ])
-
-    def test_register_opts_grouped(self):
-        self.fconf.register_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')],
-                                 group='blaa')
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertIn('bar', self.fconf.blaa)
-        self.assertNotIn('blaa', self.conf)
-
-    def test_register_known_cli_opt_grouped(self):
-        self.conf.register_cli_opt(cfg.StrOpt('foo'), group='blaa')
-        self.fconf.register_cli_opt(cfg.StrOpt('foo'), group='blaa')
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertIn('blaa', self.fconf)
-        self.assertIn('blaa', self.conf)
-
-    def test_register_unknown_cli_opt_grouped(self):
-        with testtools.ExpectedException(cfgfilter.CliOptRegisteredError):
-            self.fconf.register_cli_opt(cfg.StrOpt('foo'), group='blaa')
-
-    def test_register_known_cli_opts_grouped(self):
-        self.conf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')],
-                                    group='blaa')
-        self.fconf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')],
-                                     group='blaa')
-        self.assertIn('foo', self.fconf.blaa)
-        self.assertIn('bar', self.fconf.blaa)
-        self.assertIn('blaa', self.fconf)
-        self.assertIn('blaa', self.conf)
-
-    def test_register_unknown_opts_grouped(self):
-        self.conf.register_cli_opts([cfg.StrOpt('bar')], group='blaa')
-        with testtools.ExpectedException(cfgfilter.CliOptRegisteredError):
-            self.fconf.register_cli_opts([
-                cfg.StrOpt('foo'),
-                cfg.StrOpt('bar')
-            ], group='blaa')
-
-    def test_unknown_opt(self):
-        self.assertNotIn('foo', self.fconf)
-        self.assertEqual(1, len(self.fconf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
-        self.assertNotIn('blaa', self.conf)
-
-    def test_blocked_opt(self):
-        self.conf.register_opt(cfg.StrOpt('foo'))
-
-        self.assertIn('foo', self.conf)
-        self.assertEqual(2, len(self.conf))
-        self.assertIsNone(self.conf.foo)
-        self.assertNotIn('foo', self.fconf)
-        self.assertEqual(1, len(self.fconf))
-        self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
-
-    def test_already_registered_opt(self):
-        self.conf.register_opt(cfg.StrOpt('foo'))
-        self.fconf.register_opt(cfg.StrOpt('foo'))
-
-        self.assertIn('foo', self.conf)
-        self.assertEqual(2, len(self.conf))
-        self.assertIsNone(self.conf.foo)
-        self.assertIn('foo', self.fconf)
-        self.assertEqual(2, len(self.fconf))
-        self.assertIsNone(self.fconf.foo)
-
-        self.conf.set_override('foo', 'bar')
-
-        self.assertEqual('bar', self.conf.foo)
-        self.assertEqual('bar', self.fconf.foo)
-
-    def test_already_registered_opts(self):
-        self.conf.register_opts([cfg.StrOpt('foo'),
-                                 cfg.StrOpt('fu')])
-        self.fconf.register_opts([cfg.StrOpt('foo'),
-                                  cfg.StrOpt('bu')])
-
-        self.assertIn('foo', self.conf)
-        self.assertIn('fu', self.conf)
-        self.assertNotIn('bu', self.conf)
-        self.assertEqual(3, len(self.conf))
-        self.assertIsNone(self.conf.foo)
-        self.assertIsNone(self.conf.fu)
-        self.assertIn('foo', self.fconf)
-        self.assertIn('bu', self.fconf)
-        self.assertNotIn('fu', self.fconf)
-        self.assertEqual(3, len(self.fconf))
-        self.assertIsNone(self.fconf.foo)
-        self.assertIsNone(self.fconf.bu)
-
-        self.conf.set_override('foo', 'bar')
-
-        self.assertEqual('bar', self.conf.foo)
-        self.assertEqual('bar', self.fconf.foo)
-
-    def test_already_registered_cli_opt(self):
-        self.conf.register_cli_opt(cfg.StrOpt('foo'))
-        self.fconf.register_cli_opt(cfg.StrOpt('foo'))
-
-        self.assertIn('foo', self.conf)
-        self.assertEqual(2, len(self.conf))
-        self.assertIsNone(self.conf.foo)
-        self.assertIn('foo', self.fconf)
-        self.assertEqual(2, len(self.fconf))
-        self.assertIsNone(self.fconf.foo)
-
-        self.conf.set_override('foo', 'bar')
-
-        self.assertEqual('bar', self.conf.foo)
-        self.assertEqual('bar', self.fconf.foo)
-
-    def test_already_registered_cli_opts(self):
-        self.conf.register_cli_opts([cfg.StrOpt('foo'),
-                                     cfg.StrOpt('fu')])
-        self.fconf.register_cli_opts([cfg.StrOpt('foo'),
-                                      cfg.StrOpt('fu')])
-
-        self.assertIn('foo', self.conf)
-        self.assertIn('fu', self.conf)
-        self.assertEqual(3, len(self.conf))
-        self.assertIsNone(self.conf.foo)
-        self.assertIsNone(self.conf.fu)
-        self.assertIn('foo', self.fconf)
-        self.assertIn('fu', self.fconf)
-        self.assertEqual(3, len(self.fconf))
-        self.assertIsNone(self.fconf.foo)
-        self.assertIsNone(self.fconf.fu)
-
-        self.conf.set_override('foo', 'bar')
-
-        self.assertEqual('bar', self.conf.foo)
-        self.assertEqual('bar', self.fconf.foo)
-
-
-class ImportTestCase(BaseTestCase):
-
-    def setUp(self):
-        super(ImportTestCase, self).setUp(cfg.CONF)
-
-    def test_import_opt(self):
-        self.assertFalse(hasattr(self.conf, 'fblaa'))
-        self.conf.import_opt('fblaa', 'oslo_config.tests.testmods.fblaa_opt')
-        self.assertTrue(hasattr(self.conf, 'fblaa'))
-        self.assertFalse(hasattr(self.fconf, 'fblaa'))
-        self.fconf.import_opt('fblaa', 'oslo_config.tests.testmods.fblaa_opt')
-        self.assertTrue(hasattr(self.fconf, 'fblaa'))
-
-    def test_import_opt_in_group(self):
-        self.assertFalse(hasattr(self.conf, 'fbar'))
-        self.conf.import_opt('foo', 'oslo_config.tests.testmods.fbar_foo_opt',
-                             group='fbar')
-        self.assertTrue(hasattr(self.conf, 'fbar'))
-        self.assertTrue(hasattr(self.conf.fbar, 'foo'))
-        self.assertFalse(hasattr(self.fconf, 'fbar'))
-        self.fconf.import_opt('foo', 'oslo_config.tests.testmods.fbar_foo_opt',
-                              group='fbar')
-        self.assertTrue(hasattr(self.fconf, 'fbar'))
-        self.assertTrue(hasattr(self.fconf.fbar, 'foo'))
-
-    def test_import_group(self):
-        self.assertFalse(hasattr(self.conf, 'fbaar'))
-        self.conf.import_group('fbaar',
-                               'oslo_config.tests.testmods.fbaar_baa_opt')
-        self.assertTrue(hasattr(self.conf, 'fbaar'))
-        self.assertTrue(hasattr(self.conf.fbaar, 'baa'))
-        self.assertFalse(hasattr(self.fconf, 'fbaar'))
-        self.fconf.import_group('fbaar',
-                                'oslo_config.tests.testmods.fbaar_baa_opt')
-        self.assertTrue(hasattr(self.fconf, 'fbaar'))
-        self.assertTrue(hasattr(self.fconf.fbaar, 'baa'))
-
-
-class ExposeTestCase(BaseTestCase):
-
-    def test_expose_opt(self):
-        self.assertFalse(hasattr(self.conf, 'foo'))
-        self.assertFalse(hasattr(self.fconf, 'foo'))
-
-        self.conf.register_opt(cfg.StrOpt('foo'))
-        self.conf.set_override('foo', 'bar')
-
-        self.assertTrue(hasattr(self.conf, 'foo'))
-        self.assertEqual('bar', self.conf.foo)
-        self.assertFalse(hasattr(self.fconf, 'foo'))
-
-        self.fconf.expose_opt('foo')
-        self.assertTrue(hasattr(self.conf, 'foo'))
-        self.assertTrue(hasattr(self.fconf, 'foo'))
-        self.assertEqual('bar', self.fconf.foo)
-
-    def test_expose_opt_with_group(self):
-        self.assertFalse(hasattr(self.conf, 'foo'))
-        self.assertFalse(hasattr(self.fconf, 'foo'))
-
-        self.conf.register_opt(cfg.StrOpt('foo'), group='group')
-        self.conf.set_override('foo', 'bar', group='group')
-
-        self.assertTrue(hasattr(self.conf.group, 'foo'))
-        self.assertEqual('bar', self.conf.group.foo)
-        self.assertFalse(hasattr(self.fconf, 'group'))
-
-        self.fconf.expose_opt('foo', group='group')
-        self.assertTrue(hasattr(self.conf.group, 'foo'))
-        self.assertTrue(hasattr(self.fconf.group, 'foo'))
-        self.assertEqual('bar', self.fconf.group.foo)
-
-    def test_expose_group(self):
-        self.conf.register_opts([cfg.StrOpt('foo'),
-                                 cfg.StrOpt('bar')], group='group')
-        self.conf.register_opts([cfg.StrOpt('foo'),
-                                 cfg.StrOpt('bar')], group='another')
-        self.conf.set_override('foo', 'a', group='group')
-        self.conf.set_override('bar', 'b', group='group')
-
-        self.fconf.expose_group('group')
-
-        self.assertEqual('a', self.fconf.group.foo)
-        self.assertEqual('b', self.fconf.group.bar)
-        self.assertFalse(hasattr(self.fconf, 'another'))
-        self.assertTrue(hasattr(self.conf, 'another'))

=== added file 'releasenotes/notes/drop-python27-support-87f1b4089d4cc78b.yaml'
--- a/releasenotes/notes/drop-python27-support-87f1b4089d4cc78b.yaml	1970-01-01 00:00:00 +0000
+++ b/releasenotes/notes/drop-python27-support-87f1b4089d4cc78b.yaml	2020-02-10 12:21:43 +0000
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    Support for Python 2.7 has been dropped. The minimum version of Python now
+    supported is Python 3.6.

=== added file 'releasenotes/notes/positional-arguments-are-required-22ddca72e6f523bf.yaml'
--- a/releasenotes/notes/positional-arguments-are-required-22ddca72e6f523bf.yaml	1970-01-01 00:00:00 +0000
+++ b/releasenotes/notes/positional-arguments-are-required-22ddca72e6f523bf.yaml	2017-03-30 18:24:31 +0000
@@ -0,0 +1,12 @@
+---
+upgrade:
+  - |
+    Positional options are now required by default, to match argparse's default
+    behavior. To revert this behavior (and maintain optional positional
+    arguments), you need to explicitly specify ``positional=True,
+    required=False`` as part of the options definition.
+fixes:
+  - |
+    On the command line, oslo.config now returns command usage information from
+    argparse (instead of dumping a backtrace) when required arguments are
+    missing.

=== modified file 'releasenotes/source/index.rst'
--- a/releasenotes/source/index.rst	2019-09-04 09:39:50 +0000
+++ b/releasenotes/source/index.rst	2020-03-27 12:59:54 +0000
@@ -6,6 +6,7 @@
     :maxdepth: 1
 
     unreleased
+    train
     stein
     rocky
     queens

=== added file 'releasenotes/source/train.rst'
--- a/releasenotes/source/train.rst	1970-01-01 00:00:00 +0000
+++ b/releasenotes/source/train.rst	2019-09-20 16:31:06 +0000
@@ -0,0 +1,6 @@
+==========================
+Train Series Release Notes
+==========================
+
+.. release-notes::
+   :branch: stable/train

=== modified file 'requirements.txt'
--- a/requirements.txt	2019-03-20 19:34:02 +0000
+++ b/requirements.txt	2020-03-27 12:59:54 +0000
@@ -9,5 +9,4 @@
 oslo.i18n>=3.15.3 # Apache-2.0
 rfc3986>=1.2.0 # Apache-2.0
 PyYAML>=3.12 # MIT
-enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
 requests>=2.18.0 # Apache-2.0

=== modified file 'setup.cfg'
--- a/setup.cfg	2019-09-04 09:39:50 +0000
+++ b/setup.cfg	2020-03-27 12:59:54 +0000
@@ -1,11 +1,12 @@
 [metadata]
 name = oslo.config
-author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
 summary = Oslo Configuration API
 description-file =
     README.rst
+author = OpenStack
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/oslo.config/latest/
+python-requires = >=3.6
 classifier =
     Development Status :: 5 - Production/Stable
     Environment :: OpenStack
@@ -14,20 +15,16 @@
     License :: OSI Approved :: Apache Software License
     Operating System :: OS Independent
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3 :: Only
+    Programming Language :: Python :: Implementation :: CPython
 
 [files]
 packages =
     oslo_config
 
-[global]
-setup-hooks =
-    pbr.hooks.setup_hook
-
 [entry_points]
 console_scripts =
     oslo-config-generator = oslo_config.generator:main
@@ -37,5 +34,3 @@
 oslo.config.driver =
     remote_file = oslo_config.sources._uri:URIConfigurationSourceDriver
 
-[bdist_wheel]
-universal = 1

=== modified file 'setup.py'
--- a/setup.py	2017-08-02 20:02:02 +0000
+++ b/setup.py	2020-03-27 12:59:54 +0000
@@ -13,17 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
 import setuptools
 
-# In python < 2.7.4, a lazy loading of package `pbr` will break
-# setuptools if some other modules registered functions in `atexit`.
-# solution from: http://bugs.python.org/issue15881#msg170215
-try:
-    import multiprocessing  # noqa
-except ImportError:
-    pass
-
 setuptools.setup(
     setup_requires=['pbr>=2.0.0'],
     pbr=True)

=== modified file 'tox.ini'
--- a/tox.ini	2020-03-26 13:13:25 +0000
+++ b/tox.ini	2020-03-27 12:59:54 +0000
@@ -1,10 +1,11 @@
 [tox]
-minversion = 2.0
+minversion = 3.2.0
 distribute = False
-envlist = py27,py37,pep8
+envlist = py37,pep8
+ignore_basepython_conflict = true
 
 [testenv]
-install_command = pip install {opts} {packages}
+basepython = python3
 whitelist_externals =
   find
 setenv =
@@ -12,7 +13,7 @@
   OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:1}
   OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:60}
 deps =
-  -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/train}
+  -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/test-requirements.txt
   -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
@@ -22,25 +23,19 @@
   stestr slowest
 
 [testenv:lower-constraints]
-basepython = python3
 deps =
   -c{toxinidir}/lower-constraints.txt
   -r{toxinidir}/test-requirements.txt
   -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
 
-[testenv:py27]
-basepython = python2.7
-
 [testenv:pep8]
-basepython = python3
 commands =
   flake8
   # Run security linter
   bandit -r oslo_config -x tests -n5
 
 [testenv:cover]
-basepython = python3
 setenv =
     PYTHON=coverage run --source oslo_config --parallel-mode
 commands =
@@ -52,19 +47,18 @@
   coverage report --show-missing
 
 [testenv:venv]
-basepython = python3
 commands = {posargs}
 
 [testenv:docs]
-basepython = python3
 whitelist_externals = rm
-deps = -r{toxinidir}/doc/requirements.txt
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
+  -r{toxinidir}/doc/requirements.txt
 commands =
   rm -fr doc/build
-  sphinx-build -W -b html doc/source doc/build/html {posargs}
+  sphinx-build -W --keep-going -b html doc/source doc/build/html {posargs}
 
 [testenv:bandit]
-basepython = python3
 commands = bandit -r oslo_config -x tests -n5
 
 [flake8]
@@ -73,9 +67,12 @@
 exclude = .tox,dist,doc,*.egg,build
 
 [testenv:releasenotes]
-basepython = python3
-deps = -r{toxinidir}/doc/requirements.txt
-commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+whitelist_externals =
+  rm
+deps = {[testenv:docs]deps}
+commands =
+  rm -rf releasenotes/build
+  sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html
 
 [hacking]
 import_exceptions = oslo_config._i18n