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.ZonedDateTime;
25 import java.time.temporal.ChronoUnit;
26 import java.util.Objects;
27 import java.util.Optional;
28
29 import javax.annotation.Nullable;
30
31 import com.cronutils.descriptor.CronDescriptor;
32 import com.cronutils.model.Cron;
33 import com.cronutils.model.time.ExecutionTime;
34 import com.google.common.annotations.VisibleForTesting;
35 import com.google.common.base.MoreObjects;
36 import com.google.common.base.MoreObjects.ToStringHelper;
37
38 import com.linecorp.centraldogma.common.Author;
39 import com.linecorp.centraldogma.server.MirrorException;
40 import com.linecorp.centraldogma.server.command.CommandExecutor;
41 import com.linecorp.centraldogma.server.mirror.Mirror;
42 import com.linecorp.centraldogma.server.mirror.MirrorCredential;
43 import com.linecorp.centraldogma.server.mirror.MirrorDirection;
44 import com.linecorp.centraldogma.server.storage.repository.Repository;
45
46 public abstract class AbstractMirror implements Mirror {
47
48 private static final CronDescriptor CRON_DESCRIPTOR = CronDescriptor.instance();
49
50 protected static final Author MIRROR_AUTHOR = new Author("Mirror", "mirror@localhost.localdomain");
51
52 private final Cron schedule;
53 private final MirrorDirection direction;
54 private final MirrorCredential credential;
55 private final Repository localRepo;
56 private final String localPath;
57 private final URI remoteRepoUri;
58 private final String remotePath;
59 @Nullable
60 private final String remoteBranch;
61 @Nullable
62 private final String gitignore;
63 private final ExecutionTime executionTime;
64 private final long jitterMillis;
65
66 protected AbstractMirror(Cron schedule, MirrorDirection direction, MirrorCredential credential,
67 Repository localRepo, String localPath,
68 URI remoteRepoUri, String remotePath, @Nullable String remoteBranch,
69 @Nullable String gitignore) {
70
71 this.schedule = requireNonNull(schedule, "schedule");
72 this.direction = requireNonNull(direction, "direction");
73 this.credential = requireNonNull(credential, "credential");
74 this.localRepo = requireNonNull(localRepo, "localRepo");
75 this.localPath = normalizePath(requireNonNull(localPath, "localPath"));
76 this.remoteRepoUri = requireNonNull(remoteRepoUri, "remoteRepoUri");
77 this.remotePath = normalizePath(requireNonNull(remotePath, "remotePath"));
78 this.remoteBranch = remoteBranch;
79 this.gitignore = gitignore;
80
81 executionTime = ExecutionTime.forCron(this.schedule);
82
83
84
85 jitterMillis = Math.abs(Objects.hash(this.schedule.asString(), this.direction,
86 this.localRepo.parent().name(), this.localRepo.name(),
87 this.remoteRepoUri, this.remotePath, this.remoteBranch) /
88 (Integer.MAX_VALUE / 60000));
89 }
90
91 @Override
92 public final Cron schedule() {
93 return schedule;
94 }
95
96 @Override
97 public final ZonedDateTime nextExecutionTime(ZonedDateTime lastExecutionTime) {
98 return nextExecutionTime(lastExecutionTime, jitterMillis);
99 }
100
101 @VisibleForTesting
102 ZonedDateTime nextExecutionTime(ZonedDateTime lastExecutionTime, long jitterMillis) {
103 requireNonNull(lastExecutionTime, "lastExecutionTime");
104 final Optional<ZonedDateTime> next = executionTime.nextExecution(
105 lastExecutionTime.minus(jitterMillis, ChronoUnit.MILLIS));
106 if (next.isPresent()) {
107 return next.get().plus(jitterMillis, ChronoUnit.MILLIS);
108 }
109 throw new IllegalArgumentException(
110 "no next execution time for " + CRON_DESCRIPTOR.describe(schedule) + ", lastExecutionTime: " +
111 lastExecutionTime);
112 }
113
114 @Override
115 public MirrorDirection direction() {
116 return direction;
117 }
118
119 @Override
120 public final MirrorCredential credential() {
121 return credential;
122 }
123
124 @Override
125 public final Repository localRepo() {
126 return localRepo;
127 }
128
129 @Override
130 public final String localPath() {
131 return localPath;
132 }
133
134 @Override
135 public final URI remoteRepoUri() {
136 return remoteRepoUri;
137 }
138
139 @Override
140 public final String remotePath() {
141 return remotePath;
142 }
143
144 @Override
145 public final String remoteBranch() {
146 return remoteBranch;
147 }
148
149 @Override
150 public final String gitignore() {
151 return gitignore;
152 }
153
154 @Override
155 public final void mirror(File workDir, CommandExecutor executor, int maxNumFiles, long maxNumBytes) {
156 try {
157 switch (direction()) {
158 case LOCAL_TO_REMOTE:
159 mirrorLocalToRemote(workDir, maxNumFiles, maxNumBytes);
160 break;
161 case REMOTE_TO_LOCAL:
162 mirrorRemoteToLocal(workDir, executor, maxNumFiles, maxNumBytes);
163 break;
164 }
165 } catch (InterruptedException e) {
166
167 Thread.currentThread().interrupt();
168 } catch (MirrorException e) {
169 throw e;
170 } catch (Exception e) {
171 throw new MirrorException(e);
172 }
173 }
174
175 protected abstract void mirrorLocalToRemote(
176 File workDir, int maxNumFiles, long maxNumBytes) throws Exception;
177
178 protected abstract void mirrorRemoteToLocal(
179 File workDir, CommandExecutor executor, int maxNumFiles, long maxNumBytes) throws Exception;
180
181 @Override
182 public String toString() {
183 final ToStringHelper helper = MoreObjects.toStringHelper("")
184 .omitNullValues()
185 .add("schedule", CronDescriptor.instance().describe(schedule))
186 .add("direction", direction)
187 .add("localProj", localRepo.parent().name())
188 .add("localRepo", localRepo.name())
189 .add("localPath", localPath)
190 .add("remoteRepo", remoteRepoUri)
191 .add("remotePath", remotePath)
192 .add("remoteBranch", remoteBranch)
193 .add("gitignore", gitignore)
194 .add("credential", credential);
195
196 return helper.toString();
197 }
198 }