Coverage for nova/api/openstack/compute/rescue.py: 100%

57 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +0000

1# Copyright 2011 OpenStack Foundation 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); you may 

4# not use this file except in compliance with the License. You may obtain 

5# a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15"""The rescue mode extension.""" 

16 

17from webob import exc 

18 

19from nova.api.openstack import api_version_request 

20from nova.api.openstack import common 

21from nova.api.openstack.compute.schemas import rescue as schema 

22from nova.api.openstack import wsgi 

23from nova.api import validation 

24from nova.compute import api as compute 

25import nova.conf 

26from nova import exception 

27from nova.policies import rescue as rescue_policies 

28from nova import utils 

29 

30CONF = nova.conf.CONF 

31 

32 

33class RescueController(wsgi.Controller): 

34 def __init__(self): 

35 super(RescueController, self).__init__() 

36 self.compute_api = compute.API() 

37 

38 # TODO(cyeoh): Should be responding here with 202 Accept 

39 # because rescue is an async call, but keep to 200 

40 # for backwards compatibility reasons. 

41 @wsgi.expected_errors((400, 404, 409, 501)) 

42 @wsgi.action('rescue') 

43 @validation.schema(schema.rescue) 

44 @validation.response_body_schema(schema.rescue_response) 

45 def _rescue(self, req, id, body): 

46 """Rescue an instance.""" 

47 context = req.environ["nova.context"] 

48 

49 if body['rescue'] and 'adminPass' in body['rescue']: 

50 password = body['rescue']['adminPass'] 

51 else: 

52 password = utils.generate_password() 

53 

54 instance = common.get_instance(self.compute_api, context, id) 

55 context.can(rescue_policies.BASE_POLICY_NAME, 

56 target={'user_id': instance.user_id, 

57 'project_id': instance.project_id}) 

58 rescue_image_ref = None 

59 if body['rescue']: 

60 rescue_image_ref = body['rescue'].get('rescue_image_ref') 

61 allow_bfv_rescue = api_version_request.is_supported(req, '2.87') 

62 try: 

63 self.compute_api.rescue(context, instance, 

64 rescue_password=password, 

65 rescue_image_ref=rescue_image_ref, 

66 allow_bfv_rescue=allow_bfv_rescue) 

67 except ( 

68 exception.InstanceIsLocked, 

69 exception.InvalidVolume, 

70 ) as e: 

71 raise exc.HTTPConflict(explanation=e.format_message()) 

72 except exception.InstanceInvalidState as e: 

73 common.raise_http_conflict_for_instance_invalid_state( 

74 e, 'rescue', id) 

75 except ( 

76 exception.InstanceNotRescuable, 

77 exception.UnsupportedRescueImage, 

78 ) as e: 

79 raise exc.HTTPBadRequest(explanation=e.format_message()) 

80 

81 if CONF.api.enable_instance_password: 

82 return {'adminPass': password} 

83 else: 

84 return {} 

85 

86 @wsgi.response(202) 

87 @wsgi.expected_errors((404, 409, 501)) 

88 @wsgi.action('unrescue') 

89 @validation.schema(schema.unrescue) 

90 @validation.response_body_schema(schema.unrescue_response) 

91 def _unrescue(self, req, id, body): 

92 """Unrescue an instance.""" 

93 context = req.environ["nova.context"] 

94 instance = common.get_instance(self.compute_api, context, id) 

95 context.can(rescue_policies.UNRESCUE_POLICY_NAME, 

96 target={'project_id': instance.project_id}) 

97 try: 

98 self.compute_api.unrescue(context, instance) 

99 except exception.InstanceIsLocked as e: 

100 raise exc.HTTPConflict(explanation=e.format_message()) 

101 except exception.InstanceInvalidState as state_error: 

102 common.raise_http_conflict_for_instance_invalid_state(state_error, 

103 'unrescue', 

104 id)