1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.mirror;
18
19 import static com.linecorp.centraldogma.server.mirror.MirrorUtil.normalizePath;
20 import static java.util.Objects.requireNonNull;
21
22 import java.io.File;
23 import java.net.URI;
24 import java.time.Instant;
25 import java.time.ZonedDateTime;
26 import java.time.temporal.ChronoUnit;
27 import java.util.Objects;
28 import java.util.Optional;
29
30 import javax.annotation.Nullable;
31
32 import com.cronutils.descriptor.CronDescriptor;
33 import com.cronutils.model.Cron;
34 import com.cronutils.model.time.ExecutionTime;
35 import com.google.common.annotations.VisibleForTesting;
36 import com.google.common.base.MoreObjects;
37 import com.google.common.base.MoreObjects.ToStringHelper;
38
39 import com.linecorp.centraldogma.common.Author;
40 import com.linecorp.centraldogma.common.MirrorException;
41 import com.linecorp.centraldogma.server.command.CommandExecutor;
42 import com.linecorp.centraldogma.server.credential.Credential;
43 import com.linecorp.centraldogma.server.mirror.Mirror;
44 import com.linecorp.centraldogma.server.mirror.MirrorDirection;
45 import com.linecorp.centraldogma.server.mirror.MirrorResult;
46 import com.linecorp.centraldogma.server.mirror.MirrorStatus;
47 import com.linecorp.centraldogma.server.storage.repository.Repository;
48
49 public abstract class AbstractMirror implements Mirror {
50
51 private static final CronDescriptor CRON_DESCRIPTOR = CronDescriptor.instance();
52
53 protected static final Author MIRROR_AUTHOR = new Author("Mirror", "mirror@localhost.localdomain");
54
55 private final String id;
56 private final boolean enabled;
57 private final MirrorDirection direction;
58 private final Credential credential;
59 private final Repository localRepo;
60 private final String localPath;
61 private final URI remoteRepoUri;
62 private final String remotePath;
63 private final String remoteBranch;
64 @Nullable
65 private final String gitignore;
66 @Nullable
67 private final String zone;
68 @Nullable
69 private final Cron schedule;
70 @Nullable
71 private final ExecutionTime executionTime;
72 private final long jitterMillis;
73
74 protected AbstractMirror(String id, boolean enabled, @Nullable Cron schedule, MirrorDirection direction,
75 Credential credential, Repository localRepo, String localPath,
76 URI remoteRepoUri, String remotePath, String remoteBranch,
77 @Nullable String gitignore, @Nullable String zone) {
78 this.id = requireNonNull(id, "id");
79 this.enabled = enabled;
80 this.direction = requireNonNull(direction, "direction");
81 this.credential = requireNonNull(credential, "credential");
82 this.localRepo = requireNonNull(localRepo, "localRepo");
83 this.localPath = normalizePath(requireNonNull(localPath, "localPath"));
84 this.remoteRepoUri = requireNonNull(remoteRepoUri, "remoteRepoUri");
85 this.remotePath = normalizePath(requireNonNull(remotePath, "remotePath"));
86 this.remoteBranch = requireNonNull(remoteBranch, "remoteBranch");
87 this.gitignore = gitignore;
88 this.zone = zone;
89
90 if (schedule != null) {
91 this.schedule = requireNonNull(schedule, "schedule");
92 executionTime = ExecutionTime.forCron(this.schedule);
93
94
95
96 jitterMillis = Math.abs(Objects.hash(this.schedule.asString(), this.direction,
97 this.localRepo.parent().name(), this.localRepo.name(),
98 this.remoteRepoUri, this.remotePath, this.remoteBranch) /
99 (Integer.MAX_VALUE / 60000));
100 } else {
101 this.schedule = null;
102 executionTime = null;
103 jitterMillis = -1;
104 }
105 }
106
107 @Override
108 public String id() {
109 return id;
110 }
111
112 @Override
113 public final Cron schedule() {
114 return schedule;
115 }
116
117 @Override
118 public final ZonedDateTime nextExecutionTime(ZonedDateTime lastExecutionTime) {
119 return nextExecutionTime(lastExecutionTime, jitterMillis);
120 }
121
122 @VisibleForTesting
123 ZonedDateTime nextExecutionTime(ZonedDateTime lastExecutionTime, long jitterMillis) {
124 requireNonNull(lastExecutionTime, "lastExecutionTime");
125 final Optional<ZonedDateTime> next = executionTime.nextExecution(
126 lastExecutionTime.minus(jitterMillis, ChronoUnit.MILLIS));
127 if (next.isPresent()) {
128 return next.get().plus(jitterMillis, ChronoUnit.MILLIS);
129 }
130 throw new IllegalArgumentException(
131 "no next execution time for " + CRON_DESCRIPTOR.describe(schedule) + ", lastExecutionTime: " +
132 lastExecutionTime);
133 }
134
135 @Override
136 public MirrorDirection direction() {
137 return direction;
138 }
139
140 @Override
141 public final Credential credential() {
142 return credential;
143 }
144
145 @Override
146 public final Repository localRepo() {
147 return localRepo;
148 }
149
150 @Override
151 public final String localPath() {
152 return localPath;
153 }
154
155 @Override
156 public final URI remoteRepoUri() {
157 return remoteRepoUri;
158 }
159
160 @Override
161 public final String remotePath() {
162 return remotePath;
163 }
164
165 @Override
166 public final String remoteBranch() {
167 return remoteBranch;
168 }
169
170 @Override
171 public final String gitignore() {
172 return gitignore;
173 }
174
175 @Override
176 public final boolean enabled() {
177 return enabled;
178 }
179
180 @Nullable
181 @Override
182 public String zone() {
183 return zone;
184 }
185
186 @Override
187 public final MirrorResult mirror(File workDir, CommandExecutor executor, int maxNumFiles,
188 long maxNumBytes, Instant triggeredTime) {
189 try {
190 switch (direction()) {
191 case LOCAL_TO_REMOTE:
192 return mirrorLocalToRemote(workDir, maxNumFiles, maxNumBytes, triggeredTime);
193 case REMOTE_TO_LOCAL:
194 return mirrorRemoteToLocal(workDir, executor, maxNumFiles, maxNumBytes, triggeredTime);
195 default:
196 throw new Error("Should never reach here");
197 }
198 } catch (InterruptedException e) {
199
200 Thread.currentThread().interrupt();
201 throw new MirrorException(e);
202 } catch (MirrorException e) {
203 throw e;
204 } catch (Exception e) {
205 final String message = e.getMessage();
206 if (message != null) {
207 throw new MirrorException(message, e);
208 } else {
209 throw new MirrorException(e);
210 }
211 }
212 }
213
214 protected abstract MirrorResult mirrorLocalToRemote(
215 File workDir, int maxNumFiles, long maxNumBytes, Instant triggeredTime) throws Exception;
216
217 protected abstract MirrorResult mirrorRemoteToLocal(
218 File workDir, CommandExecutor executor, int maxNumFiles, long maxNumBytes, Instant triggeredTime)
219 throws Exception;
220
221 protected final MirrorResult newMirrorResult(MirrorStatus mirrorStatus, @Nullable String description,
222 Instant triggeredTime) {
223 return new MirrorResult(id, localRepo.parent().name(), localRepo.name(), mirrorStatus, description,
224 triggeredTime, Instant.now(), zone);
225 }
226
227 @Override
228 public String toString() {
229 final ToStringHelper helper = MoreObjects.toStringHelper("")
230 .omitNullValues()
231 .add("direction", direction)
232 .add("localProj", localRepo.parent().name())
233 .add("localRepo", localRepo.name())
234 .add("localPath", localPath)
235 .add("remoteRepo", remoteRepoUri)
236 .add("remotePath", remotePath)
237 .add("remoteBranch", remoteBranch)
238 .add("gitignore", gitignore)
239 .add("credential", credential);
240 if (schedule != null) {
241 helper.add("schedule", CronDescriptor.instance().describe(schedule));
242 }
243 return helper.toString();
244 }
245 }