Check the latest version of cloudbaseinit

We're hitting an API with the current version and we expect to receive a
response if the current version is not the latest. This helps the user
to see that a new version of cloudbaseinit is available, which is a big
improvement than going to the github page to see the latest version.

Change-Id: Ibfb721973c84474c8fef8e1989dfb7566938134f
This commit is contained in:
Claudiu Popa 2015-06-30 21:30:43 +03:00
parent 5a42ef3b07
commit bccc7d5215
5 changed files with 167 additions and 3 deletions

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import sys
from oslo.config import cfg
@ -29,6 +30,10 @@ opts = [
cfg.BoolOpt('stop_service_on_exit', default=True, help='In case of '
'execution as a service, specifies if the service '
'must be gracefully stopped before exiting'),
cfg.BoolOpt('check_latest_version', default=True, help='Check if '
'there is a newer version of cloudbase-init available. '
'If this option is activated, a log message will be '
'emitted if there is a newer version available.')
]
CONF = cfg.CONF
@ -94,6 +99,13 @@ class InitManager(object):
'supported' % plugin_name)
return supported
@staticmethod
def _check_latest_version():
if CONF.check_latest_version:
log_version = functools.partial(
LOG.info, 'Found new version of cloudbase-init %s')
version.check_latest_version(log_version)
def configure_host(self):
LOG.info('Cloudbase-Init version: %s', version.get_version())
@ -104,6 +116,8 @@ class InitManager(object):
LOG.info('Metadata service loaded: \'%s\'' %
service.get_name())
self._check_latest_version()
instance_id = service.get_instance_id()
LOG.debug('Instance id: %s', instance_id)

View File

@ -136,6 +136,7 @@ class InitManagerTest(unittest.TestCase):
def test_check_plugin_os_requirements_other_requirenments(self):
self._test_check_plugin_os_requirements(('linux', (5, 2)))
@mock.patch('cloudbaseinit.init.InitManager._check_latest_version')
@mock.patch('cloudbaseinit.version.get_version')
@mock.patch('cloudbaseinit.init.InitManager'
'._check_plugin_os_requirements')
@ -147,7 +148,8 @@ class InitManagerTest(unittest.TestCase):
mock_get_os_utils, mock_load_plugins,
mock_exec_plugin,
mock_check_os_requirements,
mock_get_version, expected_logging,
mock_get_version, mock_check_latest_version,
expected_logging,
version, name, instance_id, reboot=True):
mock_get_version.return_value = version
@ -175,6 +177,7 @@ class InitManagerTest(unittest.TestCase):
self.osutils.reboot.assert_called_once_with()
else:
self.assertFalse(self.osutils.reboot.called)
mock_check_latest_version.assert_called_once_with()
def _test_configure_host_with_logging(self, extra_logging, reboot=True):
instance_id = 'fake id'
@ -209,3 +212,22 @@ class InitManagerTest(unittest.TestCase):
def test_configure_host_reboot(self):
self._test_configure_host_with_logging(
extra_logging=['Rebooting'])
@testutils.ConfPatcher('check_latest_version', False)
@mock.patch('cloudbaseinit.version.check_latest_version')
def test_configure_host(self, mock_check_last_version):
self._init._check_latest_version()
self.assertFalse(mock_check_last_version.called)
@testutils.ConfPatcher('check_latest_version', True)
@mock.patch('functools.partial')
@mock.patch('cloudbaseinit.version.check_latest_version')
def test_configure_host_with_version_check(self, mock_check_last_version,
mock_partial):
self._init._check_latest_version()
mock_check_last_version.assert_called_once_with(
mock_partial.return_value)
mock_partial.assert_called_once_with(
init.LOG.info, 'Found new version of cloudbase-init %s')

View File

@ -12,19 +12,99 @@
# License for the specific language governing permissions and limitations
# under the License.
import importlib
import unittest
import mock
import six
from cloudbaseinit import version
from cloudbaseinit.tests import testutils
class TestVersion(unittest.TestCase):
def setUp(self):
self.version = importlib.import_module('cloudbaseinit.version')
@mock.patch('pbr.version.VersionInfo')
def test_get_version(self, mock_version_info):
package_version = version.get_version()
package_version = self.version.get_version()
mock_version_info.assert_called_once_with('cloudbase-init')
release_string = mock_version_info.return_value.release_string
self.assertEqual(release_string.return_value, package_version)
@mock.patch('requests.get')
@mock.patch('json.loads')
def test__read_url(self, mock_loads, mock_get):
mock_url = mock.Mock()
result = self.version._read_url(mock_url)
headers = {'User-Agent': self.version._PRODUCT_NAME}
mock_get.assert_called_once_with(mock_url, verify=six.PY3,
headers=headers)
request = mock_get.return_value
request.raise_for_status.assert_called_once_with()
mock_loads.assert_called_once_with(request.text)
self.assertEqual(mock_loads.return_value, result)
@mock.patch('requests.get')
def test__read_url_empty_text(self, mock_get):
mock_get.return_value.text = None
result = self.version._read_url(mock.Mock())
self.assertIsNone(result)
@mock.patch('threading.Thread')
def test_check_latest_version(self, mock_thread):
mock_callback = mock.Mock()
self.version.check_latest_version(mock_callback)
mock_thread.assert_called_once_with(
target=self.version._check_latest_version,
args=(mock_callback, ))
thread = mock_thread.return_value
thread.start.assert_called_once_with()
@mock.patch('cloudbaseinit.version._read_url')
def test__check_latest_version(self, mock_read_url):
mock_read_url.return_value = {'new_version': 42}
mock_callback = mock.Mock()
self.version._check_latest_version(mock_callback)
mock_callback.assert_called_once_with(42)
@mock.patch('cloudbaseinit.version._read_url')
def test__check_latest_version_fails(self, mock_read_url):
mock_read_url.side_effect = Exception('no worky')
mock_callback = mock.Mock()
with testutils.LogSnatcher('cloudbaseinit.version') as snatcher:
self.version._check_latest_version(mock_callback)
expected_logging = ['Failed checking for new versions: no worky']
self.assertEqual(expected_logging, snatcher.output)
self.assertFalse(mock_callback.called)
@mock.patch('cloudbaseinit.version._read_url')
def test__check_latest_version_no_content(self, mock_read_url):
mock_read_url.return_value = None
mock_callback = mock.Mock()
self.version._check_latest_version(mock_callback)
self.assertFalse(mock_callback.called)
@mock.patch('cloudbaseinit.version._read_url')
def test__check_latest_version_no_new_version(self, mock_read_url):
mock_read_url.return_value = {'new_versio': 42}
mock_callback = mock.Mock()
result = self.version._check_latest_version(mock_callback)
self.assertFalse(mock_callback.called)
self.assertIsNone(result)

View File

@ -12,7 +12,54 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import threading
import pbr.version
import requests
import six
from cloudbaseinit.openstack.common import log as logging
_UPDATE_CHECK_URL = 'https://www.cloudbase.it/checkupdates.php?p={0}&v={1}'
_PRODUCT_NAME = 'Cloudbase-Init'
LOG = logging.getLogger(__name__)
def _read_url(url):
# Disable certificate verification on Python 2 as
# requests's CA list is incomplete. Works fine on Python3.
req = requests.get(url, verify=six.PY3,
headers={'User-Agent': _PRODUCT_NAME})
req.raise_for_status()
if req.text:
return json.loads(req.text)
def _check_latest_version(callback):
product_version = get_version()
url = _UPDATE_CHECK_URL.format(_PRODUCT_NAME, product_version)
try:
content = _read_url(url)
if not content:
return
version = content.get('new_version')
if version:
callback(version)
except Exception as exc:
LOG.debug('Failed checking for new versions: %s', exc)
return
def check_latest_version(done_callback):
"""Try to obtain the latest version of the product."""
thread = threading.Thread(target=_check_latest_version,
args=(done_callback, ))
thread.daemon = True
thread.start()
def get_version():

View File

@ -10,3 +10,4 @@ oauthlib
netifaces
PyYAML
tzlocal
requests