~ubuntu-branches/ubuntu/wily/heat/wily-proposed

« back to all changes in this revision

Viewing changes to heat/tests/test_software_deployment.py

  • Committer: Package Import Robot
  • Author(s): James Page, Corey Bryant, James Page
  • Date: 2015-03-30 11:11:18 UTC
  • mfrom: (1.1.23)
  • Revision ID: package-import@ubuntu.com-20150330111118-2qpycylx6swu4yhj
Tags: 2015.1~b3-0ubuntu1
[ Corey Bryant ]
* New upstream milestone release for OpenStack kilo:
  - d/control: Align with upstream dependencies.
  - d/p/sudoers_patch.patch: Rebased.
  - d/p/fix-requirements.patch: Rebased.

[ James Page ]
* d/p/fixup-assert-regex.patch: Tweak test to use assertRegexpMatches.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#    under the License.
13
13
 
14
14
import copy
 
15
import re
 
16
import uuid
15
17
 
16
18
import mock
17
19
import six
19
21
from heat.common import exception as exc
20
22
from heat.common.i18n import _
21
23
from heat.engine.clients.os import nova
 
24
from heat.engine.clients.os import swift
22
25
from heat.engine import parser
23
 
from heat.engine.resources.software_config import software_deployment as sd
 
26
from heat.engine.resources.openstack.heat import software_deployment as sd
24
27
from heat.engine import rsrc_defn
25
28
from heat.engine import template
26
29
from heat.tests import common
81
84
        }
82
85
    }
83
86
 
 
87
    template_temp_url_signal = {
 
88
        'HeatTemplateFormatVersion': '2012-12-12',
 
89
        'Resources': {
 
90
            'deployment_mysql': {
 
91
                'Type': 'OS::Heat::SoftwareDeployment',
 
92
                'Properties': {
 
93
                    'server': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
 
94
                    'config': '48e8ade1-9196-42d5-89a2-f709fde42632',
 
95
                    'input_values': {'foo': 'bar', 'bink': 'bonk'},
 
96
                    'signal_transport': 'TEMP_URL_SIGNAL',
 
97
                    'name': '00_run_me_first'
 
98
                }
 
99
            }
 
100
        }
 
101
    }
 
102
 
84
103
    template_delete_suspend_resume = {
85
104
        'HeatTemplateFormatVersion': '2012-12-12',
86
105
        'Resources': {
129
148
        props['user_data_format'] = 'SOFTWARE_CONFIG'
130
149
        self._create_stack(self.template_with_server)
131
150
        sd = self.deployment
 
151
        self.assertEqual('CFN_SIGNAL', sd.properties.get('signal_transport'))
132
152
        sd.validate()
133
153
        server = self.stack['server']
134
154
        self.assertTrue(server.user_data_software_config())
281
301
                'name': 'deploy_resource_name',
282
302
                'type': 'String',
283
303
                'value': 'deployment_mysql'
 
304
            }, {
 
305
                'description': ('How the server should signal to heat with '
 
306
                                'the deployment output values.'),
 
307
                'name': 'deploy_signal_transport',
 
308
                'type': 'String',
 
309
                'value': 'NO_SIGNAL'
284
310
            }],
285
311
            'options': {},
286
312
            'outputs': []
372
398
                'name': 'deploy_resource_name',
373
399
                'type': 'String',
374
400
                'value': 'deployment_mysql'
 
401
            }, {
 
402
                'description': ('How the server should signal to heat with '
 
403
                                'the deployment output values.'),
 
404
                'name': 'deploy_signal_transport',
 
405
                'type': 'String',
 
406
                'value': 'NO_SIGNAL'
375
407
            }],
376
408
            'options': {},
377
409
            'outputs': []
482
514
 
483
515
        self.deployment.resource_id = sd['id']
484
516
        self.deployment.handle_delete()
485
 
 
 
517
        self.deployment.check_delete_complete()
486
518
        self.assertEqual(
487
519
            (self.ctx, sd['id']),
488
520
            self.rpc_client.delete_software_deployment.call_args[0])
489
521
 
 
522
    def test_handle_delete_resource_id_is_None(self):
 
523
        self._create_stack(self.template_delete_suspend_resume)
 
524
        self.mock_software_config()
 
525
        self.assertIsNone(self.deployment.handle_delete())
 
526
 
490
527
    def test_delete_complete(self):
491
528
        self._create_stack(self.template_delete_suspend_resume)
492
529
 
528
565
        self.rpc_client.delete_software_deployment.side_effect = nf
529
566
        self.rpc_client.delete_software_config.side_effect = nf
530
567
        self.assertIsNone(self.deployment.handle_delete())
 
568
        self.assertTrue(self.deployment.check_delete_complete())
531
569
        self.assertEqual(
532
570
            (self.ctx, derived_sc['id']),
533
571
            self.rpc_client.delete_software_config.call_args[0])
635
673
    def test_handle_signal_ok_zero(self):
636
674
        self._create_stack(self.template)
637
675
        self.deployment.resource_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
638
 
        sc = {
639
 
            'outputs': [
640
 
                {'name': 'foo'},
641
 
                {'name': 'foo2'},
642
 
                {'name': 'failed', 'error_output': True}
643
 
            ]
644
 
        }
645
 
        sd = {
646
 
            'output_values': {},
647
 
            'status': self.deployment.IN_PROGRESS
648
 
        }
649
 
        self.rpc_client.show_software_deployment.return_value = sd
650
 
        self.rpc_client.show_software_config.return_value = sc
 
676
        rpcc = self.rpc_client
 
677
        rpcc.signal_software_deployment.return_value = 'deployment succeeded'
651
678
        details = {
652
679
            'foo': 'bar',
653
680
            'deploy_status_code': 0
654
681
        }
655
682
        ret = self.deployment.handle_signal(details)
656
683
        self.assertEqual('deployment succeeded', ret)
657
 
        self.assertEqual({
658
 
            'deployment_id': 'c8a19429-7fde-47ea-a42f-40045488226c',
659
 
            'output_values': {
660
 
                'foo': 'bar',
661
 
                'deploy_status_code': 0,
662
 
                'deploy_stderr': None,
663
 
                'deploy_stdout': None
664
 
            },
665
 
            'status': 'COMPLETE',
666
 
            'status_reason': 'Outputs received'},
667
 
            self.rpc_client.update_software_deployment.call_args[1])
 
684
        ca = rpcc.signal_software_deployment.call_args[0]
 
685
        self.assertEqual(self.ctx, ca[0])
 
686
        self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', ca[1])
 
687
        self.assertEqual({'foo': 'bar', 'deploy_status_code': 0}, ca[2])
 
688
        self.assertIsNotNone(ca[3])
668
689
 
669
690
    def test_handle_signal_ok_str_zero(self):
670
691
        self._create_stack(self.template)
671
692
        self.deployment.resource_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
672
 
        sc = {
673
 
            'outputs': [
674
 
                {'name': 'foo'},
675
 
                {'name': 'foo2'},
676
 
                {'name': 'failed', 'error_output': True}
677
 
            ]
678
 
        }
679
 
        sd = {
680
 
            'output_values': {},
681
 
            'status': self.deployment.IN_PROGRESS
682
 
        }
683
 
        self.rpc_client.show_software_deployment.return_value = sd
684
 
        self.rpc_client.show_software_config.return_value = sc
 
693
        rpcc = self.rpc_client
 
694
        rpcc.signal_software_deployment.return_value = 'deployment succeeded'
685
695
        details = {
686
696
            'foo': 'bar',
687
697
            'deploy_status_code': '0'
688
698
        }
689
699
        ret = self.deployment.handle_signal(details)
690
700
        self.assertEqual('deployment succeeded', ret)
691
 
        self.assertEqual({
692
 
            'deployment_id': 'c8a19429-7fde-47ea-a42f-40045488226c',
693
 
            'output_values': {
694
 
                'foo': 'bar',
695
 
                'deploy_status_code': '0',
696
 
                'deploy_stderr': None,
697
 
                'deploy_stdout': None
698
 
            },
699
 
            'status': 'COMPLETE',
700
 
            'status_reason': 'Outputs received'},
701
 
            self.rpc_client.update_software_deployment.call_args[1])
 
701
        ca = rpcc.signal_software_deployment.call_args[0]
 
702
        self.assertEqual(self.ctx, ca[0])
 
703
        self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', ca[1])
 
704
        self.assertEqual({'foo': 'bar', 'deploy_status_code': '0'}, ca[2])
 
705
        self.assertIsNotNone(ca[3])
702
706
 
703
707
    def test_handle_signal_failed(self):
704
708
        self._create_stack(self.template)
705
709
        self.deployment.resource_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
706
 
        sc = {
707
 
            'outputs': [
708
 
                {'name': 'foo'},
709
 
                {'name': 'foo2'},
710
 
                {'name': 'failed', 'error_output': True}
711
 
            ]
712
 
        }
713
 
        sd = {
714
 
            'output_values': {},
715
 
            'status': self.deployment.IN_PROGRESS
716
 
        }
717
 
        self.rpc_client.show_software_deployment.return_value = sd
718
 
        self.rpc_client.show_software_config.return_value = sc
 
710
        rpcc = self.rpc_client
 
711
        rpcc.signal_software_deployment.return_value = 'deployment failed'
 
712
 
719
713
        details = {'failed': 'no enough memory found.'}
720
714
        ret = self.deployment.handle_signal(details)
721
715
        self.assertEqual('deployment failed', ret)
722
 
        self.assertEqual({
723
 
            'deployment_id': 'c8a19429-7fde-47ea-a42f-40045488226c',
724
 
            'output_values': {
725
 
                'deploy_status_code': None,
726
 
                'deploy_stderr': None,
727
 
                'deploy_stdout': None,
728
 
                'failed': 'no enough memory found.'
729
 
            },
730
 
            'status': 'FAILED',
731
 
            'status_reason': 'failed : no enough memory found.'},
732
 
            self.rpc_client.update_software_deployment.call_args[1])
 
716
        ca = rpcc.signal_software_deployment.call_args[0]
 
717
        self.assertEqual(self.ctx, ca[0])
 
718
        self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', ca[1])
 
719
        self.assertEqual(details, ca[2])
 
720
        self.assertIsNotNone(ca[3])
733
721
 
734
722
        # Test bug 1332355, where details contains a translateable message
735
723
        details = {'failed': _('need more memory.')}
736
 
        self.deployment.handle_signal(details)
737
 
        self.assertEqual({
738
 
            'deployment_id': 'c8a19429-7fde-47ea-a42f-40045488226c',
739
 
            'output_values': {
740
 
                'deploy_status_code': None,
741
 
                'deploy_stderr': None,
742
 
                'deploy_stdout': None,
743
 
                'failed': 'need more memory.'
744
 
            },
745
 
            'status': 'FAILED',
746
 
            'status_reason': 'failed : need more memory.'},
747
 
            self.rpc_client.update_software_deployment.call_args[1])
 
724
        ret = self.deployment.handle_signal(details)
 
725
        self.assertEqual('deployment failed', ret)
 
726
        ca = rpcc.signal_software_deployment.call_args[0]
 
727
        self.assertEqual(self.ctx, ca[0])
 
728
        self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', ca[1])
 
729
        self.assertEqual(details, ca[2])
 
730
        self.assertIsNotNone(ca[3])
748
731
 
749
732
    def test_handle_status_code_failed(self):
750
733
        self._create_stack(self.template)
751
734
        self.deployment.resource_id = 'c8a19429-7fde-47ea-a42f-40045488226c'
752
 
        sd = {
753
 
            'outputs': [],
754
 
            'output_values': {},
755
 
            'status': self.deployment.IN_PROGRESS
756
 
        }
757
 
        self.rpc_client.show_software_deployment.return_value = sd
 
735
        rpcc = self.rpc_client
 
736
        rpcc.signal_software_deployment.return_value = 'deployment failed'
 
737
 
758
738
        details = {
759
739
            'deploy_stdout': 'A thing happened',
760
740
            'deploy_stderr': 'Then it broke',
761
741
            'deploy_status_code': -1
762
742
        }
763
743
        self.deployment.handle_signal(details)
764
 
        self.assertEqual(
765
 
            'c8a19429-7fde-47ea-a42f-40045488226c',
766
 
            self.rpc_client.show_software_deployment.call_args[0][1])
767
 
        self.assertEqual({
768
 
            'deployment_id': 'c8a19429-7fde-47ea-a42f-40045488226c',
769
 
            'output_values': {
770
 
                'deploy_stdout': 'A thing happened',
771
 
                'deploy_stderr': 'Then it broke',
772
 
                'deploy_status_code': -1
773
 
            },
774
 
            'status': 'FAILED',
775
 
            'status_reason': ('deploy_status_code : Deployment exited '
776
 
                              'with non-zero status code: -1')},
777
 
            self.rpc_client.update_software_deployment.call_args[1])
 
744
        ca = rpcc.signal_software_deployment.call_args[0]
 
745
        self.assertEqual(self.ctx, ca[0])
 
746
        self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', ca[1])
 
747
        self.assertEqual(details, ca[2])
 
748
        self.assertIsNotNone(ca[3])
778
749
 
779
750
    def test_handle_signal_not_waiting(self):
780
751
        self._create_stack(self.template)
781
 
        sd = {
782
 
            'status': self.deployment.COMPLETE
783
 
        }
784
 
        self.rpc_client.show_software_deployment.return_value = sd
 
752
        rpcc = self.rpc_client
 
753
        rpcc.signal_software_deployment.return_value = None
785
754
        details = None
786
755
        self.assertIsNone(self.deployment.handle_signal(details))
 
756
        ca = rpcc.signal_software_deployment.call_args[0]
 
757
        self.assertEqual(self.ctx, ca[0])
 
758
        self.assertIsNone(ca[1])
 
759
        self.assertIsNone(ca[2])
 
760
        self.assertIsNotNone(ca[3])
787
761
 
788
762
    def test_fn_get_att(self):
789
763
        self._create_stack(self.template)
828
802
        self._create_stack(self.template)
829
803
 
830
804
        self.mock_software_config()
 
805
        sd = self.mock_deployment()
 
806
        rsrc = self.stack['deployment_mysql']
 
807
 
 
808
        self.rpc_client.show_software_deployment.return_value = sd
 
809
        self.deployment.resource_id = sd['id']
 
810
        config_id = '0ff2e903-78d7-4cca-829e-233af3dae705'
 
811
        prop_diff = {'config': config_id}
 
812
        props = copy.copy(rsrc.properties.data)
 
813
        props.update(prop_diff)
 
814
        snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(), props)
 
815
 
 
816
        # by default (no 'actions' property) SoftwareDeployment must only
 
817
        # trigger for CREATE and UPDATE
 
818
        self.assertIsNotNone(self.deployment.handle_create())
 
819
        self.assertIsNotNone(self.deployment.handle_update(
 
820
            json_snippet=snippet, tmpl_diff=None, prop_diff=prop_diff))
 
821
        # ... but it must not trigger for SUSPEND, RESUME and DELETE
 
822
        self.assertIsNone(self.deployment.handle_suspend())
 
823
        self.assertIsNone(self.deployment.handle_resume())
 
824
        self.assertIsNone(self.deployment.handle_delete())
 
825
 
 
826
    def test_handle_action_for_component(self):
 
827
        self._create_stack(self.template)
 
828
 
 
829
        self.mock_software_component()
 
830
        sd = self.mock_deployment()
 
831
        rsrc = self.stack['deployment_mysql']
 
832
 
 
833
        self.rpc_client.show_software_deployment.return_value = sd
 
834
        self.deployment.resource_id = sd['id']
 
835
        config_id = '0ff2e903-78d7-4cca-829e-233af3dae705'
 
836
        prop_diff = {'config': config_id}
 
837
        props = copy.copy(rsrc.properties.data)
 
838
        props.update(prop_diff)
 
839
        snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(), props)
 
840
 
 
841
        # for a SoftwareComponent, SoftwareDeployment must always trigger
 
842
        self.assertIsNotNone(self.deployment.handle_create())
 
843
        self.assertIsNotNone(self.deployment.handle_update(
 
844
            json_snippet=snippet, tmpl_diff=None, prop_diff=prop_diff))
 
845
        self.assertIsNotNone(self.deployment.handle_suspend())
 
846
        self.assertIsNotNone(self.deployment.handle_resume())
 
847
        self.assertIsNotNone(self.deployment.handle_delete())
 
848
 
 
849
    def test_get_temp_url(self):
 
850
        dep_data = {}
 
851
 
 
852
        sc = mock.MagicMock()
 
853
        scc = self.patch(
 
854
            'heat.engine.clients.os.swift.SwiftClientPlugin._create')
 
855
        scc.return_value = sc
 
856
        sc.head_account.return_value = {
 
857
            'x-account-meta-temp-url-key': 'secrit'
 
858
        }
 
859
        sc.url = 'http://192.0.2.1/v1/AUTH_test_tenant_id'
 
860
 
 
861
        self._create_stack(self.template_temp_url_signal)
 
862
 
 
863
        def data_set(key, value, redact=False):
 
864
            dep_data[key] = value
 
865
 
 
866
        self.deployment.data_set = data_set
 
867
        self.deployment.data = mock.Mock(
 
868
            return_value=dep_data)
 
869
 
 
870
        self.deployment.id = 23
 
871
        self.deployment.uuid = str(uuid.uuid4())
 
872
        self.deployment.action = self.deployment.CREATE
 
873
        container = self.deployment.physical_resource_name()
 
874
 
 
875
        temp_url = self.deployment._get_temp_url()
 
876
        temp_url_pattern = re.compile(
 
877
            '^http://192.0.2.1/v1/AUTH_test_tenant_id/'
 
878
            '(software_deployment_test_stack-deployment_mysql-.*)/(.*)'
 
879
            '\\?temp_url_sig=.*&temp_url_expires=\\d*$')
 
880
        self.assertRegexpMatches(temp_url, temp_url_pattern)
 
881
        m = temp_url_pattern.search(temp_url)
 
882
        object_name = m.group(2)
 
883
        self.assertEqual(container, m.group(1))
 
884
        self.assertEqual(dep_data['signal_object_name'], object_name)
 
885
 
 
886
        self.assertEqual(dep_data['signal_temp_url'], temp_url)
 
887
 
 
888
        self.assertEqual(temp_url, self.deployment._get_temp_url())
 
889
 
 
890
        sc.put_container.assert_called_once_with(container)
 
891
        sc.put_object.assert_called_once_with(container, object_name, '')
 
892
 
 
893
    def test_delete_temp_url(self):
 
894
        object_name = str(uuid.uuid4())
 
895
        dep_data = {
 
896
            'signal_object_name': object_name
 
897
        }
 
898
        self._create_stack(self.template_temp_url_signal)
 
899
 
 
900
        self.deployment.data_delete = mock.MagicMock()
 
901
        self.deployment.data = mock.Mock(
 
902
            return_value=dep_data)
 
903
 
 
904
        sc = mock.MagicMock()
 
905
        sc.head_container.return_value = {
 
906
            'x-container-object-count': 0
 
907
        }
 
908
        scc = self.patch(
 
909
            'heat.engine.clients.os.swift.SwiftClientPlugin._create')
 
910
        scc.return_value = sc
 
911
 
 
912
        self.deployment.id = 23
 
913
        self.deployment.uuid = str(uuid.uuid4())
 
914
        container = self.deployment.physical_resource_name()
 
915
        self.deployment._delete_temp_url()
 
916
        sc.delete_object.assert_called_once_with(container, object_name)
 
917
        self.assertEqual(
 
918
            [mock.call('signal_object_name'), mock.call('signal_temp_url')],
 
919
            self.deployment.data_delete.mock_calls)
 
920
 
 
921
        swift_exc = swift.SwiftClientPlugin.exceptions_module
 
922
        sc.delete_object.side_effect = swift_exc.ClientException(
 
923
            'Not found', http_status=404)
 
924
        self.deployment._delete_temp_url()
 
925
        self.assertEqual(
 
926
            [mock.call('signal_object_name'), mock.call('signal_temp_url'),
 
927
             mock.call('signal_object_name'), mock.call('signal_temp_url')],
 
928
            self.deployment.data_delete.mock_calls)
 
929
 
 
930
        del(dep_data['signal_object_name'])
 
931
        self.deployment.physical_resource_name = mock.Mock()
 
932
        self.deployment._delete_temp_url()
 
933
        self.assertFalse(self.deployment.physical_resource_name.called)
 
934
 
 
935
    def test_handle_action_temp_url(self):
 
936
 
 
937
        self._create_stack(self.template_temp_url_signal)
 
938
        dep_data = {
 
939
            'signal_temp_url': (
 
940
                'http://192.0.2.1/v1/AUTH_a/b/c'
 
941
                '?temp_url_sig=ctemp_url_expires=1234')
 
942
        }
 
943
        self.deployment.data = mock.Mock(
 
944
            return_value=dep_data)
 
945
 
 
946
        self.mock_software_config()
831
947
 
832
948
        for action in ('DELETE', 'SUSPEND', 'RESUME'):
833
949
            self.assertIsNone(self.deployment._handle_action(action))
834
950
        for action in ('CREATE', 'UPDATE'):
835
951
            self.assertIsNotNone(self.deployment._handle_action(action))
836
952
 
837
 
    def test_handle_action_for_component(self):
838
 
        self._create_stack(self.template)
839
 
 
840
 
        self.mock_software_component()
841
 
 
842
 
        for action in ('CREATE', 'UPDATE', 'DELETE', 'SUSPEND', 'RESUME'):
843
 
            self.assertIsNotNone(self.deployment._handle_action(action))
844
 
 
845
953
 
846
954
class SoftwareDeploymentsTest(common.HeatTestCase):
847
955
 
977
1085
    def test_validate(self):
978
1086
        stack = utils.parse_stack(self.template)
979
1087
        snip = stack.t.resource_definitions(stack)['deploy_mysql']
980
 
        resg = sd.SoftwareDeployments('test', snip, stack)
 
1088
        resg = sd.SoftwareDeployments('deploy_mysql', snip, stack)
981
1089
        self.assertIsNone(resg.validate())