1# Licensed under the Apache License, Version 2.0 (the "License"); 2# you may not use this file except in compliance with the License. 3# You may obtain a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, 9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10# implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14from unittest import mock 15 16import six 17import swiftclient.client 18import testscenarios 19import testtools 20from testtools import matchers 21 22from heatclient.common import deployment_utils 23from heatclient import exc 24from heatclient.v1 import software_configs 25 26 27load_tests = testscenarios.load_tests_apply_scenarios 28 29 30def mock_sc(group=None, config=None, options=None, 31 inputs=None, outputs=None): 32 return software_configs.SoftwareConfig(None, { 33 'group': group, 34 'config': config, 35 'options': options or {}, 36 'inputs': inputs or [], 37 'outputs': outputs or [], 38 }, True) 39 40 41class DerivedConfigTest(testtools.TestCase): 42 43 scenarios = [ 44 ('defaults', dict( 45 action='UPDATE', 46 source=mock_sc(), 47 name='s1', 48 input_values=None, 49 server_id='1234', 50 signal_transport='NO_SIGNAL', 51 signal_id=None, 52 result={ 53 'config': '', 54 'group': 'Heat::Ungrouped', 55 'inputs': [{ 56 'description': 'ID of the server being deployed to', 57 'name': 'deploy_server_id', 58 'type': 'String', 59 'value': '1234' 60 }, { 61 'description': 'Name of the current action ' 62 'being deployed', 63 'name': 'deploy_action', 64 'type': 'String', 65 'value': 'UPDATE' 66 }, { 67 'description': 'How the server should signal to ' 68 'heat with the deployment output values.', 69 'name': 'deploy_signal_transport', 70 'type': 'String', 71 'value': 'NO_SIGNAL'}], 72 'name': 's1', 73 'options': {}, 74 'outputs': []})), 75 ('defaults_empty', dict( 76 action='UPDATE', 77 source={}, 78 name='s1', 79 input_values=None, 80 server_id='1234', 81 signal_transport='NO_SIGNAL', 82 signal_id=None, 83 result={ 84 'config': '', 85 'group': 'Heat::Ungrouped', 86 'inputs': [{ 87 'description': 'ID of the server being deployed to', 88 'name': 'deploy_server_id', 89 'type': 'String', 90 'value': '1234' 91 }, { 92 'description': 'Name of the current action ' 93 'being deployed', 94 'name': 'deploy_action', 95 'type': 'String', 96 'value': 'UPDATE' 97 }, { 98 'description': 'How the server should signal to ' 99 'heat with the deployment output values.', 100 'name': 'deploy_signal_transport', 101 'type': 'String', 102 'value': 'NO_SIGNAL'}], 103 'name': 's1', 104 'options': {}, 105 'outputs': []})), 106 107 ('config_values', dict( 108 action='UPDATE', 109 source=mock_sc( 110 group='puppet', 111 config='do the foo', 112 inputs=[ 113 {'name': 'one', 'default': '1'}, 114 {'name': 'two'}], 115 options={'option1': 'value'}, 116 outputs=[ 117 {'name': 'output1'}, 118 {'name': 'output2'}], 119 ), 120 name='s2', 121 input_values={'one': 'foo', 'two': 'bar', 'three': 'baz'}, 122 server_id='1234', 123 signal_transport='NO_SIGNAL', 124 signal_id=None, 125 result={ 126 'config': 'do the foo', 127 'group': 'puppet', 128 'inputs': [{ 129 'name': 'one', 130 'default': '1', 131 'value': 'foo' 132 }, { 133 'name': 'two', 134 'value': 'bar' 135 }, { 136 'name': 'three', 137 'type': 'String', 138 'value': 'baz' 139 }, { 140 'description': 'ID of the server being deployed to', 141 'name': 'deploy_server_id', 142 'type': 'String', 143 'value': '1234' 144 }, { 145 'description': 'Name of the current action ' 146 'being deployed', 147 'name': 'deploy_action', 148 'type': 'String', 149 'value': 'UPDATE' 150 }, { 151 'description': 'How the server should signal to ' 152 'heat with the deployment output values.', 153 'name': 'deploy_signal_transport', 154 'type': 'String', 155 'value': 'NO_SIGNAL' 156 }], 157 'name': 's2', 158 'options': {'option1': 'value'}, 159 'outputs': [ 160 {'name': 'output1'}, 161 {'name': 'output2'}]})), 162 ('temp_url', dict( 163 action='UPDATE', 164 source=mock_sc(), 165 name='s1', 166 input_values=None, 167 server_id='1234', 168 signal_transport='TEMP_URL_SIGNAL', 169 signal_id='http://192.0.2.1:8080/foo', 170 result={ 171 'config': '', 172 'group': 'Heat::Ungrouped', 173 'inputs': [{ 174 'description': 'ID of the server being deployed to', 175 'name': 'deploy_server_id', 176 'type': 'String', 177 'value': '1234' 178 }, { 179 'description': 'Name of the current action ' 180 'being deployed', 181 'name': 'deploy_action', 182 'type': 'String', 183 'value': 'UPDATE' 184 }, { 185 'description': 'How the server should signal to ' 186 'heat with the deployment output values.', 187 'name': 'deploy_signal_transport', 188 'type': 'String', 189 'value': 'TEMP_URL_SIGNAL' 190 }, { 191 'description': 'ID of signal to use for signaling ' 192 'output values', 193 'name': 'deploy_signal_id', 194 'type': 'String', 195 'value': 'http://192.0.2.1:8080/foo' 196 }, { 197 'description': 'HTTP verb to use for signaling ' 198 'output values', 199 'name': 'deploy_signal_verb', 200 'type': 'String', 201 'value': 'PUT'}], 202 'name': 's1', 203 'options': {}, 204 'outputs': []})), 205 ('unsupported', dict( 206 action='UPDATE', 207 source=mock_sc(), 208 name='s1', 209 input_values=None, 210 server_id='1234', 211 signal_transport='ASDF', 212 signal_id=None, 213 result_error=exc.CommandError, 214 result_error_msg='Unsupported signal transport ASDF', 215 result=None)), 216 ] 217 218 def test_build_derived_config_params(self): 219 try: 220 self.assertEqual( 221 self.result, 222 deployment_utils.build_derived_config_params( 223 action=self.action, 224 source=self.source, 225 name=self.name, 226 input_values=self.input_values, 227 server_id=self.server_id, 228 signal_transport=self.signal_transport, 229 signal_id=self.signal_id)) 230 except Exception as e: 231 if not self.result_error: 232 raise e 233 self.assertIsInstance(e, self.result_error) 234 self.assertEqual(self.result_error_msg, six.text_type(e)) 235 236 237class TempURLSignalTest(testtools.TestCase): 238 239 @mock.patch.object(swiftclient.client, 'Connection') 240 def test_create_swift_client(self, sc_conn): 241 auth = mock.MagicMock() 242 auth.get_token.return_value = '1234' 243 auth.get_endpoint.return_value = 'http://192.0.2.1:8080' 244 245 session = mock.MagicMock() 246 247 args = mock.MagicMock() 248 args.os_region_name = 'Region1' 249 args.os_project_name = 'project' 250 args.os_username = 'user' 251 args.os_cacert = None 252 args.insecure = True 253 254 sc_conn.return_value = mock.MagicMock() 255 256 sc = deployment_utils.create_swift_client(auth, session, args) 257 258 self.assertEqual(sc_conn.return_value, sc) 259 260 self.assertEqual( 261 mock.call(session), 262 auth.get_token.call_args) 263 264 self.assertEqual( 265 mock.call( 266 session, 267 service_type='object-store', 268 region_name='Region1'), 269 auth.get_endpoint.call_args) 270 271 self.assertEqual( 272 mock.call( 273 cacert=None, 274 insecure=True, 275 key=None, 276 tenant_name='project', 277 preauthtoken='1234', 278 authurl=None, 279 user='user', 280 preauthurl='http://192.0.2.1:8080', 281 auth_version='2.0'), 282 sc_conn.call_args) 283 284 def test_create_temp_url(self): 285 swift_client = mock.MagicMock() 286 swift_client.url = ("http://fake-host.com:8080/v1/AUTH_demo") 287 swift_client.head_account = mock.Mock(return_value={ 288 'x-account-meta-temp-url-key': '123456'}) 289 swift_client.post_account = mock.Mock() 290 291 uuid_pattern = ('[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB]' 292 '[a-f0-9]{3}-[a-f0-9]{12}') 293 url = deployment_utils.create_temp_url(swift_client, 'bar', 60) 294 self.assertFalse(swift_client.post_account.called) 295 regexp = (r"http://fake-host.com:8080/v1/AUTH_demo/bar-%s" 296 r"/%s\?temp_url_sig=[0-9a-f]{40}&" 297 r"temp_url_expires=[0-9]{10}" % (uuid_pattern, uuid_pattern)) 298 self.assertThat(url, matchers.MatchesRegex(regexp)) 299 300 timeout = int(url.split('=')[-1]) 301 self.assertTrue(timeout < 2147483647) 302 303 def test_get_temp_url_no_account_key(self): 304 swift_client = mock.MagicMock() 305 swift_client.url = ("http://fake-host.com:8080/v1/AUTH_demo") 306 head_account = {} 307 308 def post_account(data): 309 head_account.update(data) 310 311 swift_client.head_account = mock.Mock(return_value=head_account) 312 swift_client.post_account = post_account 313 314 self.assertNotIn('x-account-meta-temp-url-key', head_account) 315 deployment_utils.create_temp_url(swift_client, 'bar', 60, 'foo') 316 self.assertIn('x-account-meta-temp-url-key', head_account) 317 318 def test_build_signal_id_no_signal(self): 319 hc = mock.MagicMock() 320 args = mock.MagicMock() 321 args.signal_transport = 'NO_SIGNAL' 322 self.assertIsNone(deployment_utils.build_signal_id(hc, args)) 323 324 def test_build_signal_id_no_client_auth(self): 325 hc = mock.MagicMock() 326 args = mock.MagicMock() 327 args.os_no_client_auth = True 328 args.signal_transport = 'TEMP_URL_SIGNAL' 329 e = self.assertRaises(exc.CommandError, 330 deployment_utils.build_signal_id, hc, args) 331 self.assertEqual(( 332 'Cannot use --os-no-client-auth, auth required to create ' 333 'a Swift TempURL.'), 334 six.text_type(e)) 335 336 @mock.patch.object(deployment_utils, 'create_temp_url') 337 @mock.patch.object(deployment_utils, 'create_swift_client') 338 def test_build_signal_id(self, csc, ctu): 339 hc = mock.MagicMock() 340 args = mock.MagicMock() 341 args.name = 'foo' 342 args.timeout = 60 343 args.os_no_client_auth = False 344 args.signal_transport = 'TEMP_URL_SIGNAL' 345 csc.return_value = mock.MagicMock() 346 temp_url = ( 347 'http://fake-host.com:8080/v1/AUTH_demo/foo/' 348 'a81a74d5-c395-4269-9670-ddd0824fd696' 349 '?temp_url_sig=6a68371d602c7a14aaaa9e3b3a63b8b85bd9a503' 350 '&temp_url_expires=1425270977') 351 ctu.return_value = temp_url 352 353 self.assertEqual( 354 temp_url, deployment_utils.build_signal_id(hc, args)) 355 self.assertEqual( 356 mock.call(hc.http_client.auth, hc.http_client.session, args), 357 csc.call_args) 358 self.assertEqual( 359 mock.call(csc.return_value, 'foo', 60), 360 ctu.call_args) 361