diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/__init__.py b/cloudbaseinit/plugins/windows/userdata-plugins/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/plugins/windows/userdata-plugins/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/__init__.py b/cloudbaseinit/tests/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/metadata/__init__.py b/cloudbaseinit/tests/metadata/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/metadata/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/metadata/fake_json_response.py b/cloudbaseinit/tests/metadata/fake_json_response.py new file mode 100644 index 00000000..5b51feb6 --- /dev/null +++ b/cloudbaseinit/tests/metadata/fake_json_response.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +def get_fake_metadata_json(version): + if version == '2013-04-04': + return {"random_seed": + "Wn51FGjZa3vlZtTxJuPr96oCf+X8jqbA9U2XR5wNdnApy1fz" + "/2NNssUwPoNzG6etw9RBn+XiZ0zKWnFzMsTopaN7WwYjWTnIsVw3cpIk" + "Td579wQgoEr1ANqhfO3qTvkOVNMhzTAw1ps+wqRmkLxH+1qYJnX06Gcd" + "KRRGkWTaOSlTkieA0LO2oTGFlbFDWcOW2vT5BvSBmqP7vNLzbLDMTc7M" + "IWRBzwmtcVPC17QL6EhZJTUcZ0mTz7l0R0DocLmFwHEXFEEr+q4WaJjt" + "1ejOOxVM3tiT7D8YpRZnnGNPfvEhq1yVMUoi8yv9pFmMmXicNBhm6zDK" + "VjcWk0gfbvaQcMnnOLrrE1VxAAzyNyPIXBI/H7AAHz2ECz7dgd2/4ocv" + "3bmTRY3hhcUKtNuat2IOvSGgMBUGdWnLorQGFz8t0/bcYhE0Dve35U6H" + "mtj78ydV/wmQWG0iq49NX6hk+VUmZtSZztlkbsaa7ajNjZ+Md9oZtlhX" + "Z5vJuhRXnHiCm7dRNO8Xo6HffEBH5A4smQ1T2Kda+1c18DZrY7+iQJRi" + "fa6witPCw0tXkQ6nlCLqL2weJD1XMiTZLSM/XsZFGGSkKCKvKLEqQrI/" + "XFUq/TA6B4aLGFlmmhOO/vMJcht06O8qVU/xtd5Mv/MRFzYaSG568Z/m" + "hk4vYLYdQYAA+pXRW9A=", + "uuid": "4b32ddf7-7941-4c36-a854-a1f5ac45b318", + "availability_zone": "nova", + "hostname": "windows.novalocal", + "launch_index": 0, + "public_keys": {"key": "ssh-rsa " + "AAAAB3NzaC1yc2EAAAADAQABAAABA" + "QDf7kQHq7zvBod3yIZs0tB/AOOZz5pab7qt/h" + "78VF7yi6qTsFdUnQxRue43R/75wa9EEyokgYR" + "LKIN+Jq2A5tXNMcK+rNOCzLJFtioAwEl+S6VL" + "G9jfkbUv++7zoSMOsanNmEDvG0B79MpyECFCl" + "th2DsdE4MQypify35U5ri5Qi7E6PEYAsU65LF" + "MG2boeCIB29BEooE6AgPr2DuJeJ+2uw+YScF9" + "FV3og4Wyz5zipPVh8YpVev6dlg0tRWUrCtZF9" + "IODpCTrT3vsPRG3xz7CppR+vGi/1gLXHtJCRj" + "frHwkY6cXyhypNmkU99K/wMqSv30vsDwdnsQ1" + "q3YhLarMHB Generated by Nova\n", + "name": "windows"}, + "network_config": {"content_path": "network", + 'debian_config': 'iface eth0 inet static' + 'address 10.11.12.13' + 'broadcast 0.0.0.0' + 'netmask 255.255.255.255' + 'gateway 1.2.3.4' + 'dns-nameserver 8.8.8.8'}} diff --git a/cloudbaseinit/tests/metadata/services/__init__.py b/cloudbaseinit/tests/metadata/services/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/metadata/services/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/metadata/services/configdrive/__init__.py b/cloudbaseinit/tests/metadata/services/configdrive/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/metadata/services/configdrive/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/metadata/services/configdrive/test_configdrive.py b/cloudbaseinit/tests/metadata/services/configdrive/test_configdrive.py new file mode 100644 index 00000000..dd28414b --- /dev/null +++ b/cloudbaseinit/tests/metadata/services/configdrive/test_configdrive.py @@ -0,0 +1,84 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import os +import sys +import unittest +import uuid + +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF +_win32com_mock = mock.MagicMock() +_ctypes_mock = mock.MagicMock() +_ctypes_util_mock = mock.MagicMock() +_win32com_client_mock = mock.MagicMock() +_pywintypes_mock = mock.MagicMock() +_mock_dict = {'win32com': _win32com_mock, + 'ctypes': _ctypes_mock, + 'ctypes.util': _ctypes_util_mock, + 'win32com.client': _win32com_client_mock, + 'pywintypes': _pywintypes_mock} + + +class ConfigDriveServiceTest(unittest.TestCase): + @mock.patch.dict(sys.modules, _mock_dict) + def setUp(self): + configdrive = importlib.import_module('cloudbaseinit.metadata.services' + '.configdrive.configdrive') + self._config_drive = configdrive.ConfigDriveService() + + def tearDown(self): + reload(sys) + + @mock.patch('cloudbaseinit.metadata.services.configdrive.manager.' + 'ConfigDriveManager.get_config_drive_files') + @mock.patch('tempfile.gettempdir') + @mock.patch('os.path.join') + def test_load(self, mock_join, mock_gettempdir, + mock_get_config_drive_files): + uuid.uuid4 = mock.MagicMock() + fake_path = os.path.join('fake', 'path') + fake_path_found = os.path.join(fake_path, 'found') + uuid.uuid4.return_value = 'random' + mock_get_config_drive_files.return_value = fake_path_found + mock_join.return_value = fake_path + response = self._config_drive.load() + mock_join.assert_called_with(mock_gettempdir(), 'random') + mock_get_config_drive_files.assert_called_once_with( + fake_path, CONF.config_drive_raw_hhd, CONF.config_drive_cdrom) + self.assertEqual(self._config_drive._metadata_path, fake_path) + self.assertEqual(response, fake_path_found) + + @mock.patch('os.path.normpath') + @mock.patch('os.path.join') + def test_get_data(self, mock_join, mock_normpath): + fake_path = os.path.join('fake', 'path') + with mock.patch('__builtin__.open', + mock.mock_open(read_data='fake data'), create=True): + response = self._config_drive._get_data(fake_path) + self.assertEqual(response, 'fake data') + mock_join.assert_called_with( + self._config_drive._metadata_path, fake_path) + + @mock.patch('shutil.rmtree') + def test_cleanup(self, mock_rmtree): + fake_path = os.path.join('fake', 'path') + self._config_drive._metadata_path = fake_path + self._config_drive.cleanup() + self.assertEqual(self._config_drive._metadata_path, None) diff --git a/cloudbaseinit/tests/metadata/services/test_ec2service.py b/cloudbaseinit/tests/metadata/services/test_ec2service.py new file mode 100644 index 00000000..2fa4f004 --- /dev/null +++ b/cloudbaseinit/tests/metadata/services/test_ec2service.py @@ -0,0 +1,144 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import os +import unittest + +from cloudbaseinit.metadata.services import ec2service +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF + + +class Ec2ServiceTest(unittest.TestCase): + + def setUp(self): + self._ec2service = ec2service.EC2Service() + + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '.get_meta_data') + def _test_load(self, mock_get_meta_data, side_effect): + mock_get_meta_data.side_effect = [side_effect] + response = self._ec2service.load() + mock_get_meta_data.assert_called_once_with('openstack') + if side_effect is Exception: + self.assertFalse(response) + else: + self.assertTrue(response) + + def test_load_exception(self): + self._test_load(side_effect=Exception) + + def test_load(self): + self._test_load(side_effect='fake data') + + @mock.patch('posixpath.join') + @mock.patch('urllib2.Request') + @mock.patch('urllib2.urlopen') + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '._load_public_keys') + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '._check_EC2') + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '._get_EC2_value') + def _test_get_data(self, mock_get_EC2_value, mock_check_EC2, + mock_load_public_keys, mock_urlopen, + mock_Request, mock_join, check_ec2, data_type): + mock_path = mock.MagicMock() + mock_req = mock.MagicMock() + mock_response = mock.MagicMock() + fake_path = os.path.join('fake', 'path') + mock_join.return_value = fake_path + mock_check_EC2.return_value = check_ec2 + mock_Request.return_value = mock_req + mock_urlopen.return_value = mock_response + mock_response.read.return_value = 'fake data' + mock_path.endswith.return_value = data_type + + if check_ec2 is None: + self.assertRaises(Exception, self._ec2service._get_data, + mock_path) + + elif data_type is 'meta_data.json': + response = self._ec2service._get_data(mock_path) + print response + for key in ec2service.ec2nodes: + mock_get_EC2_value.assert_called_with(key) + mock_load_public_keys.assert_called_with() + + elif data_type is 'user_data': + response = self._ec2service._get_data(mock_path) + mock_join.assert_called_with(CONF.ec2_metadata_base_url, + 'user-data') + mock_Request.assert_called_once_with(fake_path) + mock_urlopen.assert_called_once_with(mock_req) + mock_response.read.assert_called_once_with() + self.assertEqual(response, 'fake data') + + def test_get_data_metadata_json(self): + self._test_get_data(check_ec2=True, data_type='meta_data.json') + + def test_get_data_user_data(self): + self._test_get_data(check_ec2=True, data_type='user_data') + + def test_get_data_no_EC2(self): + self._test_get_data(check_ec2=None, data_type=None) + + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '._get_EC2_value') + def _test_check_EC2(self, mock_get_EC2_value, side_effect): + mock_get_EC2_value.side_effect = [side_effect] + response = self._ec2service._check_EC2() + if side_effect is Exception: + self.assertFalse(response) + else: + self.assertTrue(response) + + def test_check_EC2_Exception(self): + self._test_check_EC2(side_effect=Exception) + + def test_check_EC2(self): + self._test_check_EC2(side_effect='fake value') + + @mock.patch('posixpath.join') + @mock.patch('urllib2.Request') + @mock.patch('urllib2.urlopen') + def test_get_EC2_value(self, mock_urlopen, mock_Request, mock_join): + mock_key = mock.MagicMock() + mock_response = mock.MagicMock() + fake_path = os.path.join('fake', 'path') + mock_join.return_value = fake_path + mock_Request.return_value = 'fake req' + mock_urlopen.return_value = mock_response + mock_response.read.return_value = 'fake data' + response = self._ec2service._get_EC2_value(mock_key) + mock_join.assert_called_with(CONF.ec2_metadata_base_url, + 'meta-data', mock_key) + mock_Request.assert_called_once_with(fake_path) + mock_urlopen.assert_called_once_with('fake req') + mock_response.read.assert_called_once_with() + self.assertEqual(response, 'fake data') + + @mock.patch('cloudbaseinit.metadata.services.ec2service.EC2Service' + '._get_EC2_value') + def test_load_public_keys(self, mock_get_EC2_value): + data = {} + key_list = mock.MagicMock() + mock_get_EC2_value.return_value = key_list + self._ec2service._load_public_keys(data) + mock_get_EC2_value.assert_called_with('public-keys/') + self.assertEqual(data['public_keys'], {}) diff --git a/cloudbaseinit/tests/metadata/services/test_httpservice.py b/cloudbaseinit/tests/metadata/services/test_httpservice.py new file mode 100644 index 00000000..97899836 --- /dev/null +++ b/cloudbaseinit/tests/metadata/services/test_httpservice.py @@ -0,0 +1,156 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import os +import unittest +import urllib2 + +from cloudbaseinit.metadata.services import base +from cloudbaseinit.metadata.services import httpservice +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF + + +class HttpServiceTest(unittest.TestCase): + def setUp(self): + CONF.set_override('retry_count_interval', 0) + self._httpservice = httpservice.HttpService() + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('urlparse.urlparse') + def _test_check_metadata_ip_route(self, mock_urlparse, mock_get_os_utils, + side_effect): + mock_utils = mock.MagicMock() + mock_split = mock.MagicMock() + mock_get_os_utils.return_value = mock_utils + mock_utils.check_os_version.return_value = True + mock_urlparse().netloc.split.return_value = mock_split + mock_split[0].startswith.return_value = True + mock_utils.check_static_route_exists.return_value = False + mock_utils.get_default_gateway.return_value = (1, '0.0.0.0') + mock_utils.add_static_route.side_effect = [side_effect] + self._httpservice._check_metadata_ip_route() + mock_utils.check_os_version.assert_called_once_with(6, 0) + mock_urlparse.assert_called_with(CONF.metadata_base_url) + mock_split[0].startswith.assert_called_once_with("169.254.") + mock_utils.check_static_route_exists.assert_called_once_with( + mock_split[0]) + mock_utils.get_default_gateway.assert_called_once_with() + mock_utils.add_static_route.assert_called_once_with( + mock_split[0], "255.255.255.255", '0.0.0.0', 1, 10) + + def test_test_check_metadata_ip_route(self): + self._test_check_metadata_ip_route(side_effect=None) + + def test_test_check_metadata_ip_route_fail(self): + self._test_check_metadata_ip_route(side_effect=Exception) + + @mock.patch('cloudbaseinit.metadata.services.httpservice.HttpService' + '._check_metadata_ip_route') + @mock.patch('cloudbaseinit.metadata.services.httpservice.HttpService' + '.get_meta_data') + def _test_load(self, mock_get_meta_data, mock_check_metadata_ip_route, + side_effect): + mock_get_meta_data.side_effect = [side_effect] + response = self._httpservice.load() + mock_check_metadata_ip_route.assert_called_once_with() + mock_get_meta_data.assert_called_once_with('openstack') + if side_effect: + self.assertEqual(response, False) + else: + self.assertEqual(response, True) + + def test_load(self): + self._test_load(side_effect=None) + + def test_load_exception(self): + self._test_load(side_effect=Exception) + + @mock.patch('urllib2.urlopen') + def _test_get_response(self, mock_urlopen, side_effect): + mock_req = mock.MagicMock + if side_effect and side_effect.code is 404: + mock_urlopen.side_effect = [side_effect] + self.assertRaises(base.NotExistingMetadataException, + self._httpservice._get_response, + mock_req) + elif side_effect and side_effect.code: + mock_urlopen.side_effect = [side_effect] + self.assertRaises(Exception, self._httpservice._get_response, + mock_req) + else: + mock_urlopen.return_value = 'fake url' + response = self._httpservice._get_response(mock_req) + self.assertEqual(response, 'fake url') + + def test_get_response_fail_HTTPError(self): + error = urllib2.HTTPError("http://169.254.169.254/", 404, + 'test error 404', {}, None) + self._test_get_response(side_effect=error) + + def test_get_response_fail_other_exception(self): + error = urllib2.HTTPError("http://169.254.169.254/", 409, + 'test error 409', {}, None) + self._test_get_response(side_effect=error) + + def test_get_response(self): + self._test_get_response(side_effect=None) + + @mock.patch('cloudbaseinit.metadata.services.httpservice.HttpService' + '._get_response') + @mock.patch('posixpath.join') + @mock.patch('urllib2.Request') + def test_get_data(self, mock_Request, mock_posix_join, + mock_get_response): + fake_path = os.path.join('fake', 'path') + mock_data = mock.MagicMock() + mock_norm_path = mock.MagicMock() + mock_req = mock.MagicMock() + mock_get_response.return_value = mock_data + mock_posix_join.return_value = mock_norm_path + mock_Request.return_value = mock_req + + response = self._httpservice._get_data(fake_path) + + mock_posix_join.assert_called_with(CONF.metadata_base_url, fake_path) + mock_Request.assert_called_once_with(mock_norm_path) + mock_get_response.assert_called_once_with(mock_req) + self.assertEqual(response, mock_data.read()) + + @mock.patch('cloudbaseinit.metadata.services.httpservice.HttpService' + '._get_response') + @mock.patch('posixpath.join') + @mock.patch('urllib2.Request') + def test_post_data(self, mock_Request, mock_posix_join, + mock_get_response): + fake_path = os.path.join('fake', 'path') + fake_data = 'fake data' + mock_data = mock.MagicMock() + mock_norm_path = mock.MagicMock() + mock_req = mock.MagicMock() + mock_get_response.return_value = mock_data + mock_posix_join.return_value = mock_norm_path + mock_Request.return_value = mock_req + + response = self._httpservice._post_data(fake_path, fake_data) + + mock_posix_join.assert_called_with(CONF.metadata_base_url, + fake_path) + mock_Request.assert_called_once_with(mock_norm_path, data=fake_data) + mock_get_response.assert_called_once_with(mock_req) + self.assertEqual(response, True) diff --git a/cloudbaseinit/tests/metadata/test_factory.py b/cloudbaseinit/tests/metadata/test_factory.py new file mode 100644 index 00000000..ba1de9a1 --- /dev/null +++ b/cloudbaseinit/tests/metadata/test_factory.py @@ -0,0 +1,41 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.metadata import factory + + +class MetadataServiceFactoryTests(unittest.TestCase): + def setUp(self): + self._factory = factory.MetadataServiceFactory() + + @mock.patch('cloudbaseinit.utils.classloader.ClassLoader.load_class') + def _test_get_metadata_service(self, mock_load_class, ret_value): + mock_load_class.side_effect = ret_value + if ret_value is Exception: + self.assertRaises(Exception, self._factory.get_metadata_service) + else: + response = self._factory.get_metadata_service() + self.assertEqual(response, mock_load_class()()) + + def test_get_metadata_service(self): + m = mock.MagicMock() + self._test_get_metadata_service(ret_value=m) + + def test_get_metadata_service_exception(self): + self._test_get_metadata_service(ret_value=Exception) diff --git a/cloudbaseinit/tests/osutils/__init__.py b/cloudbaseinit/tests/osutils/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/osutils/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/osutils/test_factory.py b/cloudbaseinit/tests/osutils/test_factory.py new file mode 100644 index 00000000..8b2b90c1 --- /dev/null +++ b/cloudbaseinit/tests/osutils/test_factory.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import os +import unittest + +from cloudbaseinit.osutils import factory + + +class OSUtilsFactory(unittest.TestCase): + def setUp(self): + self._factory = factory.OSUtilsFactory() + + @mock.patch('cloudbaseinit.utils.classloader.ClassLoader.load_class') + def _test_get_os_utils(self, mock_load_class, fake_name): + os.name = fake_name + self._factory.get_os_utils() + if fake_name == 'nt': + mock_load_class.assert_called_with( + 'cloudbaseinit.osutils.windows.WindowsUtils') + elif fake_name == 'posix': + mock_load_class.assert_called_with( + 'cloudbaseinit.osutils.posix.PosixUtils') + + def test_get_os_utils_windows(self): + self._test_get_os_utils(fake_name='nt') + + def test_get_os_utils_posix(self): + self._test_get_os_utils(fake_name='posix') diff --git a/cloudbaseinit/tests/osutils/test_windows.py b/cloudbaseinit/tests/osutils/test_windows.py new file mode 100644 index 00000000..675e1987 --- /dev/null +++ b/cloudbaseinit/tests/osutils/test_windows.py @@ -0,0 +1,975 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ctypes +import mock +import time +import sys +import unittest + +if sys.platform == 'win32': + import _winreg + import win32process + import win32security + import wmi + + from ctypes import windll + from ctypes import wintypes + from cloudbaseinit.osutils import windows as windows_utils +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF + + +@unittest.skipUnless(sys.platform == "win32", "requires Windows") +class WindowsUtilsTest(unittest.TestCase): + '''Tests for the windows utils class''' + + _CONFIG_NAME = 'FakeConfig' + _DESTINATION = '192.168.192.168' + _GATEWAY = '10.7.1.1' + _NETMASK = '255.255.255.0' + _PASSWORD = 'Passw0rd' + _SECTION = 'fake_section' + _USERNAME = 'Admin' + + def setUp(self): + self._winutils = windows_utils.WindowsUtils() + self._conn = mock.MagicMock() + + def test_enable_shutdown_privilege(self): + fake_process = mock.MagicMock() + fake_token = True + private_LUID = 'fakeid' + win32process.GetCurrentProcess = mock.MagicMock( + return_value=fake_process) + win32security.OpenProcessToken = mock.MagicMock( + return_value=fake_token) + win32security.LookupPrivilegeValue = mock.MagicMock( + return_value=private_LUID) + win32security.AdjustTokenPrivileges = mock.MagicMock() + self._winutils._enable_shutdown_privilege() + privilege = [(private_LUID, win32security.SE_PRIVILEGE_ENABLED)] + win32security.AdjustTokenPrivileges.assert_called_with( + fake_token, + False, + privilege) + + win32security.OpenProcessToken.assert_called_with( + fake_process, + win32security.TOKEN_ADJUST_PRIVILEGES | + win32security.TOKEN_QUERY) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._enable_shutdown_privilege') + def _test_reboot(self, mock_enable_shutdown_privilege, ret_value): + windll.advapi32.InitiateSystemShutdownW = mock.MagicMock( + return_value=ret_value) + + if not ret_value: + self.assertRaises(Exception, self._winutils.reboot) + else: + self._winutils.reboot() + + windll.advapi32.InitiateSystemShutdownW.assert_called_with( + 0, + "Cloudbase-Init reboot", + 0, True, True) + + def test_reboot(self): + self._test_reboot(ret_value=True) + + def test_reboot_failed(self): + self._test_reboot(ret_value=None) + + @mock.patch('wmi.WMI') + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._sanitize_wmi_input') + def _test_get_user_wmi_object(self, mock_sanitize_wmi_input, mock_WMI, + returnvalue): + mock_WMI.return_value = self._conn + mock_sanitize_wmi_input.return_value = self._USERNAME + self._conn.query.return_value = returnvalue + response = self._winutils._get_user_wmi_object(self._USERNAME) + self._conn.query.assert_called_with("SELECT * FROM Win32_Account " + "where name = \'%s\'" % + self._USERNAME) + mock_sanitize_wmi_input.assert_called_with(self._USERNAME) + mock_WMI.assert_called_with(moniker='//./root/cimv2') + if returnvalue: + self.assertTrue(response is not None) + else: + self.assertTrue(response is None) + + def test_get_user_wmi_object(self): + caption = 'fake' + self._test_get_user_wmi_object(returnvalue=caption) + + def test_no_user_wmi_object(self): + empty_caption = '' + self._test_get_user_wmi_object(returnvalue=empty_caption) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_user_wmi_object') + def _test_user_exists(self, mock_get_user_wmi_object, returnvalue): + mock_get_user_wmi_object.return_value = returnvalue + response = self._winutils.user_exists(returnvalue) + mock_get_user_wmi_object.assert_called_with(returnvalue) + if returnvalue: + self.assertTrue(response) + else: + self.assertFalse(response) + + def test_user_exists(self): + self._test_user_exists(returnvalue=self._USERNAME) + + def test_username_does_not_exist(self): + self._test_user_exists(returnvalue=None) + + def test_sanitize_wmi_input(self): + unsanitised = ' \' ' + response = self._winutils._sanitize_wmi_input(unsanitised) + sanitised = ' \'\' ' + self.assertEqual(response, sanitised) + + def test_sanitize_shell_input(self): + unsanitised = ' " ' + response = self._winutils.sanitize_shell_input(unsanitised) + sanitised = ' \\" ' + self.assertEqual(response, sanitised) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._set_user_password_expiration') + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.execute_process') + def _test_create_or_change_user(self, mock_execute_process, + mock_set_user_password_expiration, + create, password_expires, ret_value=0): + args = ['NET', 'USER', self._USERNAME, self._PASSWORD] + if create: + args.append('/ADD') + mock_execute_process.return_value = (None, None, ret_value) + if not ret_value: + self._winutils._create_or_change_user(self._USERNAME, + self._PASSWORD, create, + password_expires) + mock_set_user_password_expiration.assert_called_with( + self._USERNAME, password_expires) + else: + self.assertRaises( + Exception, self._winutils._create_or_change_user, + self._USERNAME, self._PASSWORD, create, password_expires) + mock_execute_process.assert_called_with(args) + + def test_create_user_and_add_password_expire_true(self): + self._test_create_or_change_user(create=True, password_expires=True) + + def test_create_user_and_add_password_expire_false(self): + self._test_create_or_change_user(create=True, password_expires=False) + + def test_add_password_expire_true(self): + self._test_create_or_change_user(create=False, password_expires=True) + + def test_add_password_expire_false(self): + self._test_create_or_change_user(create=False, password_expires=False) + + def test_create_user_and_add_password_expire_true_with_ret_value(self): + self._test_create_or_change_user(create=True, password_expires=True, + ret_value=1) + + def test_create_user_and_add_password_expire_false_with_ret_value(self): + self._test_create_or_change_user(create=True, + password_expires=False, ret_value=1) + + def test_add_password_expire_true_with_ret_value(self): + self._test_create_or_change_user(create=False, + password_expires=True, ret_value=1) + + def test_add_password_expire_false_with_ret_value(self): + self._test_create_or_change_user(create=False, + password_expires=False, ret_value=1) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_user_wmi_object') + def _test_set_user_password_expiration(self, mock_get_user_wmi_object, + fake_obj): + mock_get_user_wmi_object.return_value = fake_obj + response = self._winutils._set_user_password_expiration( + self._USERNAME, True) + if fake_obj: + self.assertTrue(fake_obj.PasswordExpires) + self.assertTrue(response) + else: + self.assertFalse(response) + + def test_set_password_expiration(self): + fake = mock.Mock() + self._test_set_user_password_expiration(fake_obj=fake) + + def test_set_password_expiration_no_object(self): + self._test_set_user_password_expiration(fake_obj=None) + + def _test_get_user_sid_and_domain(self, ret_val): + cbSid = mock.Mock() + sid = mock.Mock() + size = 1024 + cchReferencedDomainName = mock.Mock() + domainName = mock.Mock() + sidNameUse = mock.Mock() + + ctypes.create_string_buffer = mock.MagicMock(return_value=sid) + ctypes.sizeof = mock.MagicMock(return_value=size) + wintypes.DWORD = mock.MagicMock(return_value=cchReferencedDomainName) + ctypes.create_unicode_buffer = mock.MagicMock(return_value=domainName) + + ctypes.byref = mock.MagicMock() + + windll.advapi32.LookupAccountNameW = mock.MagicMock( + return_value=ret_val) + if ret_val is None: + self.assertRaises( + Exception, self._winutils._get_user_sid_and_domain, + self._USERNAME) + else: + response = self._winutils._get_user_sid_and_domain(self._USERNAME) + + windll.advapi32.LookupAccountNameW.assert_called_with( + 0, unicode(self._USERNAME), sid, ctypes.byref(cbSid), + domainName, ctypes.byref(cchReferencedDomainName), + ctypes.byref(sidNameUse)) + self.assertEqual(response, (sid, domainName.value)) + + def test_get_user_sid_and_domain(self): + fake_obj = mock.Mock() + self._test_get_user_sid_and_domain(ret_val=fake_obj) + + def test_get_user_sid_and_domain_no_return_value(self): + self._test_get_user_sid_and_domain(ret_val=None) + + def _test_add_user_to_local_group(self, ret_value): + windows_utils.Win32_LOCALGROUP_MEMBERS_INFO_3 = mock.MagicMock() + lmi = windows_utils.Win32_LOCALGROUP_MEMBERS_INFO_3() + group_name = 'Admins' + + windll.netapi32.NetLocalGroupAddMembers = mock.MagicMock( + return_value=ret_value) + + if ret_value is not 0: + self.assertRaises( + Exception, self._winutils.add_user_to_local_group, + self._USERNAME, group_name) + else: + ctypes.addressof = mock.MagicMock() + self._winutils.add_user_to_local_group(self._USERNAME, + group_name) + windll.netapi32.NetLocalGroupAddMembers.assert_called_with( + 0, unicode(group_name), 3, ctypes.addressof(lmi), 1) + self.assertEqual(lmi.lgrmi3_domainandname, unicode(self._USERNAME)) + + def test_add_user_to_local_group_no_error(self): + self._test_add_user_to_local_group(ret_value=0) + + def test_add_user_to_local_group_not_found(self): + self._test_add_user_to_local_group( + ret_value=self._winutils.NERR_GroupNotFound) + + def test_add_user_to_local_group_access_denied(self): + self._test_add_user_to_local_group( + ret_value=self._winutils.ERROR_ACCESS_DENIED) + + def test_add_user_to_local_group_no_member(self): + self._test_add_user_to_local_group( + ret_value=self._winutils.ERROR_NO_SUCH_MEMBER) + + def test_add_user_to_local_group_member_in_alias(self): + self._test_add_user_to_local_group( + ret_value=self._winutils.ERROR_MEMBER_IN_ALIAS) + + def test_add_user_to_local_group_invalid_member(self): + self._test_add_user_to_local_group( + ret_value=self._winutils.ERROR_INVALID_MEMBER) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_user_wmi_object') + def _test_get_user_sid(self, mock_get_user_wmi_object, fail): + r = mock.Mock() + if not fail: + mock_get_user_wmi_object.return_value = None + response = self._winutils.get_user_sid(self._USERNAME) + self.assertTrue(response is None) + else: + mock_get_user_wmi_object.return_value = r + response = self._winutils.get_user_sid(self._USERNAME) + self.assertTrue(response is not None) + mock_get_user_wmi_object.assert_called_with(self._USERNAME) + + def test_get_user_sid(self): + self._test_get_user_sid(fail=False) + + def test_get_user_sid_fail(self): + self._test_get_user_sid(fail=True) + + def _test_create_user_logon_session(self, logon, loaduser, + load_profile=True): + wintypes.HANDLE = mock.MagicMock() + pi = windows_utils.Win32_PROFILEINFO() + windll.advapi32.LogonUserW = mock.MagicMock(return_value=logon) + ctypes.byref = mock.MagicMock() + + if not logon: + self.assertRaises( + Exception, self._winutils.create_user_logon_session, + self._USERNAME, self._PASSWORD, domain='.', + load_profile=load_profile) + + elif load_profile and not loaduser: + windll.userenv.LoadUserProfileW = mock.MagicMock( + return_value=None) + windll.kernel32.CloseHandle = mock.MagicMock(return_value=None) + self.assertRaises(Exception, + self._winutils.create_user_logon_session, + self._USERNAME, self._PASSWORD, domain='.', + load_profile=load_profile) + + windll.userenv.LoadUserProfileW.assert_called_with( + wintypes.HANDLE(), ctypes.byref(pi)) + windll.kernel32.CloseHandle.assert_called_with(wintypes.HANDLE()) + + elif not load_profile: + response = self._winutils.create_user_logon_session( + self._USERNAME, self._PASSWORD, domain='.', + load_profile=load_profile) + self.assertTrue(response is not None) + else: + size = 1024 + windll.userenv.LoadUserProfileW = mock.MagicMock() + ctypes.sizeof = mock.MagicMock(return_value=size) + windows_utils.Win32_PROFILEINFO = mock.MagicMock( + return_value=loaduser) + + response = self._winutils.create_user_logon_session( + self._USERNAME, self._PASSWORD, domain='.', + load_profile=load_profile) + + windll.userenv.LoadUserProfileW.assert_called_with( + wintypes.HANDLE(), ctypes.byref(pi)) + self.assertTrue(response is not None) + + def test_create_user_logon_session_fail_load_false(self): + self._test_create_user_logon_session(0, 0, True) + + def test_create_user_logon_session_fail_load_true(self): + self._test_create_user_logon_session(0, 0, False) + + def test_create_user_logon_session_load_true(self): + m = mock.Mock() + n = mock.Mock() + self._test_create_user_logon_session(m, n, True) + + def test_create_user_logon_session_load_false(self): + m = mock.Mock() + n = mock.Mock() + self._test_create_user_logon_session(m, n, False) + + def test_create_user_logon_session_no_load_true(self): + m = mock.Mock() + self._test_create_user_logon_session(m, None, True) + + def test_create_user_logon_session_no_load_false(self): + m = mock.Mock() + self._test_create_user_logon_session(m, None, False) + + def test_close_user_logon_session(self): + token = mock.Mock() + windll.kernel32.CloseHandle = mock.MagicMock() + self._winutils.close_user_logon_session(token) + windll.kernel32.CloseHandle.assert_called_with(token) + + @mock.patch('ctypes.windll.kernel32.SetComputerNameExW') + def _test_set_host_name(self, mock_SetComputerNameExW, ret_value): + wmi.WMI = mock.MagicMock(return_value=self._conn) + mock_SetComputerNameExW.return_value = ret_value + if not ret_value: + self.assertRaises(Exception, self._winutils.set_host_name, + 'fake name') + else: + self._winutils.set_host_name('fake name') + mock_SetComputerNameExW.assert_called_with( + self._winutils.ComputerNamePhysicalDnsHostname, + unicode('fake name')) + + def test_set_host_name(self): + self._test_set_host_name(ret_value='fake response') + + def test_set_host_exception(self): + self._test_set_host_name(ret_value=None) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.get_user_sid') + def _test_get_user_home(self, mock_get_user_sid, user_sid): + key = mock.MagicMock() + mock_get_user_sid.return_value = user_sid + _winreg.OpenKey = mock.MagicMock(return_value=key) + _winreg.QueryValueEx = mock.MagicMock() + response = self._winutils.get_user_home(self._USERNAME) + if user_sid: + mock_get_user_sid.assert_called_with(self._USERNAME) + _winreg.OpenKey.assert_called_with( + _winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows ' + 'NT\\CurrentVersion\\ProfileList\\' + '%s' % mock_get_user_sid()) + self.assertTrue(response is not None) + _winreg.QueryValueEx.assert_called_with( + _winreg.OpenKey().__enter__(), 'ProfileImagePath') + else: + self.assertTrue(response is None) + + def test_get_user_home(self): + user = mock.MagicMock() + self._test_get_user_home(user_sid=user) + + def test_get_user_home_fail(self): + self._test_get_user_home(user_sid=None) + + @mock.patch('wmi.WMI') + def test_get_network_adapters(self, mock_WMI): + mock_WMI.return_value = self._conn + mock_response = mock.MagicMock() + self._conn.query.return_value = [mock_response] + response = self._winutils.get_network_adapters() + self._conn.query.assert_called_with( + 'SELECT * FROM Win32_NetworkAdapter WHERE AdapterTypeId = 0 AND ' + 'PhysicalAdapter = True AND MACAddress IS NOT NULL') + self.assertEqual(response, [mock_response.Name]) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._sanitize_wmi_input') + def _test_set_static_network_config(self, mock_sanitize_wmi_input, + adapter, ret_val1=None, + ret_val2=None, ret_val3=None): + wmi.WMI = mock.MagicMock(return_value=self._conn) + address = '10.10.10.10' + adapter_name = 'adapter_name' + broadcast = '0.0.0.0' + dns_list = ['8.8.8.8'] + + if not adapter: + self.assertRaises( + Exception, self._winutils.set_static_network_config, + adapter_name, address, self._NETMASK, + broadcast, self._GATEWAY, dns_list) + else: + mock_sanitize_wmi_input.return_value = adapter_name + self._conn.query.return_value = adapter + adapter_config = adapter[0].associators()[0] + adapter_config.EnableStatic = mock.MagicMock(return_value=ret_val1) + adapter_config.SetGateways = mock.MagicMock(return_value=ret_val2) + adapter_config.SetDNSServerSearchOrder = mock.MagicMock( + return_value=ret_val3) + adapter.__len__ = mock.MagicMock(return_value=1) + if ret_val1[0] > 1: + self.assertRaises( + Exception, self._winutils.set_static_network_config, + adapter_name, address, self._NETMASK, + broadcast, self._GATEWAY, dns_list) + + elif ret_val2[0] > 1: + self.assertRaises( + Exception, self._winutils.set_static_network_config, + adapter_name, address, self._NETMASK, + broadcast, self._GATEWAY, dns_list) + + elif ret_val3[0] > 1: + self.assertRaises( + Exception, self._winutils.set_static_network_config, + adapter_name, address, self._NETMASK, + broadcast, self._GATEWAY, dns_list) + + else: + response = self._winutils.set_static_network_config( + adapter_name, address, self._NETMASK, + broadcast, self._GATEWAY, dns_list) + if ret_val1[0] or ret_val2[0] or ret_val3[0] == 1: + self.assertTrue(response) + else: + self.assertFalse(response) + adapter_config.EnableStatic.assert_called_with( + [address], [self._NETMASK]) + adapter_config.SetGateways.assert_called_with( + [self._GATEWAY], [1]) + adapter_config.SetDNSServerSearchOrder.assert_called_with( + dns_list) + + self._winutils._sanitize_wmi_input.assert_called_with( + adapter_name) + adapter[0].associators.assert_called_with( + wmi_result_class='Win32_NetworkAdapterConfiguration') + self._conn.query.assert_called_with( + 'SELECT * FROM Win32_NetworkAdapter WHERE MACAddress IS ' + 'NOT NULL AND Name = \'%(adapter_name_san)s\'' % + {'adapter_name_san': adapter_name}) + + def test_set_static_network_config(self): + adapter = mock.MagicMock() + ret_val1 = (1,) + ret_val2 = (1,) + ret_val3 = (0,) + self._test_set_static_network_config(adapter=adapter, + ret_val1=ret_val1, + ret_val2=ret_val2, + ret_val3=ret_val3) + + def test_set_static_network_config_query_fail(self): + self._test_set_static_network_config(adapter=None) + + def test_set_static_network_config_cannot_set_ip(self): + adapter = mock.MagicMock() + ret_val1 = (2,) + self._test_set_static_network_config(adapter=adapter, + ret_val1=ret_val1) + + def test_set_static_network_config_cannot_set_gateway(self): + adapter = mock.MagicMock() + ret_val1 = (1,) + ret_val2 = (2,) + self._test_set_static_network_config(adapter=adapter, + ret_val1=ret_val1, + ret_val2=ret_val2) + + def test_set_static_network_config_cannot_set_DNS(self): + adapter = mock.MagicMock() + ret_val1 = (1,) + ret_val2 = (1,) + ret_val3 = (2,) + self._test_set_static_network_config(adapter=adapter, + ret_val1=ret_val1, + ret_val2=ret_val2, + ret_val3=ret_val3) + + def test_set_static_network_config_no_reboot(self): + adapter = mock.MagicMock() + ret_val1 = (0,) + ret_val2 = (0,) + ret_val3 = (0,) + self._test_set_static_network_config(adapter=adapter, + ret_val1=ret_val1, + ret_val2=ret_val2, + ret_val3=ret_val3) + + def _test_get_config_key_name(self, section): + response = self._winutils._get_config_key_name(section) + if section: + self.assertEqual( + response, self._winutils._config_key + section + '\\') + else: + self.assertEqual(response, self._winutils._config_key) + + def test_get_config_key_name_with_section(self): + self._test_get_config_key_name(self._SECTION) + + def test_get_config_key_name_no_section(self): + self._test_get_config_key_name(None) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_config_key_name') + def _test_set_config_value(self, mock_get_config_key_name, value): + key = mock.MagicMock() + key_name = self._winutils._config_key + self._SECTION + '\\' + self\ + ._CONFIG_NAME + mock_get_config_key_name.return_value = key_name + + _winreg.CreateKey = mock.MagicMock() + _winreg.REG_DWORD = mock.Mock() + _winreg.REG_SZ = mock.Mock() + _winreg.SetValueEx = mock.MagicMock() + + self._winutils.set_config_value(self._CONFIG_NAME, value, + self._SECTION) + + _winreg.CreateKey.__enter__.return_value = key + with _winreg.CreateKey as m: + assert m == key + + _winreg.CreateKey.__enter__.assert_called_with() + _winreg.CreateKey.__exit__.assert_called_with(None, None, None) + _winreg.CreateKey.assert_called_with(_winreg.HKEY_LOCAL_MACHINE, + key_name) + mock_get_config_key_name.assert_called_with(self._SECTION) + if type(value) == int: + _winreg.SetValueEx.assert_called_with( + _winreg.CreateKey().__enter__(), self._CONFIG_NAME, 0, + _winreg.REG_DWORD, value) + else: + _winreg.SetValueEx.assert_called_with( + _winreg.CreateKey().__enter__(), self._CONFIG_NAME, 0, + _winreg.REG_SZ, value) + + def test_set_config_value_int(self): + self._test_set_config_value(value=1) + + def test_set_config_value_not_int(self): + self._test_set_config_value(value='1') + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_config_key_name') + def _test_get_config_value(self, mock_get_config_key_name, value): + key_name = self._winutils._config_key + self._SECTION + '\\' + key_name += self._CONFIG_NAME + _winreg.OpenKey = mock.MagicMock() + _winreg.REG_DWORD = mock.Mock() + _winreg.REG_SZ = mock.Mock() + if type(value) == int: + regtype = _winreg.REG_DWORD + else: + regtype = _winreg.REG_SZ + _winreg.QueryValueEx = mock.MagicMock(return_value=(value, regtype)) + if value is None: + mock_get_config_key_name.side_effect = [WindowsError] + self.assertRaises(WindowsError, self._winutils.get_config_value, + self._CONFIG_NAME, None) + else: + mock_get_config_key_name.return_value = key_name + response = self._winutils.get_config_value(self._CONFIG_NAME, + self._SECTION) + _winreg.OpenKey.assert_called_with(_winreg.HKEY_LOCAL_MACHINE, + key_name) + mock_get_config_key_name.assert_called_with(self._SECTION) + _winreg.QueryValueEx.assert_called_with( + _winreg.OpenKey().__enter__(), self._CONFIG_NAME) + self.assertEqual(response, value) + + def test_get_config_value_type_int(self): + self._test_get_config_value(value=1) + + def test_get_config_value_type_str(self): + self._test_get_config_value(value='fake') + + def test_get_config_value_type_error(self): + self._test_get_config_value(value=None) + + def _test_wait_for_boot_completion(self, ret_val): + key = mock.MagicMock() + time.sleep = mock.MagicMock() + _winreg.OpenKey = mock.MagicMock() + _winreg.QueryValueEx = mock.MagicMock() + _winreg.QueryValueEx.side_effect = ret_val + self._winutils.wait_for_boot_completion() + _winreg.OpenKey.__enter__.return_value = key + _winreg.OpenKey.assert_called_with( + _winreg.HKEY_LOCAL_MACHINE, + "SYSTEM\\Setup\\Status\\SysprepStatus", 0, _winreg.KEY_READ) + + _winreg.QueryValueEx.assert_called_with( + _winreg.OpenKey().__enter__(), "GeneralizationState") + + def test_wait_for_boot_completion(self): + ret_val = [[7]] + self._test_wait_for_boot_completion(ret_val) + + @mock.patch('wmi.WMI') + def test_get_service(self, mock_WMI): + mock_WMI.return_value = self._conn + self._conn.Win32_Service.return_value = ['fake name'] + response = self._winutils._get_service('fake name') + mock_WMI.assert_called_with(moniker='//./root/cimv2') + self._conn.Win32_Service.assert_called_with(Name='fake name') + self.assertEqual(response, 'fake name') + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def test_check_service_exists(self, mock_get_service): + mock_get_service.return_value = 'not None' + response = self._winutils.check_service_exists('fake name') + self.assertEqual(response, True) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def test_get_service_status(self, mock_get_service): + mock_service = mock.MagicMock() + mock_get_service.return_value = mock_service + response = self._winutils.get_service_status('fake name') + self.assertEqual(response, mock_service.State) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def test_get_service_start_mode(self, mock_get_service): + mock_service = mock.MagicMock() + mock_get_service.return_value = mock_service + response = self._winutils.get_service_start_mode('fake name') + self.assertEqual(response, mock_service.StartMode) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def _test_set_service_start_mode(self, mock_get_service, ret_val): + mock_service = mock.MagicMock() + mock_get_service.return_value = mock_service + mock_service.ChangeStartMode.return_value = (ret_val,) + if ret_val != 0: + self.assertRaises(Exception, + self._winutils.set_service_start_mode, + 'fake name', 'fake mode') + else: + self._winutils.set_service_start_mode('fake name', 'fake mode') + mock_service.ChangeStartMode.assert_called_once_with('fake mode') + + def test_set_service_start_mode(self): + self._test_set_service_start_mode(ret_val=0) + + def test_set_service_start_mode_exception(self): + self._test_set_service_start_mode(ret_val=1) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def _test_start_service(self, mock_get_service, ret_val): + mock_service = mock.MagicMock() + mock_get_service.return_value = mock_service + mock_service.StartService.return_value = (ret_val,) + if ret_val != 0: + self.assertRaises(Exception, + self._winutils.start_service, + 'fake name') + else: + self._winutils.start_service('fake name') + mock_service.StartService.assert_called_once_with() + + def test_start_service(self): + self._test_set_service_start_mode(ret_val=0) + + def test_start_service_exception(self): + self._test_set_service_start_mode(ret_val=1) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_service') + def _test_stop_service(self, mock_get_service, ret_val): + mock_service = mock.MagicMock() + mock_get_service.return_value = mock_service + mock_service.StopService.return_value = (ret_val,) + if ret_val != 0: + self.assertRaises(Exception, + self._winutils.stop_service, + 'fake name') + else: + self._winutils.stop_service('fake name') + mock_service.StopService.assert_called_once_with() + + def test_stop_service(self): + self._test_stop_service(ret_val=0) + + def test_stop_service_exception(self): + self._test_stop_service(ret_val=1) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.stop_service') + def test_terminate(self, mock_stop_service): + time.sleep = mock.MagicMock() + self._winutils.terminate() + mock_stop_service.assert_called_with(self._winutils._service_name) + time.sleep.assert_called_with(3) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_ipv4_routing_table') + def _test_get_default_gateway(self, mock_get_ipv4_routing_table, + routing_table): + mock_get_ipv4_routing_table.return_value = [routing_table] + response = self._winutils.get_default_gateway() + mock_get_ipv4_routing_table.assert_called_once_with() + if routing_table[0] == '0.0.0.0': + self.assertEqual(response, (routing_table[3], routing_table[2])) + else: + self.assertEqual(response, (None, None)) + + def test_get_default_gateway(self): + routing_table = ['0.0.0.0', '1.1.1.1', self._GATEWAY, '8.8.8.8'] + self._test_get_default_gateway(routing_table=routing_table) + + def test_get_default_gateway_error(self): + routing_table = ['1.1.1.1', '1.1.1.1', self._GATEWAY, '8.8.8.8'] + self._test_get_default_gateway(routing_table=routing_table) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '._get_ipv4_routing_table') + def _test_check_static_route_exists(self, mock_get_ipv4_routing_table, + routing_table): + mock_get_ipv4_routing_table.return_value = [routing_table] + response = self._winutils.check_static_route_exists(self._DESTINATION) + mock_get_ipv4_routing_table.assert_called_once_with() + if routing_table[0] == self._DESTINATION: + self.assertTrue(response) + else: + self.assertFalse(response) + + def test_check_static_route_exists_true(self): + routing_table = [self._DESTINATION, '1.1.1.1', self._GATEWAY, + '8.8.8.8'] + self._test_check_static_route_exists(routing_table=routing_table) + + def test_check_static_route_exists_false(self): + routing_table = ['0.0.0.0', '1.1.1.1', self._GATEWAY, '8.8.8.8'] + self._test_check_static_route_exists(routing_table=routing_table) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.execute_process') + def _test_add_static_route(self, mock_execute_process, err): + next_hop = '10.10.10.10' + interface_index = 1 + metric = 9 + args = ['ROUTE', 'ADD', self._DESTINATION, 'MASK', self._NETMASK, + next_hop] + mock_execute_process.return_value = (None, err, None) + if err: + self.assertRaises(Exception, self._winutils.add_static_route, + self._DESTINATION, self._NETMASK, next_hop, + interface_index, metric) + else: + self._winutils.add_static_route(self._DESTINATION, self._NETMASK, + next_hop, interface_index, metric) + mock_execute_process.assert_called_with(args) + + def test_add_static_route(self): + self._test_add_static_route(err=404) + + def test_add_static_route_fail(self): + self._test_add_static_route(err=None) + + @mock.patch('ctypes.sizeof') + @mock.patch('ctypes.byref') + @mock.patch('ctypes.windll.kernel32.VerSetConditionMask') + @mock.patch('ctypes.windll.kernel32.VerifyVersionInfoW') + @mock.patch('ctypes.windll.kernel32.GetLastError') + def _test_check_os_version(self, mock_GetLastError, + mock_VerifyVersionInfoW, + mock_VerSetConditionMask, mock_byref, + mock_sizeof, ret_value, error_value=None): + mock_VerSetConditionMask.return_value = 2 + mock_VerifyVersionInfoW.return_value = ret_value + mock_GetLastError.return_value = error_value + old_version = self._winutils.ERROR_OLD_WIN_VERSION + if error_value and error_value is not old_version: + self.assertRaises(Exception, self._winutils.check_os_version, 3, + 1, 2) + mock_GetLastError.assert_called_once_with() + else: + response = self._winutils.check_os_version(3, 1, 2) + mock_sizeof.assert_called_once_with( + windows_utils.Win32_OSVERSIONINFOEX_W) + self.assertEqual(mock_VerSetConditionMask.call_count, 3) + mock_VerifyVersionInfoW.assert_called_with(mock_byref(), + 1 | 2 | 3 | 7, + 2) + if error_value is old_version: + mock_GetLastError.assert_called_with() + self.assertEqual(response, False) + else: + self.assertEqual(response, True) + + def test_check_os_version(self): + m = mock.MagicMock() + self._test_check_os_version(ret_value=m) + + def test_check_os_version_expect_False(self): + self._test_check_os_version( + ret_value=None, error_value=self._winutils.ERROR_OLD_WIN_VERSION) + + def test_check_os_version_exception(self): + self._test_check_os_version(ret_value=None, error_value=9999) + + def _test_get_volume_label(self, ret_val): + label = mock.MagicMock() + max_label_size = 261 + drive = 'Fake_drive' + ctypes.create_unicode_buffer = mock.MagicMock(return_value=label) + ctypes.windll.kernel32.GetVolumeInformationW = mock.MagicMock( + return_value=ret_val) + response = self._winutils.get_volume_label(drive) + if ret_val: + self.assertTrue(response is not None) + else: + self.assertTrue(response is None) + + ctypes.create_unicode_buffer.assert_called_with(max_label_size) + ctypes.windll.kernel32.GetVolumeInformationW.assert_called_with( + drive, label, max_label_size, 0, 0, 0, 0, 0) + + def test_get_volume_label(self): + self._test_get_volume_label('ret') + + def test_get_volume_label_no_return_value(self): + self._test_get_volume_label(None) + + @mock.patch('re.search') + @mock.patch('cloudbaseinit.osutils.base.BaseOSUtils.' + 'generate_random_password') + def test_generate_random_password(self, mock_generate_random_password, + mock_search): + length = 14 + mock_search.return_value = True + mock_generate_random_password.return_value = 'Passw0rd' + response = self._winutils.generate_random_password(length) + mock_generate_random_password.assert_called_once_with(length) + self.assertEqual(response, 'Passw0rd') + + @mock.patch('ctypes.create_unicode_buffer') + @mock.patch('ctypes.windll.kernel32.GetLogicalDriveStringsW') + def _test_get_logical_drives(self, mock_GetLogicalDriveStringsW, + mock_create_unicode_buffer, buf_length): + mock_buf = mock.MagicMock() + mock_buf.__getitem__.side_effect = ['1', '\x00'] + mock_create_unicode_buffer.return_value = mock_buf + mock_GetLogicalDriveStringsW.return_value = buf_length + if buf_length is None: + self.assertRaises(Exception, self._winutils._get_logical_drives) + else: + response = self._winutils._get_logical_drives() + print mock_buf.mock_calls + mock_create_unicode_buffer.assert_called_with(261) + mock_GetLogicalDriveStringsW.assert_called_with(260, mock_buf) + self.assertEqual(response, ['1']) + + def test_get_logical_drives_exception(self): + self._test_get_logical_drives(buf_length=None) + + def test_get_logical_drives(self): + self._test_get_logical_drives(buf_length=2) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils.' + '_get_logical_drives') + @mock.patch('cloudbaseinit.osutils.windows.kernel32') + def test_get_cdrom_drives(self, mock_kernel32, mock_get_logical_drives): + mock_get_logical_drives.return_value = ['drive'] + mock_kernel32.GetDriveTypeW.return_value = self._winutils.DRIVE_CDROM + response = self._winutils.get_cdrom_drives() + mock_get_logical_drives.assert_called_with() + self.assertEqual(response, ['drive']) + + @mock.patch('win32com.client.Dispatch') + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils._get_fw_protocol') + def _test_firewall_create_rule(self, mock_get_fw_protocol, mock_Dispatch): + self._winutils.firewall_create_rule( + name='fake name', port=9999, protocol=self._winutils.PROTOCOL_TCP) + expected = [mock.call("HNetCfg.FWOpenPort"), + mock.call("HNetCfg.FwMgr")] + self.assertEqual(mock_Dispatch.call_args_list, expected) + mock_get_fw_protocol.assert_called_once_with( + self._winutils.PROTOCOL_TCP) + + @mock.patch('win32com.client.Dispatch') + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils._get_fw_protocol') + def test_firewall_remove_rule(self, mock_get_fw_protocol, mock_Dispatch): + self._winutils.firewall_remove_rule( + name='fake name', port=9999, protocol=self._winutils.PROTOCOL_TCP) + mock_Dispatch.assert_called_once_with("HNetCfg.FwMgr") + mock_get_fw_protocol.assert_called_once_with( + self._winutils.PROTOCOL_TCP) diff --git a/cloudbaseinit/tests/plugins/__init__.py b/cloudbaseinit/tests/plugins/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/plugins/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/plugins/test_factory.py b/cloudbaseinit/tests/plugins/test_factory.py new file mode 100644 index 00000000..f45b3bc0 --- /dev/null +++ b/cloudbaseinit/tests/plugins/test_factory.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.plugins import factory +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF + + +class PluginFactoryTests(unittest.TestCase): + def setUp(self): + self._factory = factory.PluginFactory() + + @mock.patch('cloudbaseinit.utils.classloader.ClassLoader.load_class') + def test_load_plugins(self, mock_load_class): + expected = [] + for path in CONF.plugins: + expected.append(mock.call(path)) + response = self._factory.load_plugins() + self.assertEqual(mock_load_class.call_args_list, expected) + self.assertTrue(response is not None) diff --git a/cloudbaseinit/tests/plugins/windows/__init__.py b/cloudbaseinit/tests/plugins/windows/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/plugins/windows/test_createuser.py b/cloudbaseinit/tests/plugins/windows/test_createuser.py new file mode 100644 index 00000000..8ff6699f --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_createuser.py @@ -0,0 +1,77 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins import base +from cloudbaseinit.plugins.windows import createuser + +CONF = cfg.CONF + + +class CreateUserPluginTests(unittest.TestCase): + + def setUp(self): + self._create_user = createuser.CreateUserPlugin() + + def test_get_password(self): + mock_osutils = mock.MagicMock() + mock_osutils.generate_random_password.return_value = 'fake password' + response = self._create_user._get_password(mock_osutils) + mock_osutils.generate_random_password.assert_called_once_with(14) + self.assertEqual(response, 'fake password') + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('cloudbaseinit.plugins.windows.createuser.CreateUserPlugin' + '._get_password') + def _test_execute(self, mock_get_password, mock_get_os_utils, + user_exists=True): + CONF.set_override('groups', ['Admins']) + shared_data = {} + mock_token = mock.MagicMock() + mock_osutils = mock.MagicMock() + mock_service = mock.MagicMock() + mock_get_password.return_value = 'password' + mock_get_os_utils.return_value = mock_osutils + mock_osutils.user_exists.return_value = user_exists + mock_osutils.create_user_logon_session.return_value = mock_token + + response = self._create_user.execute(mock_service, shared_data) + + mock_get_os_utils.assert_called_once_with() + mock_get_password.assert_called_once_with(mock_osutils) + mock_osutils.user_exists.assert_called_once_with(CONF.username) + if user_exists: + mock_osutils.set_user_password.assert_called_once_with( + CONF.username, 'password') + else: + mock_osutils.create_user.assert_called_once_with(CONF.username, + 'password') + mock_osutils.create_user_logon_session.assert_called_once_with( + CONF.username, 'password', True) + mock_osutils.close_user_logon_session.assert_called_once_with( + mock_token) + mock_osutils.add_user_to_local_group.assert_called_once_with( + CONF.username, CONF.groups[0]) + self.assertEqual(response, (base.PLUGIN_EXECUTION_DONE, False)) + + def test_execute_user_exists(self): + self._test_execute(user_exists=True) + + def test_execute_no_user(self): + self._test_execute(user_exists=False) diff --git a/cloudbaseinit/tests/plugins/windows/test_extendvolumes.py b/cloudbaseinit/tests/plugins/windows/test_extendvolumes.py new file mode 100644 index 00000000..16334bef --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_extendvolumes.py @@ -0,0 +1,210 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import re +import sys +import unittest + +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF + +_ctypes_mock = mock.MagicMock() +_comtypes_mock = mock.MagicMock() + + +class ExtendVolumesPluginTests(unittest.TestCase): + @mock.patch.dict(sys.modules, {'comtypes': _comtypes_mock, + 'ctypes': _ctypes_mock}) + def setUp(self): + extendvolumes = importlib.import_module('cloudbaseinit.plugins.' + 'windows.extendvolumes') + self._extend_volumes = extendvolumes.ExtendVolumesPlugin() + + def tearDown(self): + reload(sys) + + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes' + '.ExtendVolumesPlugin._get_volume_index') + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes' + '.ExtendVolumesPlugin._extend_volume') + @mock.patch('cloudbaseinit.plugins.windows.vds.IVdsVolume') + def test_extend_volumes(self, _vds_mock, mock_extend_volume, + mock_get_volume_index): + mock_pack = mock.MagicMock() + mock_volume_idxs = mock.MagicMock() + mock_enum = mock.MagicMock() + mock_unk = mock.MagicMock() + mock_c = mock.MagicMock() + mock_volume = mock.MagicMock() + mock_properties = mock.MagicMock() + mock_pack.QueryVolumes.return_value = mock_enum + mock_enum.Next.side_effect = [(mock_unk, mock_c), (None, None)] + mock_unk.QueryInterface.return_value = mock_volume + mock_volume.GetProperties.return_value = mock_properties + _ctypes_mock.wstring_at.return_value = 'fake name' + mock_get_volume_index.return_value = mock_volume_idxs + self._extend_volumes._extend_volumes(mock_pack, [mock_volume_idxs]) + mock_pack.QueryVolumes.assert_called_once_with() + mock_enum.Next.assert_called_with(1) + mock_unk.QueryInterface.assert_called_once_with(_vds_mock) + mock_volume.GetProperties.assert_called_once_with() + _ctypes_mock.wstring_at.assert_called_with(mock_properties.pwszName) + mock_get_volume_index.assert_called_once_with('fake name') + mock_extend_volume.assert_called_once_with(mock_pack, mock_volume, + mock_properties) + _ctypes_mock.windll.ole32.CoTaskMemFree.assert_called_once_with( + mock_properties.pwszName) + + def test_get_volume_index(self): + mock_value = mock.MagicMock() + re.match = mock.MagicMock(return_value=mock_value) + mock_value.group.return_value = '9999' + response = self._extend_volumes._get_volume_index('$2') + mock_value.group.assert_called_once_with(1) + self.assertTrue(response == 9999) + + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes' + '.ExtendVolumesPlugin._get_volume_extents_to_resize') + @mock.patch('cloudbaseinit.plugins.windows.vds.VDS_INPUT_DISK') + def test_extend_volume(self, mock_VDS_INPUT_DISK, + mock_get_volume_extents_to_resize): + mock_disk = mock.MagicMock() + mock_pack = mock.MagicMock() + mock_volume = mock.MagicMock() + mock_properties = mock.MagicMock() + mock_volume_extent = mock.MagicMock() + mock_async = mock.MagicMock() + mock_get_volume_extents_to_resize.return_value = [(mock_volume_extent, + 9999)] + mock_VDS_INPUT_DISK.return_value = mock_disk + mock_volume.Extend.return_value = mock_async + + self._extend_volumes._extend_volume(mock_pack, mock_volume, + mock_properties) + + mock_get_volume_extents_to_resize.assert_called_once_with( + mock_pack, mock_properties.id) + _ctypes_mock.wstring_at.assert_called_with(mock_properties.pwszName) + mock_volume.Extend.assert_called_once_with( + mock_VDS_INPUT_DISK.__mul__()(), 1) + mock_async.Wait.assert_called_once_with() + + @mock.patch('cloudbaseinit.plugins.windows.vds.IVdsDisk') + @mock.patch('cloudbaseinit.plugins.windows.vds.VDS_DISK_EXTENT') + def test_get_volume_extents_to_resize(self, mock_VDS_DISK_EXTENT, + mock_IVdsDisk): + mock_pack = mock.MagicMock() + mock_extents_p = mock.MagicMock() + mock_unk = mock.MagicMock() + mock_c = mock.MagicMock() + mock_disk = mock.MagicMock() + mock_enum = mock.MagicMock() + fake_volume_id = '$1' + mock_array = mock.MagicMock() + mock_array.volumeId = fake_volume_id + mock_pack.QueryDisks.return_value = mock_enum + mock_enum.Next.side_effect = [(mock_unk, mock_c), (None, None)] + mock_unk.QueryInterface.return_value = mock_disk + mock_disk.QueryExtents.return_value = (mock_extents_p, + 1) + mock_VDS_DISK_EXTENT.__mul__().from_address.return_value = [mock_array] + + response = self._extend_volumes._get_volume_extents_to_resize( + mock_pack, fake_volume_id) + + mock_pack.QueryDisks.assert_called_once_with() + mock_enum.Next.assert_called_with(1) + mock_unk.QueryInterface.assert_called_once_with(mock_IVdsDisk) + _ctypes_mock.addressof.assert_called_with(mock_extents_p.contents) + mock_VDS_DISK_EXTENT.__mul__().from_address.assert_called_with( + _ctypes_mock.addressof(mock_extents_p.contents)) + + _ctypes_mock.pointer.assert_called_once_with( + mock_VDS_DISK_EXTENT()) + self.assertEqual(response, []) + + _ctypes_mock.windll.ole32.CoTaskMemFree.assert_called_with( + mock_extents_p) + + @mock.patch('cloudbaseinit.plugins.windows.vds.' + 'VDS_QUERY_SOFTWARE_PROVIDERS') + @mock.patch('cloudbaseinit.plugins.windows.vds.IVdsSwProvider') + def test_query_providers(self, mock_IVdsSwProvider, + mock_VDS_QUERY_SOFTWARE_PROVIDERS): + mock_svc = mock.MagicMock() + mock_enum = mock.MagicMock() + mock_unk = mock.MagicMock() + mock_c = mock.MagicMock() + mock_svc.QueryProviders.return_value = mock_enum + mock_enum.Next.side_effect = [(mock_unk, mock_c), (None, None)] + mock_unk.QueryInterface.return_value = 'fake providers' + + response = self._extend_volumes._query_providers(mock_svc) + mock_svc.QueryProviders.assert_called_once_with( + mock_VDS_QUERY_SOFTWARE_PROVIDERS) + mock_enum.Next.assert_called_with(1) + mock_unk.QueryInterface.assert_called_once_with(mock_IVdsSwProvider) + self.assertEqual(response, ['fake providers']) + + @mock.patch('cloudbaseinit.plugins.windows.vds.IVdsPack') + def test_query_packs(self, mock_IVdsPack): + mock_provider = mock.MagicMock() + mock_enum = mock.MagicMock() + mock_unk = mock.MagicMock() + mock_c = mock.MagicMock() + mock_provider.QueryPacks.return_value = mock_enum + mock_enum.Next.side_effect = [(mock_unk, mock_c), (None, None)] + mock_unk.QueryInterface.return_value = 'fake packs' + + response = self._extend_volumes._query_packs(mock_provider) + + mock_provider.QueryPacks.assert_called_once_with() + mock_enum.Next.assert_called_with(1) + mock_unk.QueryInterface.assert_called_once_with(mock_IVdsPack) + self.assertEqual(response, ['fake packs']) + + def test_get_volumes_to_extend(self): + CONF.set_override('volumes_to_extend', '1') + response = self._extend_volumes._get_volumes_to_extend() + self.assertEqual(response, [1]) + + @mock.patch('cloudbaseinit.plugins.windows.vds.load_vds_service') + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes.' + 'ExtendVolumesPlugin._query_providers') + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes.' + 'ExtendVolumesPlugin._query_packs') + @mock.patch('cloudbaseinit.plugins.windows.extendvolumes.' + 'ExtendVolumesPlugin._extend_volumes') + def test_execute(self, mock_extend_volumes, mock_query_packs, + mock_query_providers, mock_load_vds_service): + CONF.set_override('volumes_to_extend', '1') + mock_svc = mock.MagicMock() + fake_providers = ['fake providers'] + fake_packs = ['fake packs'] + mock_service = mock.MagicMock() + fake_data = 'fake data' + mock_load_vds_service.return_value = mock_svc + mock_query_providers.return_value = fake_providers + mock_query_packs.return_value = fake_packs + + self._extend_volumes.execute(mock_service, fake_data) + + mock_query_providers.assert_called_once_with(mock_svc) + mock_query_packs.assert_called_once_with('fake providers') + mock_extend_volumes.assert_called_with('fake packs', [1]) diff --git a/cloudbaseinit/tests/plugins/windows/test_networkconfig.py b/cloudbaseinit/tests/plugins/windows/test_networkconfig.py new file mode 100644 index 00000000..210028f5 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_networkconfig.py @@ -0,0 +1,79 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import re +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import networkconfig +from cloudbaseinit.tests.metadata import fake_json_response + +CONF = cfg.CONF + + +class NetworkConfigPluginPluginTests(unittest.TestCase): + + def setUp(self): + self._network_plugin = networkconfig.NetworkConfigPlugin() + self.fake_data = fake_json_response.get_fake_metadata_json( + '2013-04-04') + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + def _test_execute(self, mock_get_os_utils, search_result, no_adapters): + CONF.set_override('network_adapter', 'fake adapter') + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + re.search = mock.MagicMock(return_value=search_result) + fake_shared_data = 'fake shared data' + mock_service.get_meta_data.return_value = self.fake_data + mock_service.get_content.return_value = search_result + mock_get_os_utils.return_value = mock_osutils + mock_osutils.set_static_network_config.return_value = False + if search_result is None: + self.assertRaises(Exception, self._network_plugin.execute, + mock_service, fake_shared_data) + elif no_adapters: + CONF.set_override('network_adapter', None) + mock_osutils.get_network_adapters.return_value = None + self.assertRaises(Exception, self._network_plugin.execute, + mock_service, fake_shared_data) + + else: + response = self._network_plugin.execute(mock_service, + fake_shared_data) + + mock_service.get_meta_data.assert_called_once_with('openstack') + mock_service.get_content.assert_called_once_with( + 'openstack', self.fake_data['network_config']['content_path']) + mock_osutils.set_static_network_config.assert_called_once_with( + 'fake adapter', search_result.group('address'), + search_result.group('netmask'), + search_result.group('broadcast'), + search_result.group('gateway'), + search_result.group('dnsnameservers').strip().split(' ')) + self.assertEqual(response, (1, False)) + + def test_execute(self): + m = mock.MagicMock() + self._test_execute(search_result=m, no_adapters=False) + + def test_execute_no_debian(self): + self._test_execute(search_result=None, no_adapters=False) + + def test_execute_no_adapters(self): + m = mock.MagicMock() + self._test_execute(search_result=m, no_adapters=True) diff --git a/cloudbaseinit/tests/plugins/windows/test_sethostname.py b/cloudbaseinit/tests/plugins/windows/test_sethostname.py new file mode 100644 index 00000000..275a73d0 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_sethostname.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import sethostname +from cloudbaseinit.tests.metadata import fake_json_response + +CONF = cfg.CONF + + +class SetHostNamePluginPluginTests(unittest.TestCase): + + def setUp(self): + self._sethostname_plugin = sethostname.SetHostNamePlugin() + self.fake_data = fake_json_response.get_fake_metadata_json( + '2013-04-04') + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + def _test_execute(self, mock_get_os_utils, hostname_exists): + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + fake_shared_data = 'fake data' + mock_service.get_meta_data.return_value = self.fake_data + if hostname_exists is False: + del self.fake_data['hostname'] + mock_get_os_utils.return_value = mock_osutils + mock_osutils.set_host_name.return_value = False + response = self._sethostname_plugin.execute(mock_service, + fake_shared_data) + mock_service.get_meta_data.assert_called_once_with('openstack') + if hostname_exists is True: + mock_get_os_utils.assert_called_once_with() + mock_osutils.set_host_name.assert_called_once_with( + self.fake_data['hostname'].split('.', 1)[0]) + self.assertEqual(response, (1, False)) + + def test_execute(self): + self._test_execute(hostname_exists=True) + + def test_execute_no_hostname(self): + self._test_execute(hostname_exists=False) diff --git a/cloudbaseinit/tests/plugins/windows/test_setuserpassword.py b/cloudbaseinit/tests/plugins/windows/test_setuserpassword.py new file mode 100644 index 00000000..acf080b4 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_setuserpassword.py @@ -0,0 +1,166 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins import constants +from cloudbaseinit.plugins.windows import setuserpassword +from cloudbaseinit.tests.metadata import fake_json_response + +CONF = cfg.CONF + + +class SetUserPasswordPluginTests(unittest.TestCase): + + def setUp(self): + self._setpassword_plugin = setuserpassword.SetUserPasswordPlugin() + self.fake_data = fake_json_response.get_fake_metadata_json( + '2013-04-04') + + @mock.patch('base64.b64encode') + @mock.patch('cloudbaseinit.utils.crypt.CryptManager' + '.load_ssh_rsa_public_key') + def test_encrypt_password(self, mock_load_ssh_key, mock_b64encode): + mock_rsa = mock.MagicMock() + fake_ssh_pub_key = 'fake key' + fake_password = 'fake password' + mock_load_ssh_key.return_value = mock_rsa + mock_rsa.__enter__().public_encrypt.return_value = 'public encrypted' + mock_b64encode.return_value = 'encrypted password' + + response = self._setpassword_plugin._encrypt_password( + fake_ssh_pub_key, fake_password) + print mock_rsa.mock_calls + + mock_load_ssh_key.assert_called_with(fake_ssh_pub_key) + mock_rsa.__enter__().public_encrypt.assert_called_with('fake password') + mock_b64encode.assert_called_with('public encrypted') + self.assertEqual(response, 'encrypted password') + + def _test_get_ssh_public_key(self, data_exists): + mock_service = mock.MagicMock() + mock_service.get_meta_data.return_value = self.fake_data + if data_exists is False: + del self.fake_data['public_keys'] + response = self._setpassword_plugin._get_ssh_public_key( + mock_service) + self.assertEqual(response, False) + else: + response = self._setpassword_plugin._get_ssh_public_key( + mock_service) + mock_service.get_meta_data.assert_called_with( + 'openstack', self._setpassword_plugin._post_password_md_ver) + self.assertEqual(response, self.fake_data['public_keys']['name']) + + def test_get_ssh_plublic_key(self): + self._test_get_ssh_public_key(data_exists=True) + + def test_get_ssh_plublic_key_no_pub_keys(self): + self._test_get_ssh_public_key(data_exists=False) + + def test_get_password(self): + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + mock_service.get_meta_data.return_value = self.fake_data + CONF.set_override('inject_user_password', False) + mock_osutils.generate_random_password.return_value = 'Passw0rd' + response = self._setpassword_plugin._get_password(mock_service, + mock_osutils) + mock_service.get_meta_data.assert_called_with('openstack') + mock_osutils.generate_random_password.assert_called_once_with(14) + self.assertEqual(response, 'Passw0rd') + + @mock.patch('cloudbaseinit.plugins.windows.setuserpassword.' + 'SetUserPasswordPlugin._get_ssh_public_key') + @mock.patch('cloudbaseinit.plugins.windows.setuserpassword.' + 'SetUserPasswordPlugin._encrypt_password') + def _test_set_metadata_password(self, mock_encrypt_password, + mock_get_key, ssh_pub_key): + fake_passw0rd = 'fake Passw0rd' + mock_service = mock.MagicMock() + mock_get_key.return_value = ssh_pub_key + mock_encrypt_password.return_value = 'encrypted password' + mock_service.post_password.return_value = 'value' + + response = self._setpassword_plugin._set_metadata_password( + fake_passw0rd, mock_service) + + if ssh_pub_key is None: + self.assertEqual(response, True) + else: + mock_get_key.assert_called_once_with(mock_service) + mock_encrypt_password.assert_called_once_with(ssh_pub_key, + fake_passw0rd) + mock_service.post_password.assert_called_with( + 'encrypted password', + self._setpassword_plugin._post_password_md_ver) + self.assertEqual(response, 'value') + + def test_set_metadata_password_with_ssh_key(self): + fake_key = 'fake key' + self._test_set_metadata_password(ssh_pub_key=fake_key) + + def test_set_metadata_password_no_ssh_key(self): + self._test_set_metadata_password(ssh_pub_key=None) + + @mock.patch('cloudbaseinit.plugins.windows.setuserpassword.' + 'SetUserPasswordPlugin._get_password') + def test_set_password(self, mock_get_password): + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + mock_get_password.return_value = 'fake password' + + response = self._setpassword_plugin._set_password(mock_service, + mock_osutils, + 'fake user') + + mock_get_password.assert_called_once_with(mock_service, mock_osutils) + mock_osutils.set_user_password.assert_called_once_with('fake user', + 'fake password') + self.assertEqual(response, 'fake password') + + @mock.patch('cloudbaseinit.plugins.windows.setuserpassword.' + 'SetUserPasswordPlugin._set_password') + @mock.patch('cloudbaseinit.plugins.windows.setuserpassword.' + 'SetUserPasswordPlugin._set_metadata_password') + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + def test_execute(self, mock_get_os_utils, mock_set_metadata_password, + mock_set_password): + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + fake_shared_data = mock.MagicMock() + fake_shared_data.get.return_value = 'fake username' + mock_service.is_password_set.return_value = False + mock_get_os_utils.return_value = mock_osutils + mock_osutils.user_exists.return_value = True + mock_set_password.return_value = 'fake password' + + response = self._setpassword_plugin.execute(mock_service, + fake_shared_data) + + fake_shared_data.get.assert_called_with( + constants.SHARED_DATA_USERNAME, CONF.username) + mock_service.is_password_set.assert_called_once_with( + self._setpassword_plugin._post_password_md_ver) + mock_get_os_utils.assert_called_once_with() + mock_osutils.user_exists.assert_called_once_with('fake username') + mock_set_password.assert_called_once_with(mock_service, mock_osutils, + 'fake username') + mock_set_metadata_password.assert_called_once_with('fake password', + mock_service) + self.assertEqual(response, (2, False)) diff --git a/cloudbaseinit/tests/plugins/windows/test_sshpublickeys.py b/cloudbaseinit/tests/plugins/windows/test_sshpublickeys.py new file mode 100644 index 00000000..88451e94 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_sshpublickeys.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import os +import unittest + +from cloudbaseinit.plugins.windows import sshpublickeys +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.tests.metadata import fake_json_response + +CONF = cfg.CONF + + +class SetUserSSHPublicKeysPluginTests(unittest.TestCase): + + def setUp(self): + self._set_ssh_keys_plugin = sshpublickeys.SetUserSSHPublicKeysPlugin() + self.fake_data = fake_json_response.get_fake_metadata_json( + '2013-04-04') + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('os.path') + @mock.patch('os.makedirs') + def _test_execute(self, mock_os_makedirs, mock_os_path, + mock_get_os_utils, user_home): + mock_service = mock.MagicMock() + mock_osutils = mock.MagicMock() + fake_shared_data = 'fake data' + mock_service.get_meta_data.return_value = self.fake_data + CONF.set_override('username', 'fake user') + mock_get_os_utils.return_value = mock_osutils + mock_osutils.get_user_home.return_value = user_home + mock_os_path.exists.return_value = False + + if user_home is None: + self.assertRaises(Exception, self._set_ssh_keys_plugin, + mock_service, fake_shared_data) + else: + with mock.patch('cloudbaseinit.plugins.windows.sshpublickeys' + '.open', + mock.mock_open(), create=True): + response = self._set_ssh_keys_plugin.execute(mock_service, + fake_shared_data) + mock_service.get_meta_data.assert_called_with('openstack') + mock_osutils.get_user_home.assert_called_with('fake user') + self.assertEqual(mock_os_path.join.call_count, 2) + mock_os_makedirs.assert_called_once_with(mock_os_path.join()) + + self.assertEqual(response, (1, False)) + + def test_execute_with_user_home(self): + fake_user_home = os.path.join('fake', 'home') + self._test_execute(user_home=fake_user_home) + + def test_execute_with_no_user_home(self): + self._test_execute(user_home=None) diff --git a/cloudbaseinit/tests/plugins/windows/test_userdata.py b/cloudbaseinit/tests/plugins/windows/test_userdata.py new file mode 100644 index 00000000..12d48278 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_userdata.py @@ -0,0 +1,263 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import tempfile +import uuid +import unittest + +from cloudbaseinit.metadata.services import base as base_metadata +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import userdata +from cloudbaseinit.tests.metadata import fake_json_response + +CONF = cfg.CONF + + +class UserDataPluginTest(unittest.TestCase): + + def setUp(self): + self._userdata = userdata.UserDataPlugin() + self.fake_data = fake_json_response.get_fake_metadata_json( + '2013-04-04') + + @mock.patch('os.path') + def test_get_plugin_path(self, mock_ospath): + mock_ospath.join.return_value = 'fake path' + response = self._userdata._get_plugin_path() + mock_ospath.join.assert_called_with( + mock_ospath.dirname(mock_ospath.dirname(mock_ospath.realpath())), + "windows/userdata-plugins") + self.assertEqual(response, 'fake path') + + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._process_userdata') + def _test_execute(self, mock_process_userdata, user_data, exception): + mock_service = mock.MagicMock() + fake_shared_data = 'fake data' + if exception: + e = base_metadata.NotExistingMetadataException() + mock_service.side_effect = e + else: + mock_service.get_user_data.return_value = user_data + response = self._userdata.execute(mock_service, fake_shared_data) + mock_service.get_user_data.assert_called_with('openstack') + if user_data: + mock_process_userdata.assert_called_with(user_data) + self.assertEqual(response, (1, False)) + + def test_execute(self): + self._test_execute(user_data=self.fake_data, exception=False) + + def test_execute_no_data(self): + self._test_execute(user_data=None, exception=False) + + def test_execute_exception(self): + self._test_execute(user_data=None, exception=True) + + @mock.patch('cloudbaseinit.plugins.windows.userdata.handle') + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._parse_mime') + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._process_part') + def _test_process_userdata(self, mock_process_part, mock_parse_mime, + mock_handle, user_data): + mock_process_part().__iter__.side_effect = ['fake'] + self._userdata._process_userdata(user_data) + print mock_parse_mime.mock_calls + print mock_process_part.mock_calls + if user_data.startswith('Content-Type: multipart'): + mock_parse_mime.assert_called_once_with(user_data) + self.assertEqual(mock_process_part.call_count, 1) + else: + mock_handle.assert_called_once_with(user_data) + + def test_process_userdata_multipart(self): + user_data = 'Content-Type: multipart fake data' + self._test_process_userdata(user_data=user_data) + + def test_process_userdata(self): + user_data = 'fake data' + self._test_process_userdata(user_data=user_data) + + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._get_part_handler') + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._begin_part_process_event') + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._end_part_process_event') + def test_process_part(self, mock_end_part_process_event, + mock_begin_part_process_event, + mock_get_part_handler): + mock_part = mock.MagicMock() + mock_part_handler = mock.MagicMock() + mock_get_part_handler.return_value = mock_part_handler + + self._userdata._process_part(mock_part) + + mock_get_part_handler.assert_called_once_with(mock_part) + mock_begin_part_process_event.assert_called_once_with(mock_part) + mock_part.get_content_type.assert_called_once_with() + mock_part.get_filename.assert_called_once_with() + mock_part_handler.process.assert_called_with(mock_part) + mock_end_part_process_event.assert_called_with(mock_part) + + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._get_custom_handler') + def test_begin_part_process_event(self, mock_get_custom_handler): + mock_part = mock.MagicMock() + mock_handler = mock.MagicMock() + mock_get_custom_handler.return_value = mock_handler + self._userdata._begin_part_process_event(mock_part) + mock_part.get_filename.assert_called_with() + mock_part.get_payload.assert_called_with() + mock_handler.assert_called_with("", "__begin__", + mock_part.get_filename(), + mock_part.get_payload()) + + @mock.patch('cloudbaseinit.plugins.windows.userdata.UserDataPlugin' + '._get_custom_handler') + def test_end_part_process_event(self, mock_get_custom_handler): + mock_part = mock.MagicMock() + mock_handler = mock.MagicMock() + mock_get_custom_handler.return_value = mock_handler + self._userdata._end_part_process_event(mock_part) + mock_part.get_payload.assert_called_with() + mock_handler.assert_called_with("", "__end__", + mock_part.get_filename(), + mock_part.get_payload()) + + def test_get_custom_handler(self): + mock_part = mock.MagicMock() + mock_part.get_content_type.return_value = 0 + self._userdata.plugin_set.has_custom_handlers = True + self._userdata.plugin_set.custom_handlers = [0] + response = self._userdata._get_custom_handler(mock_part) + mock_part.get_content_type.assert_called_with() + self.assertEqual(response, 0) + + def test_get_part_handler(self): + mock_part = mock.MagicMock() + mock_part.get_content_type.return_value = 0 + self._userdata.plugin_set.set = {0: 'fake value'} + response = self._userdata._get_part_handler(mock_part) + mock_part.get_content_type.assert_called_with() + self.assertEqual(response, 'fake value') + + @mock.patch('email.message_from_string') + def test_parse_mime(self, mock_message_from_string): + mock_msg = mock.MagicMock() + mock_message_from_string.return_value = mock_msg + response = self._userdata._parse_mime(self.fake_data) + mock_message_from_string.assert_called_once_with(self.fake_data) + mock_msg.walk.assert_called_once_with() + self.assertEqual(response, mock_msg.walk()) + + @mock.patch('re.search') + @mock.patch('tempfile.gettempdir') + @mock.patch('os.remove') + @mock.patch('os.path.isdir') + @mock.patch('os.path.join') + @mock.patch('os.path.exists') + @mock.patch('os.path.expandvars') + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + def _test_handle(self, mock_get_os_utils, mock_path_expandvars, + mock_path_exists, mock_path_join, mock_path_isdir, + mock_os_remove, mock_gettempdir, mock_re_search, + fake_user_data, directory_exists): + #TODO: recheck, these are old! + mock_osutils = mock.MagicMock() + uuid.uuid4 = mock.MagicMock(return_value='randomID') + mock_path_join.return_value = 'fake_temp\\randomID' + match_instance = mock.MagicMock() + path = 'fake_temp\\randomID' + args = None + mock_get_os_utils.return_value = mock_osutils + + if fake_user_data == '^rem cmd\s': + side_effect = [match_instance] + number_of_calls = 1 + extension = '.cmd' + args = [path+extension] + shell = True + elif fake_user_data == '#!': + side_effect = [None, match_instance] + number_of_calls = 2 + extension = '.sh' + args = ['bash.exe', path+extension] + shell = False + elif fake_user_data == '#ps1\s': + side_effect = [None, None, match_instance] + number_of_calls = 3 + extension = '.ps1' + args = ['powershell.exe', '-ExecutionPolicy', 'RemoteSigned', + '-NonInteractive', path+extension] + shell = False + else: + side_effect = [None, None, None, match_instance] + number_of_calls = 4 + extension = '.ps1' + shell = False + if directory_exists: + args = [mock_path_expandvars('%windir%\\sysnative\\' + 'WindowsPowerShell\\v1.0\\' + 'powershell.exe'), + '-ExecutionPolicy', + 'RemoteSigned', '-NonInteractive', path+extension] + mock_path_isdir.return_value = True + else: + mock_path_isdir.return_value = False + + mock_re_search.side_effect = side_effect + + with mock.patch('cloudbaseinit.plugins.windows.userdata.open', + mock.mock_open(), create=True): + response = userdata.handle(fake_user_data) + + tempfile.gettempdir.assert_called_once_with() + + mock_path_join.assert_called_once_with(mock_gettempdir(), + str(uuid.uuid4())) + assert mock_re_search.call_count == number_of_calls + if args: + mock_osutils.execute_process.assert_called_with(args, shell) + + self.assertEqual(response, (1, False)) + + def test_handle_batch(self): + fake_user_data = '^rem cmd\s' + self._test_handle(fake_user_data=fake_user_data, + directory_exists=True) + + def test_handle_shell(self): + fake_user_data = '^#!' + self._test_handle(fake_user_data=fake_user_data, + directory_exists=True) + + def test_handle_powershell(self): + fake_user_data = '^#ps1\s' + self._test_handle(fake_user_data=fake_user_data, + directory_exists=True) + + def test_handle_powershell_sysnative(self): + fake_user_data = '#ps1_sysnative\s' + self._test_handle(fake_user_data=fake_user_data, + directory_exists=True) + + def test_handle_powershell_sysnative_no_sysnative(self): + fake_user_data = '#ps1_sysnative\s' + self._test_handle(fake_user_data=fake_user_data, + directory_exists=False) diff --git a/cloudbaseinit/tests/plugins/windows/test_userdata_plugins.py b/cloudbaseinit/tests/plugins/windows/test_userdata_plugins.py new file mode 100644 index 00000000..fe0b9cba --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_userdata_plugins.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest + +from cloudbaseinit.plugins.windows import userdata_plugins + + +class MultipartUserDataPluginTest(unittest.TestCase): + + def setUp(self): + fake_path = 'fake path' + self._userdata = userdata_plugins.PluginSet(fake_path) + + @mock.patch('glob.glob') + @mock.patch('cloudbaseinit.plugins.windows.userdata_plugins.' + 'load_from_file') + def test_load(self, mock_load_from_file, mock_glob): + fake_files = ['fake_file.py'] + mock_plugin = mock.MagicMock() + mock_glob.return_value = fake_files + mock_load_from_file.return_value = mock_plugin + + self._userdata.load() + mock_glob.assert_called_once_with(self._userdata.path + '/*.py') + mock_load_from_file.assert_called_once_with('fake_file.py', + self._userdata) + self.assertEqual(self._userdata.set[mock_plugin.type], mock_plugin) diff --git a/cloudbaseinit/tests/plugins/windows/test_winrmcertificateauth.py b/cloudbaseinit/tests/plugins/windows/test_winrmcertificateauth.py new file mode 100644 index 00000000..72889d27 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_winrmcertificateauth.py @@ -0,0 +1,156 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the 'License'); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import sys +import unittest + +_ctypes_mock = mock.MagicMock() +_win32com_mock = mock.MagicMock() +_pywintypes_mock = mock.MagicMock() + +mock_dict = {'ctypes': _ctypes_mock, + 'win32com': _win32com_mock, + 'pywintypes': _pywintypes_mock} + + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins import constants + +CONF = cfg.CONF + + +class ConfigWinRMCertificateAuthPluginTests(unittest.TestCase): + @mock.patch.dict(sys.modules, mock_dict) + def setUp(self): + winrmcert = importlib.import_module('cloudbaseinit.plugins.windows.' + 'winrmcertificateauth') + self.x509 = importlib.import_module('cloudbaseinit.plugins.windows' + '.x509') + self._certif_auth = winrmcert.ConfigWinRMCertificateAuthPlugin() + + def tearDown(self): + reload(sys) + + def _test_get_client_auth_cert(self, chunk): + mock_service = mock.MagicMock() + mock_meta_data = mock.MagicMock() + mock_meta = mock.MagicMock() + mock_service.get_meta_data.return_value = mock_meta_data + mock_meta_data.get.return_value = mock_meta + mock_meta.get.side_effect = chunk + mock_service.get_user_data.return_value = self.x509.PEM_HEADER + + response = self._certif_auth._get_client_auth_cert(mock_service) + mock_service.get_meta_data.assert_called_once_with('openstack') + mock_meta_data.get.assert_called_once_with('meta') + if chunk == [None]: + mock_service.get_user_data.assert_called_once_with('openstack') + mock_meta.get.assert_called_once_with('admin_cert0') + self.assertEqual(response, self.x509.PEM_HEADER) + else: + expected = [mock.call('admin_cert0'), mock.call('admin_cert1')] + self.assertEqual(mock_meta.get.call_args_list, expected) + self.assertEqual(response, 'fake data') + + def test_get_client_auth_cert(self): + chunk = ['fake data', None] + self._test_get_client_auth_cert(chunk=chunk) + + def test_get_client_auth_cert_no_cert_data(self): + self._test_get_client_auth_cert(chunk=[None]) + + def _test_get_credentials(self, fake_user, fake_password): + mock_shared_data = mock.MagicMock() + mock_shared_data.get.side_effect = [fake_user, fake_password] + if fake_user is None or fake_password is None: + self.assertRaises(Exception, + self._certif_auth._get_credentials, + mock_shared_data) + else: + response = self._certif_auth._get_credentials(mock_shared_data) + expected = [mock.call(constants.SHARED_DATA_USERNAME), + mock.call(constants.SHARED_DATA_PASSWORD)] + self.assertEqual(mock_shared_data.get.call_args_list, expected) + mock_shared_data.__setitem__.assert_called_once_with( + 'admin_password', None) + self.assertEqual(response, (fake_user, fake_password)) + + def test_test_get_credentials(self): + self._test_get_credentials(fake_user='fake user', + fake_password='fake password') + + def test_test_get_credentials_no_user(self): + self._test_get_credentials(fake_user=None, + fake_password='fake password') + + def test_test_get_credentials_no_password(self): + self._test_get_credentials(fake_user='fake user', + fake_password=None) + + @mock.patch('cloudbaseinit.plugins.windows.winrmcertificateauth' + '.ConfigWinRMCertificateAuthPlugin._get_credentials') + @mock.patch('cloudbaseinit.plugins.windows.winrmcertificateauth' + '.ConfigWinRMCertificateAuthPlugin._get_client_auth_cert') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '.import_cert') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig') + def _test_execute(self, mock_WinRMConfig, mock_import_cert, + mock_get_client_auth_cert, mock_get_credentials, + cert_data, cert_upn): + mock_service = mock.MagicMock() + mock_cert_thumprint = mock.MagicMock() + fake_credentials = ('fake user', 'fake password') + mock_shared_data = mock.MagicMock() + mock_get_client_auth_cert.return_value = cert_data + mock_get_credentials.return_value = fake_credentials + mock_import_cert.return_value = (mock_cert_thumprint, cert_upn) + mock_WinRMConfig.get_cert_mapping.return_value = True + + response = self._certif_auth.execute(mock_service, + mock_shared_data) + if not cert_data or not cert_upn: + self.assertEqual(response, (1, False)) + else: + mock_get_client_auth_cert.assert_called_once_with(mock_service) + mock_get_credentials.assert_called_once_with(mock_shared_data) + mock_import_cert.assert_called_once_with( + cert_data, store_name=self.x509.STORE_NAME_ROOT) + + mock_WinRMConfig().set_auth_config.assert_called_once_with( + certificate=True) + mock_WinRMConfig().get_cert_mapping.assert_called_once_with( + mock_cert_thumprint, cert_upn) + mock_WinRMConfig().delete_cert_mapping.assert_called_once_with( + mock_cert_thumprint, cert_upn) + mock_WinRMConfig().create_cert_mapping.assert_called_once_with( + mock_cert_thumprint, cert_upn, 'fake user', + 'fake password') + self.assertEqual(response, (1, False)) + + def test_execute(self): + cert_data = 'fake cert data' + cert_upn = mock.MagicMock() + self._test_execute(cert_data=cert_data, cert_upn=cert_upn) + + def test_execute_no_cert_data(self): + cert_upn = mock.MagicMock() + self._test_execute(cert_data=None, cert_upn=cert_upn) + + def test_execute_no_cert_upn(self): + cert_data = 'fake cert data' + self._test_execute(cert_data=cert_data, cert_upn=None) diff --git a/cloudbaseinit/tests/plugins/windows/test_winrmconfig.py b/cloudbaseinit/tests/plugins/windows/test_winrmconfig.py new file mode 100644 index 00000000..b6945659 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_winrmconfig.py @@ -0,0 +1,378 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import sys +import unittest + +from cloudbaseinit.openstack.common import cfg +if sys.platform == 'win32': + from cloudbaseinit.plugins.windows import winrmconfig + +CONF = cfg.CONF + + +@unittest.skipUnless(sys.platform == "win32", "requires Windows") +class WinRMConfigTests(unittest.TestCase): + + def setUp(self): + self._winrmconfig = winrmconfig.WinRMConfig() + + @mock.patch('win32com.client.Dispatch') + def test_get_wsman_session(self, mock_Dispatch): + mock_wsman = mock.MagicMock() + mock_Dispatch.return_value = mock_wsman + response = self._winrmconfig._get_wsman_session() + mock_Dispatch.assert_called_once_with('WSMan.Automation') + mock_wsman.CreateSession.assert_called_once_with() + self.assertEqual(response, mock_wsman.CreateSession()) + + @mock.patch('re.match') + def test_get_node_tag(self, mock_match): + mock_tag = mock.MagicMock() + response = self._winrmconfig._get_node_tag(mock_tag) + mock_match.assert_called_once_with("^{.*}(.*)$", mock_tag) + self.assertEqual(response, mock_match().groups().__getitem__()) + + @mock.patch('xml.etree.ElementTree.fromstring') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_node_tag') + def _test_parse_listener_xml(self, mock_get_node_tag, mock_fromstring, + data_xml, tag=None, text='Fake'): + mock_node = mock.MagicMock() + mock_node.tag = tag + mock_node.text = text + fake_tree = [mock_node] + mock_get_node_tag.return_value = tag + mock_fromstring.return_value = fake_tree + response = self._winrmconfig._parse_listener_xml(data_xml=data_xml) + if data_xml is None: + self.assertEqual(response, None) + else: + mock_fromstring.assert_called_once_with(data_xml) + mock_get_node_tag.assert_called_once_with(tag) + if tag is "ListeningOn": + self.assertEqual(response, {'ListeningOn': ['Fake']}) + elif tag is "Enabled": + if text is 'true': + self.assertEqual(response, {'ListeningOn': [], + 'Enabled': True}) + else: + self.assertEqual(response, {'ListeningOn': [], + 'Enabled': False}) + elif tag is 'Port': + self.assertEqual(response, {'ListeningOn': [], + 'Port': int(text)}) + else: + self.assertEqual(response, {'ListeningOn': [], + tag: text}) + + def test_parse_listener_xml_no_data(self): + self._test_parse_listener_xml(data_xml=None) + + def test_parse_listener_xml_listening_on(self): + self._test_parse_listener_xml(data_xml='fake data', tag="ListeningOn") + + def test_parse_listener_xml_enabled_true(self): + self._test_parse_listener_xml(data_xml='fake data', + tag="Enabled", text='true') + + def test_parse_listener_xml_enabled_false(self): + self._test_parse_listener_xml(data_xml='fake data', tag='Enabled', + text='false') + + def test_parse_listener_xml_port(self): + self._test_parse_listener_xml(data_xml='fake data', tag='Port', + text='9999') + + def test_parse_listener_xml_other_tag(self): + self._test_parse_listener_xml(data_xml='fake data', tag='fake tag', + text='fake text') + + @mock.patch('xml.etree.ElementTree.fromstring') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_node_tag') + def _test_parse_cert_mapping_xml(self, mock_get_node_tag, + mock_fromstring, data_xml, tag=None, + text='Fake'): + mock_node = mock.MagicMock() + mock_node.tag = tag + mock_node.text = text + fake_tree = [mock_node] + mock_get_node_tag.return_value = tag + mock_fromstring.return_value = fake_tree + response = self._winrmconfig._parse_cert_mapping_xml(data_xml=data_xml) + if data_xml is None: + self.assertEqual(response, None) + else: + mock_fromstring.assert_called_once_with(data_xml) + mock_get_node_tag.assert_called_once_with(tag) + if tag is "Enabled": + if text is 'true': + self.assertEqual(response, {'Enabled': True}) + else: + self.assertEqual(response, {'Enabled': False}) + else: + self.assertEqual(response, {tag: text}) + + def test_parse_cert_mapping_xml_no_data(self): + self._test_parse_cert_mapping_xml(data_xml=None) + + def test_parse_cert_mapping_xml_enabled_true(self): + self._test_parse_listener_xml(data_xml='fake data', + tag="Enabled", text='true') + + def test_parse_cert_mapping_xml_enabled_false(self): + self._test_parse_listener_xml(data_xml='fake data', tag='Enabled', + text='false') + + def test_parse_cert_mapping_xml_other_tag(self): + self._test_parse_listener_xml(data_xml='fake data', tag='fake tag', + text='fake text') + + def _test_get_xml_bool(self, value): + response = self._winrmconfig._get_xml_bool(value) + if value: + self.assertEqual(response, 'true') + else: + self.assertEqual(response, 'false') + + def test_get_xml_bool_true(self): + self._test_get_xml_bool(value='fake value') + + def test_get_xml_bool_false(self): + self._test_get_xml_bool(value=None) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_wsman_session') + def _test_get_resource(self, mock_get_wsman_session, resource): + fake_session = mock.MagicMock() + fake_uri = 'fake:\\uri' + fake_session.Get.side_effect = [resource] + mock_get_wsman_session.return_value = fake_session + if resource is Exception: + self.assertRaises(Exception, self._winrmconfig._get_resource, + fake_uri) + else: + response = self._winrmconfig._get_resource(fake_uri) + mock_get_wsman_session.assert_called_once_with() + fake_session.Get.assert_called_once_with(fake_uri) + self.assertEqual(response, resource) + + def test_get_resource(self): + self._test_get_resource(resource='fake resource') + + def test_get_resource_exception(self): + self._test_get_resource(resource=Exception) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_wsman_session') + def test_delete_resource(self, mock_get_wsman_session): + fake_session = mock.MagicMock() + fake_uri = 'fake:\\uri' + mock_get_wsman_session.return_value = fake_session + self._winrmconfig._delete_resource(fake_uri) + fake_session.Delete.assert_called_once_with(fake_uri) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_wsman_session') + def test_create_resource(self, mock_get_wsman_session): + fake_session = mock.MagicMock() + fake_uri = 'fake:\\uri' + mock_get_wsman_session.return_value = fake_session + self._winrmconfig._create_resource(fake_uri, 'fake data') + fake_session.Create.assert_called_once_with(fake_uri, 'fake data') + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_parse_cert_mapping_xml') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_resource') + def test_get_cert_mapping(self, mock_get_resource, + mock_parse_cert_mapping_xml): + fake_dict = {'issuer': 'issuer', + 'subject': 'subject', + 'uri': 'fake:\\uri'} + mock_parse_cert_mapping_xml.return_value = 'fake response' + mock_get_resource.return_value = 'fake resource' + response = self._winrmconfig.get_cert_mapping('issuer', 'subject', + uri='fake:\\uri') + mock_parse_cert_mapping_xml.assert_called_with('fake resource') + mock_get_resource.assert_called_with( + self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict) + self.assertEqual(response, 'fake response') + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_delete_resource') + def test_delete_cert_mapping(self, mock_delete_resource): + fake_dict = {'issuer': 'issuer', + 'subject': 'subject', + 'uri': 'fake:\\uri'} + self._winrmconfig.delete_cert_mapping('issuer', 'subject', + uri='fake:\\uri') + mock_delete_resource.assert_called_with( + self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_xml_bool') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_create_resource') + def test_create_cert_mapping(self, mock_create_resource, + mock_get_xml_bool): + fake_dict = {'issuer': 'issuer', + 'subject': 'subject', + 'uri': 'fake:\\uri'} + mock_get_xml_bool.return_value = True + self._winrmconfig.create_cert_mapping( + issuer='issuer', subject='subject', username='fake user', + password='fake password', uri='fake:\\uri', enabled=True) + mock_get_xml_bool.assert_called_once_with(True) + mock_create_resource.assert_called_once_with( + self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict, + '' + '%(enabled)s' + '%(password)s' + '%(username)s' + '' % {'enabled': True, + 'username': 'fake user', + 'password': 'fake password'}) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_resource') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_parse_listener_xml') + def test_get_listener(self, mock_parse_listener_xml, mock_get_resource): + dict = {'protocol': 'HTTPS', + 'address': 'fake:\\address'} + mock_get_resource.return_value = 'fake resource' + mock_parse_listener_xml.return_value = 'fake response' + response = self._winrmconfig.get_listener(protocol='HTTPS', + address="fake:\\address") + mock_get_resource.assert_called_with( + self._winrmconfig._SERVICE_LISTENER_URI % dict) + mock_parse_listener_xml.assert_called_once_with('fake resource') + self.assertEqual(response, 'fake response') + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_delete_resource') + def test_delete_listener(self, mock_delete_resource): + dict = {'protocol': 'HTTPS', + 'address': 'fake:\\address'} + self._winrmconfig.delete_listener(protocol='HTTPS', + address="fake:\\address") + mock_delete_resource.assert_called_with( + self._winrmconfig._SERVICE_LISTENER_URI % dict) + + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_create_resource') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_xml_bool') + def test_create_listener(self, mock_get_xml_bool, mock_create_resource): + dict = {'protocol': 'HTTPS', + 'address': 'fake:\\address'} + mock_get_xml_bool.return_value = True + self._winrmconfig.create_listener(protocol='HTTPS', + cert_thumbprint=None, + address="fake:\\address", + enabled=True) + mock_create_resource.assert_called_once_with( + self._winrmconfig._SERVICE_LISTENER_URI % dict, + '' + '%(enabled)s' + '%(cert_thumbprint)s' + '' + 'wsman' + '' % {"enabled": True, + "cert_thumbprint": None}) + + @mock.patch('xml.etree.ElementTree.fromstring') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_node_tag') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_resource') + def test_get_auth_config(self, mock_get_resource, mock_get_node_tag, + mock_fromstring): + mock_node = mock.MagicMock() + mock_node.tag = 'tag' + mock_node.text = 'value' + fake_tree = [mock_node] + mock_get_resource.return_value = 'fake data xml' + mock_fromstring.return_value = fake_tree + mock_get_node_tag.return_value = 'tag' + + response = self._winrmconfig.get_auth_config() + + mock_get_resource.assert_called_with( + self._winrmconfig._SERVICE_AUTH_URI) + mock_fromstring.assert_called_once_with('fake data xml') + mock_get_node_tag.assert_called_once_with(mock_node.tag) + self.assertEqual(response, {'tag': 'value'}) + + @mock.patch('xml.etree.ElementTree.fromstring') + @mock.patch('xml.etree.ElementTree.tostring') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_wsman_session') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig.' + '_get_xml_bool') + def test_set_auth_config(self, mock_get_xml_bool, mock_get_wsman_session, + mock_tostring, mock_fromstring): + mock_session = mock.MagicMock() + mock_tree = mock.MagicMock() + mock_node = mock.MagicMock() + base_url = 'http://schemas.microsoft.com/wbem/wsman/1/config/service/' + expected_find = [ + mock.call('.//cfg:Certificate', namespaces={ + 'cfg': base_url + 'auth'}), + mock.call('.//cfg:Kerberos', + namespaces={'cfg': base_url + 'auth'}), + mock.call('.//cfg:CbtHardeningLevel', + namespaces={'cfg': base_url + 'auth'}), + mock.call('.//cfg:Negotiate', + namespaces={'cfg': base_url + 'auth'}), + mock.call('.//cfg:CredSSP', + namespaces={'cfg': base_url + 'auth'}), + mock.call('.//cfg:Basic', + namespaces={'cfg': base_url + 'auth'})] + expected_get_xml_bool = [mock.call('certificate'), + mock.call('kerberos'), + mock.call('cbt_hardening_level'), + mock.call('negotiate'), + mock.call('credSSP'), + mock.call('basic')] + + mock_get_wsman_session.return_value = mock_session + mock_session.Get.return_value = 'fake xml' + mock_fromstring.return_value = mock_tree + mock_get_xml_bool.return_value = 'true' + mock_tostring.return_value = 'fake xml' + mock_tree.find.return_value = mock_node + mock_node.text.lower.return_value = 'old value' + + self._winrmconfig.set_auth_config( + basic='basic', kerberos='kerberos', negotiate='negotiate', + certificate='certificate', credSSP='credSSP', + cbt_hardening_level='cbt_hardening_level') + self.assertEqual(mock_tree.find.call_args_list, expected_find) + self.assertEqual(mock_get_xml_bool.call_args_list, + expected_get_xml_bool) + + mock_get_wsman_session.assert_called_once_with() + mock_session.Get.assert_called_with( + self._winrmconfig._SERVICE_AUTH_URI) + mock_fromstring.assert_called_once_with('fake xml') + mock_session.Put.assert_called_with( + self._winrmconfig._SERVICE_AUTH_URI, 'fake xml') diff --git a/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py b/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py new file mode 100644 index 00000000..a2252139 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py @@ -0,0 +1,117 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the 'License'); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import sys +import unittest + +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF +_mock_wintypes = mock.MagicMock() +mock_dict = {'ctypes.wintypes': _mock_wintypes} + + +class ConfigWinRMListenerPluginTests(unittest.TestCase): + @mock.patch.dict(sys.modules, mock_dict) + def setUp(self): + winrmlistener = importlib.import_module('cloudbaseinit.plugins.' + 'windows.winrmlistener') + self._winrmlistener = winrmlistener.ConfigWinRMListenerPlugin() + + def _test_check_winrm_service(self, service_exists): + mock_osutils = mock.MagicMock() + mock_osutils.check_service_exists.return_value = service_exists + mock_osutils.SERVICE_START_MODE_MANUAL = 'fake start' + mock_osutils.SERVICE_START_MODE_DISABLED = 'fake start' + mock_osutils.SERVICE_STATUS_STOPPED = 'fake status' + mock_osutils.get_service_start_mode.return_value = 'fake start' + mock_osutils.get_service_status.return_value = 'fake status' + + response = self._winrmlistener._check_winrm_service(mock_osutils) + if not service_exists: + self.assertEqual(response, False) + else: + + mock_osutils.get_service_start_mode.assert_called_once_with( + self._winrmlistener._winrm_service_name) + mock_osutils.get_service_start_mode.assert_called_once_with( + self._winrmlistener._winrm_service_name) + mock_osutils.set_service_start_mode.assert_called_once_with( + self._winrmlistener._winrm_service_name, + mock_osutils .SERVICE_START_MODE_AUTOMATIC) + mock_osutils.get_service_status.assert_called_once_with( + self._winrmlistener._winrm_service_name) + mock_osutils.start_service.assert_called_once_with( + self._winrmlistener._winrm_service_name) + self.assertEqual(response, True) + + def test_check_winrm_service(self): + self._test_check_winrm_service(service_exists=True) + + def test_check_winrm_service_no_service(self): + self._test_check_winrm_service(service_exists=False) + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('cloudbaseinit.plugins.windows.winrmlistener.' + 'ConfigWinRMListenerPlugin._check_winrm_service') + @mock.patch('cloudbaseinit.plugins.windows.winrmconfig.WinRMConfig') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '.create_self_signed_cert') + def _test_execute(self, mock_create_cert, mock_WinRMConfig, + mock_check_winrm_service, mock_get_os_utils, + service_status): + mock_service = mock.MagicMock() + mock_listener_config = mock.MagicMock() + mock_cert_thumbprint = mock.MagicMock() + shared_data = 'fake data' + mock_osutils = mock.MagicMock() + mock_get_os_utils.return_value = mock_osutils + mock_check_winrm_service.return_value = service_status + mock_create_cert.return_value = mock_cert_thumbprint + mock_WinRMConfig().get_listener.return_value = mock_listener_config + mock_listener_config.get.return_value = 9999 + + response = self._winrmlistener.execute(mock_service, shared_data) + + mock_get_os_utils.assert_called_once_with() + mock_check_winrm_service.assert_called_once_with(mock_osutils) + + if not service_status: + self.assertEqual(response, (2, False)) + else: + mock_WinRMConfig().set_auth_config.assert_called_once_with( + basic=CONF.winrm_enable_basic_auth) + mock_create_cert.assert_called_once_with( + self._winrmlistener._cert_subject) + + mock_WinRMConfig().get_listener.assert_called_with( + protocol="HTTPS") + mock_WinRMConfig().delete_listener.assert_called_once_with( + protocol="HTTPS") + mock_WinRMConfig().create_listener.assert_called_once_with( + protocol="HTTPS", cert_thumbprint=mock_cert_thumbprint) + mock_listener_config.get.assert_called_once_with("Port") + mock_osutils.firewall_create_rule.assert_called_once_with( + "WinRM HTTPS", 9999, mock_osutils.PROTOCOL_TCP) + self.assertEqual(response, (1, False)) + + def test_execute(self): + self._test_execute(service_status=True) + + def test_execute_service_status_is_false(self): + self._test_execute(service_status=False) diff --git a/cloudbaseinit/tests/plugins/windows/test_x509.py b/cloudbaseinit/tests/plugins/windows/test_x509.py new file mode 100644 index 00000000..de942160 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_x509.py @@ -0,0 +1,383 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import sys +import unittest + +from cloudbaseinit.openstack.common import cfg +if sys.platform == 'win32': + from cloudbaseinit.plugins.windows import cryptoapi + from cloudbaseinit.plugins.windows import x509 + +CONF = cfg.CONF + + +@unittest.skipUnless(sys.platform == "win32", "requires Windows") +class CryptoAPICertManagerTests(unittest.TestCase): + + def setUp(self): + self._x509 = x509.CryptoAPICertManager() + + @mock.patch('cloudbaseinit.plugins.windows.x509.free') + @mock.patch('ctypes.c_ubyte') + @mock.patch('ctypes.POINTER') + @mock.patch('ctypes.cast') + @mock.patch('cloudbaseinit.plugins.windows.x509.malloc') + @mock.patch('ctypes.byref') + @mock.patch('ctypes.wintypes.DWORD') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertGetCertificateContextProperty') + def _test_get_cert_thumprint(self, + mock_CertGetCertificateContextProperty, + mock_DWORD, mock_byref, mock_malloc, + mock_cast, mock_POINTER, mock_c_ubyte, + mock_free, ret_val): + + mock_pointer = mock.MagicMock() + fake_cert_context_p = 'fake context' + mock_DWORD().value = 10 + mock_CertGetCertificateContextProperty.return_value = ret_val + mock_POINTER.return_value = mock_pointer + mock_cast().contents = [16] + if not ret_val: + self.assertRaises(cryptoapi.CryptoAPIException, + self._x509._get_cert_thumprint, + fake_cert_context_p) + else: + expected = [mock.call(fake_cert_context_p, + cryptoapi.CERT_SHA1_HASH_PROP_ID, + None, mock_byref()), + mock.call(fake_cert_context_p, + cryptoapi.CERT_SHA1_HASH_PROP_ID, + mock_malloc(), mock_byref())] + response = self._x509._get_cert_thumprint(fake_cert_context_p) + self.assertEqual( + mock_CertGetCertificateContextProperty.call_args_list, + expected) + mock_malloc.assert_called_with(mock_DWORD()) + mock_cast.assert_called_with(mock_malloc(), mock_pointer) + mock_free.assert_called_with(mock_malloc()) + self.assertEqual(response, '10') + + def test_get_cert_thumprint(self): + self._test_get_cert_thumprint(ret_val=True) + + def test_get_cert_thumprint_GetCertificateContextProperty_exception(self): + self._test_get_cert_thumprint(ret_val=False) + + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CryptDestroyKey') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CryptReleaseContext') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CryptGenKey') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CryptAcquireContext') + @mock.patch('ctypes.byref') + @mock.patch('ctypes.wintypes.HANDLE') + def _test_generate_key(self, mock_HANDLE, mock_byref, + mock_CryptAcquireContext, mock_CryptGenKey, + mock_CryptReleaseContext, mock_CryptDestroyKey, + acquired_context, generate_key_ret_val): + mock_CryptAcquireContext.return_value = acquired_context + mock_CryptGenKey.return_value = generate_key_ret_val + if not acquired_context: + self.assertRaises(cryptoapi.CryptoAPIException, + self._x509._generate_key, + 'fake container', True) + else: + if generate_key_ret_val is None: + self.assertRaises(cryptoapi.CryptoAPIException, + self._x509._generate_key, 'fake container', + True) + mock_byref.assert_called_with(mock_HANDLE()) + else: + self._x509._generate_key('fake container', True) + mock_CryptAcquireContext.assert_called_with( + mock_byref(), 'fake container', None, + cryptoapi.PROV_RSA_FULL, cryptoapi.CRYPT_MACHINE_KEYSET) + mock_CryptGenKey.assert_called_with(mock_HANDLE(), + cryptoapi.AT_SIGNATURE, + 0x08000000, mock_HANDLE()) + mock_CryptDestroyKey.assert_called_once_with( + mock_HANDLE()) + mock_CryptReleaseContext.assert_called_once_with( + mock_HANDLE(), 0) + + def test_generate_key(self): + self._test_generate_key(acquired_context=True, + generate_key_ret_val='fake key') + + def test_generate_key_GetCertificateContextProperty_exception(self): + self._test_generate_key(acquired_context=False, + generate_key_ret_val='fake key') + + def test_generate_key_CryptGenKey_exception(self): + self._test_generate_key(acquired_context=True, + generate_key_ret_val=None) + + @mock.patch('cloudbaseinit.plugins.windows.x509.free') + @mock.patch('copy.copy') + @mock.patch('ctypes.byref') + @mock.patch('cloudbaseinit.plugins.windows.x509.malloc') + @mock.patch('ctypes.POINTER') + @mock.patch('ctypes.cast') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '._generate_key') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '._get_cert_thumprint') + @mock.patch('uuid.uuid4') + @mock.patch('ctypes.wintypes.DWORD') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertStrToName') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CRYPTOAPI_BLOB') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CRYPT_KEY_PROV_INFO') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CRYPT_ALGORITHM_IDENTIFIER') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'SYSTEMTIME') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'GetSystemTime') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertCreateSelfSignCertificate') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertAddEnhancedKeyUsageIdentifier') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertOpenStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertAddCertificateContextToStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertCloseStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertFreeCertificateContext') + def _test_create_self_signed_cert(self, mock_CertFreeCertificateContext, + mock_CertCloseStore, + mock_CertAddCertificateContextToStore, + mock_CertOpenStore, + mock_CertAddEnhancedKeyUsageIdentifier, + mock_CertCreateSelfSignCertificate, + mock_GetSystemTime, mock_SYSTEMTIME, + mock_CRYPT_ALGORITHM_IDENTIFIER, + mock_CRYPT_KEY_PROV_INFO, + mock_CRYPTOAPI_BLOB, + mock_CertStrToName, mock_DWORD, + mock_uuid4, mock_get_cert_thumprint, + mock_generate_key, mock_cast, + mock_POINTER, mock_malloc, mock_byref, + mock_copy, mock_free, certstr, + certificate, enhanced_key, + store_handle, context_to_store): + + mock_uuid4.return_value = 'fake_name' + mock_CertCreateSelfSignCertificate.return_value = certificate + mock_CertAddEnhancedKeyUsageIdentifier.return_value = enhanced_key + mock_CertStrToName.return_value = certstr + mock_CertOpenStore.return_value = store_handle + mock_CertAddCertificateContextToStore.return_value = context_to_store + if (certstr is None or certificate is None or enhanced_key is None + or store_handle is None or context_to_store is None): + self.assertRaises(cryptoapi.CryptoAPIException, + self._x509.create_self_signed_cert, + 'fake subject', 10, True, x509.STORE_NAME_MY) + else: + response = self._x509.create_self_signed_cert( + subject='fake subject') + mock_cast.assert_called_with(mock_malloc(), mock_POINTER()) + mock_CRYPTOAPI_BLOB.assert_called_once_with() + mock_CRYPT_KEY_PROV_INFO.assert_called_once_with() + mock_CRYPT_ALGORITHM_IDENTIFIER.assert_called_once_with() + mock_SYSTEMTIME.assert_called_once_with() + mock_GetSystemTime.assert_called_once_with(mock_byref()) + mock_copy.assert_called_once_with(mock_SYSTEMTIME()) + mock_CertCreateSelfSignCertificate.assert_called_once_with( + None, mock_byref(), 0, mock_byref(), + mock_byref(), mock_byref(), mock_byref(), None) + mock_CertAddEnhancedKeyUsageIdentifier.assert_called_with( + mock_CertCreateSelfSignCertificate(), + cryptoapi.szOID_PKIX_KP_SERVER_AUTH) + mock_CertOpenStore.assert_called_with( + cryptoapi.CERT_STORE_PROV_SYSTEM, 0, 0, + cryptoapi.CERT_SYSTEM_STORE_LOCAL_MACHINE, + unicode(x509.STORE_NAME_MY)) + mock_get_cert_thumprint.assert_called_once_with( + mock_CertCreateSelfSignCertificate()) + + mock_CertCloseStore.assert_called_once_with(store_handle, 0) + mock_CertFreeCertificateContext.assert_called_once_with( + mock_CertCreateSelfSignCertificate()) + mock_free.assert_called_once_with(mock_cast()) + + self.assertEqual(response, mock_get_cert_thumprint()) + + mock_generate_key.assert_called_once_with('fake_name', True) + + def test_create_self_signed_cert(self): + self._test_create_self_signed_cert(certstr='fake cert name', + certificate='fake certificate', + enhanced_key='fake key', + store_handle='fake handle', + context_to_store='fake context') + + def test_create_self_signed_cert_CertStrToName_fail(self): + self._test_create_self_signed_cert(certstr=None, + certificate='fake certificate', + enhanced_key='fake key', + store_handle='fake handle', + context_to_store='fake context') + + def test_create_self_signed_cert_CertCreateSelfSignCertificate_fail(self): + self._test_create_self_signed_cert(certstr='fake cert name', + certificate=None, + enhanced_key='fake key', + store_handle='fake handle', + context_to_store='fake context') + + def test_create_self_signed_cert_AddEnhancedKeyUsageIdentifier_fail(self): + self._test_create_self_signed_cert(certstr='fake cert name', + certificate='fake certificate', + enhanced_key=None, + store_handle='fake handle', + context_to_store='fake context') + + def test_create_self_signed_cert_CertOpenStore_fail(self): + self._test_create_self_signed_cert(certstr='fake cert name', + certificate='fake certificate', + enhanced_key='fake key', + store_handle=None, + context_to_store='fake context') + + def test_create_self_signed_cert_AddCertificateContextToStore_fail(self): + self._test_create_self_signed_cert(certstr='fake cert name', + certificate='fake certificate', + enhanced_key='fake key', + store_handle='fake handle', + context_to_store=None) + + def test_get_cert_base64(self): + fake_cert_data = '' + fake_cert_data += x509.PEM_HEADER + '\n' + fake_cert_data += 'fake cert' + '\n' + fake_cert_data += x509.PEM_FOOTER + response = self._x509._get_cert_base64(fake_cert_data) + self.assertEqual(response, 'fake cert') + + @mock.patch('cloudbaseinit.plugins.windows.x509.free') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '._get_cert_thumprint') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertCloseStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertFreeCertificateContext') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertGetNameString') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertAddEncodedCertificateToStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CertOpenStore') + @mock.patch('cloudbaseinit.plugins.windows.cryptoapi.' + 'CryptStringToBinaryA') + @mock.patch('cloudbaseinit.plugins.windows.x509.CryptoAPICertManager' + '._get_cert_base64') + @mock.patch('ctypes.POINTER') + @mock.patch('cloudbaseinit.plugins.windows.x509.malloc') + @mock.patch('ctypes.cast') + @mock.patch('ctypes.byref') + @mock.patch('ctypes.wintypes.DWORD') + @mock.patch('ctypes.create_unicode_buffer') + def _test_import_cert(self, mock_create_unicode_buffer, mock_DWORD, + mock_byref, mock_cast, + mock_malloc, mock_POINTER, mock_get_cert_base64, + mock_CryptStringToBinaryA, mock_CertOpenStore, + mock_CertAddEncodedCertificateToStore, + mock_CertGetNameString, + mock_CertFreeCertificateContext, + mock_CertCloseStore, mock_get_cert_thumprint, + mock_free, crypttstr, store_handle, add_enc_cert, + upn_len): + fake_cert_data = '' + fake_cert_data += x509.PEM_HEADER + '\n' + fake_cert_data += 'fake cert' + '\n' + fake_cert_data += x509.PEM_FOOTER + mock_get_cert_base64.return_value = 'fake cert' + mock_CryptStringToBinaryA.return_value = crypttstr + mock_CertOpenStore.return_value = store_handle + mock_CertAddEncodedCertificateToStore.return_value = add_enc_cert + mock_CertGetNameString.side_effect = [2, upn_len] + + expected = [mock.call('fake cert', len('fake cert'), + cryptoapi.CRYPT_STRING_BASE64, None, + mock_byref(), None, None), + mock.call('fake cert', len('fake cert'), + cryptoapi.CRYPT_STRING_BASE64, mock_cast(), + mock_byref(), None, None)] + expected2 = [mock.call(mock_POINTER()(), cryptoapi.CERT_NAME_UPN_TYPE, + 0, None, None, 0), + mock.call(mock_POINTER()(), cryptoapi.CERT_NAME_UPN_TYPE, + 0, None, mock_create_unicode_buffer(), 2)] + + if (not crypttstr or store_handle is None or add_enc_cert is None or + upn_len != 2): + self.assertRaises(cryptoapi.CryptoAPIException, + self._x509.import_cert, fake_cert_data, True, + x509.STORE_NAME_MY) + else: + response = self._x509.import_cert(fake_cert_data) + mock_cast.assert_called_with(mock_malloc(), mock_POINTER()) + self.assertEqual(mock_CryptStringToBinaryA.call_args_list, + expected) + mock_CertOpenStore.assert_called_with( + cryptoapi.CERT_STORE_PROV_SYSTEM, 0, 0, + cryptoapi.CERT_SYSTEM_STORE_LOCAL_MACHINE, + unicode(x509.STORE_NAME_MY)) + mock_CertAddEncodedCertificateToStore.assert_called_with( + mock_CertOpenStore(), + cryptoapi.X509_ASN_ENCODING | cryptoapi.PKCS_7_ASN_ENCODING, + mock_cast(), mock_DWORD(), + cryptoapi.CERT_STORE_ADD_REPLACE_EXISTING, mock_byref()) + mock_create_unicode_buffer.assert_called_with(2) + self.assertEqual(mock_CertGetNameString.call_args_list, expected2) + mock_get_cert_thumprint.assert_called_once_with(mock_POINTER()()) + mock_CertFreeCertificateContext.assert_called_once_with( + mock_POINTER()()) + mock_CertCloseStore.assert_called_once_with( + mock_CertOpenStore(), 0) + mock_free.assert_called_once_with(mock_cast()) + self.assertEqual(response, (mock_get_cert_thumprint(), + mock_create_unicode_buffer().value)) + mock_get_cert_base64.assert_called_with(fake_cert_data) + + def test_import_cert(self): + self._test_import_cert(crypttstr=True, store_handle='fake handle', + add_enc_cert='fake encoded cert', upn_len=2) + + def test_import_cert_CryptStringToBinaryA_fail(self): + self._test_import_cert(crypttstr=False, store_handle='fake handle', + add_enc_cert='fake encoded cert', upn_len=2) + + def test_import_cert_CertOpenStore_fail(self): + self._test_import_cert(crypttstr=False, store_handle=None, + add_enc_cert='fake encoded cert', upn_len=2) + + def test_import_cert_CertAddEncodedCertificateToStore_fail(self): + self._test_import_cert(crypttstr=True, store_handle='fake handle', + add_enc_cert=None, upn_len=2) + + def test_import_cert_CertGetNameString_fail(self): + self._test_import_cert(crypttstr=True, store_handle='fake handle', + add_enc_cert='fake encoded cert', upn_len=3) diff --git a/cloudbaseinit/tests/plugins/windows/userdata_plugins/__init__.py b/cloudbaseinit/tests/plugins/windows/userdata_plugins/__init__.py new file mode 100644 index 00000000..7227b295 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/userdata_plugins/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_heathandler.py b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_heathandler.py new file mode 100644 index 00000000..cba1467f --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_heathandler.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import userdata_plugins +#the name of the module includes "-", importlib.import_module is needed: +heathandler = importlib.import_module("cloudbaseinit.plugins.windows" + ".userdata-plugins.heathandler") + +CONF = cfg.CONF + + +class HeatUserDataHandlerTests(unittest.TestCase): + + def setUp(self): + parent_set = userdata_plugins.PluginSet + self._heathandler = heathandler.HeatUserDataHandler(parent_set) + + @mock.patch('cloudbaseinit.plugins.windows.userdata.handle') + def test_process(self, mock_handle): + mock_part = mock.MagicMock() + mock_part.get_filename.return_value = "cfn-userdata" + self._heathandler.process(mock_part) + mock_part.get_filename.assert_called_once_with() + mock_handle.assert_called_once_with(mock_part.get_payload()) diff --git a/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_parthandler.py b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_parthandler.py new file mode 100644 index 00000000..c1b0b3da --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_parthandler.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import os +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import userdata_plugins +#the name of the module includes "-", importlib.import_module is needed: +parthandler = importlib.import_module("cloudbaseinit.plugins.windows" + ".userdata-plugins.parthandler") + +CONF = cfg.CONF + + +class PartHandlerScriptHandlerTests(unittest.TestCase): + + def setUp(self): + parent_set = userdata_plugins.PluginSet('fake_path') + self._parthandler = parthandler.PartHandlerScriptHandler(parent_set) + + @mock.patch('imp.load_source') + @mock.patch('imp.load_compiled') + @mock.patch('cloudbaseinit.plugins.windows.userdata-plugins.parthandler' + '.__import__', create=True) + def _test_load_from_file(self, mock__import__, mock_load_compiled, + mock_load_source, filepath): + mock_module = mock.MagicMock() + mock__import__.return_value = mock_module + mod_name, file_ext = os.path.splitext(os.path.split(filepath)[-1]) + response = parthandler.load_from_file(filepath, 'fake_function') + print response + if file_ext.lower() == '.py': + mock_load_source.assert_called_with('path', filepath) + elif file_ext.lower() == '.pyc': + mock_load_compiled.assert_called_with('path', filepath) + mock__import__.assert_called_once_with('path') + self.assertEqual(response, mock_module.fake_function) + + def test_load_from_file_py(self): + fake_file_path = os.path.join(os.path.join('fake', 'file'), 'path') + self._test_load_from_file(filepath=fake_file_path + '.py') + + def test_load_from_file_pyc(self): + fake_file_path = os.path.join(os.path.join('fake', 'file'), 'path') + self._test_load_from_file(filepath=fake_file_path + '.pyc') + + @mock.patch('cloudbaseinit.plugins.windows.userdata-plugins.parthandler.' + 'load_from_file') + def test_process(self, mock_load_from_file): + mock_part = mock.MagicMock() + mock_part.get_filename.return_value = 'fake_name' + handler_path = self._parthandler.parent_set.path + "/part-handler/" + handler_path += 'fake_name' + expected = [mock.call(), + mock.call(handler_path, "list_types"), + mock.call(handler_path, "handle_part")] + mock_load_from_file().return_value = ['fake part'] + with mock.patch("cloudbaseinit.plugins.windows.userdata-plugins." + "parthandler.open", mock.mock_open(), create=True): + self._parthandler.process(mock_part) + + print mock_load_from_file.mock_calls + print self._parthandler.parent_set.custom_handlers + + mock_part.get_filename.assert_called_once_with() + mock_part.get_payload.assert_called_once_with() + self.assertEqual(mock_load_from_file.call_args_list, expected) + self.assertEqual(self._parthandler.parent_set.has_custom_handlers, + True) + self.assertEqual(self._parthandler.parent_set.custom_handlers, + {'fake part': mock_load_from_file()}) diff --git a/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_shellscript.py b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_shellscript.py new file mode 100644 index 00000000..bc61693b --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/userdata_plugins/test_shellscript.py @@ -0,0 +1,84 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import mock +import os +import unittest + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.plugins.windows import userdata_plugins +#the name of the module includes "-", importlib.import_module is needed: +shellscript = importlib.import_module("cloudbaseinit.plugins.windows" + ".userdata-plugins.shellscript") + +CONF = cfg.CONF + + +class ShellScriptHandlerTests(unittest.TestCase): + + def setUp(self): + parent_set = userdata_plugins.PluginSet('fake_path') + self._shellscript = shellscript.ShellScriptHandler(parent_set) + + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('tempfile.gettempdir') + def _test_process(self, mock_gettempdir, mock_get_os_utils, filename, + exception=False): + fake_dir_path = os.path.join("fake", "dir") + mock_osutils = mock.MagicMock() + mock_part = mock.MagicMock() + mock_part.get_filename.return_value = filename + mock_gettempdir.return_value = fake_dir_path + + mock_get_os_utils.return_value = mock_osutils + + if exception: + mock_osutils.execute_process.side_effect = [Exception] + + with mock.patch("cloudbaseinit.plugins.windows.userdata-plugins." + "shellscript.open", mock.mock_open(), create=True): + response = self._shellscript.process(mock_part) + + mock_part.get_filename.assert_called_once_with() + mock_gettempdir.assert_called_once_with() + if filename.endswith(".cmd"): + mock_osutils.execute_process.assert_called_with( + [os.path.join(fake_dir_path, filename)], True) + elif filename.endswith(".sh"): + mock_osutils.execute_process.assert_called_with( + ['bash.exe', os.path.join(fake_dir_path, filename)], False) + elif filename.endswith(".ps1"): + mock_osutils.execute_process.assert_called_with( + ['powershell.exe', '-ExecutionPolicy', 'RemoteSigned', + '-NonInteractive', os.path.join(fake_dir_path, filename)], + False) + self.assertFalse(response) + + def test_process_cmd(self): + self._test_process(filename='fake.cmd') + + def test_process_sh(self): + self._test_process(filename='fake.cmd') + + def test_process_ps1(self): + self._test_process(filename='fake.cmd') + + def test_process_other(self): + self._test_process(filename='fake.other') + + def test_process_exception(self): + self._test_process(filename='fake.cmd', exception=True) diff --git a/cloudbaseinit/tests/test_init.py b/cloudbaseinit/tests/test_init.py new file mode 100644 index 00000000..21cf0a5f --- /dev/null +++ b/cloudbaseinit/tests/test_init.py @@ -0,0 +1,141 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import unittest +import sys + +from cloudbaseinit import init +from cloudbaseinit.plugins import base +from cloudbaseinit.openstack.common import cfg + +CONF = cfg.CONF +_win32com_mock = mock.MagicMock() +_comtypes_mock = mock.MagicMock() +_pywintypes_mock = mock.MagicMock() +_ctypes_mock = mock.MagicMock() +_ctypes_util_mock = mock.MagicMock() +mock_dict = {'ctypes.util': _ctypes_util_mock, + 'win32com': _win32com_mock, + 'comtypes': _comtypes_mock, + 'pywintypes': _pywintypes_mock, + 'ctypes': _ctypes_mock} + + +class InitManagerTest(unittest.TestCase): + @mock.patch.dict(sys.modules, mock_dict) + def setUp(self): + self.osutils = mock.MagicMock() + self.plugin = mock.MagicMock() + self._init = init.InitManager() + + def tearDown(self): + reload(sys) + reload(init) + + def test_get_plugin_status(self): + self.osutils.get_config_value.return_value = 1 + response = self._init._get_plugin_status(self.osutils, 'fake plugin') + self.osutils.get_config_value.assert_called_once_with( + 'fake plugin', self._init._PLUGINS_CONFIG_SECTION) + self.assertTrue(response == 1) + + def test_set_plugin_status(self): + + self._init._set_plugin_status(self.osutils, 'fake plugin', 'status') + self.osutils.set_config_value.assert_called_once_with( + 'fake plugin', 'status', self._init._PLUGINS_CONFIG_SECTION) + + @mock.patch('cloudbaseinit.init.InitManager._get_plugin_status') + @mock.patch('cloudbaseinit.init.InitManager._set_plugin_status') + def _test_exec_plugin(self, status, mock_set_plugin_status, + mock_get_plugin_status): + fake_name = 'fake name' + self.plugin.get_name.return_value = fake_name + self.plugin.execute.return_value = (status, True) + mock_get_plugin_status.return_value = status + + response = self._init._exec_plugin(osutils=self.osutils, + service='fake service', + plugin=self.plugin, + shared_data='shared data') + + mock_get_plugin_status.assert_called_once_with(self.osutils, + fake_name) + if status is base.PLUGIN_EXECUTE_ON_NEXT_BOOT: + self.plugin.execute.assert_called_once_with('fake service', + 'shared data') + mock_set_plugin_status.assert_called_once_with(self.osutils, + fake_name, status) + self.assertTrue(response) + + def test_test_exec_plugin_execution_done(self): + self._test_exec_plugin(base.PLUGIN_EXECUTION_DONE) + + def test_test_exec_plugin(self): + self._test_exec_plugin(base.PLUGIN_EXECUTE_ON_NEXT_BOOT) + + def _test_check_plugin_os_requirements(self, requirements): + sys.platform = 'win32' + fake_name = 'fake name' + self.plugin.get_name.return_value = fake_name + self.plugin.get_os_requirements.return_value = requirements + + response = self._init._check_plugin_os_requirements(self.osutils, + self.plugin) + + self.plugin.get_name.assert_called_once_with() + self.plugin.get_os_requirements.assert_called_once_with() + if requirements[0] == 'win32': + self.assertTrue(response) + else: + self.assertFalse(response) + + def test_check_plugin_os_requirements(self): + self._test_check_plugin_os_requirements(('win32', (5, 2))) + + def test_check_plugin_os_requirements_other_requirenments(self): + self._test_check_plugin_os_requirements(('linux', (5, 2))) + + @mock.patch('cloudbaseinit.init.InitManager' + '._check_plugin_os_requirements') + @mock.patch('cloudbaseinit.init.InitManager._exec_plugin') + @mock.patch('cloudbaseinit.plugins.factory.PluginFactory.load_plugins') + @mock.patch('cloudbaseinit.osutils.factory.OSUtilsFactory.get_os_utils') + @mock.patch('cloudbaseinit.metadata.factory.MetadataServiceFactory.' + 'get_metadata_service') + def test_configure_host(self, mock_get_metadata_service, + mock_get_os_utils, mock_load_plugins, + mock_exec_plugin, + mock_check_os_requirements): + fake_service = mock.MagicMock() + fake_plugin = mock.MagicMock() + mock_load_plugins.return_value = [fake_plugin] + mock_get_os_utils.return_value = self.osutils + mock_get_metadata_service.return_value = fake_service + fake_service.get_name.return_value = 'fake name' + + self._init.configure_host() + + self.osutils.wait_for_boot_completion.assert_called_once() + mock_get_metadata_service.assert_called_once_with() + fake_service.get_name.assert_called_once_with() + mock_check_os_requirements.assert_called_once_with(self.osutils, + fake_plugin) + mock_exec_plugin.assert_called_once_with(self.osutils, fake_service, + fake_plugin, {}) + fake_service.cleanup.assert_called_once_with() + self.osutils.reboot.assert_called_once_with()