Merge lp:~gandelman-a/charm-helpers/openstack_utils into lp:charm-helpers

Proposed by Adam Gandelman
Status: Merged
Merged at revision: 46
Proposed branch: lp:~gandelman-a/charm-helpers/openstack_utils
Merge into: lp:charm-helpers
Diff against target: 267 lines (+104/-23)
2 files modified
charmhelpers/contrib/openstack/openstack_utils.py (+42/-14)
tests/contrib/openstack/test_openstack_utils.py (+62/-9)
To merge this branch: bzr merge lp:~gandelman-a/charm-helpers/openstack_utils
Reviewer Review Type Date Requested Status
James Page Approve
Adam Gandelman (community) Needs Resubmitting
Review via email: mp+173273@code.launchpad.net

Commit message

Add contrib.opentstack.openstack_utils.openstack_upgrade_available()
Allow for non-fatal querying of openstack packages that are not installed
Adds relevant tests.

Description of the change

Adds a utility openstack_upgrade_available() to check if an upgrade is available via the configured installation source. Requires an update to the version querying utilities to be optionally non-fatal.

To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote :

Minor lint:

tests/contrib/openstack/test_openstack_utils.py:60:38: E241 multiple spaces after ','
tests/contrib/openstack/test_openstack_utils.py:373:1: E303 too many blank lines (3)
make: *** [lint] Error 1

review: Needs Fixing
Revision history for this message
James Page (james-page) wrote :

Please can we switch to core/host for juju log calls:

def juju_log(msg):
    subprocess.check_call(['juju-log', msg])

This is duplicated code now - test can also be dropped.

This is not a blocker right now but it would be great if configure_installation_source could be refactored to use more of the approx same code in the fetch helper.

review: Needs Fixing
Revision history for this message
James Page (james-page) wrote :

Does this work with distro upstream versioning:

  StrictVersion(available_vers) > StrictVersion(cur_vers)

For example, python works towards a major release whereas distro use ~

  2013.2.b1 vs 2013.2~b1

review: Needs Information
46. By Adam Gandelman

Use apt_pkg.version_compare() instead of distutil's StrictVersions.

47. By Adam Gandelman

lint fix.

Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Thanks for catching the DistUtils thing. Opted to use apt_pkg.version_compare there instead. Cleaned up lint, too.

juju_log is still used locally in this helper (via error_out() and elsewhere). I'd prefer to defer that, along with the configure_installation_source refactoring to a later cleanup merge.

review: Needs Resubmitting
Revision history for this message
James Page (james-page) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/openstack_utils.py'
2--- charmhelpers/contrib/openstack/openstack_utils.py 2013-05-30 23:26:24 +0000
3+++ charmhelpers/contrib/openstack/openstack_utils.py 2013-07-09 18:50:52 +0000
4@@ -7,6 +7,14 @@
5 import os
6 import sys
7
8+from charmhelpers.core.hookenv import (
9+ config,
10+)
11+
12+from charmhelpers.core.host import (
13+ lsb_release,
14+)
15+
16 CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
17 CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
18
19@@ -46,20 +54,9 @@
20 sys.exit(1)
21
22
23-def lsb_release():
24- '''Return /etc/lsb-release in a dict'''
25- lsb = open('/etc/lsb-release', 'r')
26- d = {}
27- for l in lsb:
28- k, v = l.split('=')
29- d[k.strip()] = v.strip()
30- return d
31-
32-
33 def get_os_codename_install_source(src):
34 '''Derive OpenStack release codename from a given installation source.'''
35 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
36-
37 rel = ''
38 if src == 'distro':
39 try:
40@@ -82,6 +79,11 @@
41 return v
42
43
44+def get_os_version_install_source(src):
45+ codename = get_os_codename_install_source(src)
46+ return get_os_version_codename(codename)
47+
48+
49 def get_os_codename_version(vers):
50 '''Determine OpenStack codename from version number.'''
51 try:
52@@ -101,7 +103,7 @@
53 error_out(e)
54
55
56-def get_os_codename_package(pkg):
57+def get_os_codename_package(pkg, fatal=True):
58 '''Derive OpenStack release codename from an installed package.'''
59 apt.init()
60 cache = apt.Cache()
61@@ -109,6 +111,8 @@
62 try:
63 pkg = cache[pkg]
64 except:
65+ if not fatal:
66+ return None
67 e = 'Could not determine version of installed package: %s' % pkg
68 error_out(e)
69
70@@ -126,9 +130,12 @@
71 error_out(e)
72
73
74-def get_os_version_package(pkg):
75+def get_os_version_package(pkg, fatal=True):
76 '''Derive OpenStack version number from an installed package.'''
77- codename = get_os_codename_package(pkg)
78+ codename = get_os_codename_package(pkg, fatal=fatal)
79+
80+ if not codename:
81+ return None
82
83 if 'swift' in pkg:
84 vers_map = swift_codenames
85@@ -141,6 +148,7 @@
86 #e = "Could not determine OpenStack version for package: %s" % pkg
87 #error_out(e)
88
89+
90 def import_key(keyid):
91 cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \
92 "--recv-keys %s" % keyid
93@@ -149,6 +157,7 @@
94 except subprocess.CalledProcessError:
95 error_out("Error importing repo key %s" % keyid)
96
97+
98 def configure_installation_source(rel):
99 '''Configure apt installation source.'''
100 if rel == 'distro':
101@@ -226,3 +235,22 @@
102 "#!/bin/bash\n")
103 [rc_script.write('export %s=%s\n' % (u, p))
104 for u, p in env_vars.iteritems() if u != "script_path"]
105+
106+
107+def openstack_upgrade_available(package):
108+ """
109+ Determines if an OpenStack upgrade is available from installation
110+ source, based on version of installed package.
111+
112+ :param package: str: Name of installed package.
113+
114+ :returns: bool: : Returns True if configured installation source offers
115+ a newer version of package.
116+
117+ """
118+
119+ src = config('openstack-origin')
120+ cur_vers = get_os_version_package(package)
121+ available_vers = get_os_version_install_source(src)
122+ apt.init()
123+ return apt.version_compare(available_vers, cur_vers) == 1
124
125=== modified file 'tests/contrib/openstack/test_openstack_utils.py'
126--- tests/contrib/openstack/test_openstack_utils.py 2013-05-30 23:31:29 +0000
127+++ tests/contrib/openstack/test_openstack_utils.py 2013-07-09 18:50:52 +0000
128@@ -1,13 +1,12 @@
129 from copy import copy
130 import os
131-import apt_pkg
132 import unittest
133 from testtools import TestCase
134-from io import BytesIO
135 import charmhelpers.contrib.openstack.openstack_utils as openstack
136 import subprocess
137
138-from mock import MagicMock, patch, Mock, call
139+from mock import MagicMock, patch, call
140+
141
142 # mocked return of openstack.lsb_release()
143 FAKE_RELEASE = {
144@@ -58,9 +57,10 @@
145 ('cloud:precise-folsom/updates', url + ' precise-updates/folsom main'),
146 ('cloud:precise-grizzly/proposed', url + ' precise-proposed/grizzly main'),
147 ('cloud:precise-grizzly', url + ' precise-updates/grizzly main'),
148- ('cloud:precise-grizzly/updates', url + ' precise-updates/grizzly main'),
149+ ('cloud:precise-grizzly/updates', url + ' precise-updates/grizzly main'),
150 ]
151
152+
153 class OpenStackHelpersTestCase(TestCase):
154 def _apt_cache(self):
155 # mocks out the apt cache
156@@ -101,6 +101,13 @@
157 self.assertEquals(openstack.get_os_codename_install_source(src),
158 'havana')
159
160+ @patch.object(openstack, 'get_os_version_codename')
161+ @patch.object(openstack, 'get_os_codename_install_source')
162+ def test_os_version_from_install_source(self, codename, version):
163+ codename.return_value = 'grizzly'
164+ openstack.get_os_version_install_source('cloud:precise-grizzly')
165+ version.assert_called_with('grizzly')
166+
167 @patch('charmhelpers.contrib.openstack.openstack_utils.lsb_release')
168 def test_os_codename_from_bad_install_source(self, mocked_lsb):
169 '''Test mapping install source to OpenStack release name'''
170@@ -151,7 +158,7 @@
171 vers['os_release'])
172
173 @patch('charmhelpers.contrib.openstack.openstack_utils.error_out')
174- def test_os_codename_from_bad_package_vrsion(self, mocked_error):
175+ def test_os_codename_from_bad_package_version(self, mocked_error):
176 '''Test deriving OpenStack codename for a poorly versioned package'''
177 with patch('apt_pkg.Cache') as cache:
178 cache.return_value = self._apt_cache()
179@@ -173,6 +180,15 @@
180 _err = 'Could not determine version of installed package: foo'
181 mocked_error.assert_called_with(_err)
182
183+ def test_os_codename_from_bad_package_nonfatal(self):
184+ '''Test OpenStack codename from an uninstalled package is non-fatal'''
185+ with patch('apt_pkg.Cache') as cache:
186+ cache.return_value = self._apt_cache()
187+ self.assertEquals(
188+ None,
189+ openstack.get_os_codename_package('foo', fatal=False)
190+ )
191+
192 @patch('charmhelpers.contrib.openstack.openstack_utils.error_out')
193 def test_os_version_from_package(self, mocked_error):
194 '''Test deriving OpenStack version from an installed package'''
195@@ -198,6 +214,15 @@
196 _err = 'Could not determine version of installed package: foo'
197 mocked_error.assert_called_with(_err)
198
199+ def test_os_version_from_bad_package_nonfatal(self):
200+ '''Test OpenStack version from an uninstalled package is non-fatal'''
201+ with patch('apt_pkg.Cache') as cache:
202+ cache.return_value = self._apt_cache()
203+ self.assertEquals(
204+ None,
205+ openstack.get_os_version_package('foo', fatal=False)
206+ )
207+
208 def test_juju_log(self):
209 '''Test shelling out to juju-log'''
210 with patch('subprocess.check_call') as mocked_subprocess:
211@@ -270,8 +295,10 @@
212 for src, url in UCA_SOURCES:
213 openstack.configure_installation_source(src)
214 _import.assert_called_with(openstack.CLOUD_ARCHIVE_KEY_ID)
215- _open.assert_called_with('/etc/apt/sources.list.d/cloud-archive.list',
216- 'w')
217+ _open.assert_called_with(
218+ '/etc/apt/sources.list.d/cloud-archive.list',
219+ 'w'
220+ )
221 _file.__enter__().write.assert_called_with(url)
222
223 @patch('charmhelpers.contrib.openstack.openstack_utils.error_out')
224@@ -310,8 +337,8 @@
225 def test_save_scriptrc(self, _open):
226 '''Test generation of scriptrc from environment'''
227 scriptrc = ['#!/bin/bash\n',
228- 'export setting1=foo\n',
229- 'export setting2=bar\n']
230+ 'export setting1=foo\n',
231+ 'export setting2=bar\n']
232 _file = MagicMock(spec=file)
233 _open.return_value = _file
234 os.environ['JUJU_UNIT_NAME'] = 'testing-foo/0'
235@@ -321,6 +348,32 @@
236 for line in scriptrc:
237 _file.__enter__().write.assert_has_calls(call(line))
238
239+ @patch.object(openstack, 'lsb_release')
240+ @patch.object(openstack, 'get_os_version_package')
241+ @patch.object(openstack, 'config')
242+ def test_openstack_upgrade_detection_true(self, config, vers_pkg, lsb):
243+ """Test it detects when an openstack package has available upgrade"""
244+ lsb.return_value = FAKE_RELEASE
245+ config.return_value = 'cloud:precise-havana'
246+ vers_pkg.return_value = '2013.1.1'
247+ self.assertTrue(openstack.openstack_upgrade_available('nova-common'))
248+ # milestone to major release detection
249+ vers_pkg.return_value = '2013.2~b1'
250+ self.assertTrue(openstack.openstack_upgrade_available('nova-common'))
251+
252+ @patch.object(openstack, 'lsb_release')
253+ @patch.object(openstack, 'get_os_version_package')
254+ @patch.object(openstack, 'config')
255+ def test_openstack_upgrade_detection_false(self, config, vers_pkg, lsb):
256+ """Test it detects when an openstack upgrade is not necessary"""
257+ lsb.return_value = FAKE_RELEASE
258+ config.return_value = 'cloud:precise-folsom'
259+ vers_pkg.return_value = '2013.1.1'
260+ self.assertFalse(openstack.openstack_upgrade_available('nova-common'))
261+ # milestone to majro release detection
262+ vers_pkg.return_value = '2013.1~b1'
263+ self.assertFalse(openstack.openstack_upgrade_available('nova-common'))
264+
265
266 if __name__ == '__main__':
267 unittest.main()

Subscribers

People subscribed via source and target branches