@@ -30,6 +30,8 @@ const (
3030 KeyClusterRevision = "cluster-revision"
3131 KeyConstraints = "constraints"
3232 KeyLeader = "leader/"
33+ KeyRebootsPrefix = "reboots/data/"
34+ KeyRebootsWriteIndex = "reboots/write-index"
3335 KeyRecords = "records/"
3436 KeyRecordID = "records"
3537 KeyResourcePrefix = "resource/"
@@ -685,6 +687,179 @@ func (s Storage) GetSabakanURL(ctx context.Context) (string, error) {
685687 return s .getStringValue (ctx , KeySabakanURL )
686688}
687689
690+ func rebootsEntryKey (index int64 ) string {
691+ return fmt .Sprintf ("%s%016x" , KeyRebootsPrefix , index )
692+ }
693+
694+ // RegisterRebootsEntry enqueues a reboot queue entry to the reboot queue.
695+ // "Index" of the entry is retrieved and updated in this method. The given value is ignored.
696+ func (s Storage ) RegisterRebootsEntry (ctx context.Context , r * RebootQueueEntry ) error {
697+ RETRY:
698+ var writeIndex , writeIndexRev int64
699+ resp , err := s .Get (ctx , KeyRebootsWriteIndex )
700+ if err != nil {
701+ return err
702+ }
703+ if resp .Count != 0 {
704+ value , err := strconv .ParseInt (string (resp .Kvs [0 ].Value ), 10 , 64 )
705+ if err != nil {
706+ return err
707+ }
708+ writeIndex = value
709+ writeIndexRev = resp .Kvs [0 ].ModRevision
710+ }
711+
712+ r .Index = writeIndex
713+ data , err := json .Marshal (r )
714+ if err != nil {
715+ return err
716+ }
717+
718+ nextWriteIndex := strconv .FormatInt (writeIndex + 1 , 10 )
719+ txnResp , err := s .Txn (ctx ).
720+ If (
721+ clientv3 .Compare (clientv3 .ModRevision (KeyRebootsWriteIndex ), "=" , writeIndexRev ),
722+ ).
723+ Then (
724+ clientv3 .OpPut (rebootsEntryKey (writeIndex ), string (data )),
725+ clientv3 .OpPut (KeyRebootsWriteIndex , nextWriteIndex ),
726+ ).
727+ Commit ()
728+ if err != nil {
729+ return err
730+ }
731+ if ! txnResp .Succeeded {
732+ goto RETRY
733+ }
734+
735+ return nil
736+ }
737+
738+ // UpdateRebootsEntry updates existing reboot queue entry.
739+ // It always overwrites the contents with a CAS loop.
740+ // If the entry is not found in the reboot queue, this returns ErrNotFound.
741+ func (s Storage ) UpdateRebootsEntry (ctx context.Context , r * RebootQueueEntry ) error {
742+ key := rebootsEntryKey (r .Index )
743+ data , err := json .Marshal (r )
744+ if err != nil {
745+ return err
746+ }
747+
748+ RETRY:
749+ resp , err := s .Get (ctx , key )
750+ if err != nil {
751+ return err
752+ }
753+ if resp .Count == 0 {
754+ return ErrNotFound
755+ }
756+
757+ rev := resp .Kvs [0 ].ModRevision
758+ txnResp , err := s .Txn (ctx ).
759+ If (
760+ clientv3 .Compare (clientv3 .ModRevision (key ), "=" , rev ),
761+ ).
762+ Then (
763+ clientv3 .OpPut (key , string (data )),
764+ ).
765+ Commit ()
766+ if err != nil {
767+ return err
768+ }
769+ if ! txnResp .Succeeded {
770+ goto RETRY
771+ }
772+
773+ return nil
774+ }
775+
776+ // GetRebootsEntry loads the entry specified by the index from the reboot queue.
777+ // If the pointed entry is not found, this returns ErrNotFound.
778+ func (s Storage ) GetRebootsEntry (ctx context.Context , index int64 ) (* RebootQueueEntry , error ) {
779+ resp , err := s .Get (ctx , rebootsEntryKey (index ))
780+ if err != nil {
781+ return nil , err
782+ }
783+
784+ if len (resp .Kvs ) == 0 {
785+ return nil , ErrNotFound
786+ }
787+
788+ r := new (RebootQueueEntry )
789+ err = json .Unmarshal (resp .Kvs [0 ].Value , r )
790+ if err != nil {
791+ return nil , err
792+ }
793+
794+ return r , nil
795+ }
796+
797+ func (s Storage ) getRebootsEntries (ctx context.Context , count int64 ) ([]* RebootQueueEntry , error ) {
798+ opts := []clientv3.OpOption {
799+ clientv3 .WithPrefix (),
800+ clientv3 .WithSort (clientv3 .SortByKey , clientv3 .SortAscend ),
801+ }
802+ if count > 0 {
803+ opts = append (opts , clientv3 .WithLimit (count ))
804+ }
805+ resp , err := s .Get (ctx , KeyRebootsPrefix , opts ... )
806+ if err != nil {
807+ return nil , err
808+ }
809+
810+ if len (resp .Kvs ) == 0 {
811+ return nil , nil
812+ }
813+
814+ reboots := make ([]* RebootQueueEntry , len (resp .Kvs ))
815+ for i , kv := range resp .Kvs {
816+ r := new (RebootQueueEntry )
817+ err = json .Unmarshal (kv .Value , r )
818+ if err != nil {
819+ return nil , err
820+ }
821+ reboots [i ] = r
822+ }
823+
824+ return reboots , nil
825+ }
826+
827+ // GetRebootsEntries loads the entries from the reboot queue.
828+ func (s Storage ) GetRebootsEntries (ctx context.Context ) ([]* RebootQueueEntry , error ) {
829+ return s .getRebootsEntries (ctx , 0 )
830+ }
831+
832+ // GetRebootsFrontEntry loads the front entry from the reboot queue.
833+ // If the queue is empty, this returns ErrNotFound.
834+ func (s Storage ) GetRebootsFrontEntry (ctx context.Context ) (* RebootQueueEntry , error ) {
835+ reboots , err := s .getRebootsEntries (ctx , 1 )
836+ if err != nil {
837+ return nil , err
838+ }
839+
840+ if len (reboots ) == 0 {
841+ return nil , ErrNotFound
842+ }
843+
844+ return reboots [0 ], nil
845+ }
846+
847+ // DeleteRebootsEntry deletes the entry specified by the index from the reboot queue.
848+ func (s Storage ) DeleteRebootsEntry (ctx context.Context , leaderKey string , index int64 ) error {
849+ resp , err := s .Txn (ctx ).
850+ If (clientv3util .KeyExists (leaderKey )).
851+ Then (clientv3 .OpDelete (rebootsEntryKey (index ))).
852+ Commit ()
853+ if err != nil {
854+ return err
855+ }
856+ if ! resp .Succeeded {
857+ return ErrNoLeader
858+ }
859+
860+ return nil
861+ }
862+
688863// SetStatus stores the server status.
689864func (s Storage ) SetStatus (ctx context.Context , lease clientv3.LeaseID , st * ServerStatus ) error {
690865 data , err := json .Marshal (st )
0 commit comments