Browse Source

Allow run_update_ansible_action run with Mistral or Ansible

We extend run_update_ansible_action
to allow running the Ansible playbooks with Mistral
or executing directly thru Ansible.

This is needed  in case we need to run
exceptionally a task depending on Mistral
but Mistral is broken. For example, retry
an upgrade operation after having Mistral broken.

Change-Id: I15511b4f36260292e0ea4100b15b8e65a701b38b
(cherry picked from commit 5249fbb262)
tags/9.3.1
Mathieu Bultel 7 months ago
parent
commit
de4f45dd26

+ 2
- 0
tripleoclient/constants.py View File

@@ -72,3 +72,5 @@ DEPRECATED_SERVICES = {"OS::TripleO::Services::OpenDaylightApi":
72 72
                        "OpenDaylight to another networking backend. We "
73 73
                        "recommend you understand other networking "
74 74
                        "alternatives such as OVS or OVN. "}
75
+
76
+DEFAULT_VALIDATIONS_BASEDIR = '/usr/share/openstack-tripleo-validations'

+ 311
- 0
tripleoclient/tests/test_utils.py View File

@@ -16,11 +16,15 @@
16 16
 
17 17
 import argparse
18 18
 import datetime
19
+import logging
19 20
 import mock
20 21
 import os.path
22
+import subprocess
21 23
 import tempfile
22 24
 from uuid import uuid4
23 25
 
26
+import sys
27
+
24 28
 from unittest import TestCase
25 29
 import yaml
26 30
 
@@ -29,6 +33,313 @@ from tripleoclient import exceptions
29 33
 from tripleoclient import utils
30 34
 
31 35
 
36
+class TestRunAnsiblePlaybook(TestCase):
37
+    def setUp(self):
38
+        self.unlink_patch = mock.patch('os.unlink')
39
+        self.addCleanup(self.unlink_patch.stop)
40
+        self.unlink_patch.start()
41
+        self.mock_log = mock.Mock('logging.getLogger')
42
+        python_version = sys.version_info[0]
43
+        self.ansible_playbook_cmd = "ansible-playbook-%s" % (python_version)
44
+
45
+    @mock.patch('os.path.exists', return_value=False)
46
+    @mock.patch('tripleoclient.utils.run_command_and_log')
47
+    def test_no_playbook(self, mock_run, mock_exists):
48
+        self.assertRaises(RuntimeError,
49
+                          utils.run_ansible_playbook,
50
+                          self.mock_log,
51
+                          '/tmp',
52
+                          'non-existing.yaml',
53
+                          'localhost,'
54
+                          )
55
+        mock_exists.assert_called_once_with('/tmp/non-existing.yaml')
56
+        mock_run.assert_not_called()
57
+
58
+    @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
59
+    @mock.patch('os.path.exists', return_value=True)
60
+    @mock.patch('tripleoclient.utils.run_command_and_log')
61
+    def test_subprocess_error(self, mock_run, mock_exists, mock_mkstemp):
62
+        mock_process = mock.Mock()
63
+        mock_process.returncode = 1
64
+        mock_process.stdout.read.side_effect = ["Error\n"]
65
+        mock_run.return_value = mock_process
66
+
67
+        env = os.environ.copy()
68
+        env['ANSIBLE_LIBRARY'] = \
69
+            ('/root/.ansible/plugins/modules:'
70
+             '/usr/share/ansible/plugins/modules:'
71
+             '/usr/share/openstack-tripleo-validations/library')
72
+        env['ANSIBLE_LOOKUP_PLUGINS'] = \
73
+            ('root/.ansible/plugins/lookup:'
74
+             '/usr/share/ansible/plugins/lookup:'
75
+             '/usr/share/openstack-tripleo-validations/lookup_plugins')
76
+        env['ANSIBLE_CALLBACK_PLUGINS'] = \
77
+            ('~/.ansible/plugins/callback:'
78
+             '/usr/share/ansible/plugins/callback:'
79
+             '/usr/share/openstack-tripleo-validations/callback_plugins')
80
+        env['ANSIBLE_ROLES_PATH'] = \
81
+            ('/root/.ansible/roles:'
82
+             '/usr/share/ansible/roles:'
83
+             '/etc/ansible/roles:'
84
+             '/usr/share/openstack-tripleo-validations/roles')
85
+        env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
86
+        env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
87
+        env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
88
+
89
+        self.assertRaises(RuntimeError,
90
+                          utils.run_ansible_playbook,
91
+                          self.mock_log,
92
+                          '/tmp',
93
+                          'existing.yaml',
94
+                          'localhost,'
95
+                          )
96
+        mock_run.assert_called_once_with(self.mock_log,
97
+                                         [self.ansible_playbook_cmd,
98
+                                          '-u', 'root',
99
+                                          '-i', 'localhost,', '-v',
100
+                                          '-c', 'smart',
101
+                                          '/tmp/existing.yaml'],
102
+                                         env=env, retcode_only=False)
103
+
104
+    @mock.patch('os.path.isabs')
105
+    @mock.patch('os.path.exists', return_value=False)
106
+    @mock.patch('tripleoclient.utils.run_command_and_log')
107
+    def test_non_existing_config(self, mock_run, mock_exists, mock_isabs):
108
+        self.assertRaises(RuntimeError,
109
+                          utils.run_ansible_playbook, self.mock_log,
110
+                          '/tmp', 'existing.yaml', 'localhost,',
111
+                          '/tmp/foo.cfg'
112
+                          )
113
+        mock_exists.assert_called_once_with('/tmp/foo.cfg')
114
+        mock_isabs.assert_called_once_with('/tmp/foo.cfg')
115
+        mock_run.assert_not_called()
116
+
117
+    @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
118
+    @mock.patch('os.path.exists', return_value=True)
119
+    @mock.patch('tripleoclient.utils.run_command_and_log')
120
+    def test_run_success_default(self, mock_run, mock_exists, mock_mkstemp):
121
+        mock_process = mock.Mock()
122
+        mock_process.returncode = 0
123
+        mock_run.return_value = mock_process
124
+
125
+        retcode = utils.run_ansible_playbook(self.mock_log,
126
+                                             '/tmp',
127
+                                             'existing.yaml',
128
+                                             'localhost,')
129
+        self.assertEqual(retcode, 0)
130
+        mock_exists.assert_called_once_with('/tmp/existing.yaml')
131
+
132
+        env = os.environ.copy()
133
+        env['ANSIBLE_LIBRARY'] = \
134
+            ('/root/.ansible/plugins/modules:'
135
+             '/usr/share/ansible/plugins/modules:'
136
+             '/usr/share/openstack-tripleo-validations/library')
137
+        env['ANSIBLE_LOOKUP_PLUGINS'] = \
138
+            ('root/.ansible/plugins/lookup:'
139
+             '/usr/share/ansible/plugins/lookup:'
140
+             '/usr/share/openstack-tripleo-validations/lookup_plugins')
141
+        env['ANSIBLE_CALLBACK_PLUGINS'] = \
142
+            ('~/.ansible/plugins/callback:'
143
+             '/usr/share/ansible/plugins/callback:'
144
+             '/usr/share/openstack-tripleo-validations/callback_plugins')
145
+        env['ANSIBLE_ROLES_PATH'] = \
146
+            ('/root/.ansible/roles:'
147
+             '/usr/share/ansible/roles:'
148
+             '/etc/ansible/roles:'
149
+             '/usr/share/openstack-tripleo-validations/roles')
150
+        env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
151
+        env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
152
+        env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
153
+
154
+        mock_run.assert_called_once_with(self.mock_log,
155
+                                         [self.ansible_playbook_cmd,
156
+                                          '-u', 'root',
157
+                                          '-i', 'localhost,', '-v',
158
+                                          '-c', 'smart',
159
+                                          '/tmp/existing.yaml'],
160
+                                         env=env, retcode_only=False)
161
+
162
+    @mock.patch('os.path.isabs')
163
+    @mock.patch('os.path.exists', return_value=True)
164
+    @mock.patch('tripleoclient.utils.run_command_and_log')
165
+    def test_run_success_ansible_cfg(self, mock_run, mock_exists, mock_isabs):
166
+        mock_process = mock.Mock()
167
+        mock_process.returncode = 0
168
+        mock_run.return_value = mock_process
169
+
170
+        retcode = utils.run_ansible_playbook(self.mock_log, '/tmp',
171
+                                             'existing.yaml', 'localhost,',
172
+                                             ansible_config='/tmp/foo.cfg')
173
+        self.assertEqual(retcode, 0)
174
+
175
+        mock_isabs.assert_called_once_with('/tmp/foo.cfg')
176
+
177
+        exist_calls = [mock.call('/tmp/foo.cfg'),
178
+                       mock.call('/tmp/existing.yaml')]
179
+        mock_exists.assert_has_calls(exist_calls, any_order=False)
180
+
181
+        env = os.environ.copy()
182
+        env['ANSIBLE_LIBRARY'] = \
183
+            ('/root/.ansible/plugins/modules:'
184
+             '/usr/share/ansible/plugins/modules:'
185
+             '/usr/share/openstack-tripleo-validations/library')
186
+        env['ANSIBLE_LOOKUP_PLUGINS'] = \
187
+            ('root/.ansible/plugins/lookup:'
188
+             '/usr/share/ansible/plugins/lookup:'
189
+             '/usr/share/openstack-tripleo-validations/lookup_plugins')
190
+        env['ANSIBLE_CALLBACK_PLUGINS'] = \
191
+            ('~/.ansible/plugins/callback:'
192
+             '/usr/share/ansible/plugins/callback:'
193
+             '/usr/share/openstack-tripleo-validations/callback_plugins')
194
+        env['ANSIBLE_ROLES_PATH'] = \
195
+            ('/root/.ansible/roles:'
196
+             '/usr/share/ansible/roles:'
197
+             '/etc/ansible/roles:'
198
+             '/usr/share/openstack-tripleo-validations/roles')
199
+        env['ANSIBLE_CONFIG'] = '/tmp/foo.cfg'
200
+        env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
201
+        env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
202
+
203
+        mock_run.assert_called_once_with(self.mock_log,
204
+                                         [self.ansible_playbook_cmd,
205
+                                          '-u', 'root',
206
+                                          '-i', 'localhost,', '-v',
207
+                                          '-c', 'smart',
208
+                                          '/tmp/existing.yaml'],
209
+                                         env=env, retcode_only=False)
210
+
211
+    @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
212
+    @mock.patch('os.path.exists', return_value=True)
213
+    @mock.patch('tripleoclient.utils.run_command_and_log')
214
+    def test_run_success_connection_local(self, mock_run, mock_exists,
215
+                                          mok_mkstemp):
216
+        mock_process = mock.Mock()
217
+        mock_process.returncode = 0
218
+        mock_run.return_value = mock_process
219
+
220
+        retcode = utils.run_ansible_playbook(self.mock_log, '/tmp',
221
+                                             'existing.yaml',
222
+                                             'localhost,',
223
+                                             connection='local')
224
+        self.assertEqual(retcode, 0)
225
+        mock_exists.assert_called_once_with('/tmp/existing.yaml')
226
+        env = os.environ.copy()
227
+        env['ANSIBLE_LIBRARY'] = \
228
+            ('/root/.ansible/plugins/modules:'
229
+             '/usr/share/ansible/plugins/modules:'
230
+             '/usr/share/openstack-tripleo-validations/library')
231
+        env['ANSIBLE_LOOKUP_PLUGINS'] = \
232
+            ('root/.ansible/plugins/lookup:'
233
+             '/usr/share/ansible/plugins/lookup:'
234
+             '/usr/share/openstack-tripleo-validations/lookup_plugins')
235
+        env['ANSIBLE_CALLBACK_PLUGINS'] = \
236
+            ('~/.ansible/plugins/callback:'
237
+             '/usr/share/ansible/plugins/callback:'
238
+             '/usr/share/openstack-tripleo-validations/callback_plugins')
239
+        env['ANSIBLE_ROLES_PATH'] = \
240
+            ('/root/.ansible/roles:'
241
+             '/usr/share/ansible/roles:'
242
+             '/etc/ansible/roles:'
243
+             '/usr/share/openstack-tripleo-validations/roles')
244
+        env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
245
+        env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
246
+        env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
247
+
248
+        mock_run.assert_called_once_with(self.mock_log,
249
+                                         [self.ansible_playbook_cmd,
250
+                                          '-u', 'root',
251
+                                          '-i', 'localhost,', '-v',
252
+                                          '-c', 'local',
253
+                                          '/tmp/existing.yaml'],
254
+                                         env=env, retcode_only=False)
255
+
256
+
257
+class TestRunCommandAndLog(TestCase):
258
+    def setUp(self):
259
+        self.mock_logger = mock.Mock(spec=logging.Logger)
260
+
261
+        self.mock_process = mock.Mock()
262
+        self.mock_process.stdout.readline.side_effect = ['foo\n', 'bar\n']
263
+        self.mock_process.wait.side_effect = [0]
264
+        self.mock_process.returncode = 0
265
+
266
+        mock_sub = mock.patch('subprocess.Popen',
267
+                              return_value=self.mock_process)
268
+        self.mock_popen = mock_sub.start()
269
+        self.addCleanup(mock_sub.stop)
270
+
271
+        self.cmd = ['exit', '0']
272
+        self.e_cmd = ['exit', '1']
273
+        self.log_calls = [mock.call('foo'),
274
+                          mock.call('bar')]
275
+
276
+    def test_success_default(self):
277
+        retcode = utils.run_command_and_log(self.mock_logger, self.cmd)
278
+        self.mock_popen.assert_called_once_with(self.cmd,
279
+                                                stdout=subprocess.PIPE,
280
+                                                stderr=subprocess.STDOUT,
281
+                                                shell=False,
282
+                                                cwd=None, env=None)
283
+        self.assertEqual(retcode, 0)
284
+        self.mock_logger.warning.assert_has_calls(self.log_calls,
285
+                                                  any_order=False)
286
+
287
+    @mock.patch('subprocess.Popen')
288
+    def test_error_subprocess(self, mock_popen):
289
+        mock_process = mock.Mock()
290
+        mock_process.stdout.readline.side_effect = ['Error\n']
291
+        mock_process.wait.side_effect = [1]
292
+        mock_process.returncode = 1
293
+
294
+        mock_popen.return_value = mock_process
295
+
296
+        retcode = utils.run_command_and_log(self.mock_logger, self.e_cmd)
297
+        mock_popen.assert_called_once_with(self.e_cmd, stdout=subprocess.PIPE,
298
+                                           stderr=subprocess.STDOUT,
299
+                                           shell=False, cwd=None,
300
+                                           env=None)
301
+
302
+        self.assertEqual(retcode, 1)
303
+        self.mock_logger.warning.assert_called_once_with('Error')
304
+
305
+    def test_success_env(self):
306
+        test_env = os.environ.copy()
307
+        retcode = utils.run_command_and_log(self.mock_logger, self.cmd,
308
+                                            env=test_env)
309
+        self.mock_popen.assert_called_once_with(self.cmd,
310
+                                                stdout=subprocess.PIPE,
311
+                                                stderr=subprocess.STDOUT,
312
+                                                shell=False,
313
+                                                cwd=None, env=test_env)
314
+        self.assertEqual(retcode, 0)
315
+        self.mock_logger.warning.assert_has_calls(self.log_calls,
316
+                                                  any_order=False)
317
+
318
+    def test_success_cwd(self):
319
+        test_cwd = '/usr/local/bin'
320
+        retcode = utils.run_command_and_log(self.mock_logger, self.cmd,
321
+                                            cwd=test_cwd)
322
+        self.mock_popen.assert_called_once_with(self.cmd,
323
+                                                stdout=subprocess.PIPE,
324
+                                                stderr=subprocess.STDOUT,
325
+                                                shell=False,
326
+                                                cwd=test_cwd, env=None)
327
+        self.assertEqual(retcode, 0)
328
+        self.mock_logger.warning.assert_has_calls(self.log_calls,
329
+                                                  any_order=False)
330
+
331
+    def test_success_no_retcode(self):
332
+        run = utils.run_command_and_log(self.mock_logger, self.cmd,
333
+                                        retcode_only=False)
334
+        self.mock_popen.assert_called_once_with(self.cmd,
335
+                                                stdout=subprocess.PIPE,
336
+                                                stderr=subprocess.STDOUT,
337
+                                                shell=False,
338
+                                                cwd=None, env=None)
339
+        self.assertEqual(run, self.mock_process)
340
+        self.mock_logger.warning.assert_not_called()
341
+
342
+
32 343
 class TestWaitForStackUtil(TestCase):
33 344
     def setUp(self):
34 345
         self.mock_orchestration = mock.Mock()

+ 290
- 6
tripleoclient/utils.py View File

@@ -26,6 +26,7 @@ import six
26 26
 import socket
27 27
 import subprocess
28 28
 import sys
29
+import tempfile
29 30
 import time
30 31
 import yaml
31 32
 
@@ -36,10 +37,181 @@ from osc_lib.i18n import _
36 37
 from oslo_concurrency import processutils
37 38
 from six.moves import configparser
38 39
 
40
+from tripleo_common.utils import config
39 41
 from tripleoclient import constants
40 42
 from tripleoclient import exceptions
41 43
 
42 44
 
45
+LOG = logging.getLogger(__name__ + ".utils")
46
+
47
+
48
+def run_ansible_playbook(logger,
49
+                         workdir,
50
+                         playbook,
51
+                         inventory,
52
+                         ansible_config=None,
53
+                         retries=True,
54
+                         connection='smart',
55
+                         output_callback='json',
56
+                         python_interpreter=None,
57
+                         ssh_user='root',
58
+                         key=None,
59
+                         module_path=None,
60
+                         limit_hosts=None,
61
+                         tags='',
62
+                         skip_tags='',
63
+                         verbosity=1):
64
+    """Simple wrapper for ansible-playbook
65
+
66
+    :param logger: logger instance
67
+    :type logger: Logger
68
+
69
+    :param workdir: location of the playbook
70
+    :type workdir: String
71
+
72
+    :param playbook: playbook filename
73
+    :type playbook: String
74
+
75
+    :param inventory: either proper inventory file, or a coma-separated list
76
+    :type inventory: String
77
+
78
+    :param ansible_config: Pass either Absolute Path, or None to generate a
79
+    temporary file, or False to not manage configuration at all
80
+    :type ansible_config: String
81
+
82
+    :param retries: do you want to get a retry_file?
83
+    :type retries: Boolean
84
+
85
+    :param connection: connection type (local, smart, etc)
86
+    :type connection: String
87
+
88
+    :param output_callback: Callback for output format. Defaults to "json"
89
+    :type output_callback: String
90
+
91
+    :param python_interpreter: Absolute path for the Python interpreter
92
+    on the host where Ansible is run.
93
+    :type python_interpreter: String
94
+
95
+    :param ssh_user: user for the ssh connection
96
+    :type ssh_user: String
97
+
98
+    :param key: private key to use for the ssh connection
99
+    :type key: String
100
+
101
+    :param module_path: location of the ansible module and library
102
+    :type module_path: String
103
+
104
+    :param limit_hosts: limit the execution to the hosts
105
+    :type limit_hosts: String
106
+
107
+    :param tags: run specific tags
108
+    :type tags: String
109
+
110
+    :param skip_tags: skip specific tags
111
+    :type skip_tags: String
112
+
113
+    :param verbosity: verbosity level for Ansible execution
114
+    :type verbosity: Interger
115
+    """
116
+    env = os.environ.copy()
117
+
118
+    env['ANSIBLE_LIBRARY'] = \
119
+        ('/root/.ansible/plugins/modules:'
120
+         '/usr/share/ansible/plugins/modules:'
121
+         '%s/library' % constants.DEFAULT_VALIDATIONS_BASEDIR)
122
+    env['ANSIBLE_LOOKUP_PLUGINS'] = \
123
+        ('root/.ansible/plugins/lookup:'
124
+         '/usr/share/ansible/plugins/lookup:'
125
+         '%s/lookup_plugins' % constants.DEFAULT_VALIDATIONS_BASEDIR)
126
+    env['ANSIBLE_CALLBACK_PLUGINS'] = \
127
+        ('~/.ansible/plugins/callback:'
128
+         '/usr/share/ansible/plugins/callback:'
129
+         '%s/callback_plugins' % constants.DEFAULT_VALIDATIONS_BASEDIR)
130
+    env['ANSIBLE_ROLES_PATH'] = \
131
+        ('/root/.ansible/roles:'
132
+         '/usr/share/ansible/roles:'
133
+         '/etc/ansible/roles:'
134
+         '%s/roles' % constants.DEFAULT_VALIDATIONS_BASEDIR)
135
+    env['ANSIBLE_LOG_PATH'] = os.path.join(workdir, 'ansible.log')
136
+    env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
137
+
138
+    cleanup = False
139
+    if ansible_config is None:
140
+        _, tmp_config = tempfile.mkstemp(prefix=playbook, suffix='ansible.cfg')
141
+        with open(tmp_config, 'w+') as f:
142
+            f.write("[defaults]\nstdout_callback = %s\n" % output_callback)
143
+            if not retries:
144
+                f.write("retry_files_enabled = False\n")
145
+            f.close()
146
+        env['ANSIBLE_CONFIG'] = tmp_config
147
+        cleanup = True
148
+
149
+    elif os.path.isabs(ansible_config):
150
+        if os.path.exists(ansible_config):
151
+            env['ANSIBLE_CONFIG'] = ansible_config
152
+        else:
153
+            raise RuntimeError('No such configuration file: %s' %
154
+                               ansible_config)
155
+    elif os.path.exists(os.path.join(workdir, ansible_config)):
156
+        env['ANSIBLE_CONFIG'] = os.path.join(workdir, ansible_config)
157
+
158
+    play = os.path.join(workdir, playbook)
159
+
160
+    if os.path.exists(play):
161
+        cmd = ["ansible-playbook-{}".format(sys.version_info[0]),
162
+               '-u', ssh_user,
163
+               '-i', inventory
164
+               ]
165
+
166
+        if 0 < verbosity < 6:
167
+            cmd.extend(['-' + ('v' * verbosity)])
168
+
169
+        if key is not None:
170
+            cmd.extend(['--private-key=%s' % key])
171
+
172
+        if module_path is not None:
173
+            cmd.extend(['--module-path=%s' % module_path])
174
+
175
+        if limit_hosts is not None:
176
+            cmd.extend(['-l %s' % limit_hosts])
177
+
178
+        if tags is not '':
179
+            cmd.extend(['-t %s' % tags])
180
+
181
+        if skip_tags is not '':
182
+            cmd.extend(['--skip_tags %s' % skip_tags])
183
+
184
+        if python_interpreter is not None:
185
+            cmd.extend(['-e', 'ansible_python_interpreter=%s' %
186
+                              python_interpreter])
187
+
188
+        cmd.extend(['-c', connection, play])
189
+
190
+        proc = run_command_and_log(logger, cmd, env=env, retcode_only=False)
191
+        proc.wait()
192
+        cleanup and os.unlink(tmp_config)
193
+        if proc.returncode != 0:
194
+            raise RuntimeError(proc.stdout.read())
195
+        return proc.returncode
196
+    else:
197
+        cleanup and os.unlink(tmp_config)
198
+        raise RuntimeError('No such playbook: %s' % play)
199
+
200
+
201
+def download_ansible_playbooks(client, stack_name, output_dir='/tmp'):
202
+
203
+    log = logging.getLogger(__name__ + ".download_ansible_playbooks")
204
+    stack_config = config.Config(client)
205
+    tmp_ansible_dir = tempfile.mkdtemp(prefix='tripleo-ansible-',
206
+                                       dir=output_dir)
207
+
208
+    log.warning(_('Downloading {0} ansible playbooks...').format(stack_name))
209
+    stack_config.write_config(stack_config.fetch_config(stack_name),
210
+                              stack_name,
211
+                              tmp_ansible_dir)
212
+    return tmp_ansible_dir
213
+
214
+
43 215
 def bracket_ipv6(address):
44 216
     """Put a bracket around address if it is valid IPv6
45 217
 
@@ -892,17 +1064,89 @@ def get_tripleo_ansible_inventory(inventory_file='',
892 1064
             "Inventory file %s can not be found." % inventory_file)
893 1065
 
894 1066
 
895
-def run_update_ansible_action(log, clients, nodes, inventory, playbook,
896
-                              all_playbooks, action, ssh_user,
897
-                              skip_tags='', verbosity=1):
1067
+def run_update_ansible_action(log, clients, nodes, inventory,
1068
+                              playbook, all_playbooks, ssh_user,
1069
+                              action=None, skip_tags='',
1070
+                              verbosity='1', workdir='', priv_key=''):
1071
+
898 1072
     playbooks = [playbook]
899 1073
     if playbook == "all":
900 1074
         playbooks = all_playbooks
901 1075
     for book in playbooks:
902 1076
         log.debug("Running ansible playbook %s " % book)
903
-        action.update_ansible(clients, nodes=nodes, inventory_file=inventory,
904
-                              playbook=book, node_user=ssh_user,
905
-                              skip_tags=skip_tags, verbosity=verbosity)
1077
+        if action:
1078
+            action.update_ansible(clients, nodes=nodes,
1079
+                                  inventory_file=inventory,
1080
+                                  playbook=book, node_user=ssh_user,
1081
+                                  skip_tags=skip_tags,
1082
+                                  verbosity=verbosity)
1083
+        else:
1084
+            run_ansible_playbook(logger=LOG,
1085
+                                 workdir=workdir,
1086
+                                 playbook=book,
1087
+                                 inventory=inventory,
1088
+                                 ssh_user=ssh_user,
1089
+                                 key=ssh_private_key(workdir, priv_key),
1090
+                                 module_path='/usr/share/ansible-modules',
1091
+                                 limit_hosts=nodes,
1092
+                                 skip_tags=skip_tags)
1093
+
1094
+
1095
+def ssh_private_key(workdir, key):
1096
+    if not key:
1097
+        return None
1098
+    if (isinstance(key, six.string_types) and
1099
+            os.path.exists(key)):
1100
+        return key
1101
+
1102
+    path = os.path.join(workdir, 'ssh_private_key')
1103
+    with open(path, 'w') as ssh_key:
1104
+        ssh_key.write(key)
1105
+    os.chmod(path, 0o600)
1106
+    return path
1107
+
1108
+
1109
+def parse_extra_vars(extra_var_strings):
1110
+    """Parses extra variables like Ansible would.
1111
+
1112
+    Each element in extra_var_strings is like the raw value of -e
1113
+    parameter of ansible-playbook command. It can either be very
1114
+    simple 'key=val key2=val2' format or it can be '{ ... }'
1115
+    representing a YAML/JSON object.
1116
+
1117
+    The 'key=val key2=val2' format gets processed as if it was
1118
+    '{"key": "val", "key2": "val2"}' object, and all YAML/JSON objects
1119
+    get shallow-merged together in the order as they appear in
1120
+    extra_var_strings, latter objects taking precedence over earlier
1121
+    ones.
1122
+
1123
+    :param extra_var_strings: unparsed value(s) of -e parameter(s)
1124
+    :type extra_var_strings: list of strings
1125
+
1126
+    :returns dict representing a merged object of all extra vars
1127
+    """
1128
+    result = {}
1129
+
1130
+    for extra_var_string in extra_var_strings:
1131
+        invalid_yaml = False
1132
+
1133
+        try:
1134
+            parse_vars = yaml.safe_load(extra_var_string)
1135
+        except yaml.YAMLError:
1136
+            invalid_yaml = True
1137
+
1138
+        if invalid_yaml or not isinstance(parse_vars, dict):
1139
+            try:
1140
+                parse_vars = dict(
1141
+                    item.split('=') for item in extra_var_string.split())
1142
+            except ValueError:
1143
+                raise ValueError(
1144
+                    'Invalid format for {extra_var_string}'.format(
1145
+                        extra_var_string=extra_var_string))
1146
+
1147
+        result.update(parse_vars)
1148
+
1149
+    return result
906 1150
 
907 1151
 
908 1152
 def prepend_environment(environment_files, templates_dir, environment):
@@ -984,3 +1228,43 @@ def check_file_for_enabled_service(env_file):
984 1228
 def check_deprecated_service_is_enabled(environment_files):
985 1229
     for env_file in environment_files:
986 1230
         check_file_for_enabled_service(env_file)
1231
+
1232
+
1233
+def run_command_and_log(log, cmd, cwd=None, env=None, retcode_only=True):
1234
+    """Run command and log output
1235
+
1236
+    :param log: logger instance for logging
1237
+    :type log: Logger
1238
+
1239
+    :param cmd: command in list form
1240
+    :type cmd: List
1241
+
1242
+    :param cwd: current worknig directory for execution
1243
+    :type cmd: String
1244
+
1245
+    :param env: modified environment for command run
1246
+    :type env: List
1247
+
1248
+    :param retcode_only: Returns only retcode instead or proc objec
1249
+    :type retcdode_only: Boolean
1250
+    """
1251
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1252
+                            stderr=subprocess.STDOUT, shell=False,
1253
+                            cwd=cwd, env=env)
1254
+    if retcode_only:
1255
+        # TODO(aschultz): this should probably goto a log file
1256
+        while True:
1257
+            try:
1258
+                line = proc.stdout.readline()
1259
+            except StopIteration:
1260
+                break
1261
+            if line != b'':
1262
+                if isinstance(line, bytes):
1263
+                    line = line.decode('utf-8')
1264
+                log.warning(line.rstrip())
1265
+            else:
1266
+                break
1267
+        proc.stdout.close()
1268
+        return proc.wait()
1269
+    else:
1270
+        return proc

+ 2
- 2
tripleoclient/v1/overcloud_ffwd_upgrade.py View File

@@ -154,8 +154,8 @@ class FFWDUpgradeRun(command.Command):
154 154
         limit_hosts = ''
155 155
         oooutils.run_update_ansible_action(
156 156
             self.log, clients, limit_hosts, inventory,
157
-            constants.FFWD_UPGRADE_PLAYBOOK, [], package_update,
158
-            parsed_args.ssh_user, verbosity=verbosity)
157
+            constants.FFWD_UPGRADE_PLAYBOOK, [], parsed_args.ssh_user,
158
+            package_update, verbosity=verbosity)
159 159
 
160 160
 
161 161
 class FFWDUpgradeConverge(DeployOvercloud):

+ 1
- 1
tripleoclient/v1/overcloud_update.py View File

@@ -162,8 +162,8 @@ class UpdateRun(command.Command):
162 162
         oooutils.run_update_ansible_action(self.log, clients, nodes, inventory,
163 163
                                            playbook,
164 164
                                            constants.MINOR_UPDATE_PLAYBOOKS,
165
-                                           package_update,
166 165
                                            parsed_args.ssh_user,
166
+                                           package_update,
167 167
                                            verbosity=verbosity)
168 168
 
169 169
 

+ 3
- 3
tripleoclient/v1/overcloud_upgrade.py View File

@@ -211,10 +211,10 @@ class UpgradeRun(command.Command):
211 211
         oooutils.run_update_ansible_action(self.log, clients, limit_hosts,
212 212
                                            inventory, playbook,
213 213
                                            constants.MAJOR_UPGRADE_PLAYBOOKS,
214
-                                           package_update,
215 214
                                            parsed_args.ssh_user,
216
-                                           skip_tags=skip_tags,
217
-                                           verbosity=verbosity)
215
+                                           package_update,
216
+                                           skip_tags,
217
+                                           verbosity)
218 218
 
219 219
         playbooks = (constants.MAJOR_UPGRADE_PLAYBOOKS
220 220
                      if playbook == 'all' else playbook)

Loading…
Cancel
Save