Skip to content

Commit 059c498

Browse files
committed
CFY-4691 add ctx.py wrapper
1 parent f498771 commit 059c498

4 files changed

Lines changed: 363 additions & 7 deletions

File tree

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import tempfile
2+
import os
3+
import shutil
4+
5+
import testtools
6+
from testfixtures import log_capture
7+
8+
import cloudify.ctx_wrappers
9+
from cloudify.exceptions import NonRecoverableError
10+
from cloudify.exceptions import OperationRetry
11+
from cloudify.workflows import local
12+
from script_runner.tasks import ProcessException
13+
14+
BLUEPRINT_DIR = os.path.join(os.path.dirname(__file__), 'wrapper_blueprint')
15+
16+
17+
class PythonWrapperTests(testtools.TestCase):
18+
19+
@classmethod
20+
def setUpClass(cls):
21+
source = os.path.join(os.path.dirname(
22+
cloudify.ctx_wrappers.__file__), 'ctx-py.py')
23+
cls.tempdir = tempfile.mkdtemp()
24+
destination = os.path.join(cls.tempdir, 'ctxwrapper.py')
25+
shutil.copy(source, destination)
26+
27+
@classmethod
28+
def tearDownClass(cls):
29+
try:
30+
shutil.rmtree(cls.tempdir)
31+
except:
32+
pass
33+
34+
def setUp(self):
35+
super(PythonWrapperTests, self).setUp()
36+
self.script_path = tempfile.mktemp()
37+
self.addCleanup(self.cleanup)
38+
39+
def cleanup(self):
40+
try:
41+
os.remove(self.script_path)
42+
except:
43+
pass
44+
45+
def _prescript(self):
46+
return (
47+
'#!/usr/bin/env python\n'
48+
'import sys\n'
49+
'sys.path.append("{0}")\n'
50+
'from ctxwrapper import ctx\n'.format(self.tempdir)
51+
)
52+
53+
def _create_script(self, script):
54+
script_path = tempfile.mktemp()
55+
with open(script_path, 'w') as f:
56+
f.write(self._prescript() + script)
57+
return script_path
58+
59+
def _run(self, script,
60+
process=None,
61+
workflow_name='execute_operation',
62+
parameters=None,
63+
env_var='value',
64+
task_retries=0):
65+
66+
self.script_path = self._create_script(script)
67+
process = process or {}
68+
process.update({'ctx_proxy_type': 'unix'})
69+
70+
inputs = {
71+
'script_path': self.script_path,
72+
'process': process,
73+
'env_var': env_var
74+
}
75+
blueprint_path = os.path.join(BLUEPRINT_DIR, 'blueprint.yaml')
76+
self.env = local.init_env(blueprint_path,
77+
name=self._testMethodName,
78+
inputs=inputs)
79+
result = self.env.execute(workflow_name,
80+
parameters=parameters,
81+
task_retries=task_retries,
82+
task_retry_interval=0)
83+
if not result:
84+
result = self.env.storage.get_node_instances()[0][
85+
'runtime_properties']
86+
return result
87+
88+
def test_direct_ctx_call(self):
89+
script = ('ctx("instance runtime-properties key value")')
90+
result = self._run(script)
91+
self.assertEqual(result['key'], 'value')
92+
93+
def test_direct_bad_ctx_call(self):
94+
script = ('ctx("bad_call")')
95+
ex = self.assertRaises(ProcessException, self._run, script)
96+
self.assertIn(
97+
'RuntimeError: bad_call cannot be processed in',
98+
str(ex))
99+
100+
def test_direct_ctx_call_missing_property(self):
101+
script = ('ctx("node properties missing_node_property")')
102+
ex = self.assertRaises(ProcessException, self._run, script)
103+
self.assertIn(
104+
'Cannot override read only properties',
105+
str(ex))
106+
107+
@log_capture()
108+
def test_logger(self, capture):
109+
# Note that while we're calling debug, we're not checking
110+
# for its output as it's currently not printable.
111+
script = ('ctx.logger.debug("debug_message")\n'
112+
'ctx.logger.info("info_message")\n'
113+
'ctx.logger.warn("warning_message")\n'
114+
'ctx.logger.error("error_message")')
115+
expected_levels = ['INFO', 'WARNING', 'ERROR']
116+
expected_msgs = [
117+
'info_message',
118+
'warning_message',
119+
'error_message'
120+
]
121+
self._run(script)
122+
# first message is unrelated to the test
123+
capture.records.pop(0)
124+
for m in range(1, len(expected_levels)):
125+
self.assertEqual(
126+
'{0}: {1}'.format(
127+
capture.records[m].levelname,
128+
capture.records[m].msg),
129+
'{0}: {1}'.format(
130+
expected_levels[m],
131+
expected_msgs[m]))
132+
133+
def test_get_node_properties(self):
134+
script = ('value = ctx.node.properties["key"]\n'
135+
'ctx.returns(value)')
136+
result = self._run(script)
137+
self.assertEqual('value', result)
138+
139+
def test_get_node_properties_get_function(self):
140+
script = ('value = ctx.node.properties.get("key", "b")\n'
141+
'ctx.returns(value)')
142+
result = self._run(script)
143+
self.assertEqual('value', result)
144+
145+
def test_get_node_properties_get_function_missing_key_no_default(self):
146+
script = ('value = ctx.node.properties.get("key1")\n'
147+
'ctx.returns(type(value))')
148+
result = self._run(script)
149+
self.assertEqual("<type 'NoneType'>", str(result))
150+
151+
def test_get_node_properties_get_function_missing_key_with_default(self):
152+
script = ('value = ctx.node.properties.get("key1", "b")\n'
153+
'ctx.returns(value)')
154+
result = self._run(script)
155+
self.assertEqual('b', result)
156+
157+
def test_get_node_id(self):
158+
script = ('value = ctx.node.id\n'
159+
'ctx.returns(value)')
160+
result = self._run(script)
161+
self.assertEqual('test_node', result)
162+
163+
def test_get_node_name(self):
164+
script = ('value = ctx.node.name\n'
165+
'ctx.returns(value)')
166+
result = self._run(script)
167+
self.assertEqual('test_node', result)
168+
169+
def test_get_node_type(self):
170+
script = ('value = ctx.node.type\n'
171+
'ctx.returns(value)')
172+
result = self._run(script)
173+
self.assertEqual('cloudify.nodes.Compute', result)
174+
175+
def test_get_instance_id(self):
176+
script = ('value = ctx.instance.id\n'
177+
'ctx.returns(value)')
178+
result = self._run(script)
179+
self.assertIn('test_node_', result)
180+
181+
def test_get_instance_relationships(self):
182+
script = ('value = ctx.instance.relationships\n'
183+
'ctx.returns(value)')
184+
result = self._run(script)
185+
self.assertEqual([], eval(result))
186+
187+
def test_set_get_instance_runtime_properties(self):
188+
script = ('ctx.instance.runtime_properties["key"] = "value"\n'
189+
'value = ctx.instance.runtime_properties["key"]\n'
190+
'ctx.returns(value)')
191+
result = self._run(script)
192+
self.assertEqual('value', result)
193+
194+
def test_get_instance_runtime_properties_non_string(self):
195+
script = ('value = ctx.instance.runtime_properties["key"] = 1\n'
196+
'ctx.returns(type(value))')
197+
result = self._run(script)
198+
self.assertEqual("<type 'int'>", result)
199+
200+
def test_get_instance_runtime_properties_missing_key_no_default(self):
201+
script = ('value = ctx.instance.runtime_properties.get("key1")\n'
202+
'ctx.returns(type(value))')
203+
result = self._run(script)
204+
self.assertEqual("<type 'NoneType'>", str(result))
205+
206+
def test_get_instance_runtime_properties_missing_key_with_default(self):
207+
script = ('value = ctx.instance.runtime_properties.get("key1", "b")\n'
208+
'ctx.returns(value)')
209+
result = self._run(script)
210+
self.assertEqual('b', result)
211+
212+
def test_get_instance_host_ip(self):
213+
script = ('value = ctx.instance.host_ip\n'
214+
'ctx.returns(value)')
215+
result = self._run(script)
216+
self.assertEqual(result, '1.1.1.1')
217+
218+
def _test_download_resource(self, script, expected_content):
219+
try:
220+
result = self._run(script)
221+
self.assertTrue(result.startswith('/tmp'))
222+
self.assertTrue(result.endswith('-resource'))
223+
with open(result) as f:
224+
resulting_content = f.read()
225+
self.assertEqual(expected_content, resulting_content)
226+
finally:
227+
os.remove(result)
228+
229+
def test_download_resource(self):
230+
script = ('path = ctx.download_resource("resource")\n'
231+
'ctx.returns(path)')
232+
self._test_download_resource(
233+
script=script,
234+
expected_content='{{ ctx.node.name }}')
235+
236+
def test_download_resource_with_destination(self):
237+
fd, temp_path = tempfile.mkstemp(suffix='-resource')
238+
os.close(fd)
239+
script = ('path = ctx.download_resource("resource", "{0}")\n'
240+
'ctx.returns(path)'.format(temp_path))
241+
self._test_download_resource(
242+
script=script,
243+
expected_content='{{ ctx.node.name }}')
244+
245+
def test_download_resource_and_render(self):
246+
script = ('path = ctx.download_resource_and_render("resource")\n'
247+
'ctx.returns(path)')
248+
self._test_download_resource(
249+
script=script,
250+
expected_content='test_node')
251+
252+
def test_download_resource_and_render_with_destination(self):
253+
fd, temp_path = tempfile.mkstemp(suffix='-resource')
254+
script = ('path = ctx.download_resource_and_render("resource", "{0}")\n' # NOQA
255+
'ctx.returns(path)'.format(temp_path))
256+
self._test_download_resource(
257+
script=script,
258+
expected_content='test_node')
259+
260+
def test_download_missing_resource(self):
261+
script = ('path = ctx.download_resource("missing_resource")\n'
262+
'ctx.returns(path)')
263+
ex = self.assertRaises(ProcessException, self._run, script)
264+
self.assertIn(
265+
'IOError: [Errno 2] No such file or directory:',
266+
str(ex))
267+
268+
def test_abort_operation(self):
269+
script = ('ctx.abort_operation("abort_message")')
270+
ex = self.assertRaises(NonRecoverableError, self._run, script)
271+
self.assertIn('abort_message', str(ex))
272+
273+
def test_retry_operation(self):
274+
script = ('ctx.retry_operation("retry_message")')
275+
ex = self.assertRaises(OperationRetry, self._run, script)
276+
self.assertIn('retry_message', str(ex))

script_runner/tests/test_script_runner.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ def test_imported_ctx_crash_abort_after_return(self):
154154
try:
155155
self._run(script_path=script_path, task_retries=2)
156156
self.fail()
157-
except NonRecoverableError, e:
157+
except NonRecoverableError as e:
158158
self.assertEquals(e.message, str(ILLEGAL_CTX_OPERATION_ERROR))
159-
except Exception, e:
159+
except Exception as e:
160160
self.fail()
161161

162162
def test_crash_abort_after_return(self):
@@ -232,7 +232,7 @@ def test_crash_return_after_abort(self):
232232
try:
233233
self._run(script_path=script_path, task_retries=2)
234234
self.fail()
235-
except NonRecoverableError, e:
235+
except NonRecoverableError as e:
236236
self.assertEquals(e.message, str(ILLEGAL_CTX_OPERATION_ERROR))
237237
except Exception:
238238
self.fail()
@@ -250,7 +250,7 @@ def test_crash_return_after_retry(self):
250250
try:
251251
self._run(script_path=script_path, task_retries=2)
252252
self.fail()
253-
except NonRecoverableError, e:
253+
except NonRecoverableError as e:
254254
self.assertEquals(e.message, str(ILLEGAL_CTX_OPERATION_ERROR))
255255
except Exception:
256256
self.fail()
@@ -391,7 +391,7 @@ def test_script_error(self):
391391
try:
392392
self._run(script_path=script_path)
393393
self.fail()
394-
except tasks.ProcessException, e:
394+
except tasks.ProcessException as e:
395395
expected_exit_code = 1 if IS_WINDOWS else 127
396396
if IS_WINDOWS:
397397
expected_stderr = "'command_that_does_not_exist' is not " \
@@ -419,7 +419,7 @@ def test_script_error_from_bad_ctx_request(self):
419419
try:
420420
self._run(script_path=script_path)
421421
self.fail()
422-
except tasks.ProcessException, e:
422+
except tasks.ProcessException as e:
423423
self.assertIn(os.path.basename(script_path), e.command)
424424
self.assertEqual(e.exit_code, 1)
425425
self.assertIn('RequestError', e.stderr)
@@ -679,7 +679,7 @@ def test_url_status_code_404(self):
679679
try:
680680
self.test_http_url()
681681
self.fail()
682-
except NonRecoverableError, e:
682+
except NonRecoverableError as e:
683683
self.assertIn('status code: 404', str(e))
684684

685685
def test_blueprint_resource(self):

0 commit comments

Comments
 (0)