Merge ~mpontillo/maas:rack-version-utils into maas:master
- Git
- lp:~mpontillo/maas
- rack-version-utils
- Merge into master
Proposed by
Mike Pontillo
Status: | Merged |
---|---|
Approved by: | Mike Pontillo |
Approved revision: | f660088482f7947bda6654935deb03c382b7f5df |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~mpontillo/maas:rack-version-utils |
Merge into: | maas:master |
Diff against target: |
1137 lines (+461/-159) 17 files modified
dev/null (+0/-142) src/maasserver/__init__.py (+3/-1) src/maasserver/api/tests/test_version.py (+1/-1) src/maasserver/api/version.py (+1/-1) src/maasserver/bootresources.py (+4/-4) src/maasserver/bootsources.py (+1/-1) src/maasserver/context_processors.py (+1/-1) src/maasserver/tests/test_bootresources.py (+1/-1) src/maasserver/tests/test_bootsources.py (+1/-1) src/maasserver/websockets/handlers/bootresource.py (+1/-1) src/maasserver/websockets/handlers/general.py (+1/-1) src/maasserver/websockets/handlers/tests/test_bootresource.py (+1/-1) src/metadataserver/builtin_scripts/__init__.py (+1/-1) src/metadataserver/builtin_scripts/tests/test_builtin_scripts.py (+1/-1) src/provisioningserver/utils/tests/test_version.py (+293/-0) src/provisioningserver/utils/version.py (+149/-0) utilities/check-imports (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Newell Jensen | Pending | ||
Review via email: mp+327333@code.launchpad.net |
This proposal supersedes a proposal from 2017-07-12.
Commit message
Make version check utilities work for the rack.
This allows rack-only installations to determine their version.
Also moves code to determine MAAS version to the src/provisionin
Description of the change
To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote : Posted in a previous version of this proposal | # |
review:
Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote : | # |
Self-approving previously approved branch. (I resubmitted to land two related branches together.)
review:
Approve
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b rack-version-utils lp:~mpontillo/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
- f660088... by Mike Pontillo
-
Fix imports.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/__init__.py b/src/maasserver/__init__.py | |||
2 | index cd6b4d6..7d85981 100644 | |||
3 | --- a/src/maasserver/__init__.py | |||
4 | +++ b/src/maasserver/__init__.py | |||
5 | @@ -13,8 +13,10 @@ __all__ = [ | |||
6 | 13 | import logging | 13 | import logging |
7 | 14 | from os import environ | 14 | from os import environ |
8 | 15 | 15 | ||
9 | 16 | from provisioningserver.utils import version | ||
10 | 16 | 17 | ||
12 | 17 | __version__ = '2.3.0' | 18 | |
13 | 19 | __version__ = version.DEFAULT_VERSION | ||
14 | 18 | 20 | ||
15 | 19 | default_app_config = 'maasserver.apps.MAASConfig' | 21 | default_app_config = 'maasserver.apps.MAASConfig' |
16 | 20 | 22 | ||
17 | diff --git a/src/maasserver/api/tests/test_version.py b/src/maasserver/api/tests/test_version.py | |||
18 | index b9204d2..defa1fb 100644 | |||
19 | --- a/src/maasserver/api/tests/test_version.py | |||
20 | +++ b/src/maasserver/api/tests/test_version.py | |||
21 | @@ -13,7 +13,7 @@ from django.conf import settings | |||
22 | 13 | from django.core.urlresolvers import reverse | 13 | from django.core.urlresolvers import reverse |
23 | 14 | from maasserver.api.version import API_CAPABILITIES_LIST | 14 | from maasserver.api.version import API_CAPABILITIES_LIST |
24 | 15 | from maasserver.testing.api import APITestCase | 15 | from maasserver.testing.api import APITestCase |
26 | 16 | from maasserver.utils import version as version_module | 16 | from provisioningserver.utils import version as version_module |
27 | 17 | 17 | ||
28 | 18 | 18 | ||
29 | 19 | class TestVersionAPIBasics(APITestCase.ForAnonymousAndUserAndAdmin): | 19 | class TestVersionAPIBasics(APITestCase.ForAnonymousAndUserAndAdmin): |
30 | diff --git a/src/maasserver/api/version.py b/src/maasserver/api/version.py | |||
31 | index 3fc557b..77bb317 100644 | |||
32 | --- a/src/maasserver/api/version.py | |||
33 | +++ b/src/maasserver/api/version.py | |||
34 | @@ -12,7 +12,7 @@ import json | |||
35 | 12 | 12 | ||
36 | 13 | from django.http import HttpResponse | 13 | from django.http import HttpResponse |
37 | 14 | from maasserver.api.support import AnonymousOperationsHandler | 14 | from maasserver.api.support import AnonymousOperationsHandler |
39 | 15 | from maasserver.utils.version import get_maas_version_subversion | 15 | from provisioningserver.utils.version import get_maas_version_subversion |
40 | 16 | 16 | ||
41 | 17 | # MAAS capabilities. See docs/version.rst for documentation. | 17 | # MAAS capabilities. See docs/version.rst for documentation. |
42 | 18 | CAP_NETWORKS_MANAGEMENT = 'networks-management' | 18 | CAP_NETWORKS_MANAGEMENT = 'networks-management' |
43 | diff --git a/src/maasserver/bootresources.py b/src/maasserver/bootresources.py | |||
44 | index 996a6a0..fd7ede4 100644 | |||
45 | --- a/src/maasserver/bootresources.py | |||
46 | +++ b/src/maasserver/bootresources.py | |||
47 | @@ -77,10 +77,6 @@ from maasserver.utils.orm import ( | |||
48 | 77 | with_connection, | 77 | with_connection, |
49 | 78 | ) | 78 | ) |
50 | 79 | from maasserver.utils.threads import deferToDatabase | 79 | from maasserver.utils.threads import deferToDatabase |
51 | 80 | from maasserver.utils.version import ( | ||
52 | 81 | get_maas_version_tuple, | ||
53 | 82 | get_maas_version_user_agent, | ||
54 | 83 | ) | ||
55 | 84 | from provisioningserver.config import is_dev_environment | 80 | from provisioningserver.config import is_dev_environment |
56 | 85 | from provisioningserver.events import EVENT_TYPES | 81 | from provisioningserver.events import EVENT_TYPES |
57 | 86 | from provisioningserver.import_images.download_descriptions import ( | 82 | from provisioningserver.import_images.download_descriptions import ( |
58 | @@ -111,6 +107,10 @@ from provisioningserver.utils.twisted import ( | |||
59 | 111 | pause, | 107 | pause, |
60 | 112 | synchronous, | 108 | synchronous, |
61 | 113 | ) | 109 | ) |
62 | 110 | from provisioningserver.utils.version import ( | ||
63 | 111 | get_maas_version_tuple, | ||
64 | 112 | get_maas_version_user_agent, | ||
65 | 113 | ) | ||
66 | 114 | from simplestreams import util as sutil | 114 | from simplestreams import util as sutil |
67 | 115 | from simplestreams.mirrors import ( | 115 | from simplestreams.mirrors import ( |
68 | 116 | BasicMirrorWriter, | 116 | BasicMirrorWriter, |
69 | diff --git a/src/maasserver/bootsources.py b/src/maasserver/bootsources.py | |||
70 | index 999a6d8..2cd5ab2 100644 | |||
71 | --- a/src/maasserver/bootsources.py | |||
72 | +++ b/src/maasserver/bootsources.py | |||
73 | @@ -27,7 +27,6 @@ from maasserver.models import ( | |||
74 | 27 | ) | 27 | ) |
75 | 28 | from maasserver.utils.orm import transactional | 28 | from maasserver.utils.orm import transactional |
76 | 29 | from maasserver.utils.threads import deferToDatabase | 29 | from maasserver.utils.threads import deferToDatabase |
77 | 30 | from maasserver.utils.version import get_maas_version_user_agent | ||
78 | 31 | from provisioningserver.auth import get_maas_user_gpghome | 30 | from provisioningserver.auth import get_maas_user_gpghome |
79 | 32 | from provisioningserver.config import ( | 31 | from provisioningserver.config import ( |
80 | 33 | DEFAULT_IMAGES_URL, | 32 | DEFAULT_IMAGES_URL, |
81 | @@ -44,6 +43,7 @@ from provisioningserver.utils.twisted import ( | |||
82 | 44 | asynchronous, | 43 | asynchronous, |
83 | 45 | FOREVER, | 44 | FOREVER, |
84 | 46 | ) | 45 | ) |
85 | 46 | from provisioningserver.utils.version import get_maas_version_user_agent | ||
86 | 47 | from requests.exceptions import ConnectionError | 47 | from requests.exceptions import ConnectionError |
87 | 48 | from twisted.internet.defer import inlineCallbacks | 48 | from twisted.internet.defer import inlineCallbacks |
88 | 49 | 49 | ||
89 | diff --git a/src/maasserver/context_processors.py b/src/maasserver/context_processors.py | |||
90 | index da8e817..697ec15 100644 | |||
91 | --- a/src/maasserver/context_processors.py | |||
92 | +++ b/src/maasserver/context_processors.py | |||
93 | @@ -11,7 +11,7 @@ __all__ = [ | |||
94 | 11 | from django.conf import settings | 11 | from django.conf import settings |
95 | 12 | from maasserver.config import RegionConfiguration | 12 | from maasserver.config import RegionConfiguration |
96 | 13 | from maasserver.models import Config | 13 | from maasserver.models import Config |
98 | 14 | from maasserver.utils.version import ( | 14 | from provisioningserver.utils.version import ( |
99 | 15 | get_maas_doc_version, | 15 | get_maas_doc_version, |
100 | 16 | get_maas_version_ui, | 16 | get_maas_version_ui, |
101 | 17 | ) | 17 | ) |
102 | diff --git a/src/maasserver/tests/test_bootresources.py b/src/maasserver/tests/test_bootresources.py | |||
103 | index 135715b..b3fd049 100644 | |||
104 | --- a/src/maasserver/tests/test_bootresources.py | |||
105 | +++ b/src/maasserver/tests/test_bootresources.py | |||
106 | @@ -92,7 +92,6 @@ from maasserver.utils.orm import ( | |||
107 | 92 | transactional, | 92 | transactional, |
108 | 93 | ) | 93 | ) |
109 | 94 | from maasserver.utils.threads import deferToDatabase | 94 | from maasserver.utils.threads import deferToDatabase |
110 | 95 | from maasserver.utils.version import get_maas_version_user_agent | ||
111 | 96 | from maastesting.matchers import ( | 95 | from maastesting.matchers import ( |
112 | 97 | MockCalledOnce, | 96 | MockCalledOnce, |
113 | 98 | MockCalledOnceWith, | 97 | MockCalledOnceWith, |
114 | @@ -115,6 +114,7 @@ from provisioningserver.utils.twisted import ( | |||
115 | 115 | asynchronous, | 114 | asynchronous, |
116 | 116 | DeferredValue, | 115 | DeferredValue, |
117 | 117 | ) | 116 | ) |
118 | 117 | from provisioningserver.utils.version import get_maas_version_user_agent | ||
119 | 118 | from testtools.matchers import ( | 118 | from testtools.matchers import ( |
120 | 119 | Contains, | 119 | Contains, |
121 | 120 | ContainsAll, | 120 | ContainsAll, |
122 | diff --git a/src/maasserver/tests/test_bootsources.py b/src/maasserver/tests/test_bootsources.py | |||
123 | index ecba7ce..d7f8ce8 100644 | |||
124 | --- a/src/maasserver/tests/test_bootsources.py | |||
125 | +++ b/src/maasserver/tests/test_bootsources.py | |||
126 | @@ -42,7 +42,6 @@ from maasserver.testing.testcase import ( | |||
127 | 42 | MAASTransactionServerTestCase, | 42 | MAASTransactionServerTestCase, |
128 | 43 | ) | 43 | ) |
129 | 44 | from maasserver.tests.test_bootresources import SimplestreamsEnvFixture | 44 | from maasserver.tests.test_bootresources import SimplestreamsEnvFixture |
130 | 45 | from maasserver.utils.version import get_maas_version_user_agent | ||
131 | 46 | from maastesting.matchers import MockCalledOnceWith | 45 | from maastesting.matchers import MockCalledOnceWith |
132 | 47 | from provisioningserver.config import DEFAULT_IMAGES_URL | 46 | from provisioningserver.config import DEFAULT_IMAGES_URL |
133 | 48 | from provisioningserver.import_images import ( | 47 | from provisioningserver.import_images import ( |
134 | @@ -52,6 +51,7 @@ from provisioningserver.import_images.boot_image_mapping import ( | |||
135 | 52 | BootImageMapping, | 51 | BootImageMapping, |
136 | 53 | ) | 52 | ) |
137 | 54 | from provisioningserver.import_images.helpers import ImageSpec | 53 | from provisioningserver.import_images.helpers import ImageSpec |
138 | 54 | from provisioningserver.utils.version import get_maas_version_user_agent | ||
139 | 55 | from requests.exceptions import ConnectionError | 55 | from requests.exceptions import ConnectionError |
140 | 56 | from testtools.matchers import HasLength | 56 | from testtools.matchers import HasLength |
141 | 57 | 57 | ||
142 | diff --git a/src/maasserver/utils/tests/test_version.py b/src/maasserver/utils/tests/test_version.py | |||
143 | 58 | deleted file mode 100644 | 58 | deleted file mode 100644 |
144 | index 92b31dd..0000000 | |||
145 | --- a/src/maasserver/utils/tests/test_version.py | |||
146 | +++ /dev/null | |||
147 | @@ -1,278 +0,0 @@ | |||
148 | 1 | # Copyright 2015-2016 Canonical Ltd. This software is licensed under the | ||
149 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
150 | 3 | |||
151 | 4 | """Test version utilities.""" | ||
152 | 5 | |||
153 | 6 | __all__ = [] | ||
154 | 7 | |||
155 | 8 | import os.path | ||
156 | 9 | import random | ||
157 | 10 | from unittest import skipUnless | ||
158 | 11 | from unittest.mock import ( | ||
159 | 12 | MagicMock, | ||
160 | 13 | sentinel, | ||
161 | 14 | ) | ||
162 | 15 | |||
163 | 16 | from maasserver import __version__ as old_version | ||
164 | 17 | from maasserver.utils import version | ||
165 | 18 | from maastesting import root | ||
166 | 19 | from maastesting.matchers import MockCalledOnceWith | ||
167 | 20 | from maastesting.testcase import MAASTestCase | ||
168 | 21 | from provisioningserver.utils import ( | ||
169 | 22 | shell, | ||
170 | 23 | snappy, | ||
171 | 24 | ) | ||
172 | 25 | from testtools.matchers import ( | ||
173 | 26 | GreaterThan, | ||
174 | 27 | Is, | ||
175 | 28 | IsInstance, | ||
176 | 29 | ) | ||
177 | 30 | |||
178 | 31 | |||
179 | 32 | class TestGetVersionFromAPT(MAASTestCase): | ||
180 | 33 | |||
181 | 34 | def test__creates_cache_with_None_progress(self): | ||
182 | 35 | mock_Cache = self.patch(version.apt_pkg, "Cache") | ||
183 | 36 | version.get_version_from_apt(version.REGION_PACKAGE_NAME) | ||
184 | 37 | self.assertThat(mock_Cache, MockCalledOnceWith(None)) | ||
185 | 38 | |||
186 | 39 | def test__returns_empty_string_if_package_not_in_cache(self): | ||
187 | 40 | self.patch(version.apt_pkg, "Cache") | ||
188 | 41 | self.assertEqual( | ||
189 | 42 | "", | ||
190 | 43 | version.get_version_from_apt(version.REGION_PACKAGE_NAME)) | ||
191 | 44 | |||
192 | 45 | def test__returns_empty_string_if_not_current_ver_from_package(self): | ||
193 | 46 | package = MagicMock() | ||
194 | 47 | package.current_ver = None | ||
195 | 48 | mock_cache = { | ||
196 | 49 | version.REGION_PACKAGE_NAME: package, | ||
197 | 50 | } | ||
198 | 51 | self.patch(version.apt_pkg, "Cache").return_value = mock_cache | ||
199 | 52 | self.assertEqual( | ||
200 | 53 | "", | ||
201 | 54 | version.get_version_from_apt(version.REGION_PACKAGE_NAME)) | ||
202 | 55 | |||
203 | 56 | def test__returns_ver_str_from_package(self): | ||
204 | 57 | package = MagicMock() | ||
205 | 58 | package.current_ver.ver_str = sentinel.ver_str | ||
206 | 59 | mock_cache = { | ||
207 | 60 | version.REGION_PACKAGE_NAME: package, | ||
208 | 61 | } | ||
209 | 62 | self.patch(version.apt_pkg, "Cache").return_value = mock_cache | ||
210 | 63 | self.assertIs( | ||
211 | 64 | sentinel.ver_str, | ||
212 | 65 | version.get_version_from_apt(version.REGION_PACKAGE_NAME)) | ||
213 | 66 | |||
214 | 67 | |||
215 | 68 | class TestGetMAASBranchVersion(MAASTestCase): | ||
216 | 69 | |||
217 | 70 | def test__returns_None_if_this_is_not_a_branch(self): | ||
218 | 71 | self.patch(version, "__file__", "/") | ||
219 | 72 | self.assertIsNone(version.get_maas_branch_version()) | ||
220 | 73 | |||
221 | 74 | def test__returns_None_if_bzr_crashes(self): | ||
222 | 75 | call_and_check = self.patch(shell, "call_and_check") | ||
223 | 76 | call_and_check.side_effect = shell.ExternalProcessError(2, "cmd") | ||
224 | 77 | self.assertIsNone(version.get_maas_branch_version()) | ||
225 | 78 | |||
226 | 79 | def test__returns_None_if_bzr_not_found(self): | ||
227 | 80 | call_and_check = self.patch(shell, "call_and_check") | ||
228 | 81 | call_and_check.side_effect = FileNotFoundError() | ||
229 | 82 | self.assertIsNone(version.get_maas_branch_version()) | ||
230 | 83 | |||
231 | 84 | def test__returns_None_if_bzr_emits_something_thats_not_a_number(self): | ||
232 | 85 | call_and_check = self.patch(shell, "call_and_check") | ||
233 | 86 | call_and_check.return_value = b"???" | ||
234 | 87 | self.assertIsNone(version.get_maas_branch_version()) | ||
235 | 88 | |||
236 | 89 | @skipUnless(os.path.isdir(os.path.join(root, ".bzr")), "Not a branch") | ||
237 | 90 | def test__returns_revno_for_this_branch(self): | ||
238 | 91 | revno = version.get_maas_branch_version() | ||
239 | 92 | self.assertThat(revno, IsInstance(int)) | ||
240 | 93 | self.assertThat(revno, GreaterThan(0)) | ||
241 | 94 | |||
242 | 95 | |||
243 | 96 | class TestExtractVersionSubversion(MAASTestCase): | ||
244 | 97 | |||
245 | 98 | scenarios = [ | ||
246 | 99 | ("with ~", { | ||
247 | 100 | "version": "2.2.0~beta4+bzr5856-0ubuntu1", | ||
248 | 101 | "output": ("2.2.0~beta4", "bzr5856-0ubuntu1"), | ||
249 | 102 | }), | ||
250 | 103 | ("without ~", { | ||
251 | 104 | "version": "2.1.0+bzr5480-0ubuntu1", | ||
252 | 105 | "output": ("2.1.0", "bzr5480-0ubuntu1"), | ||
253 | 106 | }), | ||
254 | 107 | ("without ~ or +", { | ||
255 | 108 | "version": "2.1.0-0ubuntu1", | ||
256 | 109 | "output": ("2.1.0", "0ubuntu1"), | ||
257 | 110 | }), | ||
258 | 111 | ] | ||
259 | 112 | |||
260 | 113 | def test__returns_version_subversion(self): | ||
261 | 114 | self.assertEqual( | ||
262 | 115 | self.output, version.extract_version_subversion(self.version)) | ||
263 | 116 | |||
264 | 117 | |||
265 | 118 | class TestVersionTestCase(MAASTestCase): | ||
266 | 119 | """MAASTestCase that resets the cache used by utility methods.""" | ||
267 | 120 | |||
268 | 121 | def setUp(self): | ||
269 | 122 | super(TestVersionTestCase, self).setUp() | ||
270 | 123 | for attribute in vars(version).values(): | ||
271 | 124 | if hasattr(attribute, "cache_clear"): | ||
272 | 125 | attribute.cache_clear() | ||
273 | 126 | |||
274 | 127 | |||
275 | 128 | class TestGetMAASVersion(TestVersionTestCase): | ||
276 | 129 | |||
277 | 130 | def test__calls_get_version_from_apt(self): | ||
278 | 131 | mock_apt = self.patch(version, "get_version_from_apt") | ||
279 | 132 | mock_apt.return_value = sentinel.version | ||
280 | 133 | self.expectThat( | ||
281 | 134 | version.get_maas_version(), Is(sentinel.version)) | ||
282 | 135 | self.expectThat( | ||
283 | 136 | mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME)) | ||
284 | 137 | |||
285 | 138 | def test__uses_snappy_get_snap_version(self): | ||
286 | 139 | self.patch(snappy, 'running_in_snap').return_value = True | ||
287 | 140 | self.patch(snappy, 'get_snap_version').return_value = sentinel.version | ||
288 | 141 | self.assertEqual(sentinel.version, version.get_maas_version()) | ||
289 | 142 | |||
290 | 143 | |||
291 | 144 | class TestGetMAASVersionSubversion(TestVersionTestCase): | ||
292 | 145 | |||
293 | 146 | def test__returns_package_version(self): | ||
294 | 147 | mock_apt = self.patch(version, "get_version_from_apt") | ||
295 | 148 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
296 | 149 | self.assertEqual( | ||
297 | 150 | ("1.8.0~alpha4", "bzr356-0ubuntu1"), | ||
298 | 151 | version.get_maas_version_subversion()) | ||
299 | 152 | |||
300 | 153 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
301 | 154 | mock_version = self.patch(version, "get_version_from_apt") | ||
302 | 155 | mock_version.return_value = "" | ||
303 | 156 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
304 | 157 | mock_branch_version.return_value = None | ||
305 | 158 | self.assertEqual( | ||
306 | 159 | (old_version, "unknown"), | ||
307 | 160 | version.get_maas_version_subversion()) | ||
308 | 161 | |||
309 | 162 | def test__returns_from_source_and_revno_from_branch(self): | ||
310 | 163 | mock_version = self.patch(version, "get_version_from_apt") | ||
311 | 164 | mock_version.return_value = "" | ||
312 | 165 | revno = random.randint(1, 5000) | ||
313 | 166 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
314 | 167 | mock_branch_version.return_value = revno | ||
315 | 168 | self.assertEqual( | ||
316 | 169 | ("%s from source" % old_version, "bzr%d" % revno), | ||
317 | 170 | version.get_maas_version_subversion()) | ||
318 | 171 | |||
319 | 172 | |||
320 | 173 | class TestGetMAASVersionUI(TestVersionTestCase): | ||
321 | 174 | |||
322 | 175 | def test__returns_package_version(self): | ||
323 | 176 | mock_apt = self.patch(version, "get_version_from_apt") | ||
324 | 177 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
325 | 178 | self.assertEqual( | ||
326 | 179 | "1.8.0~alpha4 (bzr356-0ubuntu1)", version.get_maas_version_ui()) | ||
327 | 180 | |||
328 | 181 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
329 | 182 | mock_version = self.patch(version, "get_version_from_apt") | ||
330 | 183 | mock_version.return_value = "" | ||
331 | 184 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
332 | 185 | mock_branch_version.return_value = None | ||
333 | 186 | self.assertEqual( | ||
334 | 187 | "%s (unknown)" % old_version, | ||
335 | 188 | version.get_maas_version_ui()) | ||
336 | 189 | |||
337 | 190 | def test__returns_from_source_and_revno_from_branch(self): | ||
338 | 191 | mock_version = self.patch(version, "get_version_from_apt") | ||
339 | 192 | mock_version.return_value = "" | ||
340 | 193 | revno = random.randint(1, 5000) | ||
341 | 194 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
342 | 195 | mock_branch_version.return_value = revno | ||
343 | 196 | self.assertEqual( | ||
344 | 197 | "%s from source (bzr%d)" % (old_version, revno), | ||
345 | 198 | version.get_maas_version_ui()) | ||
346 | 199 | |||
347 | 200 | |||
348 | 201 | class TestGetMAASVersionUserAgent(TestVersionTestCase): | ||
349 | 202 | |||
350 | 203 | def test__returns_package_version(self): | ||
351 | 204 | mock_apt = self.patch(version, "get_version_from_apt") | ||
352 | 205 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
353 | 206 | self.assertEqual( | ||
354 | 207 | "maas/1.8.0~alpha4/bzr356-0ubuntu1", | ||
355 | 208 | version.get_maas_version_user_agent()) | ||
356 | 209 | |||
357 | 210 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
358 | 211 | mock_version = self.patch(version, "get_version_from_apt") | ||
359 | 212 | mock_version.return_value = "" | ||
360 | 213 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
361 | 214 | mock_branch_version.return_value = None | ||
362 | 215 | self.assertEqual( | ||
363 | 216 | "maas/%s/unknown" % old_version, | ||
364 | 217 | version.get_maas_version_user_agent()) | ||
365 | 218 | |||
366 | 219 | def test__returns_from_source_and_revno_from_branch(self): | ||
367 | 220 | mock_version = self.patch(version, "get_version_from_apt") | ||
368 | 221 | mock_version.return_value = "" | ||
369 | 222 | revno = random.randint(1, 5000) | ||
370 | 223 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
371 | 224 | mock_branch_version.return_value = revno | ||
372 | 225 | self.assertEqual( | ||
373 | 226 | "maas/%s from source/bzr%d" % (old_version, revno), | ||
374 | 227 | version.get_maas_version_user_agent()) | ||
375 | 228 | |||
376 | 229 | |||
377 | 230 | class TestGetMAASDocVersion(TestVersionTestCase): | ||
378 | 231 | |||
379 | 232 | def test__returns_doc_version_with_greater_than_1_decimals(self): | ||
380 | 233 | mock_apt = self.patch(version, "get_version_from_apt") | ||
381 | 234 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
382 | 235 | self.assertEqual("1.8", version.get_maas_doc_version()) | ||
383 | 236 | |||
384 | 237 | def test__returns_doc_version_with_equal_to_1_decimals(self): | ||
385 | 238 | mock_apt = self.patch(version, "get_version_from_apt") | ||
386 | 239 | mock_apt.return_value = "1.8~alpha4+bzr356-0ubuntu1" | ||
387 | 240 | self.assertEqual("1.8", version.get_maas_doc_version()) | ||
388 | 241 | |||
389 | 242 | def test__returns_empty_if_version_is_empty(self): | ||
390 | 243 | mock_apt = self.patch(version, "get_version_from_apt") | ||
391 | 244 | mock_apt.return_value = "" | ||
392 | 245 | self.assertEqual("", version.get_maas_doc_version()) | ||
393 | 246 | |||
394 | 247 | |||
395 | 248 | class TestVersionMethodsCached(TestVersionTestCase): | ||
396 | 249 | |||
397 | 250 | scenarios = [ | ||
398 | 251 | ("get_maas_version", dict(method="get_maas_version")), | ||
399 | 252 | ("get_maas_version_subversion", dict( | ||
400 | 253 | method="get_maas_version_subversion")), | ||
401 | 254 | ("get_maas_version_ui", dict(method="get_maas_version_ui")), | ||
402 | 255 | ("get_maas_doc_version", dict(method="get_maas_doc_version")), | ||
403 | 256 | ] | ||
404 | 257 | |||
405 | 258 | def test_method_is_cached(self): | ||
406 | 259 | mock_apt = self.patch(version, "get_version_from_apt") | ||
407 | 260 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
408 | 261 | cached_method = getattr(version, self.method) | ||
409 | 262 | first_return_value = cached_method() | ||
410 | 263 | second_return_value = cached_method() | ||
411 | 264 | # The return value is not empty (full unit tests have been performed | ||
412 | 265 | # earlier). | ||
413 | 266 | self.assertNotIn(first_return_value, [b'', '', None]) | ||
414 | 267 | self.assertEqual(first_return_value, second_return_value) | ||
415 | 268 | # Apt has only been called once. | ||
416 | 269 | self.expectThat( | ||
417 | 270 | mock_apt, MockCalledOnceWith(version.REGION_PACKAGE_NAME)) | ||
418 | 271 | |||
419 | 272 | |||
420 | 273 | class TestGetMAASVersionTuple(MAASTestCase): | ||
421 | 274 | |||
422 | 275 | def test_get_maas_version_tuple(self): | ||
423 | 276 | self.assertEquals( | ||
424 | 277 | '.'.join([str(i) for i in version.get_maas_version_tuple()]), | ||
425 | 278 | version.get_maas_version_subversion()[0]) | ||
426 | diff --git a/src/maasserver/utils/version.py b/src/maasserver/utils/version.py | |||
427 | 279 | deleted file mode 100644 | 0 | deleted file mode 100644 |
428 | index bdd6424..0000000 | |||
429 | --- a/src/maasserver/utils/version.py | |||
430 | +++ /dev/null | |||
431 | @@ -1,142 +0,0 @@ | |||
432 | 1 | # Copyright 2015-2016 Canonical Ltd. This software is licensed under the | ||
433 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
434 | 3 | |||
435 | 4 | """Version utilities.""" | ||
436 | 5 | |||
437 | 6 | __all__ = [ | ||
438 | 7 | "get_maas_doc_version", | ||
439 | 8 | "get_maas_version_subversion", | ||
440 | 9 | "get_maas_version_ui", | ||
441 | 10 | ] | ||
442 | 11 | |||
443 | 12 | from functools import lru_cache | ||
444 | 13 | import re | ||
445 | 14 | |||
446 | 15 | from maasserver import __version__ as old_version | ||
447 | 16 | from maasserver.api.logger import maaslog | ||
448 | 17 | from provisioningserver.utils import ( | ||
449 | 18 | shell, | ||
450 | 19 | snappy, | ||
451 | 20 | ) | ||
452 | 21 | |||
453 | 22 | # Only import apt_pkg and initialize when not running in a snap. | ||
454 | 23 | if not snappy.running_in_snap(): | ||
455 | 24 | import apt_pkg | ||
456 | 25 | apt_pkg.init() | ||
457 | 26 | |||
458 | 27 | # Name of maas package to get version from. | ||
459 | 28 | REGION_PACKAGE_NAME = "maas-region-api" | ||
460 | 29 | |||
461 | 30 | |||
462 | 31 | def get_version_from_apt(package): | ||
463 | 32 | """Return the version output from `apt_pkg.Cache` for the given package or | ||
464 | 33 | an error message if the package data is not valid.""" | ||
465 | 34 | try: | ||
466 | 35 | cache = apt_pkg.Cache(None) | ||
467 | 36 | except SystemError: | ||
468 | 37 | maaslog.error( | ||
469 | 38 | 'Installed version could not be determined. Ensure ' | ||
470 | 39 | '/var/lib/dpkg/status is valid.') | ||
471 | 40 | return "" | ||
472 | 41 | |||
473 | 42 | version = None | ||
474 | 43 | if package in cache: | ||
475 | 44 | apt_package = cache[package] | ||
476 | 45 | version = apt_package.current_ver | ||
477 | 46 | |||
478 | 47 | return version.ver_str if version is not None else "" | ||
479 | 48 | |||
480 | 49 | |||
481 | 50 | def extract_version_subversion(version): | ||
482 | 51 | """Return a tuple (version, subversion) from the given apt version.""" | ||
483 | 52 | main_version, subversion = re.split('[+|-]', version, 1) | ||
484 | 53 | return main_version, subversion | ||
485 | 54 | |||
486 | 55 | |||
487 | 56 | def get_maas_branch_version(): | ||
488 | 57 | """Return the Bazaar revision for this running MAAS. | ||
489 | 58 | |||
490 | 59 | :return: An integer if MAAS is running from a Bazaar working tree, else | ||
491 | 60 | `None`. The revision number is only representative of the BRANCH, not | ||
492 | 61 | the working tree. | ||
493 | 62 | """ | ||
494 | 63 | try: | ||
495 | 64 | revno = shell.call_and_check(("bzr", "revno", __file__)) | ||
496 | 65 | except shell.ExternalProcessError: | ||
497 | 66 | # We may not be in a Bazaar working tree, or any manner of other | ||
498 | 67 | # errors. For the purposes of this function we don't care; simply say | ||
499 | 68 | # we don't know. | ||
500 | 69 | return None | ||
501 | 70 | except FileNotFoundError: | ||
502 | 71 | # Bazaar is not installed. We don't care and simply say we don't know. | ||
503 | 72 | return None | ||
504 | 73 | else: | ||
505 | 74 | # `bzr revno` can return '???' when it can't find the working tree's | ||
506 | 75 | # current revision in the branch. Hopefully a fairly unlikely thing to | ||
507 | 76 | # happen, but we guard against it, and other ills, here. | ||
508 | 77 | try: | ||
509 | 78 | return int(revno) | ||
510 | 79 | except ValueError: | ||
511 | 80 | return None | ||
512 | 81 | |||
513 | 82 | |||
514 | 83 | @lru_cache(maxsize=1) | ||
515 | 84 | def get_maas_version(): | ||
516 | 85 | """Return the apt or snap version for the main MAAS package.""" | ||
517 | 86 | if snappy.running_in_snap(): | ||
518 | 87 | return snappy.get_snap_version() | ||
519 | 88 | else: | ||
520 | 89 | return get_version_from_apt(REGION_PACKAGE_NAME) | ||
521 | 90 | |||
522 | 91 | |||
523 | 92 | @lru_cache(maxsize=1) | ||
524 | 93 | def get_maas_version_subversion(): | ||
525 | 94 | """Return a tuple with the MAAS version and the MAAS subversion.""" | ||
526 | 95 | version = get_maas_version() | ||
527 | 96 | if version: | ||
528 | 97 | return extract_version_subversion(version) | ||
529 | 98 | else: | ||
530 | 99 | # Get the branch information | ||
531 | 100 | branch_version = get_maas_branch_version() | ||
532 | 101 | if branch_version is None: | ||
533 | 102 | # Not installed not in branch, then no way to identify. This should | ||
534 | 103 | # not happen, but just in case. | ||
535 | 104 | return old_version, "unknown" | ||
536 | 105 | else: | ||
537 | 106 | return "%s from source" % old_version, "bzr%d" % branch_version | ||
538 | 107 | |||
539 | 108 | |||
540 | 109 | @lru_cache(maxsize=1) | ||
541 | 110 | def get_maas_version_ui(): | ||
542 | 111 | """Return the version string for the running MAAS region. | ||
543 | 112 | |||
544 | 113 | The returned string is suitable to display in the UI. | ||
545 | 114 | """ | ||
546 | 115 | version, subversion = get_maas_version_subversion() | ||
547 | 116 | return "%s (%s)" % (version, subversion) if subversion else version | ||
548 | 117 | |||
549 | 118 | |||
550 | 119 | @lru_cache(maxsize=1) | ||
551 | 120 | def get_maas_version_user_agent(): | ||
552 | 121 | """Return the version string for the running MAAS region. | ||
553 | 122 | |||
554 | 123 | The returned string is suitable to set the user agent. | ||
555 | 124 | """ | ||
556 | 125 | version, subversion = get_maas_version_subversion() | ||
557 | 126 | return "maas/%s/%s" % (version, subversion) | ||
558 | 127 | |||
559 | 128 | |||
560 | 129 | @lru_cache(maxsize=1) | ||
561 | 130 | def get_maas_doc_version(): | ||
562 | 131 | """Return the doc version for the running MAAS region.""" | ||
563 | 132 | apt_version = get_maas_version() | ||
564 | 133 | if apt_version: | ||
565 | 134 | version, _ = extract_version_subversion(apt_version) | ||
566 | 135 | return '.'.join(version.split('~')[0].split('.')[:2]) | ||
567 | 136 | else: | ||
568 | 137 | return '' | ||
569 | 138 | |||
570 | 139 | |||
571 | 140 | def get_maas_version_tuple(): | ||
572 | 141 | """Returns a tuple of the MAAS version without the svn rev.""" | ||
573 | 142 | return tuple(int(x) for x in old_version.split('.')) | ||
574 | diff --git a/src/maasserver/websockets/handlers/bootresource.py b/src/maasserver/websockets/handlers/bootresource.py | |||
575 | index 1df2ecd..68742d9 100644 | |||
576 | --- a/src/maasserver/websockets/handlers/bootresource.py | |||
577 | +++ b/src/maasserver/websockets/handlers/bootresource.py | |||
578 | @@ -43,7 +43,6 @@ from maasserver.models import ( | |||
579 | 43 | from maasserver.utils.converters import human_readable_bytes | 43 | from maasserver.utils.converters import human_readable_bytes |
580 | 44 | from maasserver.utils.orm import transactional | 44 | from maasserver.utils.orm import transactional |
581 | 45 | from maasserver.utils.threads import deferToDatabase | 45 | from maasserver.utils.threads import deferToDatabase |
582 | 46 | from maasserver.utils.version import get_maas_version_user_agent | ||
583 | 47 | from maasserver.websockets.base import ( | 46 | from maasserver.websockets.base import ( |
584 | 48 | Handler, | 47 | Handler, |
585 | 49 | HandlerError, | 48 | HandlerError, |
586 | @@ -65,6 +64,7 @@ from provisioningserver.utils.twisted import ( | |||
587 | 65 | callOut, | 64 | callOut, |
588 | 66 | FOREVER, | 65 | FOREVER, |
589 | 67 | ) | 66 | ) |
590 | 67 | from provisioningserver.utils.version import get_maas_version_user_agent | ||
591 | 68 | from twisted.internet.defer import Deferred | 68 | from twisted.internet.defer import Deferred |
592 | 69 | 69 | ||
593 | 70 | 70 | ||
594 | diff --git a/src/maasserver/websockets/handlers/general.py b/src/maasserver/websockets/handlers/general.py | |||
595 | index b15300a..e5df4f0 100644 | |||
596 | --- a/src/maasserver/websockets/handlers/general.py | |||
597 | +++ b/src/maasserver/websockets/handlers/general.py | |||
598 | @@ -33,9 +33,9 @@ from maasserver.utils.osystems import ( | |||
599 | 33 | list_osystem_choices, | 33 | list_osystem_choices, |
600 | 34 | list_release_choices, | 34 | list_release_choices, |
601 | 35 | ) | 35 | ) |
602 | 36 | from maasserver.utils.version import get_maas_version_ui | ||
603 | 37 | from maasserver.websockets.base import Handler | 36 | from maasserver.websockets.base import Handler |
604 | 38 | import petname | 37 | import petname |
605 | 38 | from provisioningserver.utils.version import get_maas_version_ui | ||
606 | 39 | 39 | ||
607 | 40 | 40 | ||
608 | 41 | class GeneralHandler(Handler): | 41 | class GeneralHandler(Handler): |
609 | diff --git a/src/maasserver/websockets/handlers/tests/test_bootresource.py b/src/maasserver/websockets/handlers/tests/test_bootresource.py | |||
610 | index 57940a7..87438ea 100644 | |||
611 | --- a/src/maasserver/websockets/handlers/tests/test_bootresource.py | |||
612 | +++ b/src/maasserver/websockets/handlers/tests/test_bootresource.py | |||
613 | @@ -32,7 +32,6 @@ from maasserver.utils.orm import ( | |||
614 | 32 | get_one, | 32 | get_one, |
615 | 33 | reload_object, | 33 | reload_object, |
616 | 34 | ) | 34 | ) |
617 | 35 | from maasserver.utils.version import get_maas_version_user_agent | ||
618 | 36 | from maasserver.websockets.base import ( | 35 | from maasserver.websockets.base import ( |
619 | 37 | HandlerError, | 36 | HandlerError, |
620 | 38 | HandlerValidationError, | 37 | HandlerValidationError, |
621 | @@ -54,6 +53,7 @@ from provisioningserver.import_images.testing.factory import ( | |||
622 | 54 | make_image_spec, | 53 | make_image_spec, |
623 | 55 | set_resource, | 54 | set_resource, |
624 | 56 | ) | 55 | ) |
625 | 56 | from provisioningserver.utils.version import get_maas_version_user_agent | ||
626 | 57 | from testtools.matchers import ( | 57 | from testtools.matchers import ( |
627 | 58 | ContainsAll, | 58 | ContainsAll, |
628 | 59 | HasLength, | 59 | HasLength, |
629 | diff --git a/src/metadataserver/builtin_scripts/__init__.py b/src/metadataserver/builtin_scripts/__init__.py | |||
630 | index 934a3a1..8cff96f 100644 | |||
631 | --- a/src/metadataserver/builtin_scripts/__init__.py | |||
632 | +++ b/src/metadataserver/builtin_scripts/__init__.py | |||
633 | @@ -15,10 +15,10 @@ from attr.validators import ( | |||
634 | 15 | instance_of, | 15 | instance_of, |
635 | 16 | optional, | 16 | optional, |
636 | 17 | ) | 17 | ) |
637 | 18 | from maasserver.utils.version import get_maas_version | ||
638 | 19 | from metadataserver.enum import SCRIPT_TYPE | 18 | from metadataserver.enum import SCRIPT_TYPE |
639 | 20 | from metadataserver.models import Script | 19 | from metadataserver.models import Script |
640 | 21 | from provisioningserver.utils.fs import read_text_file | 20 | from provisioningserver.utils.fs import read_text_file |
641 | 21 | from provisioningserver.utils.version import get_maas_version | ||
642 | 22 | from zope.interface import ( | 22 | from zope.interface import ( |
643 | 23 | Attribute, | 23 | Attribute, |
644 | 24 | implementer, | 24 | implementer, |
645 | diff --git a/src/metadataserver/builtin_scripts/tests/test_builtin_scripts.py b/src/metadataserver/builtin_scripts/tests/test_builtin_scripts.py | |||
646 | index 004560b..703ba94 100644 | |||
647 | --- a/src/metadataserver/builtin_scripts/tests/test_builtin_scripts.py | |||
648 | +++ b/src/metadataserver/builtin_scripts/tests/test_builtin_scripts.py | |||
649 | @@ -11,13 +11,13 @@ from maasserver.models import VersionedTextFile | |||
650 | 11 | from maasserver.testing.factory import factory | 11 | from maasserver.testing.factory import factory |
651 | 12 | from maasserver.testing.testcase import MAASServerTestCase | 12 | from maasserver.testing.testcase import MAASServerTestCase |
652 | 13 | from maasserver.utils.orm import reload_object | 13 | from maasserver.utils.orm import reload_object |
653 | 14 | from maasserver.utils.version import get_maas_version | ||
654 | 15 | from metadataserver.builtin_scripts import ( | 14 | from metadataserver.builtin_scripts import ( |
655 | 16 | BUILTIN_SCRIPTS, | 15 | BUILTIN_SCRIPTS, |
656 | 17 | load_builtin_scripts, | 16 | load_builtin_scripts, |
657 | 18 | ) | 17 | ) |
658 | 19 | from metadataserver.enum import SCRIPT_TYPE_CHOICES | 18 | from metadataserver.enum import SCRIPT_TYPE_CHOICES |
659 | 20 | from metadataserver.models import Script | 19 | from metadataserver.models import Script |
660 | 20 | from provisioningserver.utils.version import get_maas_version | ||
661 | 21 | 21 | ||
662 | 22 | 22 | ||
663 | 23 | class TestBuiltinScripts(MAASServerTestCase): | 23 | class TestBuiltinScripts(MAASServerTestCase): |
664 | diff --git a/src/provisioningserver/utils/tests/test_version.py b/src/provisioningserver/utils/tests/test_version.py | |||
665 | 24 | new file mode 100644 | 24 | new file mode 100644 |
666 | index 0000000..410369d | |||
667 | --- /dev/null | |||
668 | +++ b/src/provisioningserver/utils/tests/test_version.py | |||
669 | @@ -0,0 +1,293 @@ | |||
670 | 1 | # Copyright 2015-2017 Canonical Ltd. This software is licensed under the | ||
671 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
672 | 3 | |||
673 | 4 | """Test version utilities.""" | ||
674 | 5 | |||
675 | 6 | __all__ = [] | ||
676 | 7 | |||
677 | 8 | |||
678 | 9 | import os.path | ||
679 | 10 | import random | ||
680 | 11 | from unittest import skipUnless | ||
681 | 12 | from unittest.mock import ( | ||
682 | 13 | MagicMock, | ||
683 | 14 | sentinel, | ||
684 | 15 | ) | ||
685 | 16 | |||
686 | 17 | from maastesting import root | ||
687 | 18 | from maastesting.matchers import MockCalledOnceWith | ||
688 | 19 | from maastesting.testcase import MAASTestCase | ||
689 | 20 | from provisioningserver.utils import ( | ||
690 | 21 | shell, | ||
691 | 22 | snappy, | ||
692 | 23 | version, | ||
693 | 24 | ) | ||
694 | 25 | from provisioningserver.utils.version import DEFAULT_VERSION as old_version | ||
695 | 26 | from testtools.matchers import ( | ||
696 | 27 | GreaterThan, | ||
697 | 28 | Is, | ||
698 | 29 | IsInstance, | ||
699 | 30 | ) | ||
700 | 31 | |||
701 | 32 | |||
702 | 33 | class TestGetVersionFromAPT(MAASTestCase): | ||
703 | 34 | |||
704 | 35 | def test__creates_cache_with_None_progress(self): | ||
705 | 36 | mock_Cache = self.patch(version.apt_pkg, "Cache") | ||
706 | 37 | version.get_version_from_apt(version.REGION_PACKAGE_NAME) | ||
707 | 38 | self.assertThat(mock_Cache, MockCalledOnceWith(None)) | ||
708 | 39 | |||
709 | 40 | def test__returns_empty_string_if_package_not_in_cache(self): | ||
710 | 41 | self.patch(version.apt_pkg, "Cache") | ||
711 | 42 | self.assertEqual( | ||
712 | 43 | "", | ||
713 | 44 | version.get_version_from_apt(version.REGION_PACKAGE_NAME)) | ||
714 | 45 | |||
715 | 46 | def test__returns_empty_string_if_not_current_ver_from_package(self): | ||
716 | 47 | package = MagicMock() | ||
717 | 48 | package.current_ver = None | ||
718 | 49 | mock_cache = { | ||
719 | 50 | version.REGION_PACKAGE_NAME: package, | ||
720 | 51 | } | ||
721 | 52 | self.patch(version.apt_pkg, "Cache").return_value = mock_cache | ||
722 | 53 | self.assertEqual( | ||
723 | 54 | "", | ||
724 | 55 | version.get_version_from_apt(version.REGION_PACKAGE_NAME)) | ||
725 | 56 | |||
726 | 57 | def test__returns_ver_str_from_package(self): | ||
727 | 58 | package = MagicMock() | ||
728 | 59 | package.current_ver.ver_str = sentinel.ver_str | ||
729 | 60 | mock_cache = { | ||
730 | 61 | version.RACK_PACKAGE_NAME: package | ||
731 | 62 | } | ||
732 | 63 | self.patch(version.apt_pkg, "Cache").return_value = mock_cache | ||
733 | 64 | self.assertIs( | ||
734 | 65 | sentinel.ver_str, | ||
735 | 66 | version.get_version_from_apt(version.RACK_PACKAGE_NAME)) | ||
736 | 67 | |||
737 | 68 | def test__returns_ver_str_from_second_package_if_first_not_found(self): | ||
738 | 69 | package = MagicMock() | ||
739 | 70 | package.current_ver.ver_str = sentinel.ver_str | ||
740 | 71 | mock_cache = { | ||
741 | 72 | version.REGION_PACKAGE_NAME: package, | ||
742 | 73 | } | ||
743 | 74 | self.patch(version.apt_pkg, "Cache").return_value = mock_cache | ||
744 | 75 | self.assertIs( | ||
745 | 76 | sentinel.ver_str, | ||
746 | 77 | version.get_version_from_apt( | ||
747 | 78 | version.RACK_PACKAGE_NAME, version.REGION_PACKAGE_NAME)) | ||
748 | 79 | |||
749 | 80 | |||
750 | 81 | class TestGetMAASBranchVersion(MAASTestCase): | ||
751 | 82 | |||
752 | 83 | def test__returns_None_if_this_is_not_a_branch(self): | ||
753 | 84 | self.patch(version, "__file__", "/") | ||
754 | 85 | self.assertIsNone(version.get_maas_branch_version()) | ||
755 | 86 | |||
756 | 87 | def test__returns_None_if_bzr_crashes(self): | ||
757 | 88 | call_and_check = self.patch(shell, "call_and_check") | ||
758 | 89 | call_and_check.side_effect = shell.ExternalProcessError(2, "cmd") | ||
759 | 90 | self.assertIsNone(version.get_maas_branch_version()) | ||
760 | 91 | |||
761 | 92 | def test__returns_None_if_bzr_not_found(self): | ||
762 | 93 | call_and_check = self.patch(shell, "call_and_check") | ||
763 | 94 | call_and_check.side_effect = FileNotFoundError() | ||
764 | 95 | self.assertIsNone(version.get_maas_branch_version()) | ||
765 | 96 | |||
766 | 97 | def test__returns_None_if_bzr_emits_something_thats_not_a_number(self): | ||
767 | 98 | call_and_check = self.patch(shell, "call_and_check") | ||
768 | 99 | call_and_check.return_value = b"???" | ||
769 | 100 | self.assertIsNone(version.get_maas_branch_version()) | ||
770 | 101 | |||
771 | 102 | @skipUnless(os.path.isdir(os.path.join(root, ".bzr")), "Not a branch") | ||
772 | 103 | def test__returns_revno_for_this_branch(self): | ||
773 | 104 | revno = version.get_maas_branch_version() | ||
774 | 105 | self.assertThat(revno, IsInstance(int)) | ||
775 | 106 | self.assertThat(revno, GreaterThan(0)) | ||
776 | 107 | |||
777 | 108 | |||
778 | 109 | class TestExtractVersionSubversion(MAASTestCase): | ||
779 | 110 | |||
780 | 111 | scenarios = [ | ||
781 | 112 | ("with ~", { | ||
782 | 113 | "version": "2.2.0~beta4+bzr5856-0ubuntu1", | ||
783 | 114 | "output": ("2.2.0~beta4", "bzr5856-0ubuntu1"), | ||
784 | 115 | }), | ||
785 | 116 | ("without ~", { | ||
786 | 117 | "version": "2.1.0+bzr5480-0ubuntu1", | ||
787 | 118 | "output": ("2.1.0", "bzr5480-0ubuntu1"), | ||
788 | 119 | }), | ||
789 | 120 | ("without ~ or +", { | ||
790 | 121 | "version": "2.1.0-0ubuntu1", | ||
791 | 122 | "output": ("2.1.0", "0ubuntu1"), | ||
792 | 123 | }), | ||
793 | 124 | ] | ||
794 | 125 | |||
795 | 126 | def test__returns_version_subversion(self): | ||
796 | 127 | self.assertEqual( | ||
797 | 128 | self.output, version.extract_version_subversion(self.version)) | ||
798 | 129 | |||
799 | 130 | |||
800 | 131 | class TestVersionTestCase(MAASTestCase): | ||
801 | 132 | """MAASTestCase that resets the cache used by utility methods.""" | ||
802 | 133 | |||
803 | 134 | def setUp(self): | ||
804 | 135 | super(TestVersionTestCase, self).setUp() | ||
805 | 136 | for attribute in vars(version).values(): | ||
806 | 137 | if hasattr(attribute, "cache_clear"): | ||
807 | 138 | attribute.cache_clear() | ||
808 | 139 | |||
809 | 140 | |||
810 | 141 | class TestGetMAASVersion(TestVersionTestCase): | ||
811 | 142 | |||
812 | 143 | def test__calls_get_version_from_apt(self): | ||
813 | 144 | mock_apt = self.patch(version, "get_version_from_apt") | ||
814 | 145 | mock_apt.return_value = sentinel.version | ||
815 | 146 | self.expectThat( | ||
816 | 147 | version.get_maas_version(), Is(sentinel.version)) | ||
817 | 148 | self.expectThat( | ||
818 | 149 | mock_apt, MockCalledOnceWith( | ||
819 | 150 | version.RACK_PACKAGE_NAME, version.REGION_PACKAGE_NAME)) | ||
820 | 151 | |||
821 | 152 | def test__uses_snappy_get_snap_version(self): | ||
822 | 153 | self.patch(snappy, 'running_in_snap').return_value = True | ||
823 | 154 | self.patch(snappy, 'get_snap_version').return_value = sentinel.version | ||
824 | 155 | self.assertEqual(sentinel.version, version.get_maas_version()) | ||
825 | 156 | |||
826 | 157 | |||
827 | 158 | class TestGetMAASVersionSubversion(TestVersionTestCase): | ||
828 | 159 | |||
829 | 160 | def test__returns_package_version(self): | ||
830 | 161 | mock_apt = self.patch(version, "get_version_from_apt") | ||
831 | 162 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
832 | 163 | self.assertEqual( | ||
833 | 164 | ("1.8.0~alpha4", "bzr356-0ubuntu1"), | ||
834 | 165 | version.get_maas_version_subversion()) | ||
835 | 166 | |||
836 | 167 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
837 | 168 | mock_version = self.patch(version, "get_version_from_apt") | ||
838 | 169 | mock_version.return_value = "" | ||
839 | 170 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
840 | 171 | mock_branch_version.return_value = None | ||
841 | 172 | self.assertEqual( | ||
842 | 173 | (old_version, "unknown"), | ||
843 | 174 | version.get_maas_version_subversion()) | ||
844 | 175 | |||
845 | 176 | def test__returns_from_source_and_revno_from_branch(self): | ||
846 | 177 | mock_version = self.patch(version, "get_version_from_apt") | ||
847 | 178 | mock_version.return_value = "" | ||
848 | 179 | revno = random.randint(1, 5000) | ||
849 | 180 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
850 | 181 | mock_branch_version.return_value = revno | ||
851 | 182 | self.assertEqual( | ||
852 | 183 | ("%s from source" % old_version, "bzr%d" % revno), | ||
853 | 184 | version.get_maas_version_subversion()) | ||
854 | 185 | |||
855 | 186 | |||
856 | 187 | class TestGetMAASVersionUI(TestVersionTestCase): | ||
857 | 188 | |||
858 | 189 | def test__returns_package_version(self): | ||
859 | 190 | mock_apt = self.patch(version, "get_version_from_apt") | ||
860 | 191 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
861 | 192 | self.assertEqual( | ||
862 | 193 | "1.8.0~alpha4 (bzr356-0ubuntu1)", version.get_maas_version_ui()) | ||
863 | 194 | |||
864 | 195 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
865 | 196 | mock_version = self.patch(version, "get_version_from_apt") | ||
866 | 197 | mock_version.return_value = "" | ||
867 | 198 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
868 | 199 | mock_branch_version.return_value = None | ||
869 | 200 | self.assertEqual( | ||
870 | 201 | "%s (unknown)" % old_version, | ||
871 | 202 | version.get_maas_version_ui()) | ||
872 | 203 | |||
873 | 204 | def test__returns_from_source_and_revno_from_branch(self): | ||
874 | 205 | mock_version = self.patch(version, "get_version_from_apt") | ||
875 | 206 | mock_version.return_value = "" | ||
876 | 207 | revno = random.randint(1, 5000) | ||
877 | 208 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
878 | 209 | mock_branch_version.return_value = revno | ||
879 | 210 | self.assertEqual( | ||
880 | 211 | "%s from source (bzr%d)" % (old_version, revno), | ||
881 | 212 | version.get_maas_version_ui()) | ||
882 | 213 | |||
883 | 214 | |||
884 | 215 | class TestGetMAASVersionUserAgent(TestVersionTestCase): | ||
885 | 216 | |||
886 | 217 | def test__returns_package_version(self): | ||
887 | 218 | mock_apt = self.patch(version, "get_version_from_apt") | ||
888 | 219 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
889 | 220 | self.assertEqual( | ||
890 | 221 | "maas/1.8.0~alpha4/bzr356-0ubuntu1", | ||
891 | 222 | version.get_maas_version_user_agent()) | ||
892 | 223 | |||
893 | 224 | def test__returns_unknown_if_version_is_empty_and_not_bzr_branch(self): | ||
894 | 225 | mock_version = self.patch(version, "get_version_from_apt") | ||
895 | 226 | mock_version.return_value = "" | ||
896 | 227 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
897 | 228 | mock_branch_version.return_value = None | ||
898 | 229 | self.assertEqual( | ||
899 | 230 | "maas/%s/unknown" % old_version, | ||
900 | 231 | version.get_maas_version_user_agent()) | ||
901 | 232 | |||
902 | 233 | def test__returns_from_source_and_revno_from_branch(self): | ||
903 | 234 | mock_version = self.patch(version, "get_version_from_apt") | ||
904 | 235 | mock_version.return_value = "" | ||
905 | 236 | revno = random.randint(1, 5000) | ||
906 | 237 | mock_branch_version = self.patch(version, "get_maas_branch_version") | ||
907 | 238 | mock_branch_version.return_value = revno | ||
908 | 239 | self.assertEqual( | ||
909 | 240 | "maas/%s from source/bzr%d" % (old_version, revno), | ||
910 | 241 | version.get_maas_version_user_agent()) | ||
911 | 242 | |||
912 | 243 | |||
913 | 244 | class TestGetMAASDocVersion(TestVersionTestCase): | ||
914 | 245 | |||
915 | 246 | def test__returns_doc_version_with_greater_than_1_decimals(self): | ||
916 | 247 | mock_apt = self.patch(version, "get_version_from_apt") | ||
917 | 248 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
918 | 249 | self.assertEqual("1.8", version.get_maas_doc_version()) | ||
919 | 250 | |||
920 | 251 | def test__returns_doc_version_with_equal_to_1_decimals(self): | ||
921 | 252 | mock_apt = self.patch(version, "get_version_from_apt") | ||
922 | 253 | mock_apt.return_value = "1.8~alpha4+bzr356-0ubuntu1" | ||
923 | 254 | self.assertEqual("1.8", version.get_maas_doc_version()) | ||
924 | 255 | |||
925 | 256 | def test__returns_empty_if_version_is_empty(self): | ||
926 | 257 | mock_apt = self.patch(version, "get_version_from_apt") | ||
927 | 258 | mock_apt.return_value = "" | ||
928 | 259 | self.assertEqual("", version.get_maas_doc_version()) | ||
929 | 260 | |||
930 | 261 | |||
931 | 262 | class TestVersionMethodsCached(TestVersionTestCase): | ||
932 | 263 | |||
933 | 264 | scenarios = [ | ||
934 | 265 | ("get_maas_version", dict(method="get_maas_version")), | ||
935 | 266 | ("get_maas_version_subversion", dict( | ||
936 | 267 | method="get_maas_version_subversion")), | ||
937 | 268 | ("get_maas_version_ui", dict(method="get_maas_version_ui")), | ||
938 | 269 | ("get_maas_doc_version", dict(method="get_maas_doc_version")), | ||
939 | 270 | ] | ||
940 | 271 | |||
941 | 272 | def test_method_is_cached(self): | ||
942 | 273 | mock_apt = self.patch(version, "get_version_from_apt") | ||
943 | 274 | mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1" | ||
944 | 275 | cached_method = getattr(version, self.method) | ||
945 | 276 | first_return_value = cached_method() | ||
946 | 277 | second_return_value = cached_method() | ||
947 | 278 | # The return value is not empty (full unit tests have been performed | ||
948 | 279 | # earlier). | ||
949 | 280 | self.assertNotIn(first_return_value, [b'', '', None]) | ||
950 | 281 | self.assertEqual(first_return_value, second_return_value) | ||
951 | 282 | # Apt has only been called once. | ||
952 | 283 | self.expectThat( | ||
953 | 284 | mock_apt, MockCalledOnceWith( | ||
954 | 285 | version.RACK_PACKAGE_NAME, version.REGION_PACKAGE_NAME)) | ||
955 | 286 | |||
956 | 287 | |||
957 | 288 | class TestGetMAASVersionTuple(MAASTestCase): | ||
958 | 289 | |||
959 | 290 | def test_get_maas_version_tuple(self): | ||
960 | 291 | self.assertEquals( | ||
961 | 292 | '.'.join([str(i) for i in version.get_maas_version_tuple()]), | ||
962 | 293 | version.get_maas_version_subversion()[0]) | ||
963 | diff --git a/src/provisioningserver/utils/version.py b/src/provisioningserver/utils/version.py | |||
964 | 0 | new file mode 100644 | 294 | new file mode 100644 |
965 | index 0000000..f372b0d | |||
966 | --- /dev/null | |||
967 | +++ b/src/provisioningserver/utils/version.py | |||
968 | @@ -0,0 +1,149 @@ | |||
969 | 1 | # Copyright 2015-2017 Canonical Ltd. This software is licensed under the | ||
970 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
971 | 3 | |||
972 | 4 | """Version utilities.""" | ||
973 | 5 | |||
974 | 6 | __all__ = [ | ||
975 | 7 | "get_maas_doc_version", | ||
976 | 8 | "get_maas_version_subversion", | ||
977 | 9 | "get_maas_version_ui", | ||
978 | 10 | ] | ||
979 | 11 | |||
980 | 12 | from functools import lru_cache | ||
981 | 13 | import re | ||
982 | 14 | |||
983 | 15 | from provisioningserver.logger import get_maas_logger | ||
984 | 16 | from provisioningserver.utils import ( | ||
985 | 17 | shell, | ||
986 | 18 | snappy, | ||
987 | 19 | ) | ||
988 | 20 | |||
989 | 21 | |||
990 | 22 | maaslog = get_maas_logger('version') | ||
991 | 23 | |||
992 | 24 | DEFAULT_VERSION = "2.3.0" | ||
993 | 25 | |||
994 | 26 | # Only import apt_pkg and initialize when not running in a snap. | ||
995 | 27 | if not snappy.running_in_snap(): | ||
996 | 28 | import apt_pkg | ||
997 | 29 | apt_pkg.init() | ||
998 | 30 | |||
999 | 31 | # Name of maas package to get version from. | ||
1000 | 32 | REGION_PACKAGE_NAME = "maas-region-api" | ||
1001 | 33 | RACK_PACKAGE_NAME = "maas-rack-controller" | ||
1002 | 34 | |||
1003 | 35 | |||
1004 | 36 | def get_version_from_apt(*packages): | ||
1005 | 37 | """Return the version output from `apt_pkg.Cache` for the given package(s), | ||
1006 | 38 | or log an error message if the package data is not valid.""" | ||
1007 | 39 | try: | ||
1008 | 40 | cache = apt_pkg.Cache(None) | ||
1009 | 41 | except SystemError: | ||
1010 | 42 | maaslog.error( | ||
1011 | 43 | 'Installed version could not be determined. Ensure ' | ||
1012 | 44 | '/var/lib/dpkg/status is valid.') | ||
1013 | 45 | return "" | ||
1014 | 46 | |||
1015 | 47 | version = None | ||
1016 | 48 | for package in packages: | ||
1017 | 49 | if package in cache: | ||
1018 | 50 | apt_package = cache[package] | ||
1019 | 51 | version = apt_package.current_ver | ||
1020 | 52 | break | ||
1021 | 53 | |||
1022 | 54 | return version.ver_str if version is not None else "" | ||
1023 | 55 | |||
1024 | 56 | |||
1025 | 57 | def extract_version_subversion(version): | ||
1026 | 58 | """Return a tuple (version, subversion) from the given apt version.""" | ||
1027 | 59 | main_version, subversion = re.split('[+|-]', version, 1) | ||
1028 | 60 | return main_version, subversion | ||
1029 | 61 | |||
1030 | 62 | |||
1031 | 63 | def get_maas_branch_version(): | ||
1032 | 64 | """Return the Bazaar revision for this running MAAS. | ||
1033 | 65 | |||
1034 | 66 | :return: An integer if MAAS is running from a Bazaar working tree, else | ||
1035 | 67 | `None`. The revision number is only representative of the BRANCH, not | ||
1036 | 68 | the working tree. | ||
1037 | 69 | """ | ||
1038 | 70 | try: | ||
1039 | 71 | revno = shell.call_and_check(("bzr", "revno", __file__)) | ||
1040 | 72 | except shell.ExternalProcessError: | ||
1041 | 73 | # We may not be in a Bazaar working tree, or any manner of other | ||
1042 | 74 | # errors. For the purposes of this function we don't care; simply say | ||
1043 | 75 | # we don't know. | ||
1044 | 76 | return None | ||
1045 | 77 | except FileNotFoundError: | ||
1046 | 78 | # Bazaar is not installed. We don't care and simply say we don't know. | ||
1047 | 79 | return None | ||
1048 | 80 | else: | ||
1049 | 81 | # `bzr revno` can return '???' when it can't find the working tree's | ||
1050 | 82 | # current revision in the branch. Hopefully a fairly unlikely thing to | ||
1051 | 83 | # happen, but we guard against it, and other ills, here. | ||
1052 | 84 | try: | ||
1053 | 85 | return int(revno) | ||
1054 | 86 | except ValueError: | ||
1055 | 87 | return None | ||
1056 | 88 | |||
1057 | 89 | |||
1058 | 90 | @lru_cache(maxsize=1) | ||
1059 | 91 | def get_maas_version(): | ||
1060 | 92 | """Return the apt or snap version for the main MAAS package.""" | ||
1061 | 93 | if snappy.running_in_snap(): | ||
1062 | 94 | return snappy.get_snap_version() | ||
1063 | 95 | else: | ||
1064 | 96 | return get_version_from_apt(RACK_PACKAGE_NAME, REGION_PACKAGE_NAME) | ||
1065 | 97 | |||
1066 | 98 | |||
1067 | 99 | @lru_cache(maxsize=1) | ||
1068 | 100 | def get_maas_version_subversion(): | ||
1069 | 101 | """Return a tuple with the MAAS version and the MAAS subversion.""" | ||
1070 | 102 | version = get_maas_version() | ||
1071 | 103 | if version: | ||
1072 | 104 | return extract_version_subversion(version) | ||
1073 | 105 | else: | ||
1074 | 106 | # Get the branch information | ||
1075 | 107 | branch_version = get_maas_branch_version() | ||
1076 | 108 | if branch_version is None: | ||
1077 | 109 | # Not installed not in branch, then no way to identify. This should | ||
1078 | 110 | # not happen, but just in case. | ||
1079 | 111 | return DEFAULT_VERSION, "unknown" | ||
1080 | 112 | else: | ||
1081 | 113 | return "%s from source" % DEFAULT_VERSION, "bzr%d" % branch_version | ||
1082 | 114 | |||
1083 | 115 | |||
1084 | 116 | @lru_cache(maxsize=1) | ||
1085 | 117 | def get_maas_version_ui(): | ||
1086 | 118 | """Return the version string for the running MAAS region. | ||
1087 | 119 | |||
1088 | 120 | The returned string is suitable to display in the UI. | ||
1089 | 121 | """ | ||
1090 | 122 | version, subversion = get_maas_version_subversion() | ||
1091 | 123 | return "%s (%s)" % (version, subversion) if subversion else version | ||
1092 | 124 | |||
1093 | 125 | |||
1094 | 126 | @lru_cache(maxsize=1) | ||
1095 | 127 | def get_maas_version_user_agent(): | ||
1096 | 128 | """Return the version string for the running MAAS region. | ||
1097 | 129 | |||
1098 | 130 | The returned string is suitable to set the user agent. | ||
1099 | 131 | """ | ||
1100 | 132 | version, subversion = get_maas_version_subversion() | ||
1101 | 133 | return "maas/%s/%s" % (version, subversion) | ||
1102 | 134 | |||
1103 | 135 | |||
1104 | 136 | @lru_cache(maxsize=1) | ||
1105 | 137 | def get_maas_doc_version(): | ||
1106 | 138 | """Return the doc version for the running MAAS region.""" | ||
1107 | 139 | apt_version = get_maas_version() | ||
1108 | 140 | if apt_version: | ||
1109 | 141 | version, _ = extract_version_subversion(apt_version) | ||
1110 | 142 | return '.'.join(version.split('~')[0].split('.')[:2]) | ||
1111 | 143 | else: | ||
1112 | 144 | return '' | ||
1113 | 145 | |||
1114 | 146 | |||
1115 | 147 | def get_maas_version_tuple(): | ||
1116 | 148 | """Returns a tuple of the MAAS version without the svn rev.""" | ||
1117 | 149 | return tuple(int(x) for x in DEFAULT_VERSION.split('.')) | ||
1118 | diff --git a/utilities/check-imports b/utilities/check-imports | |||
1119 | index 405ca8d..d047e70 100755 | |||
1120 | --- a/utilities/check-imports | |||
1121 | +++ b/utilities/check-imports | |||
1122 | @@ -195,6 +195,7 @@ RackControllerRule = Rule( | |||
1123 | 195 | Allow("apiclient.creds.*"), | 195 | Allow("apiclient.creds.*"), |
1124 | 196 | Allow("apiclient.maas_client.*"), | 196 | Allow("apiclient.maas_client.*"), |
1125 | 197 | Allow("apiclient.utils.*"), | 197 | Allow("apiclient.utils.*"), |
1126 | 198 | Allow("apt_pkg"), | ||
1127 | 198 | Allow("attr|attr.**"), | 199 | Allow("attr|attr.**"), |
1128 | 199 | Allow("bson|bson.**"), | 200 | Allow("bson|bson.**"), |
1129 | 200 | Allow("crochet|crochet.**"), | 201 | Allow("crochet|crochet.**"), |
1130 | @@ -245,7 +246,6 @@ RegionControllerRule = Rule( | |||
1131 | 245 | Allow("apiclient.creds.*"), | 246 | Allow("apiclient.creds.*"), |
1132 | 246 | Allow("apiclient.multipart.*"), | 247 | Allow("apiclient.multipart.*"), |
1133 | 247 | Allow("apiclient.utils.*"), | 248 | Allow("apiclient.utils.*"), |
1134 | 248 | Allow("apt_pkg"), | ||
1135 | 249 | Allow("attr|attr.**"), | 249 | Allow("attr|attr.**"), |
1136 | 250 | Allow("bson"), | 250 | Allow("bson"), |
1137 | 251 | Allow("convoy|convoy.**"), | 251 | Allow("convoy|convoy.**"), |
Looks good. Just one small suggestion.