@@ -414,17 +414,19 @@ def __init__(self):
414
414
pass
415
415
416
416
@staticmethod
417
- def _print_dict (dct , dict_property = "Property" , dict_value = 'Value' ):
417
+ def _print_dict (dct , dict_property = "Property" , dict_value = 'Value' ,
418
+ sort_key = None ):
418
419
"""Print a `dict` as a table of two columns.
419
420
420
421
:param dct: `dict` to print
421
422
:param dict_property: name of the first column
422
423
:param wrap: wrapping for the second column
423
424
:param dict_value: header label for the value (second) column
425
+ :param sort_key: key used for sorting the dict
424
426
"""
425
427
pt = prettytable .PrettyTable ([dict_property , dict_value ])
426
428
pt .align = 'l'
427
- for k , v in sorted (dct .items ()):
429
+ for k , v in sorted (dct .items (), key = sort_key ):
428
430
# convert dict to str to check length
429
431
if isinstance (v , dict ):
430
432
v = six .text_type (v )
@@ -495,9 +497,11 @@ def version(self):
495
497
'max_rows as a batch size for each iteration.' ))
496
498
@args ('--purge' , action = 'store_true' , dest = 'purge' , default = False ,
497
499
help = 'Purge all data from shadow tables after archive completes' )
500
+ @args ('--all-cells' , action = 'store_true' , dest = 'all_cells' ,
501
+ default = False , help = 'Run command across all cells.' )
498
502
def archive_deleted_rows (self , max_rows = 1000 , verbose = False ,
499
503
until_complete = False , purge = False ,
500
- before = None ):
504
+ before = None , all_cells = False ):
501
505
"""Move deleted rows from production tables to shadow tables.
502
506
503
507
Returns 0 if nothing was archived, 1 if some number of rows were
@@ -520,7 +524,7 @@ def archive_deleted_rows(self, max_rows=1000, verbose=False,
520
524
# NOTE(tssurya): This check has been added to validate if the API
521
525
# DB is reachable or not as this is essential for purging the
522
526
# related API database records of the deleted instances.
523
- objects .CellMappingList .get_all (ctxt )
527
+ cell_mappings = objects .CellMappingList .get_all (ctxt )
524
528
except db_exc .CantStartEngineError :
525
529
print (_ ('Failed to connect to API DB so aborting this archival '
526
530
'attempt. Please check your config file to make sure that '
@@ -538,59 +542,146 @@ def archive_deleted_rows(self, max_rows=1000, verbose=False,
538
542
before_date = None
539
543
540
544
table_to_rows_archived = {}
541
- deleted_instance_uuids = []
542
545
if until_complete and verbose :
543
546
sys .stdout .write (_ ('Archiving' ) + '..' ) # noqa
544
- while True :
545
- try :
546
- run , deleted_instance_uuids = db .archive_deleted_rows (
547
- max_rows , before = before_date )
548
- except KeyboardInterrupt :
549
- run = {}
550
- if until_complete and verbose :
551
- print ('.' + _ ('stopped' )) # noqa
552
- break
553
- for k , v in run .items ():
554
- table_to_rows_archived .setdefault (k , 0 )
555
- table_to_rows_archived [k ] += v
556
- if deleted_instance_uuids :
557
- table_to_rows_archived .setdefault ('instance_mappings' , 0 )
558
- table_to_rows_archived .setdefault ('request_specs' , 0 )
559
- table_to_rows_archived .setdefault ('instance_group_member' , 0 )
560
- deleted_mappings = objects .InstanceMappingList .destroy_bulk (
561
- ctxt , deleted_instance_uuids )
562
- table_to_rows_archived ['instance_mappings' ] += deleted_mappings
563
- deleted_specs = objects .RequestSpec .destroy_bulk (
564
- ctxt , deleted_instance_uuids )
565
- table_to_rows_archived ['request_specs' ] += deleted_specs
566
- deleted_group_members = (
567
- objects .InstanceGroup .destroy_members_bulk (
568
- ctxt , deleted_instance_uuids ))
569
- table_to_rows_archived ['instance_group_member' ] += (
570
- deleted_group_members )
571
- if not until_complete :
572
- break
573
- elif not run :
574
- if verbose :
575
- print ('.' + _ ('complete' )) # noqa
547
+
548
+ interrupt = False
549
+
550
+ if all_cells :
551
+ # Sort first by cell name, then by table:
552
+ # +--------------------------------+-------------------------+
553
+ # | Table | Number of Rows Archived |
554
+ # +--------------------------------+-------------------------+
555
+ # | cell0.block_device_mapping | 1 |
556
+ # | cell1.block_device_mapping | 1 |
557
+ # | cell1.instance_actions | 2 |
558
+ # | cell1.instance_actions_events | 2 |
559
+ # | cell2.block_device_mapping | 1 |
560
+ # | cell2.instance_actions | 2 |
561
+ # | cell2.instance_actions_events | 2 |
562
+ # ...
563
+ def sort_func (item ):
564
+ cell_name , table = item [0 ].split ('.' )
565
+ return cell_name , table
566
+ print_sort_func = sort_func
567
+ else :
568
+ cell_mappings = [None ]
569
+ print_sort_func = None
570
+ total_rows_archived = 0
571
+ for cell_mapping in cell_mappings :
572
+ # NOTE(Kevin_Zheng): No need to calculate limit for each
573
+ # cell if until_complete=True.
574
+ # We need not adjust max rows to avoid exceeding a specified total
575
+ # limit because with until_complete=True, we have no total limit.
576
+ if until_complete :
577
+ max_rows_to_archive = max_rows
578
+ elif max_rows > total_rows_archived :
579
+ # We reduce the max rows to archive based on what we've
580
+ # archived so far to avoid potentially exceeding the specified
581
+ # total limit.
582
+ max_rows_to_archive = max_rows - total_rows_archived
583
+ else :
576
584
break
577
- if verbose :
578
- sys .stdout .write ('.' )
585
+ # If all_cells=False, cell_mapping is None
586
+ with context .target_cell (ctxt , cell_mapping ) as cctxt :
587
+ cell_name = cell_mapping .name if cell_mapping else None
588
+ try :
589
+ rows_archived = self ._do_archive (
590
+ table_to_rows_archived ,
591
+ cctxt ,
592
+ max_rows_to_archive ,
593
+ until_complete ,
594
+ verbose ,
595
+ before_date ,
596
+ cell_name )
597
+ except KeyboardInterrupt :
598
+ interrupt = True
599
+ break
600
+ # TODO(melwitt): Handle skip/warn for unreachable cells. Note
601
+ # that cell_mappings = [None] if not --all-cells
602
+ total_rows_archived += rows_archived
603
+
604
+ if until_complete and verbose :
605
+ if interrupt :
606
+ print ('.' + _ ('stopped' )) # noqa
607
+ else :
608
+ print ('.' + _ ('complete' )) # noqa
609
+
579
610
if verbose :
580
611
if table_to_rows_archived :
581
612
self ._print_dict (table_to_rows_archived , _ ('Table' ),
582
- dict_value = _ ('Number of Rows Archived' ))
613
+ dict_value = _ ('Number of Rows Archived' ),
614
+ sort_key = print_sort_func )
583
615
else :
584
616
print (_ ('Nothing was archived.' ))
585
617
586
618
if table_to_rows_archived and purge :
587
619
if verbose :
588
620
print (_ ('Rows were archived, running purge...' ))
589
- self .purge (purge_all = True , verbose = verbose )
621
+ self .purge (purge_all = True , verbose = verbose , all_cells = all_cells )
590
622
591
623
# NOTE(danms): Return nonzero if we archived something
592
624
return int (bool (table_to_rows_archived ))
593
625
626
+ def _do_archive (self , table_to_rows_archived , cctxt , max_rows ,
627
+ until_complete , verbose , before_date , cell_name ):
628
+ """Helper function for archiving deleted rows for a cell.
629
+
630
+ This will archive deleted rows for a cell database and remove the
631
+ associated API database records for deleted instances.
632
+
633
+ :param table_to_rows_archived: Dict tracking the number of rows
634
+ archived by <cell_name>.<table name>. Example:
635
+ {'cell0.instances': 2,
636
+ 'cell1.instances': 5}
637
+ :param cctxt: Cell-targeted nova.context.RequestContext if archiving
638
+ across all cells
639
+ :param max_rows: Maximum number of deleted rows to archive
640
+ :param until_complete: Whether to run continuously until all deleted
641
+ rows are archived
642
+ :param verbose: Whether to print how many rows were archived per table
643
+ :param before_date: Archive rows that were deleted before this date
644
+ :param cell_name: Name of the cell or None if not archiving across all
645
+ cells
646
+ """
647
+ ctxt = context .get_admin_context ()
648
+ while True :
649
+ run , deleted_instance_uuids , total_rows_archived = \
650
+ db .archive_deleted_rows (cctxt , max_rows , before = before_date )
651
+ for table_name , rows_archived in run .items ():
652
+ if cell_name :
653
+ table_name = cell_name + '.' + table_name
654
+ table_to_rows_archived .setdefault (table_name , 0 )
655
+ table_to_rows_archived [table_name ] += rows_archived
656
+ if deleted_instance_uuids :
657
+ table_to_rows_archived .setdefault (
658
+ 'API_DB.instance_mappings' , 0 )
659
+ table_to_rows_archived .setdefault (
660
+ 'API_DB.request_specs' , 0 )
661
+ table_to_rows_archived .setdefault (
662
+ 'API_DB.instance_group_member' , 0 )
663
+ deleted_mappings = objects .InstanceMappingList .destroy_bulk (
664
+ ctxt , deleted_instance_uuids )
665
+ table_to_rows_archived [
666
+ 'API_DB.instance_mappings' ] += deleted_mappings
667
+ deleted_specs = objects .RequestSpec .destroy_bulk (
668
+ ctxt , deleted_instance_uuids )
669
+ table_to_rows_archived [
670
+ 'API_DB.request_specs' ] += deleted_specs
671
+ deleted_group_members = (
672
+ objects .InstanceGroup .destroy_members_bulk (
673
+ ctxt , deleted_instance_uuids ))
674
+ table_to_rows_archived [
675
+ 'API_DB.instance_group_member' ] += deleted_group_members
676
+ # If we're not archiving until there is nothing more to archive, we
677
+ # have reached max_rows in this cell DB or there was nothing to
678
+ # archive.
679
+ if not until_complete or not run :
680
+ break
681
+ if verbose :
682
+ sys .stdout .write ('.' )
683
+ return total_rows_archived
684
+
594
685
@args ('--before' , metavar = '<before>' , dest = 'before' ,
595
686
help = 'If specified, purge rows from shadow tables that are older '
596
687
'than this. Accepts date strings in the default format output '
0 commit comments