Merge lp:~rvb/maas/expose-version-api into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 3794
Proposed branch: lp:~rvb/maas/expose-version-api
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 496 lines (+197/-164)
5 files modified
src/maasserver/api/tests/test_version.py (+7/-0)
src/maasserver/api/version.py (+7/-0)
src/maasserver/context_processors.py (+2/-2)
src/maasserver/utils/tests/test_version.py (+125/-107)
src/maasserver/utils/version.py (+56/-55)
To merge this branch: bzr merge lp:~rvb/maas/expose-version-api
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+255357@code.launchpad.net

Commit message

Expose the version and the subversion of the running MAAS on the API.

Description of the change

Note that I'm doing a bit more caching in version.py than would be expected: I want to cache things at the low level so that calls to display the version in the UI and calls to return the version in the API call the API backend only once but I also want to cache the result of the (tiny) processing done by get_maas_version_ui() to avoid doing the string interpolation for every UI page requested.

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

Is there any chance this could be backported to 1.7?

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

No complaints. Only think the simple_cache decorator would be better in its own file.

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

Thanks for the review!

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (86.4 KiB)

The attempt to merge lp:~rvb/maas/expose-version-api into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [63.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Get:3 http://security.ubuntu.com trusty-security/main Sources [76.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Get:5 http://security.ubuntu.com trusty-security/universe Sources [19.5 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [251 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates Release [63.5 kB]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [92.0 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [190 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [108 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [490 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [263 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,621 kB in 3s (526 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-iscpy python-jinja2 python-jsonschema python-lockfile python-lxml python-mock python-netaddr python-netifaces python-nose python-oau...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (83.7 KiB)

The attempt to merge lp:~rvb/maas/expose-version-api into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-iscpy python-jinja2 python-jsonschema python-lockfile python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson pyt...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (82.8 KiB)

The attempt to merge lp:~rvb/maas/expose-version-api into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [63.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [63.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [76.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [19.5 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [252 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [190 kB]
Get:9 http://security.ubuntu.com trusty-security/universe amd64 Packages [92.0 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [108 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [490 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [263 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,621 kB in 3s (524 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-iscpy python-jinja2 python-jsonschema python-lockfile python-lxml python-mock python-netaddr python-netifaces python-nose python-oau...

Revision history for this message
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

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

Not a review, but a few comments (and one in the diff):

* Is "subversion" the best term. People often talk about major and minor
  versions.

* The distutils.version module might be of interest.

* One of the explicit reasons for the capabilities views was to
  *discourage* use of version numbers when testing for features. Is this
  therefore a regression? What's the use case?

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

Hey Gavin

> Not a review, but a few comments (and one in the diff):

> * Is "subversion" the best term. People often talk about major and minor
> versions.

AFAIK, major and minor are used for something different: "1.8.0" → major: 1, minor: 8. Here we really want version/subversion I think.

> * One of the explicit reasons for the capabilities views was to
> *discourage* use of version numbers when testing for features. Is this
> therefore a regression? What's the use case?

The capabilities are the thing other tools like Juju should use to check for functionality but some times it's useful (mostly when debugging) to be able to know exactly what the version is in order to, say, figure out if a particular bug fix is present.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/tests/test_version.py'
--- src/maasserver/api/tests/test_version.py 2014-08-18 13:23:21 +0000
+++ src/maasserver/api/tests/test_version.py 2015-04-08 15:11:40 +0000
@@ -21,6 +21,7 @@
21from django.core.urlresolvers import reverse21from django.core.urlresolvers import reverse
22from maasserver.api.version import API_CAPABILITIES_LIST22from maasserver.api.version import API_CAPABILITIES_LIST
23from maasserver.testing.testcase import MAASServerTestCase23from maasserver.testing.testcase import MAASServerTestCase
24from maasserver.utils import version as version_module
2425
2526
26class TestFindingResources(MAASServerTestCase):27class TestFindingResources(MAASServerTestCase):
@@ -31,6 +32,10 @@
31 '/api/1.0/version/', reverse('version'))32 '/api/1.0/version/', reverse('version'))
3233
33 def test_GET_returns_details(self):34 def test_GET_returns_details(self):
35 mock_apt = self.patch(version_module, "get_version_from_apt")
36 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
37 self.patch(version_module, "_cache", {})
38
34 response = self.client.get(reverse('version'))39 response = self.client.get(reverse('version'))
35 self.assertEqual(httplib.OK, response.status_code)40 self.assertEqual(httplib.OK, response.status_code)
3641
@@ -38,5 +43,7 @@
38 self.assertEqual(43 self.assertEqual(
39 {44 {
40 'capabilities': API_CAPABILITIES_LIST,45 'capabilities': API_CAPABILITIES_LIST,
46 'version': '1.8.0',
47 'subversion': 'alpha4+bzr356',
41 },48 },
42 parsed_result)49 parsed_result)
4350
=== modified file 'src/maasserver/api/version.py'
--- src/maasserver/api/version.py 2014-09-17 02:17:35 +0000
+++ src/maasserver/api/version.py 2015-04-08 15:11:40 +0000
@@ -20,6 +20,7 @@
2020
21from django.http import HttpResponse21from django.http import HttpResponse
22from maasserver.api.support import AnonymousOperationsHandler22from maasserver.api.support import AnonymousOperationsHandler
23from maasserver.utils.version import get_maas_version_subversion
2324
24# MAAS capabilities. See docs/capabilities.rst for documentation.25# MAAS capabilities. See docs/capabilities.rst for documentation.
25CAP_NETWORKS_MANAGEMENT = 'networks-management'26CAP_NETWORKS_MANAGEMENT = 'networks-management'
@@ -39,6 +40,8 @@
39 This returns a JSON dictionary with information about this40 This returns a JSON dictionary with information about this
40 MAAS instance.41 MAAS instance.
41 {42 {
43 'version': '1.8.0',
44 'subversion': 'alpha10+bzr3750',
42 'capabilities': ['capability1', 'capability2', ...]45 'capabilities': ['capability1', 'capability2', ...]
43 }46 }
44 """47 """
@@ -46,8 +49,12 @@
46 create = update = delete = None49 create = update = delete = None
4750
48 def read(self, request):51 def read(self, request):
52 version, subversion = get_maas_version_subversion()
49 version_info = {53 version_info = {
50 'capabilities': API_CAPABILITIES_LIST,54 'capabilities': API_CAPABILITIES_LIST,
55 'version': version,
56 'subversion': subversion,
57
51 }58 }
52 return HttpResponse(59 return HttpResponse(
53 version_info, mimetype='application/json; charset=utf-8',60 version_info, mimetype='application/json; charset=utf-8',
5461
=== modified file 'src/maasserver/context_processors.py'
--- src/maasserver/context_processors.py 2015-04-02 14:18:18 +0000
+++ src/maasserver/context_processors.py 2015-04-08 15:11:40 +0000
@@ -23,7 +23,7 @@
23from maasserver.models import Config23from maasserver.models import Config
24from maasserver.utils.version import (24from maasserver.utils.version import (
25 get_maas_doc_version,25 get_maas_doc_version,
26 get_maas_version,26 get_maas_version_ui,
27)27)
2828
2929
@@ -88,6 +88,6 @@
88 'site_name': Config.objects.get_config('maas_name'),88 'site_name': Config.objects.get_config('maas_name'),
89 },89 },
90 'debug': settings.DEBUG,90 'debug': settings.DEBUG,
91 'version': get_maas_version(),91 'version': get_maas_version_ui(),
92 'doc_version': get_maas_doc_version(),92 'doc_version': get_maas_doc_version(),
93 }93 }
9494
=== modified file 'src/maasserver/utils/tests/test_version.py'
--- src/maasserver/utils/tests/test_version.py 2015-03-30 19:38:57 +0000
+++ src/maasserver/utils/tests/test_version.py 2015-04-08 15:11:40 +0000
@@ -63,64 +63,6 @@
63 version.get_version_from_apt(version.REGION_PACKAGE_NAME))63 version.get_version_from_apt(version.REGION_PACKAGE_NAME))
6464
6565
66class TestFormatVersion(MAASTestCase):
67
68 scenarios = [
69 ("with ~", {
70 "version": "1.8.0~alpha4+bzr356-0ubuntu1",
71 "output": "1.8.0 (alpha4+bzr356)",
72 }),
73 ("without ~", {
74 "version": "1.8.0+bzr356-0ubuntu1",
75 "output": "1.8.0 (+bzr356)",
76 }),
77 ("without ~ or +", {
78 "version": "1.8.0-0ubuntu1",
79 "output": "1.8.0",
80 }),
81 ]
82
83 def test__returns_formatted_version(self):
84 self.assertEquals(self.output, version.format_version(self.version))
85
86
87class TestGetMAASRegionPackageVersion(MAASTestCase):
88
89 def test__returns_value_from_global(self):
90 self.patch(version, "MAAS_VERSION", sentinel.maas_version)
91 self.assertIs(
92 sentinel.maas_version, version.get_maas_region_package_version())
93
94 def test__calls_get_version_from_apt_if_global_not_set(self):
95 self.patch(version, "MAAS_VERSION", None)
96 mock_apt = self.patch(version, "get_version_from_apt")
97 version.get_maas_region_package_version()
98 self.assertThat(
99 mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME))
100
101 def test__calls_format_version_with_version_from_apt(self):
102 self.patch(version, "MAAS_VERSION", None)
103 current_ver = "1.8.0~alpha4+bzr356-0ubuntu1"
104 mock_apt = self.patch(version, "get_version_from_apt")
105 mock_apt.return_value = current_ver
106 mock_format = self.patch(version, "format_version")
107 mock_format.return_value = sentinel.format
108 self.expectThat(
109 version.get_maas_region_package_version(), Is(sentinel.format))
110 self.expectThat(
111 mock_format, MockCalledOnceWith(current_ver))
112
113 def test__sets_global_value(self):
114 self.patch(version, "MAAS_VERSION", None)
115 current_ver = "1.8.0~alpha4+bzr356-0ubuntu1"
116 mock_apt = self.patch(version, "get_version_from_apt")
117 mock_apt.return_value = current_ver
118 mock_format = self.patch(version, "format_version")
119 mock_format.return_value = sentinel.format
120 version.get_maas_region_package_version()
121 self.assertIs(sentinel.format, version.MAAS_VERSION)
122
123
124class TestGetMAASBranch(MAASTestCase):66class TestGetMAASBranch(MAASTestCase):
12567
126 def test__returns_None_if_Branch_is_None(self):68 def test__returns_None_if_Branch_is_None(self):
@@ -139,61 +81,137 @@
139 self.assertIsNone(version.get_maas_branch())81 self.assertIsNone(version.get_maas_branch())
14082
14183
142class TestGetMAASVersion(MAASTestCase):84class TestExtractVersionSubversion(MAASTestCase):
14385
144 def test__returns_version_from_get_maas_region_package_version(self):86 scenarios = [
145 mock_version = self.patch(version, "get_maas_region_package_version")87 ("with ~", {
146 mock_version.return_value = sentinel.version88 "version": "1.8.0~alpha4+bzr356-0ubuntu1",
147 self.assertIs(sentinel.version, version.get_maas_version())89 "output": ("1.8.0", "alpha4+bzr356"),
14890 }),
149 def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self):91 ("without ~", {
150 mock_version = self.patch(version, "get_maas_region_package_version")92 "version": "1.8.0+bzr356-0ubuntu1",
151 mock_version.return_value = ""93 "output": ("1.8.0", "+bzr356"),
152 mock_branch = self.patch(version, "get_maas_branch")94 }),
153 mock_branch.return_value = None95 ("without ~ or +", {
154 self.assertEquals("unknown", version.get_maas_version())96 "version": "1.8.0-0ubuntu1",
15597 "output": ("1.8.0", ""),
156 def test__returns_from_source_and_revno_from_branch(self):98 }),
157 mock_version = self.patch(version, "get_maas_region_package_version")99 ]
158 mock_version.return_value = ""100
159 revno = random.randint(1, 5000)101 def test__returns_version_subversion(self):
160 mock_branch = self.patch(version, "get_maas_branch")102 self.assertEquals(
161 mock_branch.return_value.revno.return_value = revno103 self.output, version.extract_version_subversion(self.version))
162 self.assertEquals(104
163 "from source (+bzr%s)" % revno, version.get_maas_version())105
164106class TestVersionTestCase(MAASTestCase):
165107 """MAASTestCase that resets the cache used by utility methods."""
166class TestGetMAASMainVersion(MAASTestCase):108
167109 def setUp(self):
168 def test__returns_main_version_from_package_version_with_space(self):110 super(TestVersionTestCase, self).setUp()
169 mock_version = self.patch(version, "get_maas_region_package_version")111 self.patch(version, "_cache", {})
170 mock_version.return_value = "1.8.0 (alpha4+bzr356)"112
171 self.assertEquals("1.8.0", version.get_maas_main_version())113
172114class TestGetMAASPackageVersion(TestVersionTestCase):
173 def test__returns_main_version_from_package_version_without_space(self):115
174 mock_version = self.patch(version, "get_maas_region_package_version")116 def test__calls_get_version_from_apt(self):
175 mock_version.return_value = "1.8.0"117 mock_apt = self.patch(version, "get_version_from_apt")
176 self.assertEquals("1.8.0", version.get_maas_main_version())118 mock_apt.return_value = sentinel.version
177119 self.expectThat(
178 def test__returns_empty_if_version_is_empty(self):120 version.get_maas_package_version(), Is(sentinel.version))
179 mock_version = self.patch(version, "get_maas_region_package_version")121 self.expectThat(
180 mock_version.return_value = ""122 mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME))
181 self.assertEquals("", version.get_maas_main_version())123
182124
183125class TestGetMAASVersionSubversion(TestVersionTestCase):
184class TestGetMAASDocVersion(MAASTestCase):126
127 def test__returns_package_version(self):
128 mock_apt = self.patch(version, "get_version_from_apt")
129 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
130 self.assertEquals(
131 ("1.8.0", "alpha4+bzr356"), version.get_maas_version_subversion())
132
133 def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self):
134 mock_version = self.patch(version, "get_version_from_apt")
135 mock_version.return_value = ""
136 mock_branch = self.patch(version, "get_maas_branch")
137 mock_branch.return_value = None
138 self.assertEquals(
139 ("unknown", ""), version.get_maas_version_subversion())
140
141 def test__returns_from_source_and_revno_from_branch(self):
142 mock_version = self.patch(version, "get_version_from_apt")
143 mock_version.return_value = ""
144 revno = random.randint(1, 5000)
145 mock_branch = self.patch(version, "get_maas_branch")
146 mock_branch.return_value.revno.return_value = revno
147 self.assertEquals(
148 ("from source (+bzr%s)" % revno, ""),
149 version.get_maas_version_subversion())
150
151
152class TestGetMAASVersionUI(TestVersionTestCase):
153
154 def test__returns_package_version(self):
155 mock_apt = self.patch(version, "get_version_from_apt")
156 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
157 self.assertEquals(
158 "1.8.0 (alpha4+bzr356)", version.get_maas_version_ui())
159
160 def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self):
161 mock_version = self.patch(version, "get_version_from_apt")
162 mock_version.return_value = ""
163 mock_branch = self.patch(version, "get_maas_branch")
164 mock_branch.return_value = None
165 self.assertEquals("unknown", version.get_maas_version_ui())
166
167 def test__returns_from_source_and_revno_from_branch(self):
168 mock_version = self.patch(version, "get_version_from_apt")
169 mock_version.return_value = ""
170 revno = random.randint(1, 5000)
171 mock_branch = self.patch(version, "get_maas_branch")
172 mock_branch.return_value.revno.return_value = revno
173 self.assertEquals(
174 "from source (+bzr%s)" % revno, version.get_maas_version_ui())
175
176
177class TestGetMAASDocVersion(TestVersionTestCase):
185178
186 def test__returns_doc_version_with_greater_than_1_decimals(self):179 def test__returns_doc_version_with_greater_than_1_decimals(self):
187 mock_version = self.patch(version, "get_maas_main_version")180 mock_apt = self.patch(version, "get_version_from_apt")
188 mock_version.return_value = "1.8.0"181 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
189 self.assertEquals("docs1.8", version.get_maas_doc_version())182 self.assertEquals("docs1.8", version.get_maas_doc_version())
190183
191 def test__returns_doc_version_with_equal_to_1_decimals(self):184 def test__returns_doc_version_with_equal_to_1_decimals(self):
192 mock_version = self.patch(version, "get_maas_main_version")185 mock_apt = self.patch(version, "get_version_from_apt")
193 mock_version.return_value = "1.8"186 mock_apt.return_value = "1.8~alpha4+bzr356-0ubuntu1"
194 self.assertEquals("docs1.8", version.get_maas_doc_version())187 self.assertEquals("docs1.8", version.get_maas_doc_version())
195188
196 def test__returns_just_doc_if_version_is_empty(self):189 def test__returns_just_doc_if_version_is_empty(self):
197 mock_version = self.patch(version, "get_maas_main_version")190 mock_apt = self.patch(version, "get_version_from_apt")
198 mock_version.return_value = ""191 mock_apt.return_value = ""
199 self.assertEquals("docs", version.get_maas_doc_version())192 self.assertEquals("docs", version.get_maas_doc_version())
193
194
195class TestVersionMethodsCached(TestVersionTestCase):
196
197 scenarios = [
198 ("get_maas_package_version", dict(method="get_maas_package_version")),
199 ("get_maas_version_subversion", dict(
200 method="get_maas_version_subversion")),
201 ("get_maas_version_ui", dict(method="get_maas_version_ui")),
202 ("get_maas_doc_version", dict(method="get_maas_doc_version")),
203 ]
204
205 def test_method_is_cached(self):
206 mock_apt = self.patch(version, "get_version_from_apt")
207 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
208 cached_method = getattr(version, self.method)
209 first_return_value = cached_method()
210 second_return_value = cached_method()
211 # The return value is not empty (full unit tests have been performed
212 # earlier).
213 self.assertNotIn(first_return_value, [b'', u'', None])
214 self.assertEqual(first_return_value, second_return_value)
215 # Apt has only been called once.
216 self.expectThat(
217 mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME))
200218
=== modified file 'src/maasserver/utils/version.py'
--- src/maasserver/utils/version.py 2015-03-30 17:54:35 +0000
+++ src/maasserver/utils/version.py 2015-04-08 15:11:40 +0000
@@ -14,7 +14,8 @@
14__metaclass__ = type14__metaclass__ = type
15__all__ = [15__all__ = [
16 "get_maas_doc_version",16 "get_maas_doc_version",
17 "get_maas_version",17 "get_maas_version_subversion",
18 "get_maas_version_ui",
18 ]19 ]
1920
20import apt_pkg21import apt_pkg
@@ -29,9 +30,6 @@
29# Initialize apt_pkg.30# Initialize apt_pkg.
30apt_pkg.init()31apt_pkg.init()
3132
32# Holds the version of maas.
33MAAS_VERSION = None
34
35# Name of maas package to get version from.33# Name of maas package to get version from.
36REGION_PACKAGE_NAME = "maas-region-controller-min"34REGION_PACKAGE_NAME = "maas-region-controller-min"
3735
@@ -50,34 +48,16 @@
50 return ""48 return ""
5149
5250
53def format_version(version):51def extract_version_subversion(version):
54 """Format `version` into a better looking string for the UI."""52 """Return a tuple (version, subversion) from the given apt version."""
55 if "~" in version:53 if "~" in version:
56 main_version, extra = version.split("~", 1)54 main_version, extra = version.split("~", 1)
57 return "%s (%s)" % (main_version, extra.split("-", 1)[0])55 return main_version, extra.split("-", 1)[0]
58 elif "+" in version:56 elif "+" in version:
59 main_version, extra = version.split("+", 1)57 main_version, extra = version.split("+", 1)
60 return "%s (+%s)" % (main_version, extra.split("-", 1)[0])58 return main_version, "+" + extra.split("-", 1)[0]
61 else:59 else:
62 return version.split("-", 1)[0]60 return version.split("-", 1)[0], ''
63
64
65def get_maas_region_package_version():
66 """Return the dpkg version for `REGION_PACKAGE_NAME`.
67
68 Lazy loads the version. Once loaded it will not be loaded again.
69 This is to speed up the call to this method and also to require the
70 region to be restarted to see the new version.
71 """
72 global MAAS_VERSION
73 if MAAS_VERSION is None:
74 MAAS_VERSION = get_version_from_apt(REGION_PACKAGE_NAME)
75 # Possibly this returned an empty string, meaning it's not installed
76 # and no need to call again.
77 if MAAS_VERSION:
78 # It is a valid version so set the correct format.
79 MAAS_VERSION = format_version(MAAS_VERSION)
80 return MAAS_VERSION
8161
8262
83def get_maas_branch():63def get_maas_branch():
@@ -90,41 +70,62 @@
90 return None70 return None
9171
9272
93def get_maas_version():73_cache = {}
94 """Return the version string for the running MAAS region."""74
95 # MAAS is installed from package, return the version string.75
96 version = get_maas_region_package_version()76# A very simply memoize function: when we switch to Django 1.7 we should use
97 if version:77# Django's lru_cache method.
98 return version78def simple_cache(fun):
79 def wrapped(*args, **kwargs):
80 key = hash(repr(fun) + repr(args) + repr(kwargs))
81 if not key in _cache:
82 _cache[key] = fun(*args, **kwargs)
83 return _cache[key]
84
85 wrapped.__doc__ = "%s %s" % (fun.__doc__, "(cached)")
86 return wrapped
87
88
89@simple_cache
90def get_maas_package_version():
91 """Return the apt version for the main MAAS package."""
92 return get_version_from_apt(REGION_PACKAGE_NAME)
93
94
95@simple_cache
96def get_maas_version_subversion():
97 """Return a tuple with the MAAS version and the MAAS subversion."""
98 apt_version = get_maas_package_version()
99 if apt_version:
100 return extract_version_subversion(apt_version)
99 else:101 else:
100 # Get the branch information102 # Get the branch information
101 branch = get_maas_branch()103 branch = get_maas_branch()
102 if branch is None:104 if branch is None:
103 # Not installed not in branch, then no way to identify. This should105 # Not installed not in branch, then no way to identify. This should
104 # not happen, but just in case.106 # not happen, but just in case.
105 return "unknown"107 return "unknown", ''
106 else:108 else:
107 return "from source (+bzr%s)" % branch.revno()109 return "from source (+bzr%s)" % branch.revno(), ''
108110
109111
110def get_maas_main_version():112@simple_cache
111 """Return the main version for the running MAAS region."""113def get_maas_version_ui():
112 # MAAS is installed from package, return the version string.114 """Return the version string for the running MAAS region.
113 version = get_maas_region_package_version()115
114 if version:116 The returned string is suitable to display in the UI.
115 if " " in version:117 """
116 return version.split(" ")[0]118 version, subversion = get_maas_version_subversion()
117 else:119 return "%s (%s)" % (version, subversion) if subversion else version
118 return version120
119 else:121
120 return ""122@simple_cache
121
122
123def get_maas_doc_version():123def get_maas_doc_version():
124 """Return the doc version for the running MAAS region."""124 """Return the doc version for the running MAAS region."""
125 main_version = get_maas_main_version()
126 doc_prefix = 'docs'125 doc_prefix = 'docs'
127 if not main_version:126 apt_version = get_maas_package_version()
127 if apt_version:
128 version, _ = extract_version_subversion(apt_version)
129 return doc_prefix + '.'.join(version.split('.')[:2])
130 else:
128 return doc_prefix131 return doc_prefix
129 else:
130 return doc_prefix + '.'.join(main_version.split('.')[:2])