View Javadoc
1   /*
2    * Copyright (C) 2016, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.ketch;
12  
13  import static java.util.concurrent.TimeUnit.SECONDS;
14  import static org.eclipse.jgit.internal.ketch.KetchConstants.TERM;
15  
16  import java.io.IOException;
17  import java.util.List;
18  import java.util.concurrent.TimeoutException;
19  
20  import org.eclipse.jgit.lib.CommitBuilder;
21  import org.eclipse.jgit.lib.ObjectId;
22  import org.eclipse.jgit.lib.ObjectInserter;
23  import org.eclipse.jgit.lib.Repository;
24  import org.eclipse.jgit.lib.TreeFormatter;
25  import org.eclipse.jgit.revwalk.RevCommit;
26  import org.eclipse.jgit.revwalk.RevWalk;
27  import org.eclipse.jgit.util.time.ProposedTimestamp;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * The initial {@link Round} for a leaderless repository, used to establish a
33   * leader.
34   */
35  class ElectionRound extends Round {
36  	private static final Logger log = LoggerFactory.getLogger(ElectionRound.class);
37  
38  	private long term;
39  
40  	ElectionRound(KetchLeader leader, LogIndex head) {
41  		super(leader, head);
42  	}
43  
44  	@Override
45  	void start() throws IOException {
46  		ObjectId id;
47  		try (Repository git = leader.openRepository();
48  				ProposedTimestamp ts = getSystem().getClock().propose();
49  				ObjectInserter inserter = git.newObjectInserter()) {
50  			id = bumpTerm(git, ts, inserter);
51  			inserter.flush();
52  			blockUntil(ts);
53  		}
54  		runAsync(id);
55  	}
56  
57  	@Override
58  	void success() {
59  		// Do nothing upon election, KetchLeader will copy the term.
60  	}
61  
62  	long getTerm() {
63  		return term;
64  	}
65  
66  	private ObjectId bumpTerm(Repository git, ProposedTimestamp ts,
67  			ObjectInserter inserter) throws IOException {
68  		CommitBuilder b = new CommitBuilder();
69  		if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
70  			try (RevWalklk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(git)) {
71  				RevCommit c = rw.parseCommit(acceptedOldIndex);
72  				if (getSystem().requireMonotonicLeaderElections()) {
73  					if (ts.read(SECONDS) < c.getCommitTime()) {
74  						throw new TimeIsUncertainException();
75  					}
76  				}
77  				b.setTreeId(c.getTree());
78  				b.setParentId(acceptedOldIndex);
79  				term = parseTerm(c.getFooterLines(TERM)) + 1;
80  			}
81  		} else {
82  			term = 1;
83  			b.setTreeId(inserter.insert(new TreeFormatter()));
84  		}
85  
86  		StringBuilder msg = new StringBuilder();
87  		msg.append(KetchConstants.TERM.getName())
88  				.append(": ") //$NON-NLS-1$
89  				.append(term);
90  
91  		String tag = leader.getSystem().newLeaderTag();
92  		if (tag != null && !tag.isEmpty()) {
93  			msg.append(' ').append(tag);
94  		}
95  
96  		b.setAuthor(leader.getSystem().newCommitter(ts));
97  		b.setCommitter(b.getAuthor());
98  		b.setMessage(msg.toString());
99  
100 		if (log.isDebugEnabled()) {
101 			log.debug("Trying to elect myself " + b.getMessage()); //$NON-NLS-1$
102 		}
103 		return inserter.insert(b);
104 	}
105 
106 	private static long parseTerm(List<String> footer) {
107 		if (footer.isEmpty()) {
108 			return 0;
109 		}
110 
111 		String s = footer.get(0);
112 		int p = s.indexOf(' ');
113 		if (p > 0) {
114 			s = s.substring(0, p);
115 		}
116 		return Long.parseLong(s, 10);
117 	}
118 
119 	private void blockUntil(ProposedTimestamp ts) throws IOException {
120 		try {
121 			ts.blockUntil(getSystem().getMaxWaitForMonotonicClock());
122 		} catch (InterruptedException | TimeoutException e) {
123 			throw new TimeIsUncertainException(e);
124 		}
125 	}
126 }