@@ -3192,13 +3192,176 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
3192
3192
return 1
3193
3193
3194
3194
3195
+ class ImagePropertyCommands ():
3196
+
3197
+ @action_description (_ ("Show the value of an instance image property." ))
3198
+ @args (
3199
+ 'instance_uuid' , metavar = '<instance_uuid>' ,
3200
+ help = 'UUID of the instance' )
3201
+ @args (
3202
+ 'property' , metavar = '<image_property>' ,
3203
+ help = 'Image property to show' )
3204
+ def show (self , instance_uuid = None , image_property = None ):
3205
+ """Show value of a given instance image property.
3206
+
3207
+ Return codes:
3208
+ * 0: Command completed successfully.
3209
+ * 1: An unexpected error happened.
3210
+ * 2: Instance not found.
3211
+ * 3: Image property not found.
3212
+ """
3213
+ try :
3214
+ ctxt = context .get_admin_context ()
3215
+ im = objects .InstanceMapping .get_by_instance_uuid (
3216
+ ctxt , instance_uuid )
3217
+ with context .target_cell (ctxt , im .cell_mapping ) as cctxt :
3218
+ instance = objects .Instance .get_by_uuid (
3219
+ cctxt , instance_uuid , expected_attrs = ['system_metadata' ])
3220
+ image_property = instance .system_metadata .get (
3221
+ f'image_{ image_property } ' )
3222
+ if image_property :
3223
+ print (image_property )
3224
+ return 0
3225
+ else :
3226
+ print (f'Image property { image_property } not found '
3227
+ f'for instance { instance_uuid } .' )
3228
+ return 3
3229
+ except (
3230
+ exception .InstanceNotFound ,
3231
+ exception .InstanceMappingNotFound ,
3232
+ ) as e :
3233
+ print (str (e ))
3234
+ return 2
3235
+ except Exception as e :
3236
+ print (f'Unexpected error, see nova-manage.log for the full '
3237
+ f'trace: { str (e )} ' )
3238
+ LOG .exception ('Unexpected error' )
3239
+ return 1
3240
+
3241
+ def _validate_image_properties (self , image_properties ):
3242
+ """Validate the provided image property names and values
3243
+
3244
+ :param image_properties: List of image property names and values
3245
+ """
3246
+ # Sanity check the format of the provided properties, this should be
3247
+ # in the format of name=value.
3248
+ if any (x for x in image_properties if '=' not in x ):
3249
+ raise exception .InvalidInput (
3250
+ "--property should use the format key=value" )
3251
+
3252
+ # Transform the list of delimited properties to a dict
3253
+ image_properties = dict (prop .split ('=' ) for prop in image_properties )
3254
+
3255
+ # Validate the names of each property by checking against the o.vo
3256
+ # fields currently listed by ImageProps. We can't use from_dict to
3257
+ # do this as it silently ignores invalid property keys.
3258
+ for image_property_name in image_properties .keys ():
3259
+ if image_property_name not in objects .ImageMetaProps .fields :
3260
+ raise exception .InvalidImagePropertyName (
3261
+ image_property_name = image_property_name )
3262
+
3263
+ # Validate the values by creating an object from the provided dict.
3264
+ objects .ImageMetaProps .from_dict (image_properties )
3265
+
3266
+ # Return the dict so we can update the instance system_metadata
3267
+ return image_properties
3268
+
3269
+ def _update_image_properties (self , instance , image_properties ):
3270
+ """Update instance image properties
3271
+
3272
+ :param instance: The instance to update
3273
+ :param image_properties: List of image properties and values to update
3274
+ """
3275
+ # Check the state of the instance
3276
+ allowed_states = [
3277
+ obj_fields .InstanceState .STOPPED ,
3278
+ obj_fields .InstanceState .SHELVED ,
3279
+ obj_fields .InstanceState .SHELVED_OFFLOADED ,
3280
+ ]
3281
+ if instance .vm_state not in allowed_states :
3282
+ raise exception .InstanceInvalidState (
3283
+ instance_uuid = instance .uuid , attr = 'vm_state' ,
3284
+ state = instance .vm_state ,
3285
+ method = 'image_property set (must be STOPPED, SHELVED, OR '
3286
+ 'SHELVED_OFFLOADED).' )
3287
+
3288
+ # Validate the property names and values
3289
+ image_properties = self ._validate_image_properties (image_properties )
3290
+
3291
+ # Update the image properties and save the instance record
3292
+ for image_property , value in image_properties .items ():
3293
+ instance .system_metadata [f'image_{ image_property } ' ] = value
3294
+
3295
+ # Save and return 0
3296
+ instance .save ()
3297
+ return 0
3298
+
3299
+ @action_description (_ (
3300
+ "Set the values of instance image properties stored in the database. "
3301
+ "This is only allowed for " "instances with a STOPPED, SHELVED or "
3302
+ "SHELVED_OFFLOADED vm_state." ))
3303
+ @args (
3304
+ 'instance_uuid' , metavar = '<instance_uuid>' ,
3305
+ help = 'UUID of the instance' )
3306
+ @args (
3307
+ '--property' , metavar = '<image_property>' , action = 'append' ,
3308
+ dest = 'image_properties' ,
3309
+ help = 'Image property to set using the format name=value. For example: '
3310
+ '--property hw_disk_bus=virtio --property hw_cdrom_bus=sata' )
3311
+ def set (self , instance_uuid = None , image_properties = None ):
3312
+ """Set instance image property values
3313
+
3314
+ Return codes:
3315
+ * 0: Command completed successfully.
3316
+ * 1: An unexpected error happened.
3317
+ * 2: Unable to find instance.
3318
+ * 3: Instance is in an invalid state.
3319
+ * 4: Invalid input format.
3320
+ * 5: Invalid image property name.
3321
+ * 6: Invalid image property value.
3322
+ """
3323
+ try :
3324
+ ctxt = context .get_admin_context ()
3325
+ im = objects .InstanceMapping .get_by_instance_uuid (
3326
+ ctxt , instance_uuid )
3327
+ with context .target_cell (ctxt , im .cell_mapping ) as cctxt :
3328
+ instance = objects .Instance .get_by_uuid (
3329
+ cctxt , instance_uuid , expected_attrs = ['system_metadata' ])
3330
+ return self ._update_image_properties (
3331
+ instance , image_properties )
3332
+ except ValueError as e :
3333
+ print (str (e ))
3334
+ return 6
3335
+ except exception .InvalidImagePropertyName as e :
3336
+ print (str (e ))
3337
+ return 5
3338
+ except exception .InvalidInput as e :
3339
+ print (str (e ))
3340
+ return 4
3341
+ except exception .InstanceInvalidState as e :
3342
+ print (str (e ))
3343
+ return 3
3344
+ except (
3345
+ exception .InstanceNotFound ,
3346
+ exception .InstanceMappingNotFound ,
3347
+ ) as e :
3348
+ print (str (e ))
3349
+ return 2
3350
+ except Exception as e :
3351
+ print ('Unexpected error, see nova-manage.log for the full '
3352
+ 'trace: %s ' % str (e ))
3353
+ LOG .exception ('Unexpected error' )
3354
+ return 1
3355
+
3356
+
3195
3357
CATEGORIES = {
3196
3358
'api_db' : ApiDbCommands ,
3197
3359
'cell_v2' : CellV2Commands ,
3198
3360
'db' : DbCommands ,
3199
3361
'placement' : PlacementCommands ,
3200
3362
'libvirt' : LibvirtCommands ,
3201
3363
'volume_attachment' : VolumeAttachmentCommands ,
3364
+ 'image_property' : ImagePropertyCommands ,
3202
3365
}
3203
3366
3204
3367
0 commit comments