Skip to content

Commit 28c8cce

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Implemented automatic updates plugin"
2 parents 8e5fff7 + dc7699b commit 28c8cce

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed

cloudbaseinit/conf/default.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ def __init__(self, config):
267267
help='Copies the userdata to the given file path. The path '
268268
'can include environment variables that will be expanded,'
269269
' e.g. "%%SYSTEMDRIVE%%\\CloudbaseInit\\UserData.bin"'),
270+
cfg.BoolOpt(
271+
'enable_automatic_updates', default=None,
272+
help='If set, enables or disables automatic operating '
273+
'system updates.'),
270274
]
271275

272276
self._cli_options = [

cloudbaseinit/metadata/services/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ def get_kms_host(self):
202202
def get_use_avma_licensing(self):
203203
pass
204204

205+
def get_enable_automatic_updates(self):
206+
"""Check if the metadata provider enforces automatic updates."""
207+
pass
208+
205209

206210
class BaseHTTPMetadataService(BaseMetadataService):
207211

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright (c) 2017 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from oslo_log import log as oslo_logging
16+
17+
from cloudbaseinit import conf as cloudbaseinit_conf
18+
from cloudbaseinit.plugins.common import base
19+
from cloudbaseinit.utils.windows import updates
20+
21+
CONF = cloudbaseinit_conf.CONF
22+
LOG = oslo_logging.getLogger(__name__)
23+
24+
25+
class WindowsAutoUpdatesPlugin(base.BasePlugin):
26+
def execute(self, service, shared_data):
27+
enable_updates = service.get_enable_automatic_updates()
28+
29+
if enable_updates is None:
30+
enable_updates = CONF.enable_automatic_updates
31+
if enable_updates is not None:
32+
LOG.info("Configuring automatic updates: %s", enable_updates)
33+
updates.set_automatic_updates(enable_updates)
34+
35+
return base.PLUGIN_EXECUTION_DONE, False
36+
37+
def get_os_requirements(self):
38+
return 'win32', (5, 2)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (c) 2017 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import importlib
16+
import unittest
17+
18+
try:
19+
import unittest.mock as mock
20+
except ImportError:
21+
import mock
22+
23+
from cloudbaseinit import conf as cloudbaseinit_conf
24+
from cloudbaseinit.plugins.common import base
25+
from cloudbaseinit.tests import testutils
26+
27+
CONF = cloudbaseinit_conf.CONF
28+
MODPATH = "cloudbaseinit.plugins.windows.updates"
29+
30+
31+
class WindowsAutoUpdatesPluginTest(unittest.TestCase):
32+
33+
def setUp(self):
34+
self.mock_win32com = mock.MagicMock()
35+
patcher = mock.patch.dict(
36+
"sys.modules",
37+
{
38+
"win32com": self.mock_win32com
39+
}
40+
)
41+
patcher.start()
42+
self.addCleanup(patcher.stop)
43+
updates = importlib.import_module(MODPATH)
44+
self._updates_plugin = updates.WindowsAutoUpdatesPlugin()
45+
self.snatcher = testutils.LogSnatcher(MODPATH)
46+
47+
@testutils.ConfPatcher("enable_automatic_updates", True)
48+
@mock.patch("cloudbaseinit.utils.windows.updates.set_automatic_updates")
49+
def test_execute(self, mock_set_updates):
50+
mock_service = mock.Mock()
51+
mock_shared_data = mock.Mock()
52+
mock_service.get_enable_automatic_updates.return_value = True
53+
54+
expected_res = (base.PLUGIN_EXECUTION_DONE, False)
55+
expected_logs = ["Configuring automatic updates: %s" % True]
56+
with self.snatcher:
57+
res = self._updates_plugin.execute(mock_service, mock_shared_data)
58+
self.assertEqual(res, expected_res)
59+
self.assertEqual(self.snatcher.output, expected_logs)
60+
mock_service.get_enable_automatic_updates.assert_called_once_with()
61+
mock_set_updates.assert_called_once_with(True)
62+
63+
def test_get_os_requirements(self):
64+
expected_res = ('win32', (5, 2))
65+
requirements_res = self._updates_plugin.get_os_requirements()
66+
self.assertEqual(requirements_res, expected_res)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright (c) 2017 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import importlib
16+
import unittest
17+
18+
try:
19+
import unittest.mock as mock
20+
except ImportError:
21+
import mock
22+
23+
24+
MODPATH = "cloudbaseinit.utils.windows.updates"
25+
26+
27+
class UpdatesUtilTest(unittest.TestCase):
28+
29+
def setUp(self):
30+
self._win32_com = mock.MagicMock()
31+
self._module_patcher = mock.patch.dict(
32+
'sys.modules', {
33+
'win32com': self._win32_com})
34+
self._win32_com_client = self._win32_com.client
35+
self._module_patcher.start()
36+
self._updates = importlib.import_module(MODPATH)
37+
38+
def tearDown(self):
39+
self._module_patcher.stop()
40+
41+
@mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
42+
def _test_set_automatic_updates(self, mock_get_os_utils, enabled=True):
43+
mock_osutils = mock.Mock()
44+
mock_updates = mock.Mock()
45+
mock_get_os_utils.return_value = mock_osutils
46+
mock_osutils.check_os_version.return_value = False
47+
self._win32_com_client.Dispatch.return_value = mock_updates
48+
if not enabled:
49+
self._updates.set_automatic_updates(enabled)
50+
self.assertEqual(mock_updates.Settings.NotificationLevel, 1)
51+
mock_updates.Settings.Save.assert_called_once_with()
52+
else:
53+
self._updates.set_automatic_updates(enabled)
54+
mock_get_os_utils.assert_called_once_with()
55+
self.assertIsNotNone(
56+
mock_updates.SettingsScheduledInstallationTime)
57+
mock_updates.Settings.Save.assert_called_once_with()
58+
59+
def test_set_automatic_no_updates(self):
60+
self._test_set_automatic_updates(enabled=False)
61+
62+
def test_set_automatic_updates(self):
63+
self._test_set_automatic_updates()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) 2017 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import random
16+
17+
from win32com import client
18+
19+
from cloudbaseinit.osutils import factory as osutils_factory
20+
21+
AU_DISABLED = 1
22+
AU_SCHEDULED_INSTALLATION = 4
23+
24+
MIN_INSTALL_HOUR = 1
25+
MAX_INSTALL_HOUR = 5
26+
27+
28+
def set_automatic_updates(enabled):
29+
# TODO(alexpilotti): the following settings are ignored on
30+
# Windows 10 / Windows Server 2016 build 14393
31+
auto_update = client.Dispatch("Microsoft.Update.AutoUpdate")
32+
if enabled:
33+
auto_update.Settings.NotificationLevel = AU_SCHEDULED_INSTALLATION
34+
osutils = osutils_factory.get_os_utils()
35+
if not osutils.check_os_version(6, 2):
36+
# NOTE(alexpilotti): this setting is not supported starting
37+
# with Windows 8 / Windows Server 2012
38+
hour = random.randint(MIN_INSTALL_HOUR, MAX_INSTALL_HOUR)
39+
auto_update.SettingsScheduledInstallationTime = hour
40+
else:
41+
auto_update.Settings.NotificationLevel = AU_DISABLED
42+
43+
auto_update.Settings.Save()

0 commit comments

Comments
 (0)