22
22
"""
23
23
24
24
import collections
25
+ from contextlib import contextmanager
25
26
import functools
26
27
import os
27
28
import re
@@ -144,6 +145,37 @@ def format_dict(dct, dict_property="Property", dict_value='Value',
144
145
return encodeutils .safe_encode (pt .get_string ()).decode ()
145
146
146
147
148
+ @contextmanager
149
+ def locked_instance (cell_mapping , instance , reason ):
150
+ """Context manager to lock and unlock instance,
151
+ lock state will be restored regardless of the success or failure
152
+ of target functionality.
153
+
154
+ :param cell_mapping: instance-cell-mapping
155
+ :param instance: instance to be lock and unlock
156
+ :param reason: reason, why lock is required
157
+ """
158
+
159
+ compute_api = api .API ()
160
+
161
+ initial_state = 'locked' if instance .locked else 'unlocked'
162
+ if not instance .locked :
163
+ with context .target_cell (
164
+ context .get_admin_context (),
165
+ cell_mapping
166
+ ) as cctxt :
167
+ compute_api .lock (cctxt , instance , reason = reason )
168
+ try :
169
+ yield
170
+ finally :
171
+ if initial_state == 'unlocked' :
172
+ with context .target_cell (
173
+ context .get_admin_context (),
174
+ cell_mapping
175
+ ) as cctxt :
176
+ compute_api .unlock (cctxt , instance )
177
+
178
+
147
179
class DbCommands (object ):
148
180
"""Class for managing the main database."""
149
181
@@ -2998,10 +3030,8 @@ def _refresh(self, instance_uuid, volume_id, connector):
2998
3030
:param instance_uuid: UUID of instance
2999
3031
:param volume_id: ID of volume attached to the instance
3000
3032
:param connector: Connector with which to create the new attachment
3033
+ :return status_code: volume-refresh status_code 0 on success
3001
3034
"""
3002
- volume_api = cinder .API ()
3003
- compute_rpcapi = rpcapi .ComputeAPI ()
3004
- compute_api = api .API ()
3005
3035
3006
3036
ctxt = context .get_admin_context ()
3007
3037
im = objects .InstanceMapping .get_by_instance_uuid (ctxt , instance_uuid )
@@ -3017,111 +3047,97 @@ def _refresh(self, instance_uuid, volume_id, connector):
3017
3047
state = instance .vm_state ,
3018
3048
method = 'refresh connection_info (must be stopped)' )
3019
3049
3020
- if instance .locked :
3021
- raise exception .InstanceInvalidState (
3022
- instance_uuid = instance_uuid , attr = 'locked' , state = 'True' ,
3023
- method = 'refresh connection_info (must be unlocked)' )
3024
-
3025
- compute_api .lock (
3026
- cctxt , instance ,
3027
- reason = (
3028
- f'Refreshing connection_info for BDM { bdm .uuid } '
3029
- f'associated with instance { instance_uuid } and volume '
3030
- f'{ volume_id } .' ))
3031
-
3032
- # NOTE(lyarwood): Yes this is weird but we need to recreate the admin
3033
- # context here to ensure the lock above uses a unique request-id
3034
- # versus the following refresh and eventual unlock.
3035
- ctxt = context .get_admin_context ()
3036
- with context .target_cell (ctxt , im .cell_mapping ) as cctxt :
3037
- instance_action = None
3038
- new_attachment_id = None
3050
+ locking_reason = (
3051
+ f'Refreshing connection_info for BDM { bdm .uuid } '
3052
+ f'associated with instance { instance_uuid } and volume '
3053
+ f'{ volume_id } .' )
3054
+
3055
+ with locked_instance (im .cell_mapping , instance , locking_reason ):
3056
+ return self ._do_refresh (
3057
+ cctxt , instance , volume_id , bdm , connector )
3058
+
3059
+ def _do_refresh (self , cctxt , instance ,
3060
+ volume_id , bdm , connector ):
3061
+ volume_api = cinder .API ()
3062
+ compute_rpcapi = rpcapi .ComputeAPI ()
3063
+
3064
+ new_attachment_id = None
3065
+ try :
3066
+ # Log this as an instance action so operators and users are
3067
+ # aware that this has happened.
3068
+ instance_action = objects .InstanceAction .action_start (
3069
+ cctxt , instance .uuid ,
3070
+ instance_actions .NOVA_MANAGE_REFRESH_VOLUME_ATTACHMENT )
3071
+
3072
+ # Create a blank attachment to keep the volume reserved
3073
+ new_attachment_id = volume_api .attachment_create (
3074
+ cctxt , volume_id , instance .uuid )['id' ]
3075
+
3076
+ # RPC call to the compute to cleanup the connections, which
3077
+ # will in turn unmap the volume from the compute host
3078
+ # TODO(lyarwood): Add delete_attachment as a kwarg to
3079
+ # remove_volume_connection as is available in the private
3080
+ # method within the manager.
3081
+ compute_rpcapi .remove_volume_connection (
3082
+ cctxt , instance , volume_id , instance .host )
3083
+
3084
+ # Delete the existing volume attachment if present in the bdm.
3085
+ # This isn't present when the original attachment was made
3086
+ # using the legacy cinderv2 APIs before the cinderv3 attachment
3087
+ # based APIs were present.
3088
+ if bdm .attachment_id :
3089
+ volume_api .attachment_delete (cctxt , bdm .attachment_id )
3090
+
3091
+ # Update the attachment with host connector, this regenerates
3092
+ # the connection_info that we can now stash in the bdm.
3093
+ new_connection_info = volume_api .attachment_update (
3094
+ cctxt , new_attachment_id , connector ,
3095
+ bdm .device_name )['connection_info' ]
3096
+
3097
+ # Before we save it to the BDM ensure the serial is stashed as
3098
+ # is done in various other codepaths when attaching volumes.
3099
+ if 'serial' not in new_connection_info :
3100
+ new_connection_info ['serial' ] = bdm .volume_id
3101
+
3102
+ # Save the new attachment id and connection_info to the DB
3103
+ bdm .attachment_id = new_attachment_id
3104
+ bdm .connection_info = jsonutils .dumps (new_connection_info )
3105
+ bdm .save ()
3106
+
3107
+ # Finally mark the attachment as complete, moving the volume
3108
+ # status from attaching to in-use ahead of the instance
3109
+ # restarting
3110
+ volume_api .attachment_complete (cctxt , new_attachment_id )
3111
+ return 0
3112
+
3113
+ finally :
3114
+ # If the bdm.attachment_id wasn't updated make sure we clean
3115
+ # up any attachments created during the run.
3116
+ bdm = objects .BlockDeviceMapping .get_by_volume_and_instance (
3117
+ cctxt , volume_id , instance .uuid )
3118
+ if (
3119
+ new_attachment_id and
3120
+ bdm .attachment_id != new_attachment_id
3121
+ ):
3122
+ volume_api .attachment_delete (cctxt , new_attachment_id )
3123
+
3124
+ # If we failed during attachment_update the bdm.attachment_id
3125
+ # has already been deleted so recreate it now to ensure the
3126
+ # volume is still associated with the instance and clear the
3127
+ # now stale connection_info.
3039
3128
try :
3040
- # Log this as an instance action so operators and users are
3041
- # aware that this has happened.
3042
- instance_action = objects .InstanceAction .action_start (
3043
- cctxt , instance_uuid ,
3044
- instance_actions .NOVA_MANAGE_REFRESH_VOLUME_ATTACHMENT )
3045
-
3046
- # Create a blank attachment to keep the volume reserved
3047
- new_attachment_id = volume_api .attachment_create (
3048
- cctxt , volume_id , instance_uuid )['id' ]
3049
-
3050
- # RPC call to the compute to cleanup the connections, which
3051
- # will in turn unmap the volume from the compute host
3052
- # TODO(lyarwood): Add delete_attachment as a kwarg to
3053
- # remove_volume_connection as is available in the private
3054
- # method within the manager.
3055
- compute_rpcapi .remove_volume_connection (
3056
- cctxt , instance , volume_id , instance .host )
3057
-
3058
- # Delete the existing volume attachment if present in the bdm.
3059
- # This isn't present when the original attachment was made
3060
- # using the legacy cinderv2 APIs before the cinderv3 attachment
3061
- # based APIs were present.
3062
- if bdm .attachment_id :
3063
- volume_api .attachment_delete (cctxt , bdm .attachment_id )
3064
-
3065
- # Update the attachment with host connector, this regenerates
3066
- # the connection_info that we can now stash in the bdm.
3067
- new_connection_info = volume_api .attachment_update (
3068
- cctxt , new_attachment_id , connector ,
3069
- bdm .device_name )['connection_info' ]
3070
-
3071
- # Before we save it to the BDM ensure the serial is stashed as
3072
- # is done in various other codepaths when attaching volumes.
3073
- if 'serial' not in new_connection_info :
3074
- new_connection_info ['serial' ] = bdm .volume_id
3075
-
3076
- # Save the new attachment id and connection_info to the DB
3077
- bdm .attachment_id = new_attachment_id
3078
- bdm .connection_info = jsonutils .dumps (new_connection_info )
3129
+ volume_api .attachment_get (cctxt , bdm .attachment_id )
3130
+ except exception .VolumeAttachmentNotFound :
3131
+ bdm .attachment_id = volume_api .attachment_create (
3132
+ cctxt , volume_id , instance .uuid )['id' ]
3133
+ bdm .connection_info = None
3079
3134
bdm .save ()
3080
3135
3081
- # Finally mark the attachment as complete, moving the volume
3082
- # status from attaching to in-use ahead of the instance
3083
- # restarting
3084
- volume_api .attachment_complete (cctxt , new_attachment_id )
3085
- return 0
3086
-
3087
- finally :
3088
- # If the bdm.attachment_id wasn't updated make sure we clean
3089
- # up any attachments created during the run.
3090
- bdm = objects .BlockDeviceMapping .get_by_volume_and_instance (
3091
- cctxt , volume_id , instance_uuid )
3092
- if (
3093
- new_attachment_id and
3094
- bdm .attachment_id != new_attachment_id
3095
- ):
3096
- volume_api .attachment_delete (cctxt , new_attachment_id )
3097
-
3098
- # If we failed during attachment_update the bdm.attachment_id
3099
- # has already been deleted so recreate it now to ensure the
3100
- # volume is still associated with the instance and clear the
3101
- # now stale connection_info.
3102
- try :
3103
- volume_api .attachment_get (cctxt , bdm .attachment_id )
3104
- except exception .VolumeAttachmentNotFound :
3105
- bdm .attachment_id = volume_api .attachment_create (
3106
- cctxt , volume_id , instance_uuid )['id' ]
3107
- bdm .connection_info = None
3108
- bdm .save ()
3109
-
3110
- # Finish the instance action if it was created and started
3111
- # TODO(lyarwood): While not really required we should store
3112
- # the exec and traceback in here on failure.
3113
- if instance_action :
3114
- instance_action .finish ()
3115
-
3116
- # NOTE(lyarwood): As above we need to unlock the instance with
3117
- # a fresh context and request-id to keep it unique. It's safe
3118
- # to assume that the instance is locked as this point as the
3119
- # earlier call to lock isn't part of this block.
3120
- with context .target_cell (
3121
- context .get_admin_context (),
3122
- im .cell_mapping
3123
- ) as u_cctxt :
3124
- compute_api .unlock (u_cctxt , instance )
3136
+ # Finish the instance action if it was created and started
3137
+ # TODO(lyarwood): While not really required we should store
3138
+ # the exec and traceback in here on failure.
3139
+ if instance_action :
3140
+ instance_action .finish ()
3125
3141
3126
3142
@action_description (
3127
3143
_ ("Refresh the connection info for a given volume attachment" ))
0 commit comments