From 21149582ff944585fb0a815ebe9515c1ec38eb7f Mon Sep 17 00:00:00 2001 From: Huan Xie Date: Thu, 15 Dec 2016 03:05:53 -0800 Subject: [PATCH] XenAPI Remove useless files when use os-xenapi lib XenServer has released os-xenapi lib on pypi, I have made a patch https://review.openstack.org/#/c/406059/ use os-xenapi in nova project. In this patch, we just delete those useless files and folders. Change-Id: I7cd83a492bae38fd9249f25b95663fa4385b262c Partially-Implements: blueprint add-os-xenapi-library --- .../tests/unit/virt/xenapi/client/__init__.py | 0 .../unit/virt/xenapi/client/test_objects.py | 118 ---- .../unit/virt/xenapi/client/test_session.py | 266 -------- .../unit/virt/xenapi/plugins/__init__.py | 0 .../unit/virt/xenapi/plugins/plugin_test.py | 69 -- .../virt/xenapi/plugins/test_bandwidth.py | 50 -- .../plugins/test_nova_plugin_version.py | 28 - .../xenapi/plugins/test_partition_utils.py | 108 --- .../xenapi/plugins/test_pluginlib_nova.py | 154 ----- nova/virt/xenapi/client/__init__.py | 0 nova/virt/xenapi/client/objects.py | 148 ---- nova/virt/xenapi/client/session.py | 393 ----------- plugins/xenserver/xenapi/README | 8 - .../etc/xapi.d/plugins/_bittorrent_seeder | 1 - .../etc/xapi.d/plugins/_bittorrent_seeder.py | 129 ---- .../xenserver/xenapi/etc/xapi.d/plugins/agent | 1 - .../xenapi/etc/xapi.d/plugins/agent.py | 268 -------- .../xenapi/etc/xapi.d/plugins/bandwidth | 1 - .../xenapi/etc/xapi.d/plugins/bandwidth.py | 64 -- .../xenapi/etc/xapi.d/plugins/bittorrent | 1 - .../xenapi/etc/xapi.d/plugins/bittorrent.py | 327 --------- .../xenapi/etc/xapi.d/plugins/config_file | 1 - .../xenapi/etc/xapi.d/plugins/config_file.py | 34 - .../xenapi/etc/xapi.d/plugins/console | 1 - .../xenapi/etc/xapi.d/plugins/console.py | 89 --- .../xenapi/etc/xapi.d/plugins/glance | 1 - .../xenapi/etc/xapi.d/plugins/glance.py | 632 ------------------ .../xenserver/xenapi/etc/xapi.d/plugins/ipxe | 1 - .../xenapi/etc/xapi.d/plugins/ipxe.py | 140 ---- .../xenapi/etc/xapi.d/plugins/kernel | 1 - .../xenapi/etc/xapi.d/plugins/kernel.py | 143 ---- .../xenapi/etc/xapi.d/plugins/migration | 1 - .../xenapi/etc/xapi.d/plugins/migration.py | 84 --- .../etc/xapi.d/plugins/nova_plugin_version | 1 - .../etc/xapi.d/plugins/nova_plugin_version.py | 46 -- .../etc/xapi.d/plugins/partition_utils.py | 87 --- .../etc/xapi.d/plugins/pluginlib_nova.py | 147 ---- .../xenapi/etc/xapi.d/plugins/utils.py | 519 -------------- .../xenapi/etc/xapi.d/plugins/workarounds | 1 - .../xenapi/etc/xapi.d/plugins/workarounds.py | 53 -- .../xenapi/etc/xapi.d/plugins/xenhost | 1 - .../xenapi/etc/xapi.d/plugins/xenhost.py | 626 ----------------- .../xenapi/etc/xapi.d/plugins/xenstore.py | 218 ------ 43 files changed, 4961 deletions(-) delete mode 100644 nova/tests/unit/virt/xenapi/client/__init__.py delete mode 100644 nova/tests/unit/virt/xenapi/client/test_objects.py delete mode 100644 nova/tests/unit/virt/xenapi/client/test_session.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/__init__.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/plugin_test.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/test_bandwidth.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/test_nova_plugin_version.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/test_partition_utils.py delete mode 100644 nova/tests/unit/virt/xenapi/plugins/test_pluginlib_nova.py delete mode 100644 nova/virt/xenapi/client/__init__.py delete mode 100644 nova/virt/xenapi/client/objects.py delete mode 100644 nova/virt/xenapi/client/session.py delete mode 100644 plugins/xenserver/xenapi/README delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/agent delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/console delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/console.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/glance delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/glance.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/migration delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/migration.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version.py delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/partition_utils.py delete mode 100644 plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py delete mode 100644 plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds.py delete mode 120000 plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost.py delete mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py diff --git a/nova/tests/unit/virt/xenapi/client/__init__.py b/nova/tests/unit/virt/xenapi/client/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/nova/tests/unit/virt/xenapi/client/test_objects.py b/nova/tests/unit/virt/xenapi/client/test_objects.py deleted file mode 100644 index 6730745df92c..000000000000 --- a/nova/tests/unit/virt/xenapi/client/test_objects.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) 2014 Rackspace Hosting -# 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 - -from nova.tests.unit.virt.xenapi import stubs -from nova import utils -from nova.virt.xenapi.client import objects - - -class XenAPISessionObjectTestCase(stubs.XenAPITestBaseNoDB): - def setUp(self): - super(XenAPISessionObjectTestCase, self).setUp() - self.session = mock.Mock() - self.obj = objects.XenAPISessionObject(self.session, "FAKE") - - def test_call_method_via_attr(self): - self.session.call_xenapi.return_value = "asdf" - - result = self.obj.get_X("ref") - - self.assertEqual(result, "asdf") - self.session.call_xenapi.assert_called_once_with("FAKE.get_X", "ref") - - -class ObjectsTestCase(stubs.XenAPITestBaseNoDB): - def setUp(self): - super(ObjectsTestCase, self).setUp() - self.session = mock.Mock() - - def test_VM(self): - vm = objects.VM(self.session) - vm.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VM.get_X", "ref") - - def test_SR(self): - sr = objects.SR(self.session) - sr.get_X("ref") - self.session.call_xenapi.assert_called_once_with("SR.get_X", "ref") - - def test_VDI(self): - vdi = objects.VDI(self.session) - vdi.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VDI.get_X", "ref") - - def test_VIF(self): - vdi = objects.VIF(self.session) - vdi.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VIF.get_X", "ref") - - def test_VBD(self): - vbd = objects.VBD(self.session) - vbd.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VBD.get_X", "ref") - - def test_PBD(self): - pbd = objects.PBD(self.session) - pbd.get_X("ref") - self.session.call_xenapi.assert_called_once_with("PBD.get_X", "ref") - - def test_PIF(self): - pif = objects.PIF(self.session) - pif.get_X("ref") - self.session.call_xenapi.assert_called_once_with("PIF.get_X", "ref") - - def test_VLAN(self): - vlan = objects.VLAN(self.session) - vlan.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VLAN.get_X", "ref") - - def test_host(self): - host = objects.Host(self.session) - host.get_X("ref") - self.session.call_xenapi.assert_called_once_with("host.get_X", "ref") - - def test_network(self): - network = objects.Network(self.session) - network.get_X("ref") - self.session.call_xenapi.assert_called_once_with("network.get_X", - "ref") - - def test_pool(self): - pool = objects.Pool(self.session) - pool.get_X("ref") - self.session.call_xenapi.assert_called_once_with("pool.get_X", "ref") - - -class VBDTestCase(stubs.XenAPITestBaseNoDB): - def setUp(self): - super(VBDTestCase, self).setUp() - self.session = mock.Mock() - self.session.VBD = objects.VBD(self.session) - - def test_plug(self): - self.session.VBD.plug("vbd_ref", "vm_ref") - self.session.call_xenapi.assert_called_once_with("VBD.plug", "vbd_ref") - - def test_unplug(self): - self.session.VBD.unplug("vbd_ref", "vm_ref") - self.session.call_xenapi.assert_called_once_with("VBD.unplug", - "vbd_ref") - - @mock.patch.object(utils, 'synchronized') - def test_vbd_plug_check_synchronized(self, mock_synchronized): - self.session.VBD.unplug("vbd_ref", "vm_ref") - mock_synchronized.assert_called_once_with("xenapi-vbd-vm_ref") diff --git a/nova/tests/unit/virt/xenapi/client/test_session.py b/nova/tests/unit/virt/xenapi/client/test_session.py deleted file mode 100644 index 6d575a06531c..000000000000 --- a/nova/tests/unit/virt/xenapi/client/test_session.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright (c) 2014 Rackspace Hosting -# 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 errno -import socket - -import mock - -from nova import exception -from nova.tests.unit.virt.xenapi import stubs -from nova import version -from nova.virt.xenapi.client import session -from nova.virt.xenapi import fake as xenapi_fake - - -class SessionTestCase(stubs.XenAPITestBaseNoDB): - @mock.patch.object(session.XenAPISession, '_get_platform_version') - @mock.patch.object(session.XenAPISession, '_create_session') - @mock.patch.object(session.XenAPISession, '_get_product_version_and_brand') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - def test_session_passes_version(self, mock_verify, mock_version, - create_session, mock_platform_version): - sess = mock.Mock() - create_session.return_value = sess - mock_version.return_value = ('version', 'brand') - mock_platform_version.return_value = (2, 1, 0) - session.XenAPISession('http://someserver', 'username', 'password') - - expected_version = '%s %s %s' % (version.vendor_string(), - version.product_string(), - version.version_string_with_package()) - sess.login_with_password.assert_called_with('username', 'password', - expected_version, - 'OpenStack') - - @mock.patch.object(session.XenAPISession, '_get_platform_version') - @mock.patch('eventlet.timeout.Timeout') - @mock.patch.object(session.XenAPISession, '_create_session') - @mock.patch.object(session.XenAPISession, '_get_product_version_and_brand') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - def test_session_login_with_timeout(self, mock_verify, mock_version, - create_session, mock_timeout, - mock_platform_version): - self.flags(connection_concurrent=2, group='xenserver') - sess = mock.Mock() - create_session.return_value = sess - mock_version.return_value = ('version', 'brand') - mock_platform_version.return_value = (2, 1, 0) - - session.XenAPISession('http://someserver', 'username', 'password') - self.assertEqual(2, sess.login_with_password.call_count) - self.assertEqual(2, mock_timeout.call_count) - - @mock.patch.object(session.XenAPISession, '_get_platform_version') - @mock.patch('eventlet.timeout.Timeout') - @mock.patch.object(session.XenAPISession, '_create_session') - @mock.patch.object(session.XenAPISession, '_get_product_version_and_brand') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - @mock.patch.object(session.XenAPISession, '_get_host_uuid') - @mock.patch.object(session.XenAPISession, '_get_host_ref') - def test_session_raises_exception(self, mock_ref, mock_uuid, - mock_verify, mock_version, - create_session, mock_timeout, - mock_platform_version): - import XenAPI - self.flags(connection_concurrent=2, group='xenserver') - sess = mock.Mock() - create_session.return_value = sess - # First login fails, second login in except block succeeds, - # third login for the pool succeeds - sess.login_with_password.side_effect = [ - XenAPI.Failure(['HOST_IS_SLAVE', 'master']), None, None] - mock_version.return_value = ('version', 'brand') - mock_platform_version.return_value = (2, 1, 0) - - session.XenAPISession('http://slave', 'username', 'password') - self.assertEqual(3, sess.login_with_password.call_count) - self.assertEqual(3, mock_timeout.call_count) - - @mock.patch.object(session.XenAPISession, 'call_plugin') - @mock.patch.object(session.XenAPISession, '_get_software_version') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - @mock.patch.object(session.XenAPISession, '_create_session') - def test_relax_xsm_sr_check_true(self, mock_create_session, - mock_verify_plugin_version, - mock_get_software_version, - mock_call_plugin): - sess = mock.Mock() - mock_create_session.return_value = sess - mock_get_software_version.return_value = {'product_version': '6.5.0', - 'product_brand': 'XenServer', - 'platform_version': '1.9.0'} - # mark relax-xsm-sr-check=True in /etc/xapi.conf - mock_call_plugin.return_value = "True" - xenapi_sess = session.XenAPISession( - 'http://someserver', 'username', 'password') - self.assertTrue(xenapi_sess.is_xsm_sr_check_relaxed()) - - @mock.patch.object(session.XenAPISession, 'call_plugin') - @mock.patch.object(session.XenAPISession, '_get_software_version') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - @mock.patch.object(session.XenAPISession, '_create_session') - def test_relax_xsm_sr_check_XS65_missing(self, mock_create_session, - mock_verify_plugin_version, - mock_get_software_version, - mock_call_plugin): - sess = mock.Mock() - mock_create_session.return_value = sess - mock_get_software_version.return_value = {'product_version': '6.5.0', - 'product_brand': 'XenServer', - 'platform_version': '1.9.0'} - # mark no relax-xsm-sr-check setting in /etc/xapi.conf - mock_call_plugin.return_value = "" - xenapi_sess = session.XenAPISession( - 'http://someserver', 'username', 'password') - self.assertFalse(xenapi_sess.is_xsm_sr_check_relaxed()) - - @mock.patch.object(session.XenAPISession, 'call_plugin') - @mock.patch.object(session.XenAPISession, '_get_software_version') - @mock.patch.object(session.XenAPISession, '_verify_plugin_version') - @mock.patch.object(session.XenAPISession, '_create_session') - def test_relax_xsm_sr_check_XS7_missing(self, mock_create_session, - mock_verify_plugin_version, - mock_get_software_version, - mock_call_plugin): - sess = mock.Mock() - mock_create_session.return_value = sess - mock_get_software_version.return_value = {'product_version': '7.0.0', - 'product_brand': 'XenServer', - 'platform_version': '2.1.0'} - # mark no relax-xsm-sr-check in /etc/xapi.conf - mock_call_plugin.return_value = "" - xenapi_sess = session.XenAPISession( - 'http://someserver', 'username', 'password') - self.assertTrue(xenapi_sess.is_xsm_sr_check_relaxed()) - - -class ApplySessionHelpersTestCase(stubs.XenAPITestBaseNoDB): - def setUp(self): - super(ApplySessionHelpersTestCase, self).setUp() - self.session = mock.Mock() - session.apply_session_helpers(self.session) - - def test_apply_session_helpers_add_VM(self): - self.session.VM.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VM.get_X", "ref") - - def test_apply_session_helpers_add_SR(self): - self.session.SR.get_X("ref") - self.session.call_xenapi.assert_called_once_with("SR.get_X", "ref") - - def test_apply_session_helpers_add_VDI(self): - self.session.VDI.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VDI.get_X", "ref") - - def test_apply_session_helpers_add_VIF(self): - self.session.VIF.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VIF.get_X", "ref") - - def test_apply_session_helpers_add_VBD(self): - self.session.VBD.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VBD.get_X", "ref") - - def test_apply_session_helpers_add_PBD(self): - self.session.PBD.get_X("ref") - self.session.call_xenapi.assert_called_once_with("PBD.get_X", "ref") - - def test_apply_session_helpers_add_PIF(self): - self.session.PIF.get_X("ref") - self.session.call_xenapi.assert_called_once_with("PIF.get_X", "ref") - - def test_apply_session_helpers_add_VLAN(self): - self.session.VLAN.get_X("ref") - self.session.call_xenapi.assert_called_once_with("VLAN.get_X", "ref") - - def test_apply_session_helpers_add_host(self): - self.session.host.get_X("ref") - self.session.call_xenapi.assert_called_once_with("host.get_X", "ref") - - def test_apply_session_helpers_add_network(self): - self.session.network.get_X("ref") - self.session.call_xenapi.assert_called_once_with("network.get_X", - "ref") - - def test_apply_session_helpers_add_pool(self): - self.session.pool.get_X("ref") - self.session.call_xenapi.assert_called_once_with("pool.get_X", "ref") - - -class CallPluginTestCase(stubs.XenAPITestBaseNoDB): - def _get_fake_xapisession(self): - class FakeXapiSession(session.XenAPISession): - def __init__(self, **kwargs): - "Skip the superclass's dirty init" - self.XenAPI = mock.MagicMock() - self.XenAPI.Failure = xenapi_fake.Failure - - return FakeXapiSession() - - def setUp(self): - super(CallPluginTestCase, self).setUp() - self.session = self._get_fake_xapisession() - - def test_serialized_with_retry_socket_error_conn_reset(self): - exc = socket.error() - exc.errno = errno.ECONNRESET - plugin = 'glance' - fn = 'download_vhd' - num_retries = 1 - callback = None - retry_cb = mock.Mock() - with mock.patch.object(self.session, 'call_plugin_serialized', - spec=True) as call_plugin_serialized: - call_plugin_serialized.side_effect = exc - self.assertRaises(exception.PluginRetriesExceeded, - self.session.call_plugin_serialized_with_retry, plugin, fn, - num_retries, callback, retry_cb) - call_plugin_serialized.assert_called_with(plugin, fn) - self.assertEqual(2, call_plugin_serialized.call_count) - self.assertEqual(2, retry_cb.call_count) - - def test_serialized_with_retry_socket_error_reraised(self): - exc = socket.error() - exc.errno = errno.ECONNREFUSED - plugin = 'glance' - fn = 'download_vhd' - num_retries = 1 - callback = None - retry_cb = mock.Mock() - with mock.patch.object(self.session, 'call_plugin_serialized', - spec=True) as call_plugin_serialized: - call_plugin_serialized.side_effect = exc - self.assertRaises(socket.error, - self.session.call_plugin_serialized_with_retry, plugin, fn, - num_retries, callback, retry_cb) - call_plugin_serialized.assert_called_once_with(plugin, fn) - self.assertEqual(0, retry_cb.call_count) - - def test_serialized_with_retry_socket_reset_reraised(self): - exc = socket.error() - exc.errno = errno.ECONNRESET - plugin = 'glance' - fn = 'download_vhd' - num_retries = 1 - callback = None - retry_cb = mock.Mock() - with mock.patch.object(self.session, 'call_plugin_serialized', - spec=True) as call_plugin_serialized: - call_plugin_serialized.side_effect = exc - self.assertRaises(exception.PluginRetriesExceeded, - self.session.call_plugin_serialized_with_retry, plugin, fn, - num_retries, callback, retry_cb) - call_plugin_serialized.assert_called_with(plugin, fn) - self.assertEqual(2, call_plugin_serialized.call_count) diff --git a/nova/tests/unit/virt/xenapi/plugins/__init__.py b/nova/tests/unit/virt/xenapi/plugins/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/nova/tests/unit/virt/xenapi/plugins/plugin_test.py b/nova/tests/unit/virt/xenapi/plugins/plugin_test.py deleted file mode 100644 index c08cac541187..000000000000 --- a/nova/tests/unit/virt/xenapi/plugins/plugin_test.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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 imp -import os -import sys - -import mock - -from nova import test -from nova.virt.xenapi.client import session - - -# both XenAPI and XenAPIPlugin may not exist -# in unit test environment. -sys.modules['XenAPI'] = mock.Mock() -sys.modules['XenAPIPlugin'] = mock.Mock() - - -class PluginTestBase(test.NoDBTestCase): - def setUp(self): - super(PluginTestBase, self).setUp() - self.session = mock.Mock() - session.apply_session_helpers(self.session) - - def mock_patch_object(self, target, attribute, return_val=None): - # utility function to mock object's attribute - patcher = mock.patch.object(target, attribute, return_value=return_val) - mock_one = patcher.start() - self.addCleanup(patcher.stop) - return mock_one - - def _get_plugin_path(self): - current_path = os.path.realpath(__file__) - rel_path = os.path.join(current_path, - "../../../../../../../plugins/xenserver/xenapi/etc/xapi.d/plugins") - plugin_path = os.path.abspath(rel_path) - return plugin_path - - def load_plugin(self, file_name): - # XAPI plugins run in a py24 environment and may be not compatible with - # py34's syntax. In order to prevent unit test scanning the source file - # under py34 environment, the plugins will be imported with this - # function at run time. - - plugin_path = self._get_plugin_path() - - # add plugin path into search path. - if plugin_path not in sys.path: - sys.path.append(plugin_path) - - # be sure not to create c files next to the plugins - sys.dont_write_bytecode = True - - name = file_name.split('.')[0] - path = os.path.join(plugin_path, file_name) - return imp.load_source(name, path) diff --git a/nova/tests/unit/virt/xenapi/plugins/test_bandwidth.py b/nova/tests/unit/virt/xenapi/plugins/test_bandwidth.py deleted file mode 100644 index beb9f99a7bd1..000000000000 --- a/nova/tests/unit/virt/xenapi/plugins/test_bandwidth.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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. - -from nova.tests.unit.virt.xenapi.plugins import plugin_test - - -class BandwidthTestCase(plugin_test.PluginTestBase): - def setUp(self): - super(BandwidthTestCase, self).setUp() - self.pluginlib = self.load_plugin("pluginlib_nova.py") - - # Prevent any logging to syslog - self.mock_patch_object(self.pluginlib, - 'configure_logging') - - self.bandwidth = self.load_plugin("bandwidth") - - def test_get_bandwitdth_from_proc(self): - fake_data = ['Inter-| Receive | Transmit', - 'if|bw_in i1 i2 i3 i4 i5 i6 i7|bw_out o1 o2 o3 o4 o5 o6 o7', - 'xenbr1: 1 0 0 0 0 0 0 0 11 0 0 0 0 0 0 0', - 'vif2.0: 2 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0', - 'vif2.1: 3 0 0 0 0 0 0 0 13 0 0 0 0 0 0 0', - 'vifabcd1234-c: 4 0 0 0 0 0 0 0 14 0 0 0 0 0 0 0\n'] - expect_devmap = {'2': {'1': {'bw_in': 13, 'bw_out': 3}, - '0': {'bw_in': 12, 'bw_out': 2} - } - } - - mock_read_proc_net = self.mock_patch_object( - self.bandwidth, - '_read_proc_net', - return_val=fake_data) - - devmap = self.bandwidth._get_bandwitdth_from_proc() - - self.assertTrue(mock_read_proc_net.called) - self.assertEqual(devmap, expect_devmap) diff --git a/nova/tests/unit/virt/xenapi/plugins/test_nova_plugin_version.py b/nova/tests/unit/virt/xenapi/plugins/test_nova_plugin_version.py deleted file mode 100644 index 9429e24d1255..000000000000 --- a/nova/tests/unit/virt/xenapi/plugins/test_nova_plugin_version.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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. - -from nova.tests.unit.virt.xenapi.plugins import plugin_test - - -class NovaPluginVersion(plugin_test.PluginTestBase): - def setUp(self): - super(NovaPluginVersion, self).setUp() - self.nova_plugin_version = self.load_plugin('nova_plugin_version.py') - - def test_nova_plugin_version(self): - session = 'fake_session' - expected_value = self.nova_plugin_version.PLUGIN_VERSION - return_value = self.nova_plugin_version.get_version(session) - self.assertEqual(expected_value, return_value) diff --git a/nova/tests/unit/virt/xenapi/plugins/test_partition_utils.py b/nova/tests/unit/virt/xenapi/plugins/test_partition_utils.py deleted file mode 100644 index e1fceacfc7db..000000000000 --- a/nova/tests/unit/virt/xenapi/plugins/test_partition_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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 - -from nova import test -from nova.tests.unit.virt.xenapi.plugins import plugin_test - - -class PartitionUtils(plugin_test.PluginTestBase): - def setUp(self): - super(PartitionUtils, self).setUp() - self.pluginlib = self.load_plugin("pluginlib_nova.py") - - # Prevent any logging to syslog - self.mock_patch_object(self.pluginlib, - 'configure_logging') - - self.partition_utils = self.load_plugin("partition_utils.py") - - def test_wait_for_dev_ok(self): - mock_sleep = self.mock_patch_object(self.partition_utils.time, - 'sleep') - mock_exists = self.mock_patch_object(self.partition_utils.os.path, - 'exists') - mock_exists.side_effect = [False, True] - ret = self.partition_utils.wait_for_dev('session', '/fake', 2) - - self.assertEqual(1, mock_sleep.call_count) - self.assertEqual(ret, "/fake") - - def test_wait_for_dev_timeout(self): - mock_sleep = self.mock_patch_object(self.partition_utils.time, - 'sleep') - mock_exists = self.mock_patch_object(self.partition_utils.os.path, - 'exists') - mock_exists.side_effect = [False, False, True] - ret = self.partition_utils.wait_for_dev('session', '/fake', 2) - - self.assertEqual(2, mock_sleep.call_count) - self.assertEqual(ret, "") - - def test_mkfs_removes_partitions_ok(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - mock__mkfs = self.mock_patch_object(self.partition_utils, '_mkfs') - - self.partition_utils.mkfs('session', 'fakedev', '1', 'ext3', 'label') - mock__mkfs.assert_called_with('ext3', '/dev/mapper/fakedevp1', - 'label') - expected_calls = [mock.call(['kpartx', '-avspp', '/dev/fakedev'])] - expected_calls.append(mock.call(['kpartx', '-dvspp', '/dev/fakedev'])) - mock_run.assert_has_calls(expected_calls) - - def test_mkfs_removes_partitions_exc(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - mock__mkfs = self.mock_patch_object(self.partition_utils, '_mkfs') - mock__mkfs.side_effect = test.TestingException() - - self.assertRaises(test.TestingException, self.partition_utils.mkfs, - 'session', 'fakedev', '1', 'ext3', 'label') - expected_calls = [mock.call(['kpartx', '-avspp', '/dev/fakedev'])] - expected_calls.append(mock.call(['kpartx', '-dvspp', '/dev/fakedev'])) - mock_run.assert_has_calls(expected_calls) - - def test_mkfs_ext3_no_label(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - - self.partition_utils._mkfs('ext3', '/dev/sda1', None) - mock_run.assert_called_with(['mkfs', '-t', 'ext3', '-F', '/dev/sda1']) - - def test_mkfs_ext3(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - - self.partition_utils._mkfs('ext3', '/dev/sda1', 'label') - mock_run.assert_called_with(['mkfs', '-t', 'ext3', '-F', '-L', - 'label', '/dev/sda1']) - - def test_mkfs_swap(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - - self.partition_utils._mkfs('swap', '/dev/sda1', 'ignored') - mock_run.assert_called_with(['mkswap', '/dev/sda1']) - - def test_make_partition(self): - mock_run = self.mock_patch_object(self.partition_utils.utils, - 'run_command') - - self.partition_utils.make_partition('session', 'dev', 'start', '-') - - mock_run.assert_called_with(['sfdisk', '-uS', '/dev/dev'], - 'start,;\n') diff --git a/nova/tests/unit/virt/xenapi/plugins/test_pluginlib_nova.py b/nova/tests/unit/virt/xenapi/plugins/test_pluginlib_nova.py deleted file mode 100644 index 15a18c25efe5..000000000000 --- a/nova/tests/unit/virt/xenapi/plugins/test_pluginlib_nova.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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 - -from nova.tests.unit.virt.xenapi.plugins import plugin_test - - -class FakeUnplugException(Exception): - def __init__(self, details): - self.details = details - - -class PluginlibNova(plugin_test.PluginTestBase): - def setUp(self): - super(PluginlibNova, self).setUp() - self.pluginlib_nova = self.load_plugin("pluginlib_nova.py") - - @mock.patch('socket.socket.connect') - def test_configure_logging(self, mock_connect): - name = 'fake_name' - mock_Logger_setLevel = self.mock_patch_object( - self.pluginlib_nova.logging.Logger, 'setLevel') - mock_sysh_setLevel = self.mock_patch_object( - self.pluginlib_nova.logging.handlers.SysLogHandler, 'setLevel') - mock_Formatter = self.mock_patch_object( - self.pluginlib_nova.logging, 'Formatter') - mock_sysh_setFormatter = self.mock_patch_object( - self.pluginlib_nova.logging.handlers.SysLogHandler, 'setFormatter') - mock_Logger_addHandler = self.mock_patch_object( - self.pluginlib_nova.logging.Logger, 'addHandler') - - self.pluginlib_nova.configure_logging(name) - - self.assertTrue(mock_Logger_setLevel.called) - self.assertTrue(mock_sysh_setLevel.called) - self.assertTrue(mock_Formatter.called) - self.assertTrue(mock_sysh_setFormatter.called) - self.assertTrue(mock_Logger_addHandler.called) - - def test_exists_ok(self): - fake_args = {'k1': 'v1'} - self.assertEqual('v1', self.pluginlib_nova.exists(fake_args, 'k1')) - - def test_exists_exception(self): - fake_args = {'k1': 'v1'} - self.assertRaises(self.pluginlib_nova.ArgumentError, - self.pluginlib_nova.exists, - fake_args, - 'no_key') - - def test_optional_exist(self): - fake_args = {'k1': 'v1'} - self.assertEqual('v1', - self.pluginlib_nova.optional(fake_args, 'k1')) - - def test_optional_none(self): - fake_args = {'k1': 'v1'} - self.assertIsNone(self.pluginlib_nova.optional(fake_args, - 'no_key')) - - def test_get_domain_0(self): - mock_get_this_host = self.mock_patch_object( - self.session.xenapi.session, - 'get_this_host', - return_val='fake_host_ref') - mock_get_vm_records = self.mock_patch_object( - self.session.xenapi.VM, - 'get_all_records_where', - return_val={"fake_vm_ref": "fake_value"}) - - ret_value = self.pluginlib_nova._get_domain_0(self.session) - - self.assertTrue(mock_get_this_host.called) - self.assertTrue(mock_get_vm_records.called) - self.assertEqual('fake_vm_ref', ret_value) - - def test_with_vdi_in_dom0(self): - self.mock_patch_object( - self.pluginlib_nova, - '_get_domain_0', - return_val='fake_dom0_ref') - mock_vbd_create = self.mock_patch_object( - self.session.xenapi.VBD, - 'create', - return_val='fake_vbd_ref') - mock_vbd_plug = self.mock_patch_object( - self.session.xenapi.VBD, - 'plug') - self.mock_patch_object( - self.session.xenapi.VBD, - 'get_device', - return_val='fake_device_xvda') - mock_vbd_unplug_with_retry = self.mock_patch_object( - self.pluginlib_nova, - '_vbd_unplug_with_retry') - mock_vbd_destroy = self.mock_patch_object( - self.session.xenapi.VBD, - 'destroy') - - def handle_function(vbd): - # the fake vbd handle function - self.assertEqual(vbd, 'fake_device_xvda') - self.assertTrue(mock_vbd_plug.called) - self.assertFalse(mock_vbd_unplug_with_retry.called) - return 'function_called' - - fake_vdi = 'fake_vdi' - return_value = self.pluginlib_nova.with_vdi_in_dom0( - self.session, fake_vdi, False, handle_function) - - self.assertEqual('function_called', return_value) - self.assertTrue(mock_vbd_plug.called) - self.assertTrue(mock_vbd_unplug_with_retry.called) - self.assertTrue(mock_vbd_destroy.called) - args, kwargs = mock_vbd_create.call_args - self.assertEqual('fake_dom0_ref', args[0]['VM']) - self.assertEqual('RW', args[0]['mode']) - - def test_vbd_unplug_with_retry_success_at_first_time(self): - self.pluginlib_nova._vbd_unplug_with_retry(self.session, - 'fake_vbd_ref') - self.assertEqual(1, self.session.xenapi.VBD.unplug.call_count) - - def test_vbd_unplug_with_retry_detached_already(self): - error = FakeUnplugException(['DEVICE_ALREADY_DETACHED']) - - self.session.xenapi.VBD.unplug.side_effect = error - self.pluginlib_nova.XenAPI.Failure = FakeUnplugException - self.pluginlib_nova._vbd_unplug_with_retry(self.session, - 'fake_vbd_ref') - self.assertEqual(1, self.session.xenapi.VBD.unplug.call_count) - - def test_vbd_unplug_with_retry_success_at_second_time(self): - side_effects = [FakeUnplugException(['DEVICE_DETACH_REJECTED']), - None] - - self.session.xenapi.VBD.unplug.side_effect = side_effects - self.pluginlib_nova.XenAPI.Failure = FakeUnplugException - self.pluginlib_nova._vbd_unplug_with_retry(self.session, - 'fake_vbd_ref') - self.assertEqual(2, self.session.xenapi.VBD.unplug.call_count) diff --git a/nova/virt/xenapi/client/__init__.py b/nova/virt/xenapi/client/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/nova/virt/xenapi/client/objects.py b/nova/virt/xenapi/client/objects.py deleted file mode 100644 index 7f7215c047c7..000000000000 --- a/nova/virt/xenapi/client/objects.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# 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. - -from nova import utils - - -class XenAPISessionObject(object): - """Wrapper to make calling and mocking the session easier - - The XenAPI protocol is an XML RPC API that is based around the - XenAPI database, and operations you can do on each of the objects - stored in the database, such as VM, SR, VDI, etc. - - For more details see the XenAPI docs: - http://docs.vmd.citrix.com/XenServer/6.2.0/1.0/en_gb/api/ - - Most, objects like VM, SR, VDI, etc, share a common set of methods: - * vm_ref = session.VM.create(vm_rec) - * vm_ref = session.VM.get_by_uuid(uuid) - * session.VM.destroy(vm_ref) - * vm_refs = session.VM.get_all() - - Each object also has specific messages, or functions, such as: - * session.VM.clean_reboot(vm_ref) - - Each object has fields, like "VBDs" that can be fetched like this: - * vbd_refs = session.VM.get_VBDs(vm_ref) - - You can get all the fields by fetching the full record. - However please note this is much more expensive than just - fetching the field you require: - * vm_rec = session.VM.get_record(vm_ref) - - When searching for particular objects, you may be tempted - to use get_all(), but this often leads to races as objects - get deleted under your feet. It is preferable to use the undocumented: - * vms = session.VM.get_all_records_where( - 'field "is_control_domain"="true"') - - """ - - def __init__(self, session, name): - self.session = session - self.name = name - - def _call_method(self, method_name, *args): - call = "%s.%s" % (self.name, method_name) - return self.session.call_xenapi(call, *args) - - def __getattr__(self, method_name): - return lambda *params: self._call_method(method_name, *params) - - -class VM(XenAPISessionObject): - """Virtual Machine.""" - def __init__(self, session): - super(VM, self).__init__(session, "VM") - - -class VBD(XenAPISessionObject): - """Virtual block device.""" - def __init__(self, session): - super(VBD, self).__init__(session, "VBD") - - def plug(self, vbd_ref, vm_ref): - @utils.synchronized('xenapi-vbd-' + vm_ref) - def synchronized_plug(): - self._call_method("plug", vbd_ref) - - # NOTE(johngarbutt) we need to ensure there is only ever one - # VBD.unplug or VBD.plug happening at once per VM - # due to a bug in XenServer 6.1 and 6.2 - synchronized_plug() - - def unplug(self, vbd_ref, vm_ref): - @utils.synchronized('xenapi-vbd-' + vm_ref) - def synchronized_unplug(): - self._call_method("unplug", vbd_ref) - - # NOTE(johngarbutt) we need to ensure there is only ever one - # VBD.unplug or VBD.plug happening at once per VM - # due to a bug in XenServer 6.1 and 6.2 - synchronized_unplug() - - -class VDI(XenAPISessionObject): - """Virtual disk image.""" - def __init__(self, session): - super(VDI, self).__init__(session, "VDI") - - -class VIF(XenAPISessionObject): - """Virtual Network Interface.""" - def __init__(self, session): - super(VIF, self).__init__(session, "VIF") - - -class SR(XenAPISessionObject): - """Storage Repository.""" - def __init__(self, session): - super(SR, self).__init__(session, "SR") - - -class PBD(XenAPISessionObject): - """Physical block device.""" - def __init__(self, session): - super(PBD, self).__init__(session, "PBD") - - -class PIF(XenAPISessionObject): - """Physical Network Interface.""" - def __init__(self, session): - super(PIF, self).__init__(session, "PIF") - - -class VLAN(XenAPISessionObject): - """VLAN.""" - def __init__(self, session): - super(VLAN, self).__init__(session, "VLAN") - - -class Host(XenAPISessionObject): - """XenServer hosts.""" - def __init__(self, session): - super(Host, self).__init__(session, "host") - - -class Network(XenAPISessionObject): - """Networks that VIFs are attached to.""" - def __init__(self, session): - super(Network, self).__init__(session, "network") - - -class Pool(XenAPISessionObject): - """Pool of hosts.""" - def __init__(self, session): - super(Pool, self).__init__(session, "pool") diff --git a/nova/virt/xenapi/client/session.py b/nova/virt/xenapi/client/session.py deleted file mode 100644 index 566fd150c494..000000000000 --- a/nova/virt/xenapi/client/session.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# 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 ast -import contextlib - -try: - import cPickle as pickle -except ImportError: - import pickle - -import errno -import socket -import time - -from eventlet import queue -from eventlet import timeout -from oslo_log import log as logging -from oslo_utils import versionutils -from six.moves import http_client -from six.moves import range -from six.moves import urllib - -try: - import xmlrpclib -except ImportError: - import six.moves.xmlrpc_client as xmlrpclib - -import nova.conf -from nova import context -from nova import exception -from nova.i18n import _, _LE, _LW -from nova import objects -from nova import version -from nova.virt.xenapi.client import objects as cli_objects -from nova.virt.xenapi import pool -from nova.virt.xenapi import pool_states - -LOG = logging.getLogger(__name__) - -CONF = nova.conf.CONF - - -def apply_session_helpers(session): - session.VM = cli_objects.VM(session) - session.SR = cli_objects.SR(session) - session.VDI = cli_objects.VDI(session) - session.VIF = cli_objects.VIF(session) - session.VBD = cli_objects.VBD(session) - session.PBD = cli_objects.PBD(session) - session.PIF = cli_objects.PIF(session) - session.VLAN = cli_objects.VLAN(session) - session.host = cli_objects.Host(session) - session.network = cli_objects.Network(session) - session.pool = cli_objects.Pool(session) - - -class XenAPISession(object): - """The session to invoke XenAPI SDK calls.""" - - # This is not a config option as it should only ever be - # changed in development environments. - # MAJOR VERSION: Incompatible changes with the plugins - # MINOR VERSION: Compatible changes, new plguins, etc - PLUGIN_REQUIRED_VERSION = '1.8' - - def __init__(self, url, user, pw): - version_string = version.version_string_with_package() - self.nova_version = ('%(vendor)s %(product)s %(version)s' % - {'vendor': version.vendor_string(), - 'product': version.product_string(), - 'version': version_string}) - import XenAPI - self.XenAPI = XenAPI - self._sessions = queue.Queue() - self.is_slave = False - self.host_checked = False - exception = self.XenAPI.Failure(_("Unable to log in to XenAPI " - "(is the Dom0 disk full?)")) - self.url = self._create_first_session(url, user, pw, exception) - self._populate_session_pool(url, user, pw, exception) - self.host_uuid = self._get_host_uuid() - self.host_ref = self._get_host_ref() - self.product_version, self.product_brand = \ - self._get_product_version_and_brand() - self._verify_plugin_version() - self.platform_version = self._get_platform_version() - self._cached_xsm_sr_relaxed = None - - apply_session_helpers(self) - - def _login_with_password(self, user, pw, session, exception): - with timeout.Timeout(CONF.xenserver.login_timeout, exception): - session.login_with_password(user, pw, - self.nova_version, 'OpenStack') - - def _verify_plugin_version(self): - requested_version = self.PLUGIN_REQUIRED_VERSION - current_version = self.call_plugin_serialized( - 'nova_plugin_version.py', 'get_version') - - # v2.0 is the same as v1.8, with no version bumps. Remove this once - # Ocata is released - if requested_version == '2.0' and current_version == '1.8': - return - - if not versionutils.is_compatible(requested_version, current_version): - raise self.XenAPI.Failure( - _("Plugin version mismatch (Expected %(exp)s, got %(got)s)") % - {'exp': requested_version, 'got': current_version}) - - def _create_first_session(self, url, user, pw, exception): - try: - session = self._create_session_and_login(url, user, pw, exception) - except self.XenAPI.Failure as e: - # if user and pw of the master are different, we're doomed! - if e.details[0] == 'HOST_IS_SLAVE': - master = e.details[1] - url = pool.swap_xapi_host(url, master) - session = self._create_session_and_login(url, user, pw, - exception) - self.is_slave = True - else: - raise - self._sessions.put(session) - return url - - def _populate_session_pool(self, url, user, pw, exception): - for i in range(CONF.xenserver.connection_concurrent - 1): - session = self._create_session_and_login(url, user, pw, exception) - self._sessions.put(session) - - def _get_host_uuid(self): - if self.is_slave: - aggr = objects.AggregateList.get_by_host( - context.get_admin_context(), - CONF.host, key=pool_states.POOL_FLAG)[0] - if not aggr: - LOG.error(_LE('Host is member of a pool, but DB ' - 'says otherwise')) - raise exception.AggregateHostNotFound() - return aggr.metadata[CONF.host] - else: - with self._get_session() as session: - host_ref = session.xenapi.session.get_this_host(session.handle) - return session.xenapi.host.get_uuid(host_ref) - - def _get_product_version_and_brand(self): - """Return a tuple of (major, minor, rev) for the host version and - a string of the product brand. - """ - software_version = self._get_software_version() - - product_version_str = software_version.get('product_version') - # Product version is only set in some cases (e.g. XCP, XenServer) and - # not in others (e.g. xenserver-core, XAPI-XCP). - # In these cases, the platform version is the best number to use. - if product_version_str is None: - product_version_str = software_version.get('platform_version', - '0.0.0') - product_brand = software_version.get('product_brand') - product_version = versionutils.convert_version_to_tuple( - product_version_str) - - return product_version, product_brand - - def _get_platform_version(self): - """Return a tuple of (major, minor, rev) for the host version""" - software_version = self._get_software_version() - platform_version_str = software_version.get('platform_version', - '0.0.0') - platform_version = versionutils.convert_version_to_tuple( - platform_version_str) - return platform_version - - def _get_software_version(self): - return self.call_xenapi('host.get_software_version', self.host_ref) - - def get_session_id(self): - """Return a string session_id. Used for vnc consoles.""" - with self._get_session() as session: - return str(session._session) - - @contextlib.contextmanager - def _get_session(self): - """Return exclusive session for scope of with statement.""" - session = self._sessions.get() - try: - yield session - finally: - self._sessions.put(session) - - def _get_host_ref(self): - """Return the xenapi host on which nova-compute runs on.""" - with self._get_session() as session: - return session.xenapi.host.get_by_uuid(self.host_uuid) - - def call_xenapi(self, method, *args): - """Call the specified XenAPI method on a background thread.""" - with self._get_session() as session: - return session.xenapi_request(method, args) - - def call_plugin(self, plugin, fn, args): - """Call host.call_plugin on a background thread.""" - # NOTE(armando): pass the host uuid along with the args so that - # the plugin gets executed on the right host when using XS pools - args['host_uuid'] = self.host_uuid - - # TODO(sfinucan): Once the required plugin version is bumped to v2.0, - # we can assume that all files will have a '.py' extension. Until then, - # handle hosts without this extension by rewriting all calls to plugins - # to exclude the '.py' extension. This is made possible through the - # temporary inclusion of symlinks to plugins. - # NOTE(sfinucan): 'partition_utils.py' was the only plugin with a '.py' - # extension before this change was enacted, hence this plugin is - # excluded - if not plugin == 'partition_utils.py': - plugin = plugin.rstrip('.py') - - with self._get_session() as session: - return self._unwrap_plugin_exceptions( - session.xenapi.host.call_plugin, - self.host_ref, plugin, fn, args) - - def call_plugin_serialized(self, plugin, fn, *args, **kwargs): - params = {'params': pickle.dumps(dict(args=args, kwargs=kwargs))} - rv = self.call_plugin(plugin, fn, params) - return pickle.loads(rv) - - def call_plugin_serialized_with_retry(self, plugin, fn, num_retries, - callback, retry_cb=None, *args, - **kwargs): - """Allows a plugin to raise RetryableError so we can try again.""" - attempts = num_retries + 1 - sleep_time = 0.5 - for attempt in range(1, attempts + 1): - try: - if attempt > 1: - time.sleep(sleep_time) - sleep_time = min(2 * sleep_time, 15) - - callback_result = None - if callback: - callback_result = callback(kwargs) - - msg = ('%(plugin)s.%(fn)s attempt %(attempt)d/%(attempts)d, ' - 'callback_result: %(callback_result)s') - LOG.debug(msg, - {'plugin': plugin, 'fn': fn, 'attempt': attempt, - 'attempts': attempts, - 'callback_result': callback_result}) - return self.call_plugin_serialized(plugin, fn, *args, **kwargs) - except self.XenAPI.Failure as exc: - if self._is_retryable_exception(exc, fn): - LOG.warning(_LW('%(plugin)s.%(fn)s failed. ' - 'Retrying call.'), - {'plugin': plugin, 'fn': fn}) - if retry_cb: - retry_cb(exc=exc) - else: - raise - except socket.error as exc: - if exc.errno == errno.ECONNRESET: - LOG.warning(_LW('Lost connection to XenAPI during call to ' - '%(plugin)s.%(fn)s. Retrying call.'), - {'plugin': plugin, 'fn': fn}) - if retry_cb: - retry_cb(exc=exc) - else: - raise - - raise exception.PluginRetriesExceeded(num_retries=num_retries) - - def _is_retryable_exception(self, exc, fn): - _type, method, error = exc.details[:3] - if error == 'RetryableError': - LOG.debug("RetryableError, so retrying %(fn)s", {'fn': fn}, - exc_info=True) - return True - elif "signal" in method: - LOG.debug("Error due to a signal, retrying %(fn)s", {'fn': fn}, - exc_info=True) - return True - else: - return False - - def _create_session(self, url): - """Stubout point. This can be replaced with a mock session.""" - self.is_local_connection = url == "unix://local" - if self.is_local_connection: - return self.XenAPI.xapi_local() - return self.XenAPI.Session(url) - - def _create_session_and_login(self, url, user, pw, exception): - session = self._create_session(url) - self._login_with_password(user, pw, session, exception) - return session - - def _unwrap_plugin_exceptions(self, func, *args, **kwargs): - """Parse exception details.""" - try: - return func(*args, **kwargs) - except self.XenAPI.Failure as exc: - LOG.debug("Got exception: %s", exc) - if (len(exc.details) == 4 and - exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and - exc.details[2] == 'Failure'): - params = None - try: - params = ast.literal_eval(exc.details[3]) - except Exception: - raise exc - raise self.XenAPI.Failure(params) - else: - raise - except xmlrpclib.ProtocolError as exc: - LOG.debug("Got exception: %s", exc) - raise - - def get_rec(self, record_type, ref): - try: - return self.call_xenapi('%s.get_record' % record_type, ref) - except self.XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - - return None - - def get_all_refs_and_recs(self, record_type): - """Retrieve all refs and recs for a Xen record type. - - Handles race-conditions where the record may be deleted between - the `get_all` call and the `get_record` call. - """ - - return self.call_xenapi('%s.get_all_records' % record_type).items() - - @contextlib.contextmanager - def custom_task(self, label, desc=''): - """Return exclusive session for scope of with statement.""" - name = 'nova-%s' % (label) - task_ref = self.call_xenapi("task.create", name, - desc) - try: - LOG.debug('Created task %s with ref %s', name, task_ref) - yield task_ref - finally: - self.call_xenapi("task.destroy", task_ref) - LOG.debug('Destroyed task ref %s', task_ref) - - @contextlib.contextmanager - def http_connection(session): - conn = None - - xs_url = urllib.parse.urlparse(session.url) - LOG.debug("Creating http(s) connection to %s", session.url) - if xs_url.scheme == 'http': - conn = http_client.HTTPConnection(xs_url.netloc) - elif xs_url.scheme == 'https': - conn = http_client.HTTPSConnection(xs_url.netloc) - - conn.connect() - try: - yield conn - finally: - conn.close() - - def is_xsm_sr_check_relaxed(self): - if self._cached_xsm_sr_relaxed is None: - config_value = self.call_plugin('config_file', 'get_val', - key='relax-xsm-sr-check') - if not config_value: - version_str = '.'.join(str(v) for v in self.platform_version) - if versionutils.is_compatible('2.1.0', version_str, - same_major=False): - self._cached_xsm_sr_relaxed = True - else: - self._cached_xsm_sr_relaxed = False - else: - self._cached_xsm_sr_relaxed = config_value.lower() == 'true' - - return self._cached_xsm_sr_relaxed diff --git a/plugins/xenserver/xenapi/README b/plugins/xenserver/xenapi/README deleted file mode 100644 index 63d1bb7189ea..000000000000 --- a/plugins/xenserver/xenapi/README +++ /dev/null @@ -1,8 +0,0 @@ -This directory contains files that are required for the XenAPI support. -They should be installed in the XenServer / Xen Cloud Platform dom0. - -If you install them manually, you will need to ensure that the newly -added files are executable. You can do this by running the following -command (from dom0): - -chmod a+x /etc/xapi.d/plugins/* diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder b/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder deleted file mode 120000 index e4d048ce3cf2..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder +++ /dev/null @@ -1 +0,0 @@ -_bittorrent_seeder.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder.py deleted file mode 100755 index 7e603965887a..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/_bittorrent_seeder.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -"""Seed a bittorent image. This file should not be executed directly, rather it -should be kicked off by the `bittorent` dom0 plugin.""" - -import os -import sys -import time - -import libtorrent - -import pluginlib_nova - - -pluginlib_nova.configure_logging('_bittorrent_seeder') -logging = pluginlib_nova.logging - - -def _daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): - """Daemonize the current process. - - Do the UNIX double-fork magic, see Stevens' "Advanced Programming - in the UNIX Environment" for details (ISBN 0201563177). - - Source: http://www.jejik.com/articles/2007/02/ - a_simple_unix_linux_daemon_in_python/ - """ - # 1st fork - try: - pid = os.fork() - if pid > 0: - # first parent returns - return False - except OSError, e: # noqa - logging.error("fork #1 failed: %d (%s)" % ( - e.errno, e.strerror)) - return - - # decouple from parent environment - os.chdir("/") - os.setsid() - os.umask(0) - - # 2nd fork - try: - pid = os.fork() - if pid > 0: - # second parent exits - sys.exit(0) - except OSError, e: # noqa - logging.error("fork #2 failed: %d (%s)" % ( - e.errno, e.strerror)) - return - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - si = open(stdin, 'r') - so = open(stdout, 'a+') - se = open(stderr, 'a+', 0) - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - return True - - -def main(torrent_path, seed_cache_path, torrent_seed_duration, - torrent_listen_port_start, torrent_listen_port_end): - seed_time = time.time() + torrent_seed_duration - logging.debug("Seeding '%s' for %d secs" % ( - torrent_path, torrent_seed_duration)) - - child = _daemonize() - if not child: - return - - # At this point we're the daemonized child... - session = libtorrent.session() - session.listen_on(torrent_listen_port_start, torrent_listen_port_end) - - torrent_file = open(torrent_path, 'rb') - try: - torrent_data = torrent_file.read() - finally: - torrent_file.close() - - decoded_data = libtorrent.bdecode(torrent_data) - - info = libtorrent.torrent_info(decoded_data) - torrent = session.add_torrent( - info, seed_cache_path, - storage_mode=libtorrent.storage_mode_t.storage_mode_sparse) - try: - while time.time() < seed_time: - time.sleep(5) - finally: - session.remove_torrent(torrent) - - logging.debug("Seeding of '%s' finished" % torrent_path) - - -if __name__ == "__main__": - (torrent_path, seed_cache_path, torrent_seed_duration, - torrent_listen_port_start, torrent_listen_port_end) = sys.argv[1:] - torrent_seed_duration = int(torrent_seed_duration) - torrent_listen_port_start = int(torrent_listen_port_start) - torrent_listen_port_end = int(torrent_listen_port_end) - - main(torrent_path, seed_cache_path, torrent_seed_duration, - torrent_listen_port_start, torrent_listen_port_end) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent deleted file mode 120000 index c0fc525c4a0c..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ /dev/null @@ -1 +0,0 @@ -agent.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py deleted file mode 100755 index 5f01a88bce13..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2011 Citrix Systems, Inc. -# Copyright 2011 OpenStack Foundation -# Copyright 2011 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -# TODO(sfinucan): Remove the symlinks in this folder once Ocata is released - -# -# XenAPI plugin for reading/writing information to xenstore -# - -import base64 -import commands # noqa -try: - import json -except ImportError: - import simplejson as json -import time - -import XenAPIPlugin - -import pluginlib_nova -pluginlib_nova.configure_logging("agent") -import xenstore - - -DEFAULT_TIMEOUT = 30 -PluginError = pluginlib_nova.PluginError -_ = pluginlib_nova._ - - -class TimeoutError(StandardError): - pass - - -class RebootDetectedError(StandardError): - pass - - -def version(self, arg_dict): - """Get version of agent.""" - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - arg_dict["value"] = json.dumps({"name": "version", "value": "agent"}) - request_id = arg_dict["id"] - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def key_init(self, arg_dict): - """Handles the Diffie-Hellman key exchange with the agent to - establish the shared secret key used to encrypt/decrypt sensitive - info to be passed, such as passwords. Returns the shared - secret key value. - """ - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - # WARNING: Some older Windows agents will crash if the public key isn't - # a string - pub = arg_dict["pub"] - arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub}) - request_id = arg_dict["id"] - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def password(self, arg_dict): - """Writes a request to xenstore that tells the agent to set - the root password for the given VM. The password should be - encrypted using the shared secret key that was returned by a - previous call to key_init. The encrypted password value should - be passed as the value for the 'enc_pass' key in arg_dict. - """ - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - enc_pass = arg_dict["enc_pass"] - arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) - request_id = arg_dict["id"] - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def resetnetwork(self, arg_dict): - """Writes a request to xenstore that tells the agent - to reset networking. - """ - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - arg_dict['value'] = json.dumps({'name': 'resetnetwork', 'value': ''}) - request_id = arg_dict['id'] - arg_dict['path'] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def inject_file(self, arg_dict): - """Expects a file path and the contents of the file to be written. Both - should be base64-encoded in order to eliminate errors as they are passed - through the stack. Writes that information to xenstore for the agent, - which will decode the file and intended path, and create it on the - instance. The original agent munged both of these into a single entry; - the new agent keeps them separate. We will need to test for the new agent, - and write the xenstore records to match the agent version. We will also - need to test to determine if the file injection method on the agent has - been disabled, and raise a NotImplemented error if that is the case. - """ - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - b64_path = arg_dict["b64_path"] - b64_file = arg_dict["b64_contents"] - request_id = arg_dict["id"] - agent_features = _get_agent_features(self, arg_dict) - if "file_inject" in agent_features: - # New version of the agent. Agent should receive a 'value' - # key whose value is a dictionary containing 'b64_path' and - # 'b64_file'. See old version below. - arg_dict["value"] = json.dumps({"name": "file_inject", - "value": {"b64_path": b64_path, "b64_file": b64_file}}) - elif "injectfile" in agent_features: - # Old agent requires file path and file contents to be - # combined into one base64 value. - raw_path = base64.b64decode(b64_path) - raw_file = base64.b64decode(b64_file) - new_b64 = base64.b64encode("%s,%s" % (raw_path, raw_file)) - arg_dict["value"] = json.dumps({"name": "injectfile", - "value": new_b64}) - else: - # Either the methods don't exist in the agent, or they - # have been disabled. - raise NotImplementedError(_("NOT IMPLEMENTED: Agent does not" - " support file injection.")) - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def agent_update(self, arg_dict): - """Expects an URL and md5sum of the contents, then directs the agent to - update itself. - """ - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - request_id = arg_dict["id"] - url = arg_dict["url"] - md5sum = arg_dict["md5sum"] - arg_dict["value"] = json.dumps({"name": "agentupdate", - "value": "%s,%s" % (url, md5sum)}) - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - return resp - - -def _get_agent_features(self, arg_dict): - """Return an array of features that an agent supports.""" - timeout = int(arg_dict.pop('timeout', DEFAULT_TIMEOUT)) - tmp_id = commands.getoutput("uuidgen") - dct = {} - dct.update(arg_dict) - dct["value"] = json.dumps({"name": "features", "value": ""}) - dct["path"] = "data/host/%s" % tmp_id - xenstore.write_record(self, dct) - try: - resp = _wait_for_agent(self, tmp_id, dct, timeout) - except TimeoutError, e: # noqa - raise PluginError(e) - response = json.loads(resp) - if response['returncode'] != 0: - return response["message"].split(",") - else: - return {} - - -def _wait_for_agent(self, request_id, arg_dict, timeout): - """Periodically checks xenstore for a response from the agent. - The request is always written to 'data/host/{id}', and - the agent's response for that request will be in 'data/guest/{id}'. - If no value appears from the agent within the timeout specified, - the original request is deleted and a TimeoutError is raised. - """ - arg_dict["path"] = "data/guest/%s" % request_id - arg_dict["ignore_missing_path"] = True - start = time.time() - reboot_detected = False - while time.time() - start < timeout: - ret = xenstore.read_record(self, arg_dict) - # Note: the response for None with be a string that includes - # double quotes. - if ret != '"None"': - # The agent responded - return ret - - time.sleep(.5) - - # NOTE(johngarbutt) If we can't find this domid, then - # the VM has rebooted, so we must trigger domid refresh. - # Check after the sleep to give xenstore time to update - # after the VM reboot. - exists_args = { - "dom_id": arg_dict["dom_id"], - "path": "name", - } - dom_id_is_present = xenstore.record_exists(exists_args) - if not dom_id_is_present: - reboot_detected = True - break - - # No response within the timeout period; bail out - # First, delete the request record - arg_dict["path"] = "data/host/%s" % request_id - xenstore.delete_record(self, arg_dict) - - if reboot_detected: - raise RebootDetectedError(_("REBOOT: dom_id %s no longer " - "present") % arg_dict["dom_id"]) - else: - raise TimeoutError(_("TIMEOUT: No response from agent within" - " %s seconds.") % timeout) - - -if __name__ == "__main__": - XenAPIPlugin.dispatch( - {"version": version, - "key_init": key_init, - "password": password, - "resetnetwork": resetnetwork, - "inject_file": inject_file, - "agentupdate": agent_update}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth deleted file mode 120000 index aed92b6519ae..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth +++ /dev/null @@ -1 +0,0 @@ -bandwidth.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth.py deleted file mode 100755 index 91256a11c20d..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -"""Fetch Bandwidth data from VIF network devices.""" - -import utils - -import pluginlib_nova - -import re - - -pluginlib_nova.configure_logging('bandwidth') - - -def _read_proc_net(): - f = open('/proc/net/dev', 'r') - try: - return f.readlines() - finally: - f.close() - - -def _get_bandwitdth_from_proc(): - devs = [l.strip() for l in _read_proc_net()] - # ignore headers - devs = devs[2:] - vif_pattern = re.compile("^vif(\d+)\.(\d+)") - dlist = [d.split(':', 1) for d in devs if vif_pattern.match(d)] - devmap = dict() - for name, stats in dlist: - slist = stats.split() - dom, vifnum = name[3:].split('.', 1) - dev = devmap.get(dom, {}) - # Note, we deliberately swap in and out, as instance traffic - # shows up inverted due to going though the bridge. (mdragon) - dev[vifnum] = dict(bw_in=int(slist[8]), bw_out=int(slist[0])) - devmap[dom] = dev - return devmap - - -def fetch_all_bandwidth(session): - return _get_bandwitdth_from_proc() - - -if __name__ == '__main__': - utils.register_plugin_calls(fetch_all_bandwidth) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent deleted file mode 120000 index 7ca3c4e17617..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent +++ /dev/null @@ -1 +0,0 @@ -bittorrent.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent.py deleted file mode 100755 index 1bbc4ca3ba15..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bittorrent.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -"""Download images via BitTorrent.""" - -import errno -import inspect -import os -import random -import shutil -import tempfile -import time - -import libtorrent -import urllib2 - -import utils - -import pluginlib_nova - - -pluginlib_nova.configure_logging('bittorrent') -logging = pluginlib_nova.logging - -# Taken from units since we don't pull down full library -Mi = 1024 ** 2 -DEFAULT_TORRENT_CACHE = '/images/torrents' -DEFAULT_SEED_CACHE = '/images/seeds' -SEEDER_PROCESS = '_bittorrent_seeder.py' -DEFAULT_MMA = int(libtorrent.bandwidth_mixed_algo_t.prefer_tcp) -DEFAULT_MORQ = 400 -DEFAULT_MQDB = 8 * Mi -DEFAULT_MQDBLW = 0 - - -def _make_torrent_cache(): - torrent_cache_path = os.environ.get( - 'TORRENT_CACHE', DEFAULT_TORRENT_CACHE) - - if not os.path.exists(torrent_cache_path): - os.mkdir(torrent_cache_path) - - return torrent_cache_path - - -def _fetch_torrent_file(torrent_cache_path, image_id, torrent_url): - torrent_path = os.path.join( - torrent_cache_path, image_id + '.torrent') - - if not os.path.exists(torrent_path): - logging.info("Downloading %s" % torrent_url) - - # Write contents to temporary path to ensure we don't have partially - # completed files in the cache. - temp_directory = tempfile.mkdtemp(dir=torrent_cache_path) - try: - temp_path = os.path.join( - temp_directory, os.path.basename(torrent_path)) - temp_file = open(temp_path, 'wb') - try: - remote_torrent_file = urllib2.urlopen(torrent_url) - shutil.copyfileobj(remote_torrent_file, temp_file) - finally: - temp_file.close() - - os.rename(temp_path, torrent_path) - finally: - shutil.rmtree(temp_directory) - - return torrent_path - - -def _reap_old_torrent_files(torrent_cache_path, torrent_max_last_accessed): - """Delete any torrent files that haven't been accessed recently.""" - if not torrent_max_last_accessed: - logging.debug("Reaping old torrent files disabled, skipping...") - return - - logging.debug("Preparing to reap old torrent files," - " torrent_max_last_accessed=%d" % torrent_max_last_accessed) - - for fname in os.listdir(torrent_cache_path): - torrent_path = os.path.join(torrent_cache_path, fname) - last_accessed = time.time() - os.path.getatime(torrent_path) - if last_accessed > torrent_max_last_accessed: - logging.debug("Reaping '%s', last_accessed=%d" % ( - torrent_path, last_accessed)) - utils.delete_if_exists(torrent_path) - - -def _download(torrent_path, save_as_path, torrent_listen_port_start, - torrent_listen_port_end, torrent_download_stall_cutoff): - session = libtorrent.session() - session.listen_on(torrent_listen_port_start, torrent_listen_port_end) - - mixed_mode_algorithm = os.environ.get( - 'DEFAULT_MIXED_MODE_ALGORITHM', DEFAULT_MMA) - max_out_request_queue = os.environ.get( - 'DEFAULT_MAX_OUT_REQUEST_QUEUE', DEFAULT_MORQ) - max_queued_disk_bytes = os.environ.get( - 'DEFAULT_MAX_QUEUED_DISK_BYTES', DEFAULT_MQDB) - max_queued_disk_bytes_low_watermark = os.environ.get( - 'DEFAULT_MAX_QUEUED_DISK_BYTES_LOW_WATERMARK', DEFAULT_MQDBLW) - - session_opts = {'mixed_mode_algorithm': mixed_mode_algorithm, - 'max_queued_disk_bytes': max_queued_disk_bytes, - 'max_out_request_queue': max_out_request_queue, - 'max_queued_disk_bytes_low_watermark': - max_queued_disk_bytes_low_watermark} - session.set_settings(session_opts) - info = libtorrent.torrent_info( - libtorrent.bdecode(open(torrent_path, 'rb').read())) - - torrent = session.add_torrent( - info, save_as_path, - storage_mode=libtorrent.storage_mode_t.storage_mode_sparse) - - try: - last_progress = 0 - last_progress_updated = time.time() - - log_time = 0 - while not torrent.is_seed(): - s = torrent.status() - - progress = s.progress * 100 - - if progress != last_progress: - last_progress = progress - last_progress_updated = time.time() - - stall_duration = time.time() - last_progress_updated - if stall_duration > torrent_download_stall_cutoff: - logging.error( - "Download stalled: stall_duration=%d," - " torrent_download_stall_cutoff=%d" % ( - stall_duration, torrent_download_stall_cutoff)) - raise Exception("Bittorrent download stall detected, bailing!") - - log_time += 1 - if log_time % 10 == 0: - logging.debug( - '%.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d)' - ' %s %s' % (progress, s.download_rate / 1000, - s.upload_rate / 1000, s.num_peers, s.state, - torrent_path)) - time.sleep(1) - finally: - session.remove_torrent(torrent) - - logging.debug("Download of '%s' finished" % torrent_path) - - -def _should_seed(seed_path, torrent_seed_duration, torrent_seed_chance, - torrent_max_seeder_processes_per_host): - if not torrent_seed_duration: - logging.debug("Seeding disabled, skipping...") - return False - - if os.path.exists(seed_path): - logging.debug("Seed is already present, skipping....") - return False - - rand = random.random() - if rand > torrent_seed_chance: - logging.debug("%.2f > %.2f, seeding randomly skipping..." % ( - rand, torrent_seed_chance)) - return False - - num_active_seeders = len(list(_active_seeder_processes())) - if (torrent_max_seeder_processes_per_host >= 0 and - num_active_seeders >= torrent_max_seeder_processes_per_host): - logging.debug("max number of seeder processes for this host reached" - " (%d), skipping..." % - torrent_max_seeder_processes_per_host) - return False - - return True - - -def _seed(torrent_path, seed_cache_path, torrent_seed_duration, - torrent_listen_port_start, torrent_listen_port_end): - plugin_path = os.path.dirname(inspect.getabsfile(inspect.currentframe())) - seeder_path = os.path.join(plugin_path, SEEDER_PROCESS) - seed_cmd = map(str, [seeder_path, torrent_path, seed_cache_path, - torrent_seed_duration, torrent_listen_port_start, - torrent_listen_port_end]) - utils.run_command(seed_cmd) - - -def _seed_if_needed(seed_cache_path, tarball_path, torrent_path, - torrent_seed_duration, torrent_seed_chance, - torrent_listen_port_start, torrent_listen_port_end, - torrent_max_seeder_processes_per_host): - seed_filename = os.path.basename(tarball_path) - seed_path = os.path.join(seed_cache_path, seed_filename) - - if _should_seed(seed_path, torrent_seed_duration, torrent_seed_chance, - torrent_max_seeder_processes_per_host): - logging.debug("Preparing to seed '%s' for %d secs" % ( - seed_path, torrent_seed_duration)) - utils._rename(tarball_path, seed_path) - - # Daemonize and seed the image - _seed(torrent_path, seed_cache_path, torrent_seed_duration, - torrent_listen_port_start, torrent_listen_port_end) - else: - utils.delete_if_exists(tarball_path) - - -def _extract_tarball(tarball_path, staging_path): - """Extract the tarball into the staging directory.""" - tarball_fileobj = open(tarball_path, 'rb') - try: - utils.extract_tarball(tarball_fileobj, staging_path) - finally: - tarball_fileobj.close() - - -def _active_seeder_processes(): - """Yields command-line of active seeder processes. - - Roughly equivalent to performing ps | grep _bittorrent_seeder - """ - pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] - for pid in pids: - try: - cmdline = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read() - except IOError, e: # noqa - if e.errno != errno.ENOENT: - raise - - if SEEDER_PROCESS in cmdline: - yield cmdline - - -def _reap_finished_seeds(seed_cache_path): - """Delete any cached seeds where the seeder process has died.""" - logging.debug("Preparing to reap finished seeds") - missing = {} - for fname in os.listdir(seed_cache_path): - seed_path = os.path.join(seed_cache_path, fname) - missing[seed_path] = None - - for cmdline in _active_seeder_processes(): - for seed_path in missing.keys(): - seed_filename = os.path.basename(seed_path) - if seed_filename in cmdline: - del missing[seed_path] - - for seed_path in missing: - logging.debug("Reaping cached seed '%s'" % seed_path) - utils.delete_if_exists(seed_path) - - -def _make_seed_cache(): - seed_cache_path = os.environ.get('SEED_CACHE', DEFAULT_SEED_CACHE) - if not os.path.exists(seed_cache_path): - os.mkdir(seed_cache_path) - return seed_cache_path - - -def download_vhd(session, image_id, torrent_url, torrent_seed_duration, - torrent_seed_chance, torrent_max_last_accessed, - torrent_listen_port_start, torrent_listen_port_end, - torrent_download_stall_cutoff, uuid_stack, sr_path, - torrent_max_seeder_processes_per_host): - """Download an image from BitTorrent, unbundle it, and then deposit the - VHDs into the storage repository - """ - seed_cache_path = _make_seed_cache() - torrent_cache_path = _make_torrent_cache() - - # Housekeeping - _reap_finished_seeds(seed_cache_path) - _reap_old_torrent_files(torrent_cache_path, torrent_max_last_accessed) - - torrent_path = _fetch_torrent_file( - torrent_cache_path, image_id, torrent_url) - - staging_path = utils.make_staging_area(sr_path) - try: - tarball_filename = os.path.basename(torrent_path).replace( - '.torrent', '') - tarball_path = os.path.join(staging_path, tarball_filename) - - # Download tarball into staging area - _download(torrent_path, staging_path, torrent_listen_port_start, - torrent_listen_port_end, torrent_download_stall_cutoff) - - # Extract the tarball into the staging area - _extract_tarball(tarball_path, staging_path) - - # Move the VHDs from the staging area into the storage repository - vdi_list = utils.import_vhds(sr_path, staging_path, uuid_stack) - - # Seed image for others in the swarm - _seed_if_needed(seed_cache_path, tarball_path, torrent_path, - torrent_seed_duration, torrent_seed_chance, - torrent_listen_port_start, torrent_listen_port_end, - torrent_max_seeder_processes_per_host) - finally: - utils.cleanup_staging_area(staging_path) - - return vdi_list - - -if __name__ == '__main__': - utils.register_plugin_calls(download_vhd) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file b/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file deleted file mode 120000 index ec0d8d0d37ed..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file +++ /dev/null @@ -1 +0,0 @@ -config_file.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file.py deleted file mode 100755 index b4d002d904c4..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python - -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -import XenAPIPlugin - - -def get_val(session, args): - config_key = args['key'] - config_file = open('/etc/xapi.conf') - try: - for line in config_file: - split = line.split('=') - if (len(split) == 2) and (split[0].strip() == config_key): - return split[1].strip() - return "" - finally: - config_file.close() - -if __name__ == '__main__': - XenAPIPlugin.dispatch({"get_val": get_val}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/console b/plugins/xenserver/xenapi/etc/xapi.d/plugins/console deleted file mode 120000 index 9f10dfd121dc..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/console +++ /dev/null @@ -1 +0,0 @@ -console.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/console.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/console.py deleted file mode 100755 index f790631bc330..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/console.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/python - -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -""" -To configure this plugin, you must set the following xenstore key: -/local/logconsole/@ = "/var/log/xen/guest/console.%d" - -This can be done by running: -xenstore-write /local/logconsole/@ "/var/log/xen/guest/console.%d" - -WARNING: -You should ensure appropriate log rotation to ensure -guests are not able to consume too much Dom0 disk space, -and equally should not be able to stop other guests from logging. -Adding and removing the following xenstore key will reopen the log, -as will be required after a log rotate: -/local/logconsole/ -""" - -import base64 -import logging -import zlib - -import XenAPIPlugin - -import pluginlib_nova -pluginlib_nova.configure_logging("console") - -CONSOLE_LOG_DIR = '/var/log/xen/guest' -CONSOLE_LOG_FILE_PATTERN = CONSOLE_LOG_DIR + '/console.%d' - -MAX_CONSOLE_BYTES = 102400 -SEEK_SET = 0 -SEEK_END = 2 - - -def _last_bytes(file_like_object): - try: - file_like_object.seek(-MAX_CONSOLE_BYTES, SEEK_END) - except IOError, e: # noqa - if e.errno == 22: - file_like_object.seek(0, SEEK_SET) - else: - raise - return file_like_object.read() - - -def get_console_log(session, arg_dict): - try: - raw_dom_id = arg_dict['dom_id'] - except KeyError: - raise pluginlib_nova.PluginError("Missing dom_id") - try: - dom_id = int(raw_dom_id) - except ValueError: - raise pluginlib_nova.PluginError("Invalid dom_id") - - logfile = open(CONSOLE_LOG_FILE_PATTERN % dom_id, 'rb') - try: - try: - log_content = _last_bytes(logfile) - except IOError, e: # noqa - msg = "Error reading console: %s" % e - logging.debug(msg) - raise pluginlib_nova.PluginError(msg) - finally: - logfile.close() - - return base64.b64encode(zlib.compress(log_content)) - - -if __name__ == "__main__": - XenAPIPlugin.dispatch({"get_console_log": get_console_log}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance deleted file mode 120000 index 73729470a2f0..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ /dev/null @@ -1 +0,0 @@ -glance.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance.py deleted file mode 100755 index 5219c9e5b75b..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance.py +++ /dev/null @@ -1,632 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# Copyright (c) 2010 Citrix Systems, Inc. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -"""Handle the uploading and downloading of images via Glance.""" - -try: - import httplib -except ImportError: - from six.moves import http_client as httplib - -try: - import json -except ImportError: - import simplejson as json - -import md5 # noqa -import socket -import urllib2 -from urlparse import urlparse - -import pluginlib_nova -import utils - - -pluginlib_nova.configure_logging('glance') -logging = pluginlib_nova.logging -PluginError = pluginlib_nova.PluginError - -SOCKET_TIMEOUT_SECONDS = 90 - - -class RetryableError(Exception): - pass - - -def _create_connection(scheme, netloc): - if scheme == 'https': - conn = httplib.HTTPSConnection(netloc) - else: - conn = httplib.HTTPConnection(netloc) - conn.connect() - return conn - - -def _download_tarball_and_verify(request, staging_path): - # NOTE(johngarbutt) By default, there is no timeout. - # To ensure the script does not hang if we lose connection - # to glance, we add this socket timeout. - # This is here so there is no chance the timeout out has - # been adjusted by other library calls. - socket.setdefaulttimeout(SOCKET_TIMEOUT_SECONDS) - - try: - response = urllib2.urlopen(request) - except urllib2.HTTPError, error: # noqa - raise RetryableError(error) - except urllib2.URLError, error: # noqa - raise RetryableError(error) - except httplib.HTTPException, error: # noqa - # httplib.HTTPException and derivatives (BadStatusLine in particular) - # don't have a useful __repr__ or __str__ - raise RetryableError('%s: %s' % (error.__class__.__name__, error)) - - url = request.get_full_url() - logging.info("Reading image data from %s" % url) - - callback_data = {'bytes_read': 0} - checksum = md5.new() - - def update_md5(chunk): - callback_data['bytes_read'] += len(chunk) - checksum.update(chunk) - - try: - try: - utils.extract_tarball(response, staging_path, callback=update_md5) - except Exception, error: # noqa - raise RetryableError(error) - finally: - bytes_read = callback_data['bytes_read'] - logging.info("Read %d bytes from %s", bytes_read, url) - - # Use ETag if available, otherwise content-md5(v2) or - # X-Image-Meta-Checksum(v1) - etag = response.info().getheader('etag', None) - if etag is None: - etag = response.info().getheader('content-md5', None) - if etag is None: - etag = response.info().getheader('x-image-meta-checksum', None) - - # Verify checksum using ETag - checksum = checksum.hexdigest() - - if etag is None: - msg = "No ETag found for comparison to checksum %(checksum)s" - logging.info(msg % {'checksum': checksum}) - elif checksum != etag: - msg = 'ETag %(etag)s does not match computed md5sum %(checksum)s' - raise RetryableError(msg % {'checksum': checksum, 'etag': etag}) - else: - msg = "Verified image checksum %(checksum)s" - logging.info(msg % {'checksum': checksum}) - - -def _download_tarball_v1(sr_path, staging_path, image_id, glance_host, - glance_port, glance_use_ssl, extra_headers): - """Download the tarball image from Glance v1 and extract it into the - staging area. Retry if there is any failure. - """ - if glance_use_ssl: - scheme = 'https' - else: - scheme = 'http' - - endpoint = "%(scheme)s://%(glance_host)s:%(glance_port)d" % { - 'scheme': scheme, 'glance_host': glance_host, - 'glance_port': glance_port} - _download_tarball_by_url_v1(sr_path, staging_path, image_id, - endpoint, extra_headers) - - -def _download_tarball_by_url_v1( - sr_path, staging_path, image_id, glance_endpoint, extra_headers): - """Download the tarball image from Glance v1 and extract it into the - staging area. Retry if there is any failure. - """ - - url = "%(glance_endpoint)s/v1/images/%(image_id)s" % { - 'glance_endpoint': glance_endpoint, - 'image_id': image_id} - logging.info("Downloading %s with glance v1 api" % url) - - request = urllib2.Request(url, headers=extra_headers) - try: - _download_tarball_and_verify(request, staging_path) - except Exception: - logging.exception('Failed to retrieve %(url)s' % {'url': url}) - raise - - -def _download_tarball_by_url_v2( - sr_path, staging_path, image_id, glance_endpoint, extra_headers): - """Download the tarball image from Glance v2 and extract it into the - staging area. Retry if there is any failure. - """ - - url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { - 'glance_endpoint': glance_endpoint, - 'image_id': image_id} - logging.debug("Downloading %s with glance v2 api" % url) - - request = urllib2.Request(url, headers=extra_headers) - try: - _download_tarball_and_verify(request, staging_path) - except Exception: - logging.exception('Failed to retrieve %(url)s' % {'url': url}) - raise - - -def _upload_tarball_v1(staging_path, image_id, glance_host, glance_port, - glance_use_ssl, extra_headers, properties): - if glance_use_ssl: - scheme = 'https' - else: - scheme = 'http' - - url = '%s://%s:%s' % (scheme, glance_host, glance_port) - _upload_tarball_by_url_v1(staging_path, image_id, url, - extra_headers, properties) - - -def _upload_tarball_by_url_v1(staging_path, image_id, glance_endpoint, - extra_headers, properties): - """Create a tarball of the image and then stream that into Glance v1 - using chunked-transfer-encoded HTTP. - """ - # NOTE(johngarbutt) By default, there is no timeout. - # To ensure the script does not hang if we lose connection - # to glance, we add this socket timeout. - # This is here so there is no chance the timeout out has - # been adjusted by other library calls. - socket.setdefaulttimeout(SOCKET_TIMEOUT_SECONDS) - logging.debug("Uploading image %s with glance v1 api" - % image_id) - - url = "%(glance_endpoint)s/v1/images/%(image_id)s" % { - 'glance_endpoint': glance_endpoint, - 'image_id': image_id} - logging.info("Writing image data to %s" % url) - - # NOTE(sdague): this is python 2.4, which means urlparse returns a - # tuple, not a named tuple. - # 0 - scheme - # 1 - host:port (aka netloc) - # 2 - path - parts = urlparse(url) - - try: - conn = _create_connection(parts[0], parts[1]) - except Exception, error: # noqa - logging.exception('Failed to connect %(url)s' % {'url': url}) - raise RetryableError(error) - - try: - validate_image_status_before_upload_v1(conn, url, extra_headers) - - try: - # NOTE(sirp): httplib under python2.4 won't accept - # a file-like object to request - conn.putrequest('PUT', parts[2]) - - # NOTE(sirp): There is some confusion around OVF. Here's a summary - # of where we currently stand: - # 1. OVF as a container format is misnamed. We really should be - # using OVA since that is the name for the container format; - # OVF is the standard applied to the manifest file contained - # within. - # 2. We're currently uploading a vanilla tarball. In order to be - # OVF/OVA compliant, we'll need to embed a minimal OVF - # manifest as the first file. - - # NOTE(dprince): In order to preserve existing Glance properties - # we set X-Glance-Registry-Purge-Props on this request. - headers = { - 'content-type': 'application/octet-stream', - 'transfer-encoding': 'chunked', - 'x-image-meta-is-public': 'False', - 'x-image-meta-status': 'queued', - 'x-image-meta-disk-format': 'vhd', - 'x-image-meta-container-format': 'ovf', - 'x-glance-registry-purge-props': 'False'} - - headers.update(**extra_headers) - - for key, value in properties.items(): - header_key = "x-image-meta-property-%s" % key.replace('_', '-') - headers[header_key] = str(value) - - for header, value in headers.items(): - conn.putheader(header, value) - conn.endheaders() - except Exception, error: # noqa - logging.exception('Failed to upload %(url)s' % {'url': url}) - raise RetryableError(error) - - callback_data = {'bytes_written': 0} - - def send_chunked_transfer_encoded(chunk): - chunk_len = len(chunk) - callback_data['bytes_written'] += chunk_len - try: - conn.send("%x\r\n%s\r\n" % (chunk_len, chunk)) - except Exception, error: # noqa - logging.exception('Failed to upload when sending chunks') - raise RetryableError(error) - - compression_level = properties.get('xenapi_image_compression_level') - - utils.create_tarball( - None, staging_path, callback=send_chunked_transfer_encoded, - compression_level=compression_level) - - send_chunked_transfer_encoded('') # Chunked-Transfer terminator - - bytes_written = callback_data['bytes_written'] - logging.info("Wrote %d bytes to %s" % (bytes_written, url)) - - resp = conn.getresponse() - if resp.status == httplib.OK: - return - - logging.error("Unexpected response while writing image data to %s: " - "Response Status: %i, Response body: %s" - % (url, resp.status, resp.read())) - - check_resp_status_and_retry(resp, image_id, url) - - finally: - conn.close() - - -def _update_image_meta_v2(conn, image_id, extra_headers, properties): - # NOTE(sirp): There is some confusion around OVF. Here's a summary - # of where we currently stand: - # 1. OVF as a container format is misnamed. We really should be - # using OVA since that is the name for the container format; - # OVF is the standard applied to the manifest file contained - # within. - # 2. We're currently uploading a vanilla tarball. In order to be - # OVF/OVA compliant, we'll need to embed a minimal OVF - # manifest as the first file. - body = [ - {"path": "/container_format", "value": "ovf", "op": "add"}, - {"path": "/disk_format", "value": "vhd", "op": "add"}, - {"path": "/visibility", "value": "private", "op": "add"}] - - headers = {'Content-Type': 'application/openstack-images-v2.1-json-patch'} - headers.update(**extra_headers) - - for key, value in properties.items(): - prop = {"path": "/%s" % key.replace('_', '-'), - "value": key, - "op": "add"} - body.append(prop) - body = json.dumps(body) - conn.request('PATCH', '/v2/images/%s' % image_id, - body=body, headers=headers) - resp = conn.getresponse() - resp.read() - - if resp.status == httplib.OK: - return - logging.error("Image meta was not updated. Status: %s, Reason: %s" % ( - resp.status, resp.reason)) - - -def _upload_tarball_by_url_v2(staging_path, image_id, glance_endpoint, - extra_headers, properties): - """Create a tarball of the image and then stream that into Glance v2 - using chunked-transfer-encoded HTTP. - """ - # NOTE(johngarbutt) By default, there is no timeout. - # To ensure the script does not hang if we lose connection - # to glance, we add this socket timeout. - # This is here so there is no chance the timeout out has - # been adjusted by other library calls. - socket.setdefaulttimeout(SOCKET_TIMEOUT_SECONDS) - logging.debug("Uploading imaged %s with glance v2 api" - % image_id) - - url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { - 'glance_endpoint': glance_endpoint, - 'image_id': image_id} - - # NOTE(sdague): this is python 2.4, which means urlparse returns a - # tuple, not a named tuple. - # 0 - scheme - # 1 - host:port (aka netloc) - # 2 - path - parts = urlparse(url) - - try: - conn = _create_connection(parts[0], parts[1]) - except Exception, error: # noqa - raise RetryableError(error) - - try: - _update_image_meta_v2(conn, image_id, extra_headers, properties) - - validate_image_status_before_upload_v2(conn, url, extra_headers) - - try: - conn.connect() - # NOTE(sirp): httplib under python2.4 won't accept - # a file-like object to request - conn.putrequest('PUT', parts[2]) - - headers = { - 'content-type': 'application/octet-stream', - 'transfer-encoding': 'chunked'} - - headers.update(**extra_headers) - - for header, value in headers.items(): - conn.putheader(header, value) - conn.endheaders() - except Exception, error: # noqa - logging.exception('Failed to upload %(url)s' % {'url': url}) - raise RetryableError(error) - - callback_data = {'bytes_written': 0} - - def send_chunked_transfer_encoded(chunk): - chunk_len = len(chunk) - callback_data['bytes_written'] += chunk_len - try: - conn.send("%x\r\n%s\r\n" % (chunk_len, chunk)) - except Exception, error: # noqa - logging.exception('Failed to upload when sending chunks') - raise RetryableError(error) - - compression_level = properties.get('xenapi_image_compression_level') - - utils.create_tarball( - None, staging_path, callback=send_chunked_transfer_encoded, - compression_level=compression_level) - - send_chunked_transfer_encoded('') # Chunked-Transfer terminator - - bytes_written = callback_data['bytes_written'] - logging.info("Wrote %d bytes to %s" % (bytes_written, url)) - - resp = conn.getresponse() - if resp.status == httplib.NO_CONTENT: - return - - logging.error("Unexpected response while writing image data to %s: " - "Response Status: %i, Response body: %s" - % (url, resp.status, resp.read())) - - check_resp_status_and_retry(resp, image_id, url) - - finally: - conn.close() - - -def check_resp_status_and_retry(resp, image_id, url): - # Note(Jesse): This branch sorts errors into those that are permanent, - # those that are ephemeral, and those that are unexpected. - if resp.status in (httplib.BAD_REQUEST, # 400 - httplib.UNAUTHORIZED, # 401 - httplib.PAYMENT_REQUIRED, # 402 - httplib.FORBIDDEN, # 403 - httplib.NOT_FOUND, # 404 - httplib.METHOD_NOT_ALLOWED, # 405 - httplib.NOT_ACCEPTABLE, # 406 - httplib.PROXY_AUTHENTICATION_REQUIRED, # 407 - httplib.CONFLICT, # 409 - httplib.GONE, # 410 - httplib.LENGTH_REQUIRED, # 411 - httplib.PRECONDITION_FAILED, # 412 - httplib.REQUEST_ENTITY_TOO_LARGE, # 413 - httplib.REQUEST_URI_TOO_LONG, # 414 - httplib.UNSUPPORTED_MEDIA_TYPE, # 415 - httplib.REQUESTED_RANGE_NOT_SATISFIABLE, # 416 - httplib.EXPECTATION_FAILED, # 417 - httplib.UNPROCESSABLE_ENTITY, # 422 - httplib.LOCKED, # 423 - httplib.FAILED_DEPENDENCY, # 424 - httplib.UPGRADE_REQUIRED, # 426 - httplib.NOT_IMPLEMENTED, # 501 - httplib.HTTP_VERSION_NOT_SUPPORTED, # 505 - httplib.NOT_EXTENDED, # 510 - ): - raise PluginError("Got Permanent Error response [%i] while " - "uploading image [%s] to glance [%s]" - % (resp.status, image_id, url)) - # NOTE(nikhil): Only a sub-set of the 500 errors are retryable. We - # optimistically retry on 500 errors below. - elif resp.status in (httplib.REQUEST_TIMEOUT, # 408 - httplib.INTERNAL_SERVER_ERROR, # 500 - httplib.BAD_GATEWAY, # 502 - httplib.SERVICE_UNAVAILABLE, # 503 - httplib.GATEWAY_TIMEOUT, # 504 - httplib.INSUFFICIENT_STORAGE, # 507 - ): - raise RetryableError("Got Ephemeral Error response [%i] while " - "uploading image [%s] to glance [%s]" - % (resp.status, image_id, url)) - else: - # Note(Jesse): Assume unexpected errors are retryable. If you are - # seeing this error message, the error should probably be added - # to either the ephemeral or permanent error list. - raise RetryableError("Got Unexpected Error response [%i] while " - "uploading image [%s] to glance [%s]" - % (resp.status, image_id, url)) - - -def validate_image_status_before_upload_v1(conn, url, extra_headers): - try: - parts = urlparse(url) - path = parts[2] - image_id = path.split('/')[-1] - # NOTE(nikhil): Attempt to determine if the Image has a status - # of 'queued'. Because data will continued to be sent to Glance - # until it has a chance to check the Image state, discover that - # it is not 'active' and send back a 409. Hence, the data will be - # unnecessarily buffered by Glance. This wastes time and bandwidth. - # LP bug #1202785 - - conn.request('HEAD', path, headers=extra_headers) - head_resp = conn.getresponse() - # NOTE(nikhil): read the response to re-use the conn object. - body_data = head_resp.read(8192) - if len(body_data) > 8: - err_msg = ('Cannot upload data for image %(image_id)s as the ' - 'HEAD call had more than 8192 bytes of data in ' - 'the response body.' % {'image_id': image_id}) - raise PluginError("Got Permanent Error while uploading image " - "[%s] to glance [%s]. " - "Message: %s" % (image_id, url, - err_msg)) - else: - head_resp.read() - - except Exception, error: # noqa - logging.exception('Failed to HEAD the image %(image_id)s while ' - 'checking image status before attempting to ' - 'upload %(url)s' % {'image_id': image_id, - 'url': url}) - raise RetryableError(error) - - if head_resp.status != httplib.OK: - logging.error("Unexpected response while doing a HEAD call " - "to image %s , url = %s , Response Status: " - "%i" % (image_id, url, head_resp.status)) - - check_resp_status_and_retry(head_resp, image_id, url) - - else: - image_status = head_resp.getheader('x-image-meta-status') - if image_status not in ('queued', ): - err_msg = ('Cannot upload data for image %(image_id)s as the ' - 'image status is %(image_status)s' % - {'image_id': image_id, 'image_status': image_status}) - logging.exception(err_msg) - raise PluginError("Got Permanent Error while uploading image " - "[%s] to glance [%s]. " - "Message: %s" % (image_id, url, - err_msg)) - else: - logging.info('Found image %(image_id)s in status ' - '%(image_status)s. Attempting to ' - 'upload.' % {'image_id': image_id, - 'image_status': image_status}) - - -def validate_image_status_before_upload_v2(conn, url, extra_headers): - try: - parts = urlparse(url) - path = parts[2] - image_id = path.split('/')[-2] - # NOTE(nikhil): Attempt to determine if the Image has a status - # of 'queued'. Because data will continued to be sent to Glance - # until it has a chance to check the Image state, discover that - # it is not 'active' and send back a 409. Hence, the data will be - # unnecessarily buffered by Glance. This wastes time and bandwidth. - # LP bug #1202785 - - conn.request('GET', '/v2/images/%s' % image_id, headers=extra_headers) - get_resp = conn.getresponse() - except Exception, error: # noqa - logging.exception('Failed to GET the image %(image_id)s while ' - 'checking image status before attempting to ' - 'upload %(url)s' % {'image_id': image_id, - 'url': url}) - raise RetryableError(error) - - if get_resp.status != httplib.OK: - logging.error("Unexpected response while doing a GET call " - "to image %s , url = %s , Response Status: " - "%i" % (image_id, url, get_resp.status)) - - check_resp_status_and_retry(get_resp, image_id, url) - - else: - body = json.loads(get_resp.read()) - image_status = body['status'] - if image_status not in ('queued', ): - err_msg = ('Cannot upload data for image %(image_id)s as the ' - 'image status is %(image_status)s' % - {'image_id': image_id, 'image_status': image_status}) - logging.exception(err_msg) - raise PluginError("Got Permanent Error while uploading image " - "[%s] to glance [%s]. " - "Message: %s" % (image_id, url, - err_msg)) - else: - logging.info('Found image %(image_id)s in status ' - '%(image_status)s. Attempting to ' - 'upload.' % {'image_id': image_id, - 'image_status': image_status}) - get_resp.read() - - -def download_vhd2(session, image_id, endpoint, - uuid_stack, sr_path, extra_headers, api_version=1): - """Download an image from Glance v2, unbundle it, and then deposit the - VHDs into the storage repository. - """ - staging_path = utils.make_staging_area(sr_path) - try: - # Download tarball into staging area and extract it - # TODO(mfedosin): remove this check when v1 is deprecated. - if api_version == 1: - _download_tarball_by_url_v1( - sr_path, staging_path, image_id, - endpoint, extra_headers) - else: - _download_tarball_by_url_v2( - sr_path, staging_path, image_id, - endpoint, extra_headers) - - # Move the VHDs from the staging area into the storage repository - return utils.import_vhds(sr_path, staging_path, uuid_stack) - finally: - utils.cleanup_staging_area(staging_path) - - -def upload_vhd2(session, vdi_uuids, image_id, endpoint, sr_path, - extra_headers, properties, api_version=1): - """Bundle the VHDs comprising an image and then stream them into - Glance. - """ - staging_path = utils.make_staging_area(sr_path) - try: - utils.prepare_staging_area(sr_path, staging_path, vdi_uuids) - # TODO(mfedosin): remove this check when v1 is deprecated. - if api_version == 1: - _upload_tarball_by_url_v1(staging_path, image_id, - endpoint, extra_headers, properties) - else: - _upload_tarball_by_url_v2(staging_path, image_id, - endpoint, extra_headers, properties) - finally: - utils.cleanup_staging_area(staging_path) - - -if __name__ == '__main__': - utils.register_plugin_calls(download_vhd2, upload_vhd2) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe b/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe deleted file mode 120000 index ea5b79ca1545..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe +++ /dev/null @@ -1 +0,0 @@ -ipxe.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe.py deleted file mode 100755 index 556746de5d36..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2013 OpenStack Foundation -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -"""Inject network configuration into iPXE ISO for boot.""" - -import logging -import os -import shutil - -import utils - -# FIXME(sirp): should this use pluginlib from 5.6? -import pluginlib_nova -pluginlib_nova.configure_logging('ipxe') - - -ISOLINUX_CFG = """SAY iPXE ISO boot image -TIMEOUT 30 -DEFAULT ipxe.krn -LABEL ipxe.krn - KERNEL ipxe.krn - INITRD netcfg.ipxe -""" - -NETCFG_IPXE = """#!ipxe -:start -imgfree -ifclose net0 -set net0/ip %(ip_address)s -set net0/netmask %(netmask)s -set net0/gateway %(gateway)s -set dns %(dns)s -ifopen net0 -goto menu - -:menu -chain %(boot_menu_url)s -goto boot - -:boot -sanboot --no-describe --drive 0x80 -""" - - -def _write_file(filename, data): - # If the ISO was tampered with such that the destination is a symlink, - # that could allow a malicious user to write to protected areas of the - # dom0 filesystem. /HT to comstud for pointing this out. - # - # Short-term, checking that the destination is not a symlink should be - # sufficient. - # - # Long-term, we probably want to perform all file manipulations within a - # chroot jail to be extra safe. - if os.path.islink(filename): - raise RuntimeError('SECURITY: Cannot write to symlinked destination') - - logging.debug("Writing to file '%s'" % filename) - f = open(filename, 'w') - try: - f.write(data) - finally: - f.close() - - -def _unbundle_iso(sr_path, filename, path): - logging.debug("Unbundling ISO '%s'" % filename) - read_only_path = utils.make_staging_area(sr_path) - try: - utils.run_command(['mount', '-o', 'loop', filename, read_only_path]) - try: - shutil.copytree(read_only_path, path) - finally: - utils.run_command(['umount', read_only_path]) - finally: - utils.cleanup_staging_area(read_only_path) - - -def _create_iso(mkisofs_cmd, filename, path): - logging.debug("Creating ISO '%s'..." % filename) - orig_dir = os.getcwd() - os.chdir(path) - try: - utils.run_command([mkisofs_cmd, '-quiet', '-l', '-o', filename, - '-c', 'boot.cat', '-b', 'isolinux.bin', - '-no-emul-boot', '-boot-load-size', '4', - '-boot-info-table', '.']) - finally: - os.chdir(orig_dir) - - -def inject(session, sr_path, vdi_uuid, boot_menu_url, ip_address, netmask, - gateway, dns, mkisofs_cmd): - - iso_filename = '%s.img' % os.path.join(sr_path, 'iso', vdi_uuid) - - # Create staging area so we have a unique path but remove it since - # shutil.copytree will recreate it - staging_path = utils.make_staging_area(sr_path) - utils.cleanup_staging_area(staging_path) - - try: - _unbundle_iso(sr_path, iso_filename, staging_path) - - # Write Configs - _write_file(os.path.join(staging_path, 'netcfg.ipxe'), - NETCFG_IPXE % {"ip_address": ip_address, - "netmask": netmask, - "gateway": gateway, - "dns": dns, - "boot_menu_url": boot_menu_url}) - - _write_file(os.path.join(staging_path, 'isolinux.cfg'), - ISOLINUX_CFG) - - _create_iso(mkisofs_cmd, iso_filename, staging_path) - finally: - utils.cleanup_staging_area(staging_path) - - -if __name__ == "__main__": - utils.register_plugin_calls(inject) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel b/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel deleted file mode 120000 index 14b5f43d7c39..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel +++ /dev/null @@ -1 +0,0 @@ -kernel.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel.py deleted file mode 100755 index ae6def34c67a..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/kernel.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# Copyright (c) 2010 Citrix Systems, Inc. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -"""Handle the manipulation of kernel images.""" - -import errno -import os -import shutil - -import XenAPIPlugin - -import pluginlib_nova - - -pluginlib_nova.configure_logging('kernel') -logging = pluginlib_nova.logging -exists = pluginlib_nova.exists -optional = pluginlib_nova.optional -with_vdi_in_dom0 = pluginlib_nova.with_vdi_in_dom0 - - -KERNEL_DIR = '/boot/guest' - - -def _copy_vdi(dest, copy_args): - vdi_uuid = copy_args['vdi_uuid'] - vdi_size = copy_args['vdi_size'] - cached_image = copy_args['cached-image'] - - logging.debug("copying kernel/ramdisk file from %s to /boot/guest/%s", - dest, vdi_uuid) - filename = KERNEL_DIR + '/' + vdi_uuid - - # Make sure KERNEL_DIR exists, otherwise create it - if not os.path.isdir(KERNEL_DIR): - logging.debug("Creating directory %s", KERNEL_DIR) - os.makedirs(KERNEL_DIR) - - # Read data from /dev/ and write into a file on /boot/guest - of = open(filename, 'wb') - f = open(dest, 'rb') - - # Copy only vdi_size bytes - data = f.read(vdi_size) - of.write(data) - - if cached_image: - # Create a cache file. If caching is enabled, kernel images do not have - # to be fetched from glance. - cached_image = KERNEL_DIR + '/' + cached_image - logging.debug("copying kernel/ramdisk file from %s to /boot/guest/%s", - dest, cached_image) - cache_file = open(cached_image, 'wb') - cache_file.write(data) - cache_file.close() - logging.debug("Done. Filename: %s", cached_image) - - f.close() - of.close() - logging.debug("Done. Filename: %s", filename) - return filename - - -def copy_vdi(session, args): - vdi = exists(args, 'vdi-ref') - size = exists(args, 'image-size') - cached_image = optional(args, 'cached-image') - - # Use the uuid as a filename - vdi_uuid = session.xenapi.VDI.get_uuid(vdi) - copy_args = {'vdi_uuid': vdi_uuid, - 'vdi_size': int(size), - 'cached-image': cached_image} - - filename = with_vdi_in_dom0(session, vdi, False, - lambda dev: - _copy_vdi('/dev/%s' % dev, copy_args)) - return filename - - -def create_kernel_ramdisk(session, args): - """Creates a copy of the kernel/ramdisk image if it is present in the - cache. If the image is not present in the cache, it does nothing. - """ - cached_image = exists(args, 'cached-image') - image_uuid = exists(args, 'new-image-uuid') - cached_image_filename = KERNEL_DIR + '/' + cached_image - filename = KERNEL_DIR + '/' + image_uuid - - if os.path.isfile(cached_image_filename): - shutil.copyfile(cached_image_filename, filename) - logging.debug("Done. Filename: %s", filename) - else: - filename = "" - logging.debug("Cached kernel/ramdisk image not found") - return filename - - -def _remove_file(filepath): - try: - os.remove(filepath) - except OSError, exc: # noqa - if exc.errno != errno.ENOENT: - raise - - -def remove_kernel_ramdisk(session, args): - """Removes kernel and/or ramdisk from dom0's file system.""" - kernel_file = optional(args, 'kernel-file') - ramdisk_file = optional(args, 'ramdisk-file') - if kernel_file: - _remove_file(kernel_file) - if ramdisk_file: - _remove_file(ramdisk_file) - return "ok" - - -if __name__ == '__main__': - XenAPIPlugin.dispatch({'copy_vdi': copy_vdi, - 'create_kernel_ramdisk': create_kernel_ramdisk, - 'remove_kernel_ramdisk': remove_kernel_ramdisk}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration deleted file mode 120000 index c57ebc29625d..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration +++ /dev/null @@ -1 +0,0 @@ -migration.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration.py deleted file mode 100755 index e9de38c9fe62..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2010 OpenStack Foundation -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -""" -XenAPI Plugin for transferring data between host nodes -""" -import utils - -import pluginlib_nova - - -pluginlib_nova.configure_logging('migration') -logging = pluginlib_nova.logging - - -def move_vhds_into_sr(session, instance_uuid, sr_path, uuid_stack): - """Moves the VHDs from their copied location to the SR.""" - staging_path = "/images/instance%s" % instance_uuid - imported_vhds = utils.import_vhds(sr_path, staging_path, uuid_stack) - utils.cleanup_staging_area(staging_path) - return imported_vhds - - -def _rsync_vhds(instance_uuid, host, staging_path, user="root"): - if not staging_path.endswith('/'): - staging_path += '/' - - dest_path = '/images/instance%s/' % (instance_uuid) - - ip_cmd = ["/sbin/ip", "addr", "show"] - output = utils.run_command(ip_cmd) - if ' %s/' % host in output: - # If copying to localhost, don't use SSH - rsync_cmd = ["/usr/bin/rsync", "-av", "--progress", - staging_path, dest_path] - else: - ssh_cmd = 'ssh -o StrictHostKeyChecking=no' - rsync_cmd = ["/usr/bin/rsync", "-av", "--progress", "-e", ssh_cmd, - staging_path, '%s@%s:%s' % (user, host, dest_path)] - - # NOTE(hillad): rsync's progress is carriage returned, requiring - # universal_newlines for real-time output. - - rsync_proc = utils.make_subprocess(rsync_cmd, stdout=True, stderr=True, - universal_newlines=True) - while True: - rsync_progress = rsync_proc.stdout.readline() - if not rsync_progress: - break - logging.debug("[%s] %s" % (instance_uuid, rsync_progress)) - - utils.finish_subprocess(rsync_proc, rsync_cmd) - - -def transfer_vhd(session, instance_uuid, host, vdi_uuid, sr_path, seq_num): - """Rsyncs a VHD to an adjacent host.""" - staging_path = utils.make_staging_area(sr_path) - try: - utils.prepare_staging_area( - sr_path, staging_path, [vdi_uuid], seq_num=seq_num) - _rsync_vhds(instance_uuid, host, staging_path) - finally: - utils.cleanup_staging_area(staging_path) - - -if __name__ == '__main__': - utils.register_plugin_calls(move_vhds_into_sr, transfer_vhd) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version deleted file mode 120000 index db80a4178e80..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version +++ /dev/null @@ -1 +0,0 @@ -nova_plugin_version.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version.py deleted file mode 100755 index c8ff5405df32..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2013 OpenStack Foundation -# Copyright (c) 2013 Citrix Systems, Inc. -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -"""Returns the version of the nova plugins""" - -import utils - -# MAJOR VERSION: Incompatible changes -# MINOR VERSION: Compatible changes, new plugins, etc - -# NOTE(sfinucan): 2.0 will be equivalent to the last in the 1.x stream - -# 1.0 - Initial version. -# 1.1 - New call to check GC status -# 1.2 - Added support for pci passthrough devices -# 1.3 - Add vhd2 functions for doing glance operations by url -# 1.4 - Add support of Glance v2 api -# 1.5 - Added function for network configuration on ovs bridge -# 1.6 - Add function for network configuration on Linux bridge -# 1.7 - Add Partition utilities plugin -# 1.8 - Add support for calling plug-ins with the .py suffix -PLUGIN_VERSION = "1.8" - - -def get_version(session): - return PLUGIN_VERSION - -if __name__ == '__main__': - utils.register_plugin_calls(get_version) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/partition_utils.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/partition_utils.py deleted file mode 100755 index 14f56b582b44..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/partition_utils.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 OpenStack Foundation -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -import logging -import os -import time - -import pluginlib_nova as pluginlib -import utils - -pluginlib.configure_logging("disk_utils") -_ = pluginlib._ - - -def wait_for_dev(session, dev_path, max_seconds): - for i in range(0, max_seconds): - if os.path.exists(dev_path): - return dev_path - time.sleep(1) - - return "" - - -def make_partition(session, dev, partition_start, partition_end): - dev_path = utils.make_dev_path(dev) - - if partition_end != "-": - raise pluginlib.PluginError("Can only create unbounded partitions") - - utils.run_command(['sfdisk', '-uS', dev_path], - '%s,;\n' % (partition_start)) - - -def _mkfs(fs, path, label): - """Format a file or block device - - :param fs: Filesystem type (only 'swap', 'ext3' supported) - :param path: Path to file or block device to format - :param label: Volume label to use - """ - if fs == 'swap': - args = ['mkswap'] - elif fs == 'ext3': - args = ['mkfs', '-t', fs] - # add -F to force no interactive execute on non-block device. - args.extend(['-F']) - if label: - args.extend(['-L', label]) - else: - raise pluginlib.PluginError("Partition type %s not supported" % fs) - args.append(path) - utils.run_command(args) - - -def mkfs(session, dev, partnum, fs_type, fs_label): - dev_path = utils.make_dev_path(dev) - - out = utils.run_command(['kpartx', '-avspp', dev_path]) - try: - logging.info('kpartx output: %s' % out) - mapperdir = os.path.join('/dev', 'mapper') - dev_base = os.path.basename(dev) - partition_path = os.path.join(mapperdir, "%sp%s" % (dev_base, partnum)) - _mkfs(fs_type, partition_path, fs_label) - finally: - # Always remove partitions otherwise we can't unplug the VBD - utils.run_command(['kpartx', '-dvspp', dev_path]) - -if __name__ == "__main__": - utils.register_plugin_calls(wait_for_dev, - make_partition, - mkfs) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py deleted file mode 100644 index 92a5845c1cd9..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) 2010 Citrix Systems, Inc. -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# -# Helper functions for the Nova xapi plugins. In time, this will merge -# with the pluginlib.py shipped with xapi, but for now, that file is not -# very stable, so it's easiest just to have a copy of all the functions -# that we need. -# - -import gettext -import logging -import logging.handlers -import time - -import XenAPI - - -translations = gettext.translation('nova', fallback=True) -_ = translations.ugettext - - -# Logging setup - -def configure_logging(name): - log = logging.getLogger() - log.setLevel(logging.DEBUG) - sysh = logging.handlers.SysLogHandler('/dev/log') - sysh.setLevel(logging.DEBUG) - formatter = logging.Formatter('%s: %%(levelname)-8s %%(message)s' % name) - sysh.setFormatter(formatter) - log.addHandler(sysh) - - -# Exceptions - -class PluginError(Exception): - """Base Exception class for all plugin errors.""" - def __init__(self, *args): - Exception.__init__(self, *args) - - -class ArgumentError(PluginError): - """Raised when required arguments are missing, argument values are invalid, - or incompatible arguments are given. - """ - def __init__(self, *args): - PluginError.__init__(self, *args) - - -# Argument validation - -def exists(args, key): - """Validates that a freeform string argument to a RPC method call is given. - Returns the string. - """ - if key in args: - return args[key] - else: - raise ArgumentError(_('Argument %s is required.') % key) - - -def optional(args, key): - """If the given key is in args, return the corresponding value, otherwise - return None - """ - return key in args and args[key] or None - - -def _get_domain_0(session): - this_host_ref = session.xenapi.session.get_this_host(session.handle) - expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"' - expr = expr % this_host_ref - return list(session.xenapi.VM.get_all_records_where(expr).keys())[0] - - -def with_vdi_in_dom0(session, vdi, read_only, f): - dom0 = _get_domain_0(session) - vbd_rec = {} - vbd_rec['VM'] = dom0 - vbd_rec['VDI'] = vdi - vbd_rec['userdevice'] = 'autodetect' - vbd_rec['bootable'] = False - vbd_rec['mode'] = read_only and 'RO' or 'RW' - vbd_rec['type'] = 'disk' - vbd_rec['unpluggable'] = True - vbd_rec['empty'] = False - vbd_rec['other_config'] = {} - vbd_rec['qos_algorithm_type'] = '' - vbd_rec['qos_algorithm_params'] = {} - vbd_rec['qos_supported_algorithms'] = [] - logging.debug(_('Creating VBD for VDI %s ... '), vdi) - vbd = session.xenapi.VBD.create(vbd_rec) - logging.debug(_('Creating VBD for VDI %s done.'), vdi) - try: - logging.debug(_('Plugging VBD %s ... '), vbd) - session.xenapi.VBD.plug(vbd) - logging.debug(_('Plugging VBD %s done.'), vbd) - return f(session.xenapi.VBD.get_device(vbd)) - finally: - logging.debug(_('Destroying VBD for VDI %s ... '), vdi) - _vbd_unplug_with_retry(session, vbd) - try: - session.xenapi.VBD.destroy(vbd) - except XenAPI.Failure, e: # noqa - logging.error(_('Ignoring XenAPI.Failure %s'), e) - logging.debug(_('Destroying VBD for VDI %s done.'), vdi) - - -def _vbd_unplug_with_retry(session, vbd): - """Call VBD.unplug on the given VBD, with a retry if we get - DEVICE_DETACH_REJECTED. For reasons which I don't understand, we're - seeing the device still in use, even when all processes using the device - should be dead. - """ - while True: - try: - session.xenapi.VBD.unplug(vbd) - logging.debug(_('VBD.unplug successful first time.')) - return - except XenAPI.Failure, e: # noqa - if (len(e.details) > 0 and - e.details[0] == 'DEVICE_DETACH_REJECTED'): - logging.debug(_('VBD.unplug rejected: retrying...')) - time.sleep(1) - elif (len(e.details) > 0 and - e.details[0] == 'DEVICE_ALREADY_DETACHED'): - logging.debug(_('VBD.unplug successful eventually.')) - return - else: - logging.error(_('Ignoring XenAPI.Failure in VBD.unplug: %s'), - e) - return diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py deleted file mode 100644 index 03570fc55842..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py +++ /dev/null @@ -1,519 +0,0 @@ -# Copyright (c) 2012 OpenStack Foundation -# -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -"""Various utilities used by XenServer plugins.""" - -try: - import cPickle as pickle -except ImportError: - import pickle - -import errno -import logging -import os -import shutil -import signal -import subprocess -import tempfile - -import XenAPIPlugin - -LOG = logging.getLogger(__name__) -CHUNK_SIZE = 8192 - - -class CommandNotFound(Exception): - pass - - -def delete_if_exists(path): - try: - os.unlink(path) - except OSError, e: # noqa - if e.errno == errno.ENOENT: - LOG.warning("'%s' was already deleted, skipping delete", path) - else: - raise - - -def _link(src, dst): - LOG.info("Hard-linking file '%s' -> '%s'", src, dst) - os.link(src, dst) - - -def _rename(src, dst): - LOG.info("Renaming file '%s' -> '%s'", src, dst) - try: - os.rename(src, dst) - except OSError, e: # noqa - if e.errno == errno.EXDEV: - LOG.error("Invalid cross-device link. Perhaps %s and %s should " - "be symlinked on the same filesystem?", src, dst) - raise - - -def make_subprocess(cmdline, stdout=False, stderr=False, stdin=False, - universal_newlines=False, close_fds=True, env=None): - """Make a subprocess according to the given command-line string - """ - LOG.info("Running cmd '%s'", " ".join(cmdline)) - kwargs = {} - kwargs['stdout'] = stdout and subprocess.PIPE or None - kwargs['stderr'] = stderr and subprocess.PIPE or None - kwargs['stdin'] = stdin and subprocess.PIPE or None - kwargs['universal_newlines'] = universal_newlines - kwargs['close_fds'] = close_fds - kwargs['env'] = env - try: - proc = subprocess.Popen(cmdline, **kwargs) - except OSError, e: # noqa - if e.errno == errno.ENOENT: - raise CommandNotFound - else: - raise - return proc - - -class SubprocessException(Exception): - def __init__(self, cmdline, ret, out, err): - Exception.__init__(self, "'%s' returned non-zero exit code: " - "retcode=%i, out='%s', stderr='%s'" - % (cmdline, ret, out, err)) - self.cmdline = cmdline - self.ret = ret - self.out = out - self.err = err - - -def finish_subprocess(proc, cmdline, cmd_input=None, ok_exit_codes=None): - """Ensure that the process returned a zero exit code indicating success - """ - if ok_exit_codes is None: - ok_exit_codes = [0] - out, err = proc.communicate(cmd_input) - - ret = proc.returncode - if ret not in ok_exit_codes: - LOG.error("Command '%(cmdline)s' with process id '%(pid)s' expected " - "return code in '%(ok)s' but got '%(rc)s': %(err)s", - {'cmdline': cmdline, 'pid': proc.pid, 'ok': ok_exit_codes, - 'rc': ret, 'err': err}) - raise SubprocessException(' '.join(cmdline), ret, out, err) - return out - - -def run_command(cmd, cmd_input=None, ok_exit_codes=None): - """Abstracts out the basics of issuing system commands. If the command - returns anything in stderr, an exception is raised with that information. - Otherwise, the output from stdout is returned. - - cmd_input is passed to the process on standard input. - """ - proc = make_subprocess(cmd, stdout=True, stderr=True, stdin=True, - close_fds=True) - return finish_subprocess(proc, cmd, cmd_input=cmd_input, - ok_exit_codes=ok_exit_codes) - - -def try_kill_process(proc): - """Sends the given process the SIGKILL signal.""" - pid = proc.pid - LOG.info("Killing process %s", pid) - try: - os.kill(pid, signal.SIGKILL) - except Exception: - LOG.exception("Failed to kill %s", pid) - - -def make_staging_area(sr_path): - """The staging area is a place where we can temporarily store and - manipulate VHDs. The use of the staging area is different for upload and - download: - - Download - ======== - - When we download the tarball, the VHDs contained within will have names - like "snap.vhd" and "image.vhd". We need to assign UUIDs to them before - moving them into the SR. However, since 'image.vhd' may be a base_copy, we - need to link it to 'snap.vhd' (using vhd-util modify) before moving both - into the SR (otherwise the SR.scan will cause 'image.vhd' to be deleted). - The staging area gives us a place to perform these operations before they - are moved to the SR, scanned, and then registered with XenServer. - - Upload - ====== - - On upload, we want to rename the VHDs to reflect what they are, 'snap.vhd' - in the case of the snapshot VHD, and 'image.vhd' in the case of the - base_copy. The staging area provides a directory in which we can create - hard-links to rename the VHDs without affecting what's in the SR. - - - NOTE - ==== - - The staging area is created as a subdirectory within the SR in order to - guarantee that it resides within the same filesystem and therefore permit - hard-linking and cheap file moves. - """ - staging_path = tempfile.mkdtemp(dir=sr_path) - return staging_path - - -def cleanup_staging_area(staging_path): - """Remove staging area directory - - On upload, the staging area contains hard-links to the VHDs in the SR; - it's safe to remove the staging-area because the SR will keep the link - count > 0 (so the VHDs in the SR will not be deleted). - """ - if os.path.exists(staging_path): - shutil.rmtree(staging_path) - - -def _handle_old_style_images(staging_path): - """Rename files to conform to new image format, if needed. - - Old-Style: - - snap.vhd -> image.vhd -> base.vhd - - New-Style: - - 0.vhd -> 1.vhd -> ... (n-1).vhd - - The New-Style format has the benefit of being able to support a VDI chain - of arbitrary length. - """ - file_num = 0 - for filename in ('snap.vhd', 'image.vhd', 'base.vhd'): - path = os.path.join(staging_path, filename) - if os.path.exists(path): - _rename(path, os.path.join(staging_path, "%d.vhd" % file_num)) - file_num += 1 - - # Rename any format of name to 0.vhd when there is only single one - contents = os.listdir(staging_path) - if len(contents) == 1: - filename = contents[0] - if filename != '0.vhd' and filename.endswith('.vhd'): - _rename( - os.path.join(staging_path, filename), - os.path.join(staging_path, '0.vhd')) - - -def _assert_vhd_not_hidden(path): - """Sanity check to ensure that only appropriate VHDs are marked as hidden. - - If this flag is incorrectly set, then when we move the VHD into the SR, it - will be deleted out from under us. - """ - query_cmd = ["vhd-util", "query", "-n", path, "-f"] - out = run_command(query_cmd) - - for line in out.splitlines(): - if line.lower().startswith('hidden'): - value = line.split(':')[1].strip() - if value == "1": - raise Exception( - "VHD %s is marked as hidden without child" % path) - - -def _vhd_util_check(vdi_path): - check_cmd = ["vhd-util", "check", "-n", vdi_path, "-p"] - out = run_command(check_cmd, ok_exit_codes=[0, 22]) - first_line = out.splitlines()[0].strip() - return out, first_line - - -def _validate_vhd(vdi_path): - """This checks for several errors in the VHD structure. - - Most notably, it checks that the timestamp in the footer is correct, but - may pick up other errors also. - - This check ensures that the timestamps listed in the VHD footer aren't in - the future. This can occur during a migration if the clocks on the two - Dom0's are out-of-sync. This would corrupt the SR if it were imported, so - generate an exception to bail. - """ - out, first_line = _vhd_util_check(vdi_path) - - if 'invalid' in first_line: - LOG.warning("VHD invalid, attempting repair.") - repair_cmd = ["vhd-util", "repair", "-n", vdi_path] - run_command(repair_cmd) - out, first_line = _vhd_util_check(vdi_path) - - if 'invalid' in first_line: - if 'footer' in first_line: - part = 'footer' - elif 'header' in first_line: - part = 'header' - else: - part = 'setting' - - details = first_line.split(':', 1) - if len(details) == 2: - details = details[1] - else: - details = first_line - - extra = '' - if 'timestamp' in first_line: - extra = (" ensure source and destination host machines have " - "time set correctly") - - LOG.info("VDI Error details: %s", out) - - raise Exception( - "VDI '%(vdi_path)s' has an invalid %(part)s: '%(details)s'" - "%(extra)s" % {'vdi_path': vdi_path, 'part': part, - 'details': details, 'extra': extra}) - - LOG.info("VDI is valid: %s", vdi_path) - - -def _validate_vdi_chain(vdi_path): - """This check ensures that the parent pointers on the VHDs are valid - before we move the VDI chain to the SR. This is *very* important - because a bad parent pointer will corrupt the SR causing a cascade of - failures. - """ - def get_parent_path(path): - query_cmd = ["vhd-util", "query", "-n", path, "-p"] - out = run_command(query_cmd, ok_exit_codes=[0, 22]) - first_line = out.splitlines()[0].strip() - - if first_line.endswith(".vhd"): - return first_line - elif 'has no parent' in first_line: - return None - elif 'query failed' in first_line: - raise Exception("VDI '%s' not present which breaks" - " the VDI chain, bailing out" % path) - else: - raise Exception("Unexpected output '%s' from vhd-util" % out) - - cur_path = vdi_path - while cur_path: - _validate_vhd(cur_path) - cur_path = get_parent_path(cur_path) - - -def _validate_sequenced_vhds(staging_path): - """This check ensures that the VHDs in the staging area are sequenced - properly from 0 to n-1 with no gaps. - """ - seq_num = 0 - filenames = os.listdir(staging_path) - for filename in filenames: - if not filename.endswith('.vhd'): - continue - - # Ignore legacy swap embedded in the image, generated on-the-fly now - if filename == "swap.vhd": - continue - - vhd_path = os.path.join(staging_path, "%d.vhd" % seq_num) - if not os.path.exists(vhd_path): - raise Exception("Corrupt image. Expected seq number: %d. Files: %s" - % (seq_num, filenames)) - - seq_num += 1 - - -def import_vhds(sr_path, staging_path, uuid_stack): - """Move VHDs from staging area into the SR. - - The staging area is necessary because we need to perform some fixups - (assigning UUIDs, relinking the VHD chain) before moving into the SR, - otherwise the SR manager process could potentially delete the VHDs out from - under us. - - Returns: A dict of imported VHDs: - - {'root': {'uuid': 'ffff-aaaa'}} - """ - _handle_old_style_images(staging_path) - _validate_sequenced_vhds(staging_path) - - files_to_move = [] - - # Collect sequenced VHDs and assign UUIDs to them - seq_num = 0 - while True: - orig_vhd_path = os.path.join(staging_path, "%d.vhd" % seq_num) - if not os.path.exists(orig_vhd_path): - break - - # Rename (0, 1 .. N).vhd -> aaaa-bbbb-cccc-dddd.vhd - vhd_uuid = uuid_stack.pop() - vhd_path = os.path.join(staging_path, "%s.vhd" % vhd_uuid) - _rename(orig_vhd_path, vhd_path) - - if seq_num == 0: - leaf_vhd_path = vhd_path - leaf_vhd_uuid = vhd_uuid - - files_to_move.append(vhd_path) - seq_num += 1 - - # Re-link VHDs, in reverse order, from base-copy -> leaf - parent_path = None - for vhd_path in reversed(files_to_move): - if parent_path: - # Link to parent - modify_cmd = ["vhd-util", "modify", "-n", vhd_path, - "-p", parent_path] - run_command(modify_cmd) - - parent_path = vhd_path - - # Sanity check the leaf VHD - _assert_vhd_not_hidden(leaf_vhd_path) - _validate_vdi_chain(leaf_vhd_path) - - # Move files into SR - for orig_path in files_to_move: - new_path = os.path.join(sr_path, os.path.basename(orig_path)) - _rename(orig_path, new_path) - - imported_vhds = dict(root=dict(uuid=leaf_vhd_uuid)) - return imported_vhds - - -def prepare_staging_area(sr_path, staging_path, vdi_uuids, seq_num=0): - """Hard-link VHDs into staging area.""" - for vdi_uuid in vdi_uuids: - source = os.path.join(sr_path, "%s.vhd" % vdi_uuid) - link_name = os.path.join(staging_path, "%d.vhd" % seq_num) - _link(source, link_name) - seq_num += 1 - - -def create_tarball(fileobj, path, callback=None, compression_level=None): - """Create a tarball from a given path. - - :param fileobj: a file-like object holding the tarball byte-stream. - If None, then only the callback will be used. - :param path: path to create tarball from - :param callback: optional callback to call on each chunk written - :param compression_level: compression level, e.g., 9 for gzip -9. - """ - tar_cmd = ["tar", "-zc", "--directory=%s" % path, "."] - env = os.environ.copy() - if compression_level and 1 <= compression_level <= 9: - env["GZIP"] = "-%d" % compression_level - tar_proc = make_subprocess(tar_cmd, stdout=True, stderr=True, env=env) - - try: - while True: - chunk = tar_proc.stdout.read(CHUNK_SIZE) - if chunk == '': - break - - if callback: - callback(chunk) - - if fileobj: - fileobj.write(chunk) - except Exception: - try_kill_process(tar_proc) - raise - - finish_subprocess(tar_proc, tar_cmd) - - -def extract_tarball(fileobj, path, callback=None): - """Extract a tarball to a given path. - - :param fileobj: a file-like object holding the tarball byte-stream - :param path: path to extract tarball into - :param callback: optional callback to call on each chunk read - """ - tar_cmd = ["tar", "-zx", "--directory=%s" % path] - tar_proc = make_subprocess(tar_cmd, stderr=True, stdin=True) - - try: - while True: - chunk = fileobj.read(CHUNK_SIZE) - if chunk == '': - break - - if callback: - callback(chunk) - - tar_proc.stdin.write(chunk) - - # NOTE(tpownall): If we do not poll for the tar process exit - # code when tar has exited pre maturely there is the chance - # that tar will become a defunct zombie child under glance plugin - # and re parented under init forever waiting on the stdin pipe to - # close. Polling for the exit code allows us to break the pipe. - returncode = tar_proc.poll() - tar_pid = tar_proc.pid - if returncode is not None: - LOG.error("tar extract with process id '%(pid)s' " - "exited early with '%(rc)s'", - {'pid': tar_pid, 'rc': returncode}) - raise SubprocessException( - ' '.join(tar_cmd), returncode, "", "") - - except SubprocessException: - # no need to kill already dead process - raise - except Exception: - LOG.exception("Failed while sending data to tar pid: %s", tar_pid) - try_kill_process(tar_proc) - raise - - finish_subprocess(tar_proc, tar_cmd) - - -def make_dev_path(dev, partition=None, base='/dev'): - """Return a path to a particular device. - - >>> make_dev_path('xvdc') - /dev/xvdc - - >>> make_dev_path('xvdc', 1) - /dev/xvdc1 - """ - path = os.path.join(base, dev) - if partition: - path += str(partition) - return path - - -def _handle_serialization(func): - def wrapped(session, params): - params = pickle.loads(params['params']) - rv = func(session, *params['args'], **params['kwargs']) - return pickle.dumps(rv) - return wrapped - - -def register_plugin_calls(*funcs): - """Wrapper around XenAPIPlugin.dispatch which handles pickle - serialization. - """ - wrapped_dict = {} - for func in funcs: - wrapped_dict[func.__name__] = _handle_serialization(func) - XenAPIPlugin.dispatch(wrapped_dict) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds b/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds deleted file mode 120000 index 06d7b88a715e..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds +++ /dev/null @@ -1 +0,0 @@ -workarounds.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds.py deleted file mode 100755 index 52b9b6208c76..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/workarounds.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 OpenStack Foundation -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -"""Handle the uploading and downloading of images via Glance.""" - -import os -import shutil - -import utils - -import pluginlib_nova - - -pluginlib_nova.configure_logging('workarounds') - - -def _copy_vdis(sr_path, staging_path, vdi_uuids): - seq_num = 0 - for vdi_uuid in vdi_uuids: - src = os.path.join(sr_path, "%s.vhd" % vdi_uuid) - dst = os.path.join(staging_path, "%d.vhd" % seq_num) - shutil.copyfile(src, dst) - seq_num += 1 - - -def safe_copy_vdis(session, sr_path, vdi_uuids, uuid_stack): - staging_path = utils.make_staging_area(sr_path) - try: - _copy_vdis(sr_path, staging_path, vdi_uuids) - return utils.import_vhds(sr_path, staging_path, uuid_stack) - finally: - utils.cleanup_staging_area(staging_path) - - -if __name__ == '__main__': - utils.register_plugin_calls(safe_copy_vdis) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost deleted file mode 120000 index eb1af6d52ec0..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost +++ /dev/null @@ -1 +0,0 @@ -xenhost.py \ No newline at end of file diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost.py deleted file mode 100755 index f67def219e6a..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost.py +++ /dev/null @@ -1,626 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2011 OpenStack Foundation -# Copyright 2011 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true - -# -# XenAPI plugin for host operations -# - -try: - import json -except ImportError: - import simplejson as json -import logging -import re -import sys -import time - -import utils - -import pluginlib_nova as pluginlib -import XenAPI -import XenAPIPlugin - -try: - import xmlrpclib -except ImportError: - import six.moves.xmlrpc_client as xmlrpclib - - -pluginlib.configure_logging("xenhost") -_ = pluginlib._ - - -host_data_pattern = re.compile(r"\s*(\S+) \([^\)]+\) *: ?(.*)") -config_file_path = "/usr/etc/xenhost.conf" -DEFAULT_TRIES = 23 -DEFAULT_SLEEP = 10 - - -def jsonify(fnc): - def wrapper(*args, **kwargs): - return json.dumps(fnc(*args, **kwargs)) - return wrapper - - -class TimeoutError(StandardError): - pass - - -def _run_command(cmd, cmd_input=None): - """Wrap utils.run_command to raise PluginError on failure - """ - try: - return utils.run_command(cmd, cmd_input=cmd_input) - except utils.SubprocessException, e: # noqa - raise pluginlib.PluginError(e.err) - - -def _resume_compute(session, compute_ref, compute_uuid): - """Resume compute node on slave host after pool join. - - This has to happen regardless of the success or failure of the join - operation. - """ - try: - # session is valid if the join operation has failed - session.xenapi.VM.start(compute_ref, False, True) - except XenAPI.Failure: - # if session is invalid, e.g. xapi has restarted, then the pool - # join has been successful, wait for xapi to become alive again - for c in range(0, DEFAULT_TRIES): - try: - _run_command(["xe", "vm-start", "uuid=%s" % compute_uuid]) - return - except pluginlib.PluginError: - logging.exception('Waited %d seconds for the slave to ' - 'become available.' % (c * DEFAULT_SLEEP)) - time.sleep(DEFAULT_SLEEP) - raise pluginlib.PluginError('Unrecoverable error: the host has ' - 'not come back for more than %d seconds' - % (DEFAULT_SLEEP * (DEFAULT_TRIES + 1))) - - -@jsonify -def set_host_enabled(self, arg_dict): - """Sets this host's ability to accept new instances. - It will otherwise continue to operate normally. - """ - enabled = arg_dict.get("enabled") - if enabled is None: - raise pluginlib.PluginError( - _("Missing 'enabled' argument to set_host_enabled")) - - host_uuid = arg_dict['host_uuid'] - if enabled == "true": - result = _run_command(["xe", "host-enable", "uuid=%s" % host_uuid]) - elif enabled == "false": - result = _run_command(["xe", "host-disable", "uuid=%s" % host_uuid]) - else: - raise pluginlib.PluginError(_("Illegal enabled status: %s") % enabled) - # Should be empty string - if result: - raise pluginlib.PluginError(result) - # Return the current enabled status - cmd = ["xe", "host-param-get", "uuid=%s" % host_uuid, "param-name=enabled"] - host_enabled = _run_command(cmd) - if host_enabled == "true": - status = "enabled" - else: - status = "disabled" - return {"status": status} - - -def _write_config_dict(dct): - conf_file = file(config_file_path, "w") - json.dump(dct, conf_file) - conf_file.close() - - -def _get_config_dict(): - """Returns a dict containing the key/values in the config file. - If the file doesn't exist, it is created, and an empty dict - is returned. - """ - try: - conf_file = file(config_file_path) - config_dct = json.load(conf_file) - conf_file.close() - except IOError: - # File doesn't exist - config_dct = {} - # Create the file - _write_config_dict(config_dct) - return config_dct - - -@jsonify -def get_config(self, arg_dict): - """Return the value stored for the specified key, or None if no match.""" - conf = _get_config_dict() - params = arg_dict["params"] - try: - dct = json.loads(params) - except Exception: - dct = params - key = dct["key"] - ret = conf.get(key) - if ret is None: - # Can't jsonify None - return "None" - return ret - - -@jsonify -def set_config(self, arg_dict): - """Write the specified key/value pair, overwriting any existing value.""" - conf = _get_config_dict() - params = arg_dict["params"] - try: - dct = json.loads(params) - except Exception: - dct = params - key = dct["key"] - val = dct["value"] - if val is None: - # Delete the key, if present - conf.pop(key, None) - else: - conf.update({key: val}) - _write_config_dict(conf) - - -def iptables_config(session, args): - # command should be either save or restore - logging.debug("iptables_config:enter") - logging.debug("iptables_config: args=%s", args) - cmd_args = pluginlib.exists(args, 'cmd_args') - logging.debug("iptables_config: cmd_args=%s", cmd_args) - process_input = pluginlib.optional(args, 'process_input') - logging.debug("iptables_config: process_input=%s", process_input) - cmd = json.loads(cmd_args) - cmd = map(str, cmd) - - # either execute iptable-save or iptables-restore - # command must be only one of these two - # process_input must be used only with iptables-restore - if len(cmd) > 0 and cmd[0] in ('iptables-save', - 'iptables-restore', - 'ip6tables-save', - 'ip6tables-restore'): - result = _run_command(cmd, process_input) - ret_str = json.dumps(dict(out=result, err='')) - logging.debug("iptables_config:exit") - return ret_str - # else don't do anything and return an error - else: - raise pluginlib.PluginError(_("Invalid iptables command")) - - -def _ovs_add_patch_port(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - port_name = pluginlib.exists(args, 'port_name') - peer_port_name = pluginlib.exists(args, 'peer_port_name') - cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', - port_name, '--', 'add-port', bridge_name, port_name, - '--', 'set', 'interface', port_name, - 'type=patch', 'options:peer=%s' % peer_port_name] - return _run_command(cmd_args) - - -def _ovs_del_port(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - port_name = pluginlib.exists(args, 'port_name') - cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', - bridge_name, port_name] - return _run_command(cmd_args) - - -def _ovs_del_br(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - cmd_args = ['ovs-vsctl', '--', '--if-exists', - 'del-br', bridge_name] - return _run_command(cmd_args) - - -def _ovs_set_if_external_id(args): - interface = pluginlib.exists(args, 'interface') - extneral_id = pluginlib.exists(args, 'extneral_id') - value = pluginlib.exists(args, 'value') - cmd_args = ['ovs-vsctl', 'set', 'Interface', interface, - 'external-ids:%s=%s' % (extneral_id, value)] - return _run_command(cmd_args) - - -def _ovs_add_port(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - port_name = pluginlib.exists(args, 'port_name') - cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name, - '--', 'add-port', bridge_name, port_name] - return _run_command(cmd_args) - - -def _ip_link_get_dev(args): - device_name = pluginlib.exists(args, 'device_name') - cmd_args = ['ip', 'link', 'show', device_name] - return _run_command(cmd_args) - - -def _ip_link_del_dev(args): - device_name = pluginlib.exists(args, 'device_name') - cmd_args = ['ip', 'link', 'delete', device_name] - return _run_command(cmd_args) - - -def _ip_link_add_veth_pair(args): - dev1_name = pluginlib.exists(args, 'dev1_name') - dev2_name = pluginlib.exists(args, 'dev2_name') - cmd_args = ['ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer', - 'name', dev2_name] - return _run_command(cmd_args) - - -def _ip_link_set_dev(args): - device_name = pluginlib.exists(args, 'device_name') - option = pluginlib.exists(args, 'option') - cmd_args = ['ip', 'link', 'set', device_name, option] - return _run_command(cmd_args) - - -def _ip_link_set_promisc(args): - device_name = pluginlib.exists(args, 'device_name') - option = pluginlib.exists(args, 'option') - cmd_args = ['ip', 'link', 'set', device_name, 'promisc', option] - return _run_command(cmd_args) - - -def _brctl_add_br(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - cmd_args = ['brctl', 'addbr', bridge_name] - return _run_command(cmd_args) - - -def _brctl_del_br(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - cmd_args = ['brctl', 'delbr', bridge_name] - return _run_command(cmd_args) - - -def _brctl_set_fd(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - fd = pluginlib.exists(args, 'fd') - cmd_args = ['brctl', 'setfd', bridge_name, fd] - return _run_command(cmd_args) - - -def _brctl_set_stp(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - option = pluginlib.exists(args, 'option') - cmd_args = ['brctl', 'stp', bridge_name, option] - return _run_command(cmd_args) - - -def _brctl_add_if(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - if_name = pluginlib.exists(args, 'interface_name') - cmd_args = ['brctl', 'addif', bridge_name, if_name] - return _run_command(cmd_args) - - -def _brctl_del_if(args): - bridge_name = pluginlib.exists(args, 'bridge_name') - if_name = pluginlib.exists(args, 'interface_name') - cmd_args = ['brctl', 'delif', bridge_name, if_name] - return _run_command(cmd_args) - - -ALLOWED_NETWORK_CMDS = { - # allowed cmds to config OVS bridge - 'ovs_add_patch_port': _ovs_add_patch_port, - 'ovs_add_port': _ovs_add_port, - 'ovs_del_port': _ovs_del_port, - 'ovs_del_br': _ovs_del_br, - 'ovs_set_if_external_id': _ovs_set_if_external_id, - 'ip_link_add_veth_pair': _ip_link_add_veth_pair, - 'ip_link_del_dev': _ip_link_del_dev, - 'ip_link_get_dev': _ip_link_get_dev, - 'ip_link_set_dev': _ip_link_set_dev, - 'ip_link_set_promisc': _ip_link_set_promisc, - 'brctl_add_br': _brctl_add_br, - 'brctl_add_if': _brctl_add_if, - 'brctl_del_br': _brctl_del_br, - 'brctl_del_if': _brctl_del_if, - 'brctl_set_fd': _brctl_set_fd, - 'brctl_set_stp': _brctl_set_stp - } - - -def network_config(session, args): - """network config functions""" - cmd = pluginlib.exists(args, 'cmd') - if not isinstance(cmd, basestring): - msg = _("invalid command '%s'") % str(cmd) - raise pluginlib.PluginError(msg) - return - if cmd not in ALLOWED_NETWORK_CMDS: - msg = _("Dom0 execution of '%s' is not permitted") % cmd - raise pluginlib.PluginError(msg) - return - cmd_args = pluginlib.exists(args, 'args') - return ALLOWED_NETWORK_CMDS[cmd](cmd_args) - - -def _power_action(action, arg_dict): - # Host must be disabled first - host_uuid = arg_dict['host_uuid'] - result = _run_command(["xe", "host-disable", "uuid=%s" % host_uuid]) - if result: - raise pluginlib.PluginError(result) - # All running VMs must be shutdown - result = _run_command(["xe", "vm-shutdown", "--multiple", - "resident-on=%s" % host_uuid]) - if result: - raise pluginlib.PluginError(result) - cmds = {"reboot": "host-reboot", - "startup": "host-power-on", - "shutdown": "host-shutdown"} - result = _run_command(["xe", cmds[action], "uuid=%s" % host_uuid]) - # Should be empty string - if result: - raise pluginlib.PluginError(result) - return {"power_action": action} - - -@jsonify -def host_reboot(self, arg_dict): - """Reboots the host.""" - return _power_action("reboot", arg_dict) - - -@jsonify -def host_shutdown(self, arg_dict): - """Reboots the host.""" - return _power_action("shutdown", arg_dict) - - -@jsonify -def host_start(self, arg_dict): - """Starts the host. - - Currently not feasible, since the host runs on the same machine as - Xen. - """ - return _power_action("startup", arg_dict) - - -@jsonify -def host_join(self, arg_dict): - """Join a remote host into a pool. - - The pool's master is the host where the plugin is called from. The - following constraints apply: - - - The host must have no VMs running, except nova-compute, which - will be shut down (and restarted upon pool-join) automatically, - - The host must have no shared storage currently set up, - - The host must have the same license of the master, - - The host must have the same supplemental packs as the master. - """ - session = XenAPI.Session(arg_dict.get("url")) - session.login_with_password(arg_dict.get("user"), - arg_dict.get("password")) - compute_ref = session.xenapi.VM.get_by_uuid(arg_dict.get('compute_uuid')) - session.xenapi.VM.clean_shutdown(compute_ref) - try: - if arg_dict.get("force"): - session.xenapi.pool.join(arg_dict.get("master_addr"), - arg_dict.get("master_user"), - arg_dict.get("master_pass")) - else: - session.xenapi.pool.join_force(arg_dict.get("master_addr"), - arg_dict.get("master_user"), - arg_dict.get("master_pass")) - finally: - _resume_compute(session, compute_ref, arg_dict.get("compute_uuid")) - - -@jsonify -def host_data(self, arg_dict): - """Runs the commands on the xenstore host to return the current status - information. - """ - host_uuid = arg_dict['host_uuid'] - resp = _run_command(["xe", "host-param-list", "uuid=%s" % host_uuid]) - parsed_data = parse_response(resp) - # We have the raw dict of values. Extract those that we need, - # and convert the data types as needed. - ret_dict = cleanup(parsed_data) - # Add any config settings - config = _get_config_dict() - ret_dict.update(config) - return ret_dict - - -def parse_response(resp): - data = {} - for ln in resp.splitlines(): - if not ln: - continue - mtch = host_data_pattern.match(ln.strip()) - try: - k, v = mtch.groups() - data[k] = v - except AttributeError: - # Not a valid line; skip it - continue - return data - - -@jsonify -def host_uptime(self, arg_dict): - """Returns the result of the uptime command on the xenhost.""" - return {"uptime": _run_command(['uptime'])} - - -def cleanup(dct): - """Take the raw KV pairs returned and translate them into the - appropriate types, discarding any we don't need. - """ - def safe_int(val): - """Integer values will either be string versions of numbers, - or empty strings. Convert the latter to nulls. - """ - try: - return int(val) - except ValueError: - return None - - def strip_kv(ln): - return [val.strip() for val in ln.split(":", 1)] - - out = {} - -# sbs = dct.get("supported-bootloaders", "") -# out["host_supported-bootloaders"] = sbs.split("; ") -# out["host_suspend-image-sr-uuid"] = dct.get("suspend-image-sr-uuid", "") -# out["host_crash-dump-sr-uuid"] = dct.get("crash-dump-sr-uuid", "") -# out["host_local-cache-sr"] = dct.get("local-cache-sr", "") - out["enabled"] = dct.get("enabled", "true") == "true" - out["host_memory"] = omm = {} - omm["total"] = safe_int(dct.get("memory-total", "")) - omm["overhead"] = safe_int(dct.get("memory-overhead", "")) - omm["free"] = safe_int(dct.get("memory-free", "")) - omm["free-computed"] = safe_int( - dct.get("memory-free-computed", "")) - -# out["host_API-version"] = avv = {} -# avv["vendor"] = dct.get("API-version-vendor", "") -# avv["major"] = safe_int(dct.get("API-version-major", "")) -# avv["minor"] = safe_int(dct.get("API-version-minor", "")) - - out["enabled"] = dct.get("enabled", True) - out["host_uuid"] = dct.get("uuid", None) - out["host_name-label"] = dct.get("name-label", "") - out["host_name-description"] = dct.get("name-description", "") -# out["host_host-metrics-live"] = dct.get( -# "host-metrics-live", "false") == "true" - out["host_hostname"] = dct.get("hostname", "") - out["host_ip_address"] = dct.get("address", "") - oc = dct.get("other-config", "") - out["host_other-config"] = ocd = {} - if oc: - for oc_fld in oc.split("; "): - ock, ocv = strip_kv(oc_fld) - ocd[ock] = ocv - - capabilities = dct.get("capabilities", "") - out["host_capabilities"] = capabilities.replace(";", "").split() -# out["host_allowed-operations"] = dct.get( -# "allowed-operations", "").split("; ") -# lsrv = dct.get("license-server", "") -# out["host_license-server"] = ols = {} -# if lsrv: -# for lspart in lsrv.split("; "): -# lsk, lsv = lspart.split(": ") -# if lsk == "port": -# ols[lsk] = safe_int(lsv) -# else: -# ols[lsk] = lsv -# sv = dct.get("software-version", "") -# out["host_software-version"] = osv = {} -# if sv: -# for svln in sv.split("; "): -# svk, svv = strip_kv(svln) -# osv[svk] = svv - cpuinf = dct.get("cpu_info", "") - out["host_cpu_info"] = ocp = {} - if cpuinf: - for cpln in cpuinf.split("; "): - cpk, cpv = strip_kv(cpln) - if cpk in ("cpu_count", "family", "model", "stepping"): - ocp[cpk] = safe_int(cpv) - else: - ocp[cpk] = cpv -# out["host_edition"] = dct.get("edition", "") -# out["host_external-auth-service-name"] = dct.get( -# "external-auth-service-name", "") - return out - - -def query_gc(session, sr_uuid, vdi_uuid): - result = _run_command(["/opt/xensource/sm/cleanup.py", - "-q", "-u", sr_uuid]) - # Example output: "Currently running: True" - return result[19:].strip() == "True" - - -def get_pci_device_details(session): - """Returns a string that is a list of pci devices with details. - - This string is obtained by running the command lspci. With -vmm option, - it dumps PCI device data in machine readable form. This verbose format - display a sequence of records separated by a blank line. We will also - use option "-n" to get vendor_id and device_id as numeric values and - the "-k" option to get the kernel driver used if any. - """ - return _run_command(["lspci", "-vmmnk"]) - - -def get_pci_type(session, pci_device): - """Returns the type of the PCI device (type-PCI, type-VF or type-PF). - - pci-device -- The address of the pci device - """ - # We need to add the domain if it is missing - if pci_device.count(':') == 1: - pci_device = "0000:" + pci_device - output = _run_command(["ls", "/sys/bus/pci/devices/" + pci_device + "/"]) - - if "physfn" in output: - return "type-VF" - if "virtfn" in output: - return "type-PF" - return "type-PCI" - - -if __name__ == "__main__": - # Support both serialized and non-serialized plugin approaches - _, methodname = xmlrpclib.loads(sys.argv[1]) - if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type', - 'network_config']: - utils.register_plugin_calls(query_gc, - get_pci_device_details, - get_pci_type, - network_config) - - XenAPIPlugin.dispatch( - {"host_data": host_data, - "set_host_enabled": set_host_enabled, - "host_shutdown": host_shutdown, - "host_reboot": host_reboot, - "host_start": host_start, - "host_join": host_join, - "get_config": get_config, - "set_config": set_config, - "iptables_config": iptables_config, - "host_uptime": host_uptime}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py deleted file mode 100755 index bec7e2a621e3..000000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2010 Citrix Systems, Inc. -# Copyright 2010 OpenStack Foundation -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace -# which means the Nova xenapi plugins must use only Python 2.4 features - -# -# XenAPI plugin for reading/writing information to xenstore -# - -try: - import json -except ImportError: - import simplejson as json - -import utils # noqa - -import XenAPIPlugin # noqa - -import pluginlib_nova as pluginlib # noqa -pluginlib.configure_logging("xenstore") - - -class XenstoreError(pluginlib.PluginError): - """Errors that occur when calling xenstore-* through subprocesses.""" - - def __init__(self, cmd, return_code, stderr, stdout): - msg = "cmd: %s; returncode: %d; stderr: %s; stdout: %s" - msg = msg % (cmd, return_code, stderr, stdout) - self.cmd = cmd - self.return_code = return_code - self.stderr = stderr - self.stdout = stdout - pluginlib.PluginError.__init__(self, msg) - - -def jsonify(fnc): - def wrapper(*args, **kwargs): - ret = fnc(*args, **kwargs) - try: - json.loads(ret) - except ValueError: - # Value should already be JSON-encoded, but some operations - # may write raw sting values; this will catch those and - # properly encode them. - ret = json.dumps(ret) - return ret - return wrapper - - -def record_exists(arg_dict): - """Returns whether or not the given record exists. The record path - is determined from the given path and dom_id in the arg_dict. - """ - cmd = ["xenstore-exists", "/local/domain/%(dom_id)s/%(path)s" % arg_dict] - try: - _run_command(cmd) - return True - except XenstoreError, e: # noqa - if e.stderr == '': - # if stderr was empty, this just means the path did not exist - return False - # otherwise there was a real problem - raise - - -@jsonify -def read_record(self, arg_dict): - """Returns the value stored at the given path for the given dom_id. - These must be encoded as key/value pairs in arg_dict. You can - optionally include a key 'ignore_missing_path'; if this is present - and boolean True, attempting to read a non-existent path will return - the string 'None' instead of raising an exception. - """ - cmd = ["xenstore-read", "/local/domain/%(dom_id)s/%(path)s" % arg_dict] - try: - result = _run_command(cmd) - return result.strip() - except XenstoreError, e: # noqa - if not arg_dict.get("ignore_missing_path", False): - raise - if not record_exists(arg_dict): - return "None" - # Just try again in case the agent write won the race against - # the record_exists check. If this fails again, it will likely raise - # an equally meaningful XenstoreError as the one we just caught - result = _run_command(cmd) - return result.strip() - - -@jsonify -def write_record(self, arg_dict): - """Writes to xenstore at the specified path. If there is information - already stored in that location, it is overwritten. As in read_record, - the dom_id and path must be specified in the arg_dict; additionally, - you must specify a 'value' key, whose value must be a string. Typically, - you can json-ify more complex values and store the json output. - """ - cmd = ["xenstore-write", - "/local/domain/%(dom_id)s/%(path)s" % arg_dict, - arg_dict["value"]] - _run_command(cmd) - return arg_dict["value"] - - -@jsonify -def list_records(self, arg_dict): - """Returns all the stored data at or below the given path for the - given dom_id. The data is returned as a json-ified dict, with the - path as the key and the stored value as the value. If the path - doesn't exist, an empty dict is returned. - """ - dirpath = "/local/domain/%(dom_id)s/%(path)s" % arg_dict - cmd = ["xenstore-ls", dirpath.rstrip("/")] - try: - recs = _run_command(cmd) - except XenstoreError, e: # noqa - if not record_exists(arg_dict): - return {} - # Just try again in case the path was created in between - # the "ls" and the existence check. If this fails again, it will - # likely raise an equally meaningful XenstoreError - recs = _run_command(cmd) - base_path = arg_dict["path"] - paths = _paths_from_ls(recs) - ret = {} - for path in paths: - if base_path: - arg_dict["path"] = "%s/%s" % (base_path, path) - else: - arg_dict["path"] = path - rec = read_record(self, arg_dict) - try: - val = json.loads(rec) - except ValueError: - val = rec - ret[path] = val - return ret - - -@jsonify -def delete_record(self, arg_dict): - """Just like it sounds: it removes the record for the specified - VM and the specified path from xenstore. - """ - cmd = ["xenstore-rm", "/local/domain/%(dom_id)s/%(path)s" % arg_dict] - try: - return _run_command(cmd) - except XenstoreError, e: # noqa - if 'could not remove path' in e.stderr: - # Entry already gone. We're good to go. - return '' - raise - - -def _paths_from_ls(recs): - """The xenstore-ls command returns a listing that isn't terribly - useful. This method cleans that up into a dict with each path - as the key, and the associated string as the value. - """ - last_nm = "" - level = 0 - path = [] - ret = [] - for ln in recs.splitlines(): - nm, val = ln.rstrip().split(" = ") - barename = nm.lstrip() - this_level = len(nm) - len(barename) - if this_level == 0: - ret.append(barename) - level = 0 - path = [] - elif this_level == level: - # child of same parent - ret.append("%s/%s" % ("/".join(path), barename)) - elif this_level > level: - path.append(last_nm) - ret.append("%s/%s" % ("/".join(path), barename)) - level = this_level - elif this_level < level: - path = path[:this_level] - ret.append("%s/%s" % ("/".join(path), barename)) - level = this_level - last_nm = barename - return ret - - -def _run_command(cmd): - """Wrap utils.run_command to raise XenstoreError on failure - """ - try: - return utils.run_command(cmd) - except utils.SubprocessException, e: # noqa - raise XenstoreError(e.cmdline, e.ret, e.err, e.out) - -if __name__ == "__main__": - XenAPIPlugin.dispatch( - {"read_record": read_record, - "write_record": write_record, - "list_records": list_records, - "delete_record": delete_record})