@@ -529,7 +529,98 @@ def is_ipaddress(val):
529
529
return False
530
530
return True
531
531
532
+ # class for locking entire config process
533
+ class ConfigDbLock ():
534
+ def __init__ (self ):
535
+ self .lockName = "LOCK|configDbLock"
536
+ self .timeout = 10
537
+ self .pid = os .getpid ()
538
+ self .client = None
539
+
540
+ self ._acquireLock ()
541
+ return
542
+
543
+ def _acquireLock (self ):
544
+ try :
545
+ # connect to db
546
+ db_kwargs = dict ()
547
+ configdb = ConfigDBConnector (** db_kwargs )
548
+ configdb .connect ()
549
+
550
+ self .client = configdb .get_redis_client ('CONFIG_DB' )
551
+ # Set lock and expire time. Process may get killed b/w set lock and
552
+ # expire call.
553
+ if self .client .hsetnx (self .lockName , "PID" , self .pid ):
554
+ self .client .expire (self .lockName , self .timeout )
555
+ # if lock exists but expire timer not running, run expire time and
556
+ # abort.
557
+ elif not self .client .ttl (self .lockName ):
558
+ click .echo (":::Unable to acquire lock. Resetting timer and aborting:::" );
559
+ self .client .expire (self .lockName , self .timeout )
560
+ sys .exit (1 )
561
+ else :
562
+ click .echo (":::Unable to acquire lock. Aborting:::" );
563
+ sys .exit (1 )
564
+ except Exception as e :
565
+ click .echo (":::Exception: {}:::" .format (e ))
566
+ sys .exit (1 )
567
+ return
568
+
569
+ def reacquireLock (self ):
570
+ try :
571
+ # Try to set lock first
572
+ if self .client .hsetnx (self .lockName , "PID" , self .pid ):
573
+ self .client .expire (self .lockName , self .timeout )
574
+ # if lock exists, check who owns it
575
+ else :
576
+ p = self .client .pipeline (True )
577
+ # watch, we do not want to work on modified lock
578
+ p .watch (self .lockName )
579
+ # if current process holding then extend the timer
580
+ if p .hget (self .lockName , "PID" ) == str (self .pid ):
581
+ self .client .expire (self .lockName , self .timeout )
582
+ p .unwatch ()
583
+ return
584
+ else :
585
+ # some other process is holding the lock.
586
+ click .echo (":::Unable to reacquire lock (lock PID: {}, self.pid: {}):::" .\
587
+ format (p .hget (self .lockName , "PID" ), self .pid ))
588
+ p .unwatch ()
589
+ sys .exit (1 )
590
+ except Exception as e :
591
+ click .echo (":::Exception: {}:::" .format (e ))
592
+ sys .exit (1 )
593
+ return
594
+
595
+ def _releaseLock (self ):
596
+ try :
597
+ p = self .client .pipeline (True )
598
+ # watch, we do not want to work on modified lock
599
+ p .watch (self .lockName )
600
+ # if current process holding the lock then release it.
601
+ if p .hget (self .lockName , "PID" ) == str (self .pid ):
602
+ p .multi ()
603
+ p .delete (self .lockName )
604
+ p .execute ()
605
+ return
606
+ # lock may be None, if timer has expired before releasing lock.
607
+ elif not self .lockName :
608
+ return
609
+ else :
610
+ # some other process is holding the lock.
611
+ click .echo (":::Unable to release lock (lock PID: {}, self.pid: {}):::" .\
612
+ format (p .hget (self .lockName , "PID" ), self .pid ))
613
+ p .unwatch ()
614
+ except Exception as e :
615
+ click .echo (":::Exception: {}:::" .format (e ))
616
+ return
617
+
618
+ def __del__ (self ):
619
+ self ._releaseLock ()
620
+ return
621
+ # end of class configdblock
532
622
623
+ configdb_lock = ConfigDbLock ()
533
624
# This is our main entrypoint - the main 'config' command
534
625
@click .group (context_settings = CONTEXT_SETTINGS )
535
626
def config ():
@@ -547,6 +638,8 @@ def config():
547
638
@click .argument ('filename' , default = '/etc/sonic/config_db.json' , type = click .Path ())
548
639
def save (filename ):
549
640
"""Export current config DB to a file on disk."""
641
+ # reacquire lock after prompt
642
+ configdb_lock .reacquireLock ()
550
643
command = "{} -d --print-data > {}" .format (SONIC_CFGGEN_PATH , filename )
551
644
run_command (command , display_cmd = True )
552
645
@@ -557,6 +650,9 @@ def load(filename, yes):
557
650
"""Import a previous saved config DB dump file."""
558
651
if not yes :
559
652
click .confirm ('Load config from the file %s?' % filename , abort = True )
653
+ # reacquire lock after prompt
654
+ configdb_lock .reacquireLock ()
655
+
560
656
command = "{} -j {} --write-to-db" .format (SONIC_CFGGEN_PATH , filename )
561
657
run_command (command , display_cmd = True )
562
658
@@ -568,6 +664,8 @@ def reload(filename, yes, load_sysinfo):
568
664
"""Clear current configuration and import a previous saved config DB dump file."""
569
665
if not yes :
570
666
click .confirm ('Clear current config and reload config from the file %s?' % filename , abort = True )
667
+ # reacquire lock after prompt
668
+ configdb_lock .reacquireLock ()
571
669
572
670
log_info ("'reload' executing..." )
573
671
@@ -617,6 +715,8 @@ def reload(filename, yes, load_sysinfo):
617
715
@click .argument ('filename' , default = '/etc/sonic/device_desc.xml' , type = click .Path (exists = True ))
618
716
def load_mgmt_config (filename ):
619
717
"""Reconfigure hostname and mgmt interface based on device description file."""
718
+ # reacquire lock after prompt
719
+ configdb_lock .reacquireLock ()
620
720
command = "{} -M {} --write-to-db" .format (SONIC_CFGGEN_PATH , filename )
621
721
run_command (command , display_cmd = True )
622
722
#FIXME: After config DB daemon for hostname and mgmt interface is implemented, we'll no longer need to do manual configuration here
@@ -639,7 +739,10 @@ def load_mgmt_config(filename):
639
739
@click .option ('-y' , '--yes' , is_flag = True , callback = _abort_if_false ,
640
740
expose_value = False , prompt = 'Reload config from minigraph?' )
641
741
def load_minigraph ():
742
+
642
743
"""Reconfigure based on minigraph."""
744
+ # reacquire lock after prompt
745
+ configdb_lock .reacquireLock ()
643
746
log_info ("'load_minigraph' executing..." )
644
747
645
748
# get the device type
@@ -2304,6 +2407,8 @@ def ztp():
2304
2407
@click .argument ('run' , required = False , type = click .Choice (["run" ]))
2305
2408
def run (run ):
2306
2409
"""Restart ZTP of the device."""
2410
+ # reacquire lock after prompt
2411
+ configdb_lock .reacquireLock ()
2307
2412
command = "ztp run -y"
2308
2413
run_command (command , display_cmd = True )
2309
2414
@@ -2313,6 +2418,8 @@ def run(run):
2313
2418
@click .argument ('disable' , required = False , type = click .Choice (["disable" ]))
2314
2419
def disable (disable ):
2315
2420
"""Administratively Disable ZTP."""
2421
+ # reacquire lock after prompt
2422
+ configdb_lock .reacquireLock ()
2316
2423
command = "ztp disable -y"
2317
2424
run_command (command , display_cmd = True )
2318
2425
0 commit comments