58
58
from nova .db import migration
59
59
from nova import exception
60
60
from nova .i18n import _
61
+ from nova .limit import local as local_limit
62
+ from nova .limit import placement as placement_limit
61
63
from nova .network import constants
62
64
from nova .network import neutron as neutron_api
63
65
from nova import objects
70
72
from nova .objects import pci_device as pci_device_obj
71
73
from nova .objects import quotas as quotas_obj
72
74
from nova .objects import virtual_interface as virtual_interface_obj
75
+ import nova .quota
73
76
from nova import rpc
74
77
from nova .scheduler .client import report
75
78
from nova .scheduler import utils as scheduler_utils
@@ -3367,6 +3370,183 @@ def set(self, instance_uuid=None, image_properties=None):
3367
3370
return 1
3368
3371
3369
3372
3373
+ class LimitsCommands ():
3374
+
3375
+ def _create_unified_limits (self , ctxt , legacy_defaults , project_id ,
3376
+ region_id , output , dry_run ):
3377
+ return_code = 0
3378
+
3379
+ # Create registered (default) limits first.
3380
+ unified_to_legacy_names = dict (
3381
+ ** local_limit .LEGACY_LIMITS , ** placement_limit .LEGACY_LIMITS )
3382
+
3383
+ legacy_to_unified_names = dict (
3384
+ zip (unified_to_legacy_names .values (),
3385
+ unified_to_legacy_names .keys ()))
3386
+
3387
+ # For auth, a section for [keystone] is required in the config:
3388
+ #
3389
+ # [keystone]
3390
+ # region_name = RegionOne
3391
+ # user_domain_name = Default
3392
+ # password = <password>
3393
+ # username = <username>
3394
+ # auth_url = http://127.0.0.1/identity
3395
+ # auth_type = password
3396
+ # system_scope = all
3397
+ #
3398
+ # The configured user needs 'role:admin and system_scope:all' by
3399
+ # default in order to create limits in Keystone.
3400
+ keystone_api = utils .get_sdk_adapter ('identity' )
3401
+
3402
+ # Service ID is required in unified limits APIs.
3403
+ service_id = keystone_api .find_service ('nova' ).id
3404
+
3405
+ # Retrieve the existing resource limits from Keystone.
3406
+ registered_limits = keystone_api .registered_limits (region_id = region_id )
3407
+
3408
+ unified_defaults = {
3409
+ rl .resource_name : rl .default_limit for rl in registered_limits }
3410
+
3411
+ # f-strings don't seem to work well with the _() translation function.
3412
+ msg = f'Found default limits in Keystone: { unified_defaults } ...'
3413
+ output (_ (msg ))
3414
+
3415
+ # Determine which resource limits are missing in Keystone so that we
3416
+ # can create them.
3417
+ output (_ ('Creating default limits in Keystone ...' ))
3418
+ for resource , rlimit in legacy_defaults .items ():
3419
+ resource_name = legacy_to_unified_names [resource ]
3420
+ if resource_name not in unified_defaults :
3421
+ msg = f'Creating default limit: { resource_name } = { rlimit } '
3422
+ if region_id :
3423
+ msg += f' in region { region_id } '
3424
+ output (_ (msg ))
3425
+ if not dry_run :
3426
+ try :
3427
+ keystone_api .create_registered_limit (
3428
+ resource_name = resource_name ,
3429
+ default_limit = rlimit , region_id = region_id ,
3430
+ service_id = service_id )
3431
+ except Exception as e :
3432
+ msg = f'Failed to create default limit: { str (e )} '
3433
+ print (_ (msg ))
3434
+ return_code = 1
3435
+ else :
3436
+ existing_rlimit = unified_defaults [resource_name ]
3437
+ msg = (f'A default limit: { resource_name } = { existing_rlimit } '
3438
+ 'already exists in Keystone, skipping ...' )
3439
+ output (_ (msg ))
3440
+
3441
+ # Create project limits if there are any.
3442
+ if not project_id :
3443
+ return return_code
3444
+
3445
+ output (_ ('Reading project limits from the Nova API database ...' ))
3446
+ legacy_projects = objects .Quotas .get_all_by_project (ctxt , project_id )
3447
+ legacy_projects .pop ('project_id' , None )
3448
+ msg = f'Found project limits in the database: { legacy_projects } ...'
3449
+ output (_ (msg ))
3450
+
3451
+ # Retrieve existing limits from Keystone.
3452
+ project_limits = keystone_api .limits (
3453
+ project_id = project_id , region_id = region_id )
3454
+ unified_projects = {
3455
+ pl .resource_name : pl .resource_limit for pl in project_limits }
3456
+ msg = f'Found project limits in Keystone: { unified_projects } ...'
3457
+ output (_ (msg ))
3458
+
3459
+ output (_ ('Creating project limits in Keystone ...' ))
3460
+ for resource , plimit in legacy_projects .items ():
3461
+ resource_name = legacy_to_unified_names [resource ]
3462
+ if resource_name not in unified_projects :
3463
+ msg = (
3464
+ f'Creating project limit: { resource_name } = { plimit } '
3465
+ f'for project { project_id } ' )
3466
+ if region_id :
3467
+ msg += f' in region { region_id } '
3468
+ output (_ (msg ))
3469
+ if not dry_run :
3470
+ try :
3471
+ keystone_api .create_limit (
3472
+ resource_name = resource_name ,
3473
+ resource_limit = plimit , project_id = project_id ,
3474
+ region_id = region_id , service_id = service_id )
3475
+ except Exception as e :
3476
+ msg = f'Failed to create project limit: { str (e )} '
3477
+ print (_ (msg ))
3478
+ return_code = 1
3479
+ else :
3480
+ existing_plimit = unified_projects [resource_name ]
3481
+ msg = (f'A project limit: { resource_name } = { existing_plimit } '
3482
+ 'already exists in Keystone, skipping ...' )
3483
+ output (_ (msg ))
3484
+
3485
+ return return_code
3486
+
3487
+ @action_description (
3488
+ _ ("Copy quota limits from the Nova API database to Keystone." ))
3489
+ @args ('--project-id' , metavar = '<project-id>' , dest = 'project_id' ,
3490
+ help = 'Project ID for which to migrate quota limits' )
3491
+ @args ('--region-id' , metavar = '<region-id>' , dest = 'region_id' ,
3492
+ help = 'Region ID for which to migrate quota limits' )
3493
+ @args ('--verbose' , action = 'store_true' , dest = 'verbose' , default = False ,
3494
+ help = 'Provide verbose output during execution.' )
3495
+ @args ('--dry-run' , action = 'store_true' , dest = 'dry_run' , default = False ,
3496
+ help = 'Show what limits would be created without actually '
3497
+ 'creating them.' )
3498
+ def migrate_to_unified_limits (self , project_id = None , region_id = None ,
3499
+ verbose = False , dry_run = False ):
3500
+ """Migrate quota limits from legacy quotas to unified limits.
3501
+
3502
+ Return codes:
3503
+ * 0: Command completed successfully.
3504
+ * 1: An unexpected error occurred.
3505
+ * 2: Failed to connect to the database.
3506
+ """
3507
+ ctxt = context .get_admin_context ()
3508
+
3509
+ output = lambda msg : None
3510
+ if verbose :
3511
+ output = lambda msg : print (msg )
3512
+
3513
+ output (_ ('Reading default limits from the Nova API database ...' ))
3514
+
3515
+ try :
3516
+ # This will look for limits in the 'default' quota class first and
3517
+ # then fall back to the [quota] config options.
3518
+ legacy_defaults = nova .quota .QUOTAS .get_defaults (ctxt )
3519
+ except db_exc .CantStartEngineError :
3520
+ print (_ ('Failed to connect to the database so aborting this '
3521
+ 'migration attempt. Please check your config file to make '
3522
+ 'sure that [api_database]/connection and '
3523
+ '[database]/connection are set and run this '
3524
+ 'command again.' ))
3525
+ return 2
3526
+
3527
+ # Remove obsolete resource limits.
3528
+ for resource in ('fixed_ips' , 'floating_ips' , 'security_groups' ,
3529
+ 'security_group_rules' ):
3530
+ if resource in legacy_defaults :
3531
+ msg = f'Skipping obsolete limit for { resource } ...'
3532
+ output (_ (msg ))
3533
+ legacy_defaults .pop (resource )
3534
+
3535
+ msg = (
3536
+ f'Found default limits in the database: { legacy_defaults } ...' )
3537
+ output (_ (msg ))
3538
+
3539
+ try :
3540
+ return self ._create_unified_limits (
3541
+ ctxt , legacy_defaults , project_id , region_id , output , dry_run )
3542
+ except Exception as e :
3543
+ msg = (f'Unexpected error, see nova-manage.log for the full '
3544
+ f'trace: { str (e )} ' )
3545
+ print (_ (msg ))
3546
+ LOG .exception ('Unexpected error' )
3547
+ return 1
3548
+
3549
+
3370
3550
CATEGORIES = {
3371
3551
'api_db' : ApiDbCommands ,
3372
3552
'cell_v2' : CellV2Commands ,
@@ -3375,6 +3555,7 @@ def set(self, instance_uuid=None, image_properties=None):
3375
3555
'libvirt' : LibvirtCommands ,
3376
3556
'volume_attachment' : VolumeAttachmentCommands ,
3377
3557
'image_property' : ImagePropertyCommands ,
3558
+ 'limits' : LimitsCommands ,
3378
3559
}
3379
3560
3380
3561
0 commit comments