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,33 @@ 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 (), cell_mapping ) as cctxt :
165
+ compute_api .lock (cctxt , instance , reason = reason )
166
+ try :
167
+ yield
168
+ finally :
169
+ if initial_state == 'unlocked' :
170
+ with context .target_cell (
171
+ context .get_admin_context (), cell_mapping ) as cctxt :
172
+ compute_api .unlock (cctxt , instance )
173
+
174
+
147
175
class DbCommands (object ):
148
176
"""Class for managing the main database."""
149
177
@@ -2998,10 +3026,8 @@ def _refresh(self, instance_uuid, volume_id, connector):
2998
3026
:param instance_uuid: UUID of instance
2999
3027
:param volume_id: ID of volume attached to the instance
3000
3028
:param connector: Connector with which to create the new attachment
3029
+ :return status_code: volume-refresh status_code 0 on success
3001
3030
"""
3002
- volume_api = cinder .API ()
3003
- compute_rpcapi = rpcapi .ComputeAPI ()
3004
- compute_api = api .API ()
3005
3031
3006
3032
ctxt = context .get_admin_context ()
3007
3033
im = objects .InstanceMapping .get_by_instance_uuid (ctxt , instance_uuid )
@@ -3017,111 +3043,104 @@ def _refresh(self, instance_uuid, volume_id, connector):
3017
3043
state = instance .vm_state ,
3018
3044
method = 'refresh connection_info (must be stopped)' )
3019
3045
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
3039
- 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.
3046
+ locking_reason = (
3047
+ f'Refreshing connection_info for BDM { bdm .uuid } '
3048
+ f'associated with instance { instance_uuid } and volume '
3049
+ f'{ volume_id } .' )
3050
+
3051
+ with locked_instance (im .cell_mapping , instance , locking_reason ):
3052
+ return self ._do_refresh (
3053
+ cctxt , instance , volume_id , bdm , connector )
3054
+
3055
+ def _do_refresh (self , cctxt , instance ,
3056
+ volume_id , bdm , connector ):
3057
+ volume_api = cinder .API ()
3058
+ compute_rpcapi = rpcapi .ComputeAPI ()
3059
+
3060
+ new_attachment_id = None
3061
+ try :
3062
+ # Log this as an instance action so operators and users are
3063
+ # aware that this has happened.
3064
+ instance_action = objects .InstanceAction .action_start (
3065
+ cctxt , instance .uuid ,
3066
+ instance_actions .NOVA_MANAGE_REFRESH_VOLUME_ATTACHMENT )
3067
+
3068
+ # Create a blank attachment to keep the volume reserved
3069
+ new_attachment_id = volume_api .attachment_create (
3070
+ cctxt , volume_id , instance .uuid )['id' ]
3071
+
3072
+ # RPC call to the compute to cleanup the connections, which
3073
+ # will in turn unmap the volume from the compute host
3074
+ # TODO(lyarwood): Add delete_attachment as a kwarg to
3075
+ # remove_volume_connection as is available in the private
3076
+ # method within the manager.
3077
+ if instance .host == connector ['host' ]:
3055
3078
compute_rpcapi .remove_volume_connection (
3056
3079
cctxt , instance , volume_id , instance .host )
3080
+ else :
3081
+ msg = (
3082
+ f"The compute host '{ connector ['host' ]} ' in the "
3083
+ f"connector does not match the instance host "
3084
+ f"'{ instance .host } '." )
3085
+ raise exception .HostConflict (_ (msg ))
3086
+
3087
+ # Delete the existing volume attachment if present in the bdm.
3088
+ # This isn't present when the original attachment was made
3089
+ # using the legacy cinderv2 APIs before the cinderv3 attachment
3090
+ # based APIs were present.
3091
+ if bdm .attachment_id :
3092
+ volume_api .attachment_delete (cctxt , bdm .attachment_id )
3093
+
3094
+ # Update the attachment with host connector, this regenerates
3095
+ # the connection_info that we can now stash in the bdm.
3096
+ new_connection_info = volume_api .attachment_update (
3097
+ cctxt , new_attachment_id , connector ,
3098
+ bdm .device_name )['connection_info' ]
3099
+
3100
+ # Before we save it to the BDM ensure the serial is stashed as
3101
+ # is done in various other codepaths when attaching volumes.
3102
+ if 'serial' not in new_connection_info :
3103
+ new_connection_info ['serial' ] = bdm .volume_id
3104
+
3105
+ # Save the new attachment id and connection_info to the DB
3106
+ bdm .attachment_id = new_attachment_id
3107
+ bdm .connection_info = jsonutils .dumps (new_connection_info )
3108
+ bdm .save ()
3109
+
3110
+ # Finally mark the attachment as complete, moving the volume
3111
+ # status from attaching to in-use ahead of the instance
3112
+ # restarting
3113
+ volume_api .attachment_complete (cctxt , new_attachment_id )
3114
+ return 0
3057
3115
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 )
3116
+ finally :
3117
+ # If the bdm.attachment_id wasn 't updated make sure we clean
3118
+ # up any attachments created during the run.
3119
+ bdm = objects . BlockDeviceMapping . get_by_volume_and_instance (
3120
+ cctxt , volume_id , instance . uuid )
3121
+ if (
3122
+ new_attachment_id and
3123
+ bdm . attachment_id != new_attachment_id
3124
+ ):
3125
+ volume_api .attachment_delete ( cctxt , new_attachment_id )
3126
+
3127
+ # If we failed during attachment_update the bdm.attachment_id
3128
+ # has already been deleted so recreate it now to ensure the
3129
+ # volume is still associated with the instance and clear the
3130
+ # now stale connection_info .
3131
+ try :
3132
+ volume_api . attachment_get ( cctxt , bdm .attachment_id )
3133
+ except exception . VolumeAttachmentNotFound :
3134
+ bdm . attachment_id = volume_api . attachment_create (
3135
+ cctxt , volume_id , instance . uuid )[ 'id' ]
3136
+ bdm .connection_info = None
3079
3137
bdm .save ()
3080
3138
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 )
3139
+ # Finish the instance action if it was created and started
3140
+ # TODO(lyarwood): While not really required we should store
3141
+ # the exec and traceback in here on failure.
3142
+ if instance_action :
3143
+ instance_action .finish ()
3125
3144
3126
3145
@action_description (
3127
3146
_ ("Refresh the connection info for a given volume attachment" ))
@@ -3145,6 +3164,7 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
3145
3164
* 4: Instance does not exist.
3146
3165
* 5: Instance state invalid.
3147
3166
* 6: Volume is not attached to instance.
3167
+ * 7: Connector host is not correct.
3148
3168
"""
3149
3169
try :
3150
3170
# TODO(lyarwood): Make this optional and provide a rpcapi capable
@@ -3160,6 +3180,12 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
3160
3180
# Refresh the volume attachment
3161
3181
return self ._refresh (instance_uuid , volume_id , connector )
3162
3182
3183
+ except exception .HostConflict as e :
3184
+ print (
3185
+ f"The command 'nova-manage volume_attachment get_connector' "
3186
+ f"may have been run on the wrong compute host. Or the "
3187
+ f"instance host may be wrong and in need of repair.\n { e } " )
3188
+ return 7
3163
3189
except exception .VolumeBDMNotFound as e :
3164
3190
print (str (e ))
3165
3191
return 6
@@ -3172,12 +3198,15 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
3172
3198
) as e :
3173
3199
print (str (e ))
3174
3200
return 4
3175
- except ( ValueError , OSError ) :
3201
+ except ValueError as e :
3176
3202
print (
3177
3203
f'Failed to open { connector_path } . Does it contain valid '
3178
- f'connector_info data?'
3204
+ f'connector_info data?\n Error: { str ( e ) } '
3179
3205
)
3180
3206
return 3
3207
+ except OSError as e :
3208
+ print (str (e ))
3209
+ return 3
3181
3210
except exception .InvalidInput as e :
3182
3211
print (str (e ))
3183
3212
return 2
0 commit comments