Merge lp:~blake-rouse/maas/add-version-to-ui into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 3597
Proposed branch: lp:~blake-rouse/maas/add-version-to-ui
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 392 lines (+332/-2)
6 files modified
required-packages/base (+1/-0)
src/maasserver/context_processors.py (+6/-0)
src/maasserver/templates/maasserver/base.html (+1/-1)
src/maasserver/templates/maasserver/index.html (+1/-1)
src/maasserver/utils/tests/test_version.py (+194/-0)
src/maasserver/utils/version.py (+129/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/add-version-to-ui
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+251615@code.launchpad.net

Commit message

Show the installed version of MAAS on the WebUI.

This will show the installed version of the maas-region-controller package on the MAAS WebUI. If being ran from the developer environment it will show that it is from source and the revision number of the current branch.

To post a comment you must log in.
Revision history for this message
Christian Reis (kiko) wrote :

On Tue, Mar 03, 2015 at 03:22:27PM -0000, Blake Rouse wrote:
> - <span class="version">MAAS</span> <a href="http://maas.ubuntu.com/docs1.7/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/documentation/">View documentation</a>
> + <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/docs1.7/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/documentation/">View documentation</a>

You may have considered this already but could we use the version string
to automatically select the right subdirectory in the URL?
--
Christian Robottom Reis | [+1] 612 888 4935 | http://launchpad.net/~kiko
Canonical VP Hyperscale | [+55 16] 9 9112 6430

Revision history for this message
Raphaël Badin (rvb) wrote :

Feel free to disagree but I feel the caching is done at the wrong level in this branch. The result is that apt will be called a lot when running from a branch.

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Okay I made the required fixes and now have the documentation link working based on the version. More work then I wanted to do for this branch, but your welcome! :-)

Revision history for this message
Gavin Panella (allenap) wrote :

A few comments (not a review):

- Instead of searching for a particular package name, how about
  searching for the package that owns the current file? That's more
  likely to be correct *and* it reduces the coupling between upstream
  and the package. For example:

    import inspect
    from os.path import realpath

    import apt

    def find_package_of(cache, filename):
        # This is quicker than you might think.
        for pkg in cache:
            if pkg.is_installed:
                if filename in pkg.installed_files:
                    return pkg

    cache = apt.Cache()
    source = inspect.getsourcefile(find_package_of)
    package = find_package_of(cache, realpath(source))
    if package is None:
        pass # Check for branch info.
    else:
        installed = package.installed
        # Use the *source* package name and version.
        desc = "%s %s" % (installed.source_name, installed.source_version)

- When reporting the branch version, also include the branch's location,
  or its name. This is less important to the developer, but could be
  very useful in screen-grabs, for example.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Those are all great suggestions but I don't want to spend anymore time on this branch. There are many more important things that need to be completed and spending more time on this branch would be a waste of the time I have available to complete more important features.

I just did this quickly to fix a annoying bug, that will help the UI.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Those are all great suggestions but I don't want to spend anymore time on this branch.

All right then… I would have liked to see the caching problem fixed but it isn't critical.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

The caching problem is fixed. It will set the MAAS_VERSION to "", causing it not to be reloaded after the first call.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'required-packages/base'
--- required-packages/base 2015-01-20 15:56:10 +0000
+++ required-packages/base 2015-03-03 16:09:28 +0000
@@ -13,6 +13,7 @@
13libjs-angularjs13libjs-angularjs
14libpq-dev14libpq-dev
15postgresql15postgresql
16python-apt
16python-bson17python-bson
17python-bzrlib18python-bzrlib
18python-convoy19python-convoy
1920
=== modified file 'src/maasserver/context_processors.py'
--- src/maasserver/context_processors.py 2015-02-26 18:34:40 +0000
+++ src/maasserver/context_processors.py 2015-03-03 16:09:28 +0000
@@ -23,6 +23,10 @@
23from maasserver.components import get_persistent_errors23from maasserver.components import get_persistent_errors
24from maasserver.forms import get_node_edit_form24from maasserver.forms import get_node_edit_form
25from maasserver.models import Config25from maasserver.models import Config
26from maasserver.utils.version import (
27 get_maas_doc_version,
28 get_maas_version,
29 )
2630
2731
28def yui(context):32def yui(context):
@@ -86,4 +90,6 @@
86 'site_name': Config.objects.get_config('maas_name'),90 'site_name': Config.objects.get_config('maas_name'),
87 },91 },
88 'debug': settings.DEBUG,92 'debug': settings.DEBUG,
93 'version': get_maas_version(),
94 'doc_version': get_maas_doc_version(),
89 }95 }
9096
=== modified file 'src/maasserver/templates/maasserver/base.html'
--- src/maasserver/templates/maasserver/base.html 2015-02-20 17:00:30 +0000
+++ src/maasserver/templates/maasserver/base.html 2015-03-03 16:09:28 +0000
@@ -123,7 +123,7 @@
123 <div class="legal-inner">123 <div class="legal-inner">
124 {% block footer-copyright %}124 {% block footer-copyright %}
125 <p class="twelve-col">125 <p class="twelve-col">
126 <span class="version">MAAS</span> <a href="http://maas.ubuntu.com/docs1.7/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/documentation/">View documentation</a>126 <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/{{doc_version}}/">View documentation</a>
127 </p>127 </p>
128 <p class="twelve-col copy">&copy; 2015 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>128 <p class="twelve-col copy">&copy; 2015 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>
129 {% endblock %}129 {% endblock %}
130130
=== modified file 'src/maasserver/templates/maasserver/index.html'
--- src/maasserver/templates/maasserver/index.html 2015-02-20 17:00:30 +0000
+++ src/maasserver/templates/maasserver/index.html 2015-03-03 16:09:28 +0000
@@ -102,7 +102,7 @@
102 <div class="legal clearfix">102 <div class="legal clearfix">
103 <div class="legal-inner">103 <div class="legal-inner">
104 <p class="twelve-col">104 <p class="twelve-col">
105 <span class="version">MAAS</span> <a href="http://maas.ubuntu.com/docs1.7/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/documentation/">View documentation</a>105 <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="https://maas.ubuntu.com/{{doc_version}}/">View documentation</a>
106 </p>106 </p>
107 <p class="twelve-col copy">&copy; 2015 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>107 <p class="twelve-col copy">&copy; 2015 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>
108 </div>108 </div>
109109
=== added file 'src/maasserver/utils/tests/test_version.py'
--- src/maasserver/utils/tests/test_version.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/utils/tests/test_version.py 2015-03-03 16:09:28 +0000
@@ -0,0 +1,194 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test version utilities."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17import random
18
19from bzrlib.errors import NotBranchError
20from maasserver.utils import version
21from maastesting.matchers import MockCalledOnceWith
22from maastesting.testcase import MAASTestCase
23from mock import (
24 MagicMock,
25 sentinel,
26 )
27from testtools.matchers import Is
28
29
30class TestGetVersionFromAPT(MAASTestCase):
31
32 def test__returns_empty_string_if_package_not_in_cache(self):
33 self.patch(version.apt_pkg, "Cache")
34 self.assertEquals(
35 "",
36 version.get_version_from_apt(version.REGION_PACKAGE_NAME))
37
38 def test__returns_empty_string_if_not_current_ver_from_package(self):
39 package = MagicMock()
40 package.current_ver = None
41 mock_cache = {
42 version.REGION_PACKAGE_NAME: package,
43 }
44 self.patch(version.apt_pkg, "Cache").return_value = mock_cache
45 self.assertEquals(
46 "",
47 version.get_version_from_apt(version.REGION_PACKAGE_NAME))
48
49 def test__returns_ver_str_from_package(self):
50 package = MagicMock()
51 package.current_ver.ver_str = sentinel.ver_str
52 mock_cache = {
53 version.REGION_PACKAGE_NAME: package,
54 }
55 self.patch(version.apt_pkg, "Cache").return_value = mock_cache
56 self.assertIs(
57 sentinel.ver_str,
58 version.get_version_from_apt(version.REGION_PACKAGE_NAME))
59
60
61class TestFormatVersion(MAASTestCase):
62
63 scenarios = [
64 ("with ~", {
65 "version": "1.8.0~alpha4+bzr356-0ubuntu1",
66 "output": "1.8.0 (alpha4+bzr356)",
67 }),
68 ("without ~", {
69 "version": "1.8.0+bzr356-0ubuntu1",
70 "output": "1.8.0 (+bzr356)",
71 }),
72 ("without ~ or +", {
73 "version": "1.8.0-0ubuntu1",
74 "output": "1.8.0",
75 }),
76 ]
77
78 def test__returns_formatted_version(self):
79 self.assertEquals(self.output, version.format_version(self.version))
80
81
82class TestGetMAASRegionPackageVersion(MAASTestCase):
83
84 def test__returns_value_from_global(self):
85 self.patch(version, "MAAS_VERSION", sentinel.maas_version)
86 self.assertIs(
87 sentinel.maas_version, version.get_maas_region_package_version())
88
89 def test__calls_get_version_from_apt_if_global_not_set(self):
90 self.patch(version, "MAAS_VERSION", None)
91 mock_apt = self.patch(version, "get_version_from_apt")
92 version.get_maas_region_package_version()
93 self.assertThat(
94 mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME))
95
96 def test__calls_format_version_with_version_from_apt(self):
97 self.patch(version, "MAAS_VERSION", None)
98 current_ver = "1.8.0~alpha4+bzr356-0ubuntu1"
99 mock_apt = self.patch(version, "get_version_from_apt")
100 mock_apt.return_value = current_ver
101 mock_format = self.patch(version, "format_version")
102 mock_format.return_value = sentinel.format
103 self.expectThat(
104 version.get_maas_region_package_version(), Is(sentinel.format))
105 self.expectThat(
106 mock_format, MockCalledOnceWith(current_ver))
107
108 def test__sets_global_value(self):
109 self.patch(version, "MAAS_VERSION", None)
110 current_ver = "1.8.0~alpha4+bzr356-0ubuntu1"
111 mock_apt = self.patch(version, "get_version_from_apt")
112 mock_apt.return_value = current_ver
113 mock_format = self.patch(version, "format_version")
114 mock_format.return_value = sentinel.format
115 version.get_maas_region_package_version()
116 self.assertIs(sentinel.format, version.MAAS_VERSION)
117
118
119class TestGetMAASBranch(MAASTestCase):
120
121 def test__returns_None_if_Branch_is_None(self):
122 self.patch(version, "Branch", None)
123 self.assertIsNone(version.get_maas_branch())
124
125 def test__calls_Branch_open_with_current_dir(self):
126 mock_open = self.patch(version.Branch, "open")
127 mock_open.return_value = sentinel.branch
128 self.expectThat(version.get_maas_branch(), Is(sentinel.branch))
129 self.expectThat(mock_open, MockCalledOnceWith("."))
130
131 def test__returns_None_on_NotBranchError(self):
132 mock_open = self.patch(version.Branch, "open")
133 mock_open.side_effect = NotBranchError("")
134 self.assertIsNone(version.get_maas_branch())
135
136
137class TestGetMAASVersion(MAASTestCase):
138
139 def test__returns_version_from_get_maas_region_package_version(self):
140 mock_version = self.patch(version, "get_maas_region_package_version")
141 mock_version.return_value = sentinel.version
142 self.assertIs(sentinel.version, version.get_maas_version())
143
144 def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self):
145 mock_version = self.patch(version, "get_maas_region_package_version")
146 mock_version.return_value = ""
147 mock_branch = self.patch(version, "get_maas_branch")
148 mock_branch.return_value = None
149 self.assertEquals("unknown", version.get_maas_version())
150
151 def test__returns_from_source_and_revno_from_branch(self):
152 mock_version = self.patch(version, "get_maas_region_package_version")
153 mock_version.return_value = ""
154 revno = random.randint(1, 5000)
155 mock_branch = self.patch(version, "get_maas_branch")
156 mock_branch.return_value.revno.return_value = revno
157 self.assertEquals(
158 "from source (+bzr%s)" % revno, version.get_maas_version())
159
160
161class TestGetMAASMainVersion(MAASTestCase):
162
163 def test__returns_main_version_from_package_version_with_space(self):
164 mock_version = self.patch(version, "get_maas_region_package_version")
165 mock_version.return_value = "1.8.0 (alpha4+bzr356)"
166 self.assertEquals("1.8.0", version.get_maas_main_version())
167
168 def test__returns_main_version_from_package_version_without_space(self):
169 mock_version = self.patch(version, "get_maas_region_package_version")
170 mock_version.return_value = "1.8.0"
171 self.assertEquals("1.8.0", version.get_maas_main_version())
172
173 def test__returns_empty_if_version_is_empty(self):
174 mock_version = self.patch(version, "get_maas_region_package_version")
175 mock_version.return_value = ""
176 self.assertEquals("", version.get_maas_main_version())
177
178
179class TestGetMAASDocVersion(MAASTestCase):
180
181 def test__returns_doc_version_with_greater_than_1_decimals(self):
182 mock_version = self.patch(version, "get_maas_main_version")
183 mock_version.return_value = "1.8.0"
184 self.assertEquals("doc1.8", version.get_maas_doc_version())
185
186 def test__returns_doc_version_with_equal_to_1_decimals(self):
187 mock_version = self.patch(version, "get_maas_main_version")
188 mock_version.return_value = "1.8"
189 self.assertEquals("doc1.8", version.get_maas_doc_version())
190
191 def test__returns_just_doc_if_version_is_empty(self):
192 mock_version = self.patch(version, "get_maas_main_version")
193 mock_version.return_value = ""
194 self.assertEquals("doc", version.get_maas_doc_version())
0195
=== added file 'src/maasserver/utils/version.py'
--- src/maasserver/utils/version.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/utils/version.py 2015-03-03 16:09:28 +0000
@@ -0,0 +1,129 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Version utilities."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = [
16 ]
17
18import apt_pkg
19
20
21try:
22 from bzrlib.branch import Branch
23 from bzrlib.errors import NotBranchError
24except ImportError:
25 Branch = None
26
27# Initialize apt_pkg.
28apt_pkg.init()
29
30# Holds the version of maas.
31MAAS_VERSION = None
32
33# Name of maas package to get version from.
34REGION_PACKAGE_NAME = "maas-region-controller"
35
36
37def get_version_from_apt(package):
38 """Return the version output from `apt_pkg.Cache` for the given package."""
39 cache = apt_pkg.Cache()
40 version = None
41 if package in cache:
42 apt_package = cache[package]
43 version = apt_package.current_ver
44
45 if version is not None:
46 return version.ver_str
47 else:
48 return ""
49
50
51def format_version(version):
52 """Format `version` into a better looking string for the UI."""
53 if "~" in version:
54 main_version, extra = version.split("~", 1)
55 return "%s (%s)" % (main_version, extra.split("-", 1)[0])
56 elif "+" in version:
57 main_version, extra = version.split("+", 1)
58 return "%s (+%s)" % (main_version, extra.split("-", 1)[0])
59 else:
60 return version.split("-", 1)[0]
61
62
63def get_maas_region_package_version():
64 """Return the dpkg version for `REGION_PACKAGE_NAME`.
65
66 Lazy loads the version. Once loaded it will not be loaded again.
67 This is to speed up the call to this method and also to require the
68 region to be restarted to see the new version.
69 """
70 global MAAS_VERSION
71 if MAAS_VERSION is None:
72 MAAS_VERSION = get_version_from_apt(REGION_PACKAGE_NAME)
73 # Possible this returned an empty string, meaning its not installed
74 # and no need to call again.
75 if MAAS_VERSION:
76 # It is a valid version so set the correct format.
77 MAAS_VERSION = format_version(MAAS_VERSION)
78 return MAAS_VERSION
79
80
81def get_maas_branch():
82 """Return the `bzrlib.branch.Branch` for this running MAAS."""
83 if Branch is None:
84 return None
85 try:
86 return Branch.open(".")
87 except NotBranchError:
88 return None
89
90
91def get_maas_version():
92 """Return the version string for the running MAAS region."""
93 # MAAS is installed from package, return the version string.
94 version = get_maas_region_package_version()
95 if version:
96 return version
97 else:
98 # Get the branch information
99 branch = get_maas_branch()
100 if branch is None:
101 # Not installed not in branch, then no way to identify. This should
102 # not happen, but just in case.
103 return "unknown"
104 else:
105 return "from source (+bzr%s)" % branch.revno()
106
107
108def get_maas_main_version():
109 """Return the main version for the running MAAS region."""
110 # MAAS is installed from package, return the version string.
111 version = get_maas_region_package_version()
112 if version:
113 if " " in version:
114 return version.split(" ")[0]
115 else:
116 return version
117 else:
118 return ""
119
120
121def get_maas_doc_version():
122 """Return the doc version for the running MAAS region."""
123 main_version = get_maas_main_version()
124 if not main_version:
125 return "doc"
126 if main_version.count('.') > 1:
127 return "doc%s" % '.'.join(main_version.split('.')[:2])
128 else:
129 return "doc%s" % main_version