Kaynağa Gözat

Add env command availability check on OSMorphing hosts

Env variables are passed to privileged and chrooted commands
through `env(1)`, so a missing `env(1)` would surface as an unrelated
failure partway through OSMorphing.

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 3 hafta önce
ebeveyn
işleme
30df81f682

+ 1 - 0
coriolis/osmorphing/osmount/base.py

@@ -105,6 +105,7 @@ class BaseSSHOSMountTools(BaseOSMountTools):
         self._ssh = ssh
 
     def setup(self):
+        utils.check_env_command(self._ssh)
         if self._allow_ssh_env_vars():
             self._ssh.close()
             self._connect()

+ 20 - 1
coriolis/tests/osmorphing/osmount/test_base.py

@@ -110,14 +110,33 @@ class BaseSSHOSMountToolsTestCase(test_base.CoriolisBaseTestCase):
                 self.conn_info['ip'], 22)
         )
 
+    @mock.patch.object(base.utils, 'check_env_command')
     @mock.patch.object(base.BaseSSHOSMountTools, '_allow_ssh_env_vars')
     @mock.patch.object(base.BaseSSHOSMountTools, '_connect')
-    def test_setup(self, mock_connect, mock_allow_ssh_vars):
+    def test_setup(self, mock_connect, mock_allow_ssh_vars,
+                   mock_check_env_command):
         self.base_os_mount_tools.setup()
         mock_allow_ssh_vars.return_value = True
+        mock_check_env_command.assert_called_once_with(self.ssh)
         self.ssh.close.assert_called_once_with()
         mock_connect.assert_called_once_with()
 
+    @mock.patch.object(base.utils, 'check_env_command')
+    @mock.patch.object(base.BaseSSHOSMountTools, '_allow_ssh_env_vars')
+    @mock.patch.object(base.BaseSSHOSMountTools, '_connect')
+    def test_setup_without_env_command(
+            self, mock_connect, mock_allow_ssh_vars, mock_check_env_command):
+        # NOTE: env(1) carries the environment variables over to the
+        # privileged and chrooted commands, so its absence must abort the
+        # setup instead of silently dropping the proxy settings later on.
+        mock_check_env_command.side_effect = exception.CoriolisException(
+            "env is unavailable")
+
+        self.assertRaises(
+            exception.CoriolisException, self.base_os_mount_tools.setup)
+
+        mock_allow_ssh_vars.assert_not_called()
+
     @mock.patch.object(base.utils, 'exec_ssh_cmd')
     def test__exec_cmd(self, mock_exec_ssh_cmd):
         result = self.base_os_mount_tools._exec_cmd(self.cmd, timeout=120)

+ 29 - 0
coriolis/tests/test_utils.py

@@ -449,6 +449,35 @@ class UtilsTestCase(test_base.CoriolisBaseTestCase):
     def _get_executed_ssh_cmd(self):
         return self.mock_ssh.exec_command.call_args[0][0]
 
+    def test_check_env_command(self):
+        self._setup_successful_ssh_cmd()
+
+        utils.check_env_command(self.mock_ssh)
+
+        self.assertEqual("command -v env", self._get_executed_ssh_cmd())
+
+    def test_check_env_command_missing(self):
+        self.mock_stdout.read.return_value = b''
+        self.mock_stdout.channel.recv_exit_status.return_value = 1
+        self.mock_ssh.exec_command.return_value = (None, self.mock_stdout,
+                                                   self.mock_stdout)
+
+        self.assertRaises(
+            exception.CoriolisException, utils.check_env_command,
+            self.mock_ssh)
+
+    def test_check_env_command_not_found(self):
+        # 'command -v' itself being unavailable must be reported the same
+        # way as a missing env(1).
+        self.mock_stdout.read.return_value = b''
+        self.mock_stdout.channel.recv_exit_status.return_value = 127
+        self.mock_ssh.exec_command.return_value = (None, self.mock_stdout,
+                                                   self.mock_stdout)
+
+        self.assertRaises(
+            exception.CoriolisException, utils.check_env_command,
+            self.mock_ssh)
+
     def test_get_env_command_prefix(self):
         self.assertEqual("", utils.get_env_command_prefix(None))
         self.assertEqual("", utils.get_env_command_prefix({}))

+ 20 - 0
coriolis/utils.py

@@ -342,6 +342,26 @@ def get_env_command_prefix(environment):
         for key, value in environment.items())
 
 
+def check_env_command(ssh):
+    """
+    Checks that env(1) exists on the given machine, as it is what carries
+    the environment variables over to privileged and chrooted commands.
+    Raises if it is missing, so the cause is reported upfront instead of
+    surfacing as an unrelated failure much later on.
+    """
+
+    try:
+        exec_ssh_cmd(ssh, "command -v env", get_pty=True)
+    except (exception.SSHCommandFailed,
+            exception.SSHCommandNotFoundException) as ex:
+        raise exception.CoriolisException(
+            "The 'env' command is unavailable on the OSMorphing minion "
+            "machine. It is required in order to forward environment "
+            "variables to commands run on the migrated machine (such as "
+            "proxy settings). Please switch the minion machine "
+            "image/template to one that has this command in place.") from ex
+
+
 def _exec_ssh_cmd(ssh, cmd, environment=None, get_pty=False, timeout=None):
     sanitized_cmd = strutils.mask_password(cmd)
     remote_str = "<undeterminable>"