summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openstack/common/service.py10
-rw-r--r--tests/unit/test_service.py26
-rwxr-xr-xtools/list_unreleased_changes.sh6
-rwxr-xr-xtools/release_notes.py264
4 files changed, 37 insertions, 269 deletions
diff --git a/openstack/common/service.py b/openstack/common/service.py
index 66b37d85..369abdab 100644
--- a/openstack/common/service.py
+++ b/openstack/common/service.py
@@ -199,6 +199,13 @@ class ServiceWrapper(object):
class ProcessLauncher(object):
+ _signal_handlers_set = set()
+
+ @classmethod
+ def _handle_class_signals(cls, *args, **kwargs):
+ for handler in cls._signal_handlers_set:
+ handler(*args, **kwargs)
+
def __init__(self):
"""Constructor."""
@@ -210,7 +217,8 @@ class ProcessLauncher(object):
self.handle_signal()
def handle_signal(self):
- _set_signals_handler(self._handle_signal)
+ self._signal_handlers_set.add(self._handle_signal)
+ _set_signals_handler(self._handle_class_signals)
def _handle_signal(self, signo, frame):
self.sigcaught = signo
diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py
index 8ba1f74d..3d758bbc 100644
--- a/tests/unit/test_service.py
+++ b/tests/unit/test_service.py
@@ -371,7 +371,9 @@ class LauncherTest(test_base.BaseTestCase):
class ProcessLauncherTest(test_base.BaseTestCase):
- def test_stop(self):
+ @mock.patch("signal.signal")
+ def test_stop(self, signal_mock):
+ signal_mock.SIGTERM = 15
launcher = service.ProcessLauncher()
self.assertTrue(launcher.running)
@@ -383,10 +385,28 @@ class ProcessLauncherTest(test_base.BaseTestCase):
self.assertFalse(launcher.running)
self.assertFalse(launcher.children)
- self.assertEqual([mock.call(22, signal.SIGTERM),
- mock.call(222, signal.SIGTERM)],
+ self.assertEqual([mock.call(22, signal_mock.SIGTERM),
+ mock.call(222, signal_mock.SIGTERM)],
mock_kill.mock_calls)
+ @mock.patch(
+ "openstack.common.service.ProcessLauncher._signal_handlers_set",
+ new_callable=lambda: set())
+ def test__signal_handlers_set(self, signal_handlers_set_mock):
+ service.ProcessLauncher()
+ self.assertEqual(1, len(service.ProcessLauncher._signal_handlers_set))
+ service.ProcessLauncher()
+ self.assertEqual(2, len(service.ProcessLauncher._signal_handlers_set))
+
+ @mock.patch(
+ "openstack.common.service.ProcessLauncher._signal_handlers_set",
+ new_callable=lambda: set())
+ def test__handle_class_signals(self, signal_handlers_set_mock):
+ signal_handlers_set_mock.update([mock.Mock(), mock.Mock()])
+ service.ProcessLauncher._handle_class_signals()
+ for m in service.ProcessLauncher._signal_handlers_set:
+ m.assert_called_once_with()
+
class GracefulShutdownTestService(service.Service):
def __init__(self):
diff --git a/tools/list_unreleased_changes.sh b/tools/list_unreleased_changes.sh
index 4b441475..2bf33128 100755
--- a/tools/list_unreleased_changes.sh
+++ b/tools/list_unreleased_changes.sh
@@ -16,6 +16,7 @@
bindir=$(cd $(dirname $0) && pwd)
repodir=$(cd $bindir/../../.. && pwd)
+release_tools=$repodir/openstack-infra/release-tools
# Make sure no pager is configured so the output is not blocked
export PAGER=
@@ -43,6 +44,9 @@ do
then
echo "$lib has not yet been released"
else
- $bindir/release_notes.py --show-dates --changes-only $repodir/$lib $prev_tag origin/master
+ $release_tools/release_notes.py \
+ --show-dates \
+ --changes-only \
+ $repodir/$lib $prev_tag origin/master
fi
done
diff --git a/tools/release_notes.py b/tools/release_notes.py
deleted file mode 100755
index 633a3076..00000000
--- a/tools/release_notes.py
+++ /dev/null
@@ -1,264 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-
-"""Generates a standard set of release notes for a repository."""
-
-import argparse
-import glob
-import os
-import random
-import subprocess
-import sys
-
-import jinja2
-from oslo_concurrency import processutils
-import parawrap
-
-
-EMOTIONS = [
- 'chuffed',
- 'content',
- 'delighted',
- 'eager',
- 'excited',
- 'glad',
- 'gleeful',
- 'happy',
- 'overjoyed',
- 'pleased',
- 'pumped',
- 'satisfied',
- 'thrilled',
-]
-
-# This will be replaced with template values and then wrapped using parawrap
-# to correctly wrap at paragraph boundaries...
-HEADER_RELEASE_TPL = """
-The Oslo team is {{ emotion }} to announce the release of:
-
-{{ project }} {{ end_rev }}: {{ description }}
-
-For more details, please see the git log history below and:
-
- {{ milestone_url }}
-
-Please report issues through launchpad:
-
- {{ bug_url }}
-"""
-
-# This will just be replaced with template values (no wrapping applied).
-CHANGE_RELEASE_TPL = """{% if notables %}
-Notable changes
-----------------
-
-{{ notables }}
-{% endif %}
-{{ change_header }}{% if skip_requirement_merges %}
-
-NOTE: Skipping requirement commits...
-{%- endif %}
-
-{% for change in changes -%}
-{{ change }}
-{% endfor %}
-Diffstat (except docs and test files)
--------------------------------------
-
-{% for change in diff_stats -%}
-{{ change }}
-{% endfor %}
-{% if requirement_changes %}
-Requirements updates
---------------------
-
-{% for change in requirement_changes -%}
-{{ change }}
-{% endfor %}
-{% endif %}
-"""
-
-CHANGES_ONLY_TPL = """{{ change_header }}
-{% for change in changes -%}
-{{ change }}
-{% endfor %}
-"""
-
-
-def expand_template(contents, params):
- if not params:
- params = {}
- tpl = jinja2.Template(source=contents, undefined=jinja2.StrictUndefined)
- return tpl.render(**params)
-
-
-def run_cmd(cmd, cwd=None):
- # Created since currently the 'processutils' function doesn't take a
- # working directory, which we need in this example due to the different
- # working directories we run programs in...
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- cwd=cwd)
- stdout, stderr = p.communicate()
- if p.returncode != 0:
- raise processutils.ProcessExecutionError(stdout=stdout,
- stderr=stderr,
- exit_code=p.returncode,
- cmd=cmd)
- return stdout, stderr
-
-
-def is_skippable_commit(args, line):
- return (args.skip_requirement_merges and
- line.lower().endswith('updated from global requirements'))
-
-
-def main():
- parser = argparse.ArgumentParser(
- prog='release_notes',
- description=__doc__,
- formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument("library", metavar='path', action="store",
- help="library directory, for example"
- " 'openstack/cliff'",
- )
- parser.add_argument("start_revision", metavar='revision',
- action="store",
- help="start revision, for example '1.8.0'",
- )
- parser.add_argument("end_revision", metavar='revision',
- action="store",
- nargs='?',
- help="end revision, for example '1.9.0'"
- " (default: HEAD)",
- default="HEAD")
- parser.add_argument('--changes-only',
- action='store_true',
- default=False,
- help='List only the change summary, without details',
- )
- parser.add_argument("--notable-changes", metavar='path',
- action="store",
- help="a file containing any notable changes")
- parser.add_argument("--skip-requirement-merges",
- action='store_true', default=False,
- help="skip requirement update commit messages"
- " (default: False)")
- parser.add_argument("--show-dates",
- action='store_true', default=False,
- help="show dates in the change log")
- args = parser.parse_args()
-
- library_path = os.path.abspath(args.library)
- if not os.path.isfile(os.path.join(library_path, "setup.py")):
- sys.stderr.write("No 'setup.py' file found in %s\n" % library_path)
- sys.stderr.write("This will not end well...\n")
- return 1
-
- # Get the python library/program description...
- cmd = [sys.executable, 'setup.py', '--description']
- stdout, stderr = run_cmd(cmd, cwd=library_path)
- description = stdout.strip()
-
- # Get the python library/program name
- cmd = [sys.executable, 'setup.py', '--name']
- stdout, stderr = run_cmd(cmd, cwd=library_path)
- library_name = stdout.strip()
-
- # Get the commits that are in the desired range...
- git_range = "%s..%s" % (args.start_revision, args.end_revision)
- if args.show_dates:
- format = "--format=%h %ci %s"
- else:
- format = "--oneline"
- cmd = ["git", "log", "--no-color", format, "--no-merges", git_range]
- stdout, stderr = run_cmd(cmd, cwd=library_path)
- changes = []
- for commit_line in stdout.splitlines():
- commit_line = commit_line.strip()
- if not commit_line or is_skippable_commit(args, commit_line):
- continue
- else:
- changes.append(commit_line)
-
- # Filter out any requirement file changes...
- requirement_changes = []
- requirement_files = list(glob.glob(os.path.join(library_path,
- '*requirements*.txt')))
- if requirement_files:
- cmd = ['git', 'diff', '-U0', '--no-color', git_range]
- cmd.extend(requirement_files)
- stdout, stderr = run_cmd(cmd, cwd=library_path)
- requirement_changes = [line.strip()
- for line in stdout.splitlines() if line.strip()]
-
- # Get statistics about the range given...
- cmd = ['git', 'diff', '--stat', '--no-color', git_range]
- stdout, stderr = run_cmd(cmd, cwd=library_path)
- diff_stats = []
- for line in stdout.splitlines():
- line = line.strip()
- if not line or line.find("tests") != -1 or line.startswith("doc"):
- continue
- diff_stats.append(line)
-
- # Find what the bug url is...
- bug_url = ''
- with open(os.path.join(library_path, 'README.rst'), 'r') as fh:
- for line in fh:
- pieces = line.split("Bugs:", 1)
- if len(pieces) == 2:
- bug_url = pieces[1].strip()
- break
- if not bug_url:
- raise IOError("No bug url found in '%s'"
- % os.path.join(library_path, 'README.rst'))
-
- notables = ''
- if args.notable_changes:
- with open(args.notable_changes, 'r') as fh:
- notables = fh.read().rstrip()
-
- lp_url = bug_url.replace("bugs.", "").rstrip("/")
- milestone_url = lp_url + "/+milestone/%s" % args.end_revision
- change_header = ["Changes in %s %s" % (library_name, git_range)]
- change_header.append("-" * len(change_header[0]))
-
- params = {
- 'project': os.path.basename(library_path),
- 'description': description,
- 'end_rev': args.end_revision,
- 'range': git_range,
- 'lib': library_path,
- 'milestone_url': milestone_url,
- 'skip_requirement_merges': args.skip_requirement_merges,
- 'bug_url': bug_url,
- 'changes': changes,
- 'requirement_changes': requirement_changes,
- 'diff_stats': diff_stats,
- 'notables': notables,
- 'change_header': "\n".join(change_header),
- 'emotion': random.choice(EMOTIONS),
- }
- if args.changes_only:
- print(expand_template(CHANGES_ONLY_TPL, params))
- else:
- header = expand_template(HEADER_RELEASE_TPL.strip(), params)
- for line in parawrap.wrap(header):
- print(line)
- print(expand_template(CHANGE_RELEASE_TPL, params))
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())