Committed by
Gerrit Code Review
Adding ability to balance load between different cell servers.
Adding ability to specify structure/size of the cell. Change-Id: I5e87c99fe8812ba0a974d7815ab8ddc64193a608
Showing
8 changed files
with
266 additions
and
36 deletions
... | @@ -113,7 +113,10 @@ function cell { | ... | @@ -113,7 +113,10 @@ function cell { |
113 | case "$cell" in | 113 | case "$cell" in |
114 | "borrow") | 114 | "borrow") |
115 | aux="/tmp/cell-$$" | 115 | aux="/tmp/cell-$$" |
116 | - curl -sS -X POST "http://$CELL_WARDEN:4321/?duration=${2:-0}&user=${3:-$(id -un)}" \ | 116 | + spec=${3:-3+1} |
117 | + spec=${spec/+/%2B} | ||
118 | + user=${4:-$(id -un)} | ||
119 | + curl -sS -X POST "http://$CELL_WARDEN:4321/?duration=${2:-0}&spec=${spec}&user=${user}" \ | ||
117 | -d "$(cat ~/.ssh/id_rsa.pub)" > $aux | 120 | -d "$(cat ~/.ssh/id_rsa.pub)" > $aux |
118 | . $aux | 121 | . $aux |
119 | rm -f $aux | 122 | rm -f $aux | ... | ... |
... | @@ -3,15 +3,23 @@ | ... | @@ -3,15 +3,23 @@ |
3 | 3 | ||
4 | name="$1" | 4 | name="$1" |
5 | ipx="$2" | 5 | ipx="$2" |
6 | -shift 2 | 6 | +spec="$3" |
7 | +shift 3 | ||
7 | key="$@" | 8 | key="$@" |
8 | 9 | ||
9 | cd $(dirname $0) | 10 | cd $(dirname $0) |
10 | 11 | ||
12 | +nodes=${spec%+*} | ||
13 | +mininet=${spec#*+} | ||
14 | + | ||
11 | sudo lxc-attach -n bit-proxy -- bash -c "grep -qF \"$key\" /home/sdn/.ssh/authorized_keys || echo $key >> /home/sdn/.ssh/authorized_keys" | 15 | sudo lxc-attach -n bit-proxy -- bash -c "grep -qF \"$key\" /home/sdn/.ssh/authorized_keys || echo $key >> /home/sdn/.ssh/authorized_keys" |
12 | 16 | ||
13 | -./clone-node base-mininet ${ipx/x/0} $name-n "$key" | 17 | +if [ $mininet -ge 1 ]; then |
18 | + ./clone-node base-mininet ${ipx/x/0} $name-n "$key" | ||
19 | +fi | ||
14 | 20 | ||
15 | -for n in {1..3}; do | 21 | +let n=1 |
22 | +while [ $n -le $nodes ]; do | ||
16 | ./clone-node base-onos ${ipx/x/$n} $name-$n "$key" | 23 | ./clone-node base-onos ${ipx/x/$n} $name-$n "$key" |
24 | + let n=n+1 | ||
17 | done | 25 | done | ... | ... |
... | @@ -2,11 +2,19 @@ | ... | @@ -2,11 +2,19 @@ |
2 | # Destroys an LXC cell with the specified name. | 2 | # Destroys an LXC cell with the specified name. |
3 | 3 | ||
4 | name=$1 | 4 | name=$1 |
5 | +spec=$2 | ||
6 | + | ||
7 | +nodes=${spec%+*} | ||
8 | +mininet=${spec#*+} | ||
5 | 9 | ||
6 | cd $(dirname $0) | 10 | cd $(dirname $0) |
7 | 11 | ||
8 | -./destroy-node $name-n | 12 | +if [ $mininet -ge 1 ]; then |
13 | + ./destroy-node $name-n | ||
14 | +fi | ||
9 | 15 | ||
10 | -for n in {1..3}; do | 16 | +let n=1 |
17 | +while [ $n -le $nodes ]; do | ||
11 | ./destroy-node $name-$n | 18 | ./destroy-node $name-$n |
19 | + let n=n+1 | ||
12 | done | 20 | done | ... | ... |
... | @@ -42,6 +42,23 @@ | ... | @@ -42,6 +42,23 @@ |
42 | <artifactId>jetty-servlet</artifactId> | 42 | <artifactId>jetty-servlet</artifactId> |
43 | <version>8.1.18.v20150929</version> | 43 | <version>8.1.18.v20150929</version> |
44 | </dependency> | 44 | </dependency> |
45 | + <dependency> | ||
46 | + <groupId>junit</groupId> | ||
47 | + <artifactId>junit</artifactId> | ||
48 | + <version>4.12</version> | ||
49 | + <scope>test</scope> | ||
50 | + </dependency> | ||
51 | + <dependency> | ||
52 | + <groupId>org.onosproject</groupId> | ||
53 | + <artifactId>onlab-misc</artifactId> | ||
54 | + <version>${project.version}</version> | ||
55 | + <scope>test</scope> | ||
56 | + </dependency> | ||
57 | + <dependency> | ||
58 | + <groupId>org.onosproject</groupId> | ||
59 | + <artifactId>onlab-junit</artifactId> | ||
60 | + <scope>test</scope> | ||
61 | + </dependency> | ||
45 | </dependencies> | 62 | </dependencies> |
46 | 63 | ||
47 | <build> | 64 | <build> |
... | @@ -77,6 +94,11 @@ | ... | @@ -77,6 +94,11 @@ |
77 | </execution> | 94 | </execution> |
78 | </executions> | 95 | </executions> |
79 | </plugin> | 96 | </plugin> |
97 | + | ||
98 | + <plugin> | ||
99 | + <groupId>org.apache.maven.plugins</groupId> | ||
100 | + <artifactId>maven-surefire-plugin</artifactId> | ||
101 | + </plugin> | ||
80 | </plugins> | 102 | </plugins> |
81 | </build> | 103 | </build> |
82 | 104 | ... | ... |
... | @@ -27,13 +27,15 @@ final class Reservation { | ... | @@ -27,13 +27,15 @@ final class Reservation { |
27 | final String userName; | 27 | final String userName; |
28 | final long time; | 28 | final long time; |
29 | final int duration; | 29 | final int duration; |
30 | + final String cellSpec; | ||
30 | 31 | ||
31 | // Creates a new reservation record | 32 | // Creates a new reservation record |
32 | - Reservation(String cellName, String userName, long time, int duration) { | 33 | + Reservation(String cellName, String userName, long time, int duration, String cellSpec) { |
33 | this.cellName = cellName; | 34 | this.cellName = cellName; |
34 | this.userName = userName; | 35 | this.userName = userName; |
35 | this.time = time; | 36 | this.time = time; |
36 | this.duration = duration; | 37 | this.duration = duration; |
38 | + this.cellSpec = cellSpec; | ||
37 | } | 39 | } |
38 | 40 | ||
39 | /** | 41 | /** |
... | @@ -43,11 +45,12 @@ final class Reservation { | ... | @@ -43,11 +45,12 @@ final class Reservation { |
43 | */ | 45 | */ |
44 | Reservation(String line) { | 46 | Reservation(String line) { |
45 | String[] fields = line.trim().split("\t"); | 47 | String[] fields = line.trim().split("\t"); |
46 | - checkState(fields.length == 4, "Incorrect reservation encoding"); | 48 | + checkState(fields.length == 5, "Incorrect reservation encoding"); |
47 | this.cellName = fields[0]; | 49 | this.cellName = fields[0]; |
48 | this.userName = fields[1]; | 50 | this.userName = fields[1]; |
49 | this.time = Long.parseLong(fields[2]); | 51 | this.time = Long.parseLong(fields[2]); |
50 | this.duration = Integer.parseInt(fields[3]); | 52 | this.duration = Integer.parseInt(fields[3]); |
53 | + this.cellSpec = fields[4]; | ||
51 | } | 54 | } |
52 | 55 | ||
53 | /** | 56 | /** |
... | @@ -56,7 +59,7 @@ final class Reservation { | ... | @@ -56,7 +59,7 @@ final class Reservation { |
56 | * @return encoded string | 59 | * @return encoded string |
57 | */ | 60 | */ |
58 | String encode() { | 61 | String encode() { |
59 | - return String.format("%s\t%s\t%s\t%s\n", cellName, userName, time, duration); | 62 | + return String.format("%s\t%s\t%s\t%s\t%s\n", cellName, userName, time, duration, cellSpec); |
60 | } | 63 | } |
61 | 64 | ||
62 | } | 65 | } | ... | ... |
... | @@ -16,8 +16,9 @@ | ... | @@ -16,8 +16,9 @@ |
16 | 16 | ||
17 | package org.onlab.warden; | 17 | package org.onlab.warden; |
18 | 18 | ||
19 | -import com.google.common.collect.ImmutableList; | ||
20 | import com.google.common.collect.ImmutableSet; | 19 | import com.google.common.collect.ImmutableSet; |
20 | +import com.google.common.collect.Lists; | ||
21 | +import com.google.common.collect.Maps; | ||
21 | import com.google.common.io.ByteStreams; | 22 | import com.google.common.io.ByteStreams; |
22 | 23 | ||
23 | import java.io.File; | 24 | import java.io.File; |
... | @@ -27,8 +28,11 @@ import java.io.IOException; | ... | @@ -27,8 +28,11 @@ import java.io.IOException; |
27 | import java.io.InputStream; | 28 | import java.io.InputStream; |
28 | import java.io.PrintWriter; | 29 | import java.io.PrintWriter; |
29 | import java.text.SimpleDateFormat; | 30 | import java.text.SimpleDateFormat; |
31 | +import java.util.ArrayList; | ||
30 | import java.util.Date; | 32 | import java.util.Date; |
31 | import java.util.HashSet; | 33 | import java.util.HashSet; |
34 | +import java.util.List; | ||
35 | +import java.util.Map; | ||
32 | import java.util.Random; | 36 | import java.util.Random; |
33 | import java.util.Set; | 37 | import java.util.Set; |
34 | import java.util.Timer; | 38 | import java.util.Timer; |
... | @@ -52,9 +56,15 @@ class Warden { | ... | @@ -52,9 +56,15 @@ class Warden { |
52 | private static final int MINUTE = 60_000; // 1 minute | 56 | private static final int MINUTE = 60_000; // 1 minute |
53 | private static final int DEFAULT_MINUTES = 60; | 57 | private static final int DEFAULT_MINUTES = 60; |
54 | 58 | ||
59 | + private static final String DEFAULT_SPEC = "3+1"; | ||
60 | + | ||
55 | private final File log = new File("warden.log"); | 61 | private final File log = new File("warden.log"); |
56 | 62 | ||
57 | - private final File cells = new File("cells"); | 63 | + // Allow overriding these for unit tests. |
64 | + static String cmdPrefix = ""; | ||
65 | + static File root = new File("."); | ||
66 | + | ||
67 | + private final File cells = new File(root, "cells"); | ||
58 | private final File supported = new File(cells, "supported"); | 68 | private final File supported = new File(cells, "supported"); |
59 | private final File reserved = new File(cells, "reserved"); | 69 | private final File reserved = new File(cells, "reserved"); |
60 | 70 | ||
... | @@ -64,6 +74,7 @@ class Warden { | ... | @@ -64,6 +74,7 @@ class Warden { |
64 | * Creates a new cell warden. | 74 | * Creates a new cell warden. |
65 | */ | 75 | */ |
66 | Warden() { | 76 | Warden() { |
77 | + reserved.mkdirs(); | ||
67 | random.setSeed(System.currentTimeMillis()); | 78 | random.setSeed(System.currentTimeMillis()); |
68 | Timer timer = new Timer("cell-pruner", true); | 79 | Timer timer = new Timer("cell-pruner", true); |
69 | timer.schedule(new Reposessor(), MINUTE / 4, MINUTE / 2); | 80 | timer.schedule(new Reposessor(), MINUTE / 4, MINUTE / 2); |
... | @@ -84,7 +95,7 @@ class Warden { | ... | @@ -84,7 +95,7 @@ class Warden { |
84 | * | 95 | * |
85 | * @return list of cell names | 96 | * @return list of cell names |
86 | */ | 97 | */ |
87 | - private Set<String> getAvailableCells() { | 98 | + Set<String> getAvailableCells() { |
88 | Set<String> available = new HashSet<>(getCells()); | 99 | Set<String> available = new HashSet<>(getCells()); |
89 | available.removeAll(getReservedCells()); | 100 | available.removeAll(getReservedCells()); |
90 | return ImmutableSet.copyOf(available); | 101 | return ImmutableSet.copyOf(available); |
... | @@ -95,18 +106,28 @@ class Warden { | ... | @@ -95,18 +106,28 @@ class Warden { |
95 | * | 106 | * |
96 | * @return list of cell names | 107 | * @return list of cell names |
97 | */ | 108 | */ |
98 | - private Set<String> getReservedCells() { | 109 | + Set<String> getReservedCells() { |
99 | String[] list = reserved.list(); | 110 | String[] list = reserved.list(); |
100 | return list != null ? ImmutableSet.copyOf(list) : ImmutableSet.of(); | 111 | return list != null ? ImmutableSet.copyOf(list) : ImmutableSet.of(); |
101 | } | 112 | } |
102 | 113 | ||
103 | /** | 114 | /** |
115 | + * Returns the host name on which the specified cell is hosted. | ||
116 | + * | ||
117 | + * @param cellName cell name | ||
118 | + * @return host name where the cell runs | ||
119 | + */ | ||
120 | + String getCellHost(String cellName) { | ||
121 | + return getCellInfo(cellName).hostName; | ||
122 | + } | ||
123 | + | ||
124 | + /** | ||
104 | * Returns reservation for the specified user. | 125 | * Returns reservation for the specified user. |
105 | * | 126 | * |
106 | * @param userName user name | 127 | * @param userName user name |
107 | * @return cell reservation record or null if user does not have one | 128 | * @return cell reservation record or null if user does not have one |
108 | */ | 129 | */ |
109 | - private Reservation currentUserReservation(String userName) { | 130 | + Reservation currentUserReservation(String userName) { |
110 | checkNotNull(userName, USER_NOT_NULL); | 131 | checkNotNull(userName, USER_NOT_NULL); |
111 | for (String cellName : getReservedCells()) { | 132 | for (String cellName : getReservedCells()) { |
112 | Reservation reservation = currentCellReservation(cellName); | 133 | Reservation reservation = currentCellReservation(cellName); |
... | @@ -141,35 +162,66 @@ class Warden { | ... | @@ -141,35 +162,66 @@ class Warden { |
141 | * | 162 | * |
142 | * @param userName user name | 163 | * @param userName user name |
143 | * @param sshKey user ssh public key | 164 | * @param sshKey user ssh public key |
144 | - * @param minutes number of minutes for reservation | 165 | + * @param minutes optional number of minutes for reservation |
166 | + * @param cellSpec optional cell specification string | ||
145 | * @return reserved cell definition | 167 | * @return reserved cell definition |
146 | */ | 168 | */ |
147 | - synchronized String borrowCell(String userName, String sshKey, int minutes) { | 169 | + synchronized String borrowCell(String userName, String sshKey, int minutes, |
170 | + String cellSpec) { | ||
148 | checkNotNull(userName, USER_NOT_NULL); | 171 | checkNotNull(userName, USER_NOT_NULL); |
172 | + checkArgument(userName.matches("[\\w]+"), "Invalid user name %s", userName); | ||
149 | checkNotNull(sshKey, KEY_NOT_NULL); | 173 | checkNotNull(sshKey, KEY_NOT_NULL); |
150 | checkArgument(minutes < MAX_MINUTES, "Number of minutes must be less than %d", MAX_MINUTES); | 174 | checkArgument(minutes < MAX_MINUTES, "Number of minutes must be less than %d", MAX_MINUTES); |
151 | - long now = System.currentTimeMillis(); | 175 | + checkArgument(minutes >= 0, "Number of minutes must be non-negative"); |
176 | + checkArgument(cellSpec == null || cellSpec.matches("[\\d]+\\+[0-1]"), | ||
177 | + "Invalid cell spec string %s", cellSpec); | ||
152 | Reservation reservation = currentUserReservation(userName); | 178 | Reservation reservation = currentUserReservation(userName); |
153 | if (reservation == null) { | 179 | if (reservation == null) { |
154 | - checkArgument(minutes >= 0, "Number of minutes must be non-negative"); | 180 | + // If there is no reservation for the user, create one |
155 | - Set<String> cells = getAvailableCells(); | 181 | + String cellName = findAvailableCell(); |
156 | - checkState(!cells.isEmpty(), "No cells are presently available"); | 182 | + reservation = new Reservation(cellName, userName, System.currentTimeMillis(), |
157 | - String cellName = ImmutableList.copyOf(cells).get(random.nextInt(cells.size())); | 183 | + minutes == 0 ? DEFAULT_MINUTES : minutes, |
158 | - reservation = new Reservation(cellName, userName, now, minutes == 0 ? DEFAULT_MINUTES : minutes); | 184 | + cellSpec == null ? DEFAULT_SPEC : cellSpec); |
159 | } else if (minutes == 0) { | 185 | } else if (minutes == 0) { |
160 | // If minutes are 0, simply return the cell definition | 186 | // If minutes are 0, simply return the cell definition |
161 | return getCellDefinition(reservation.cellName); | 187 | return getCellDefinition(reservation.cellName); |
162 | } else { | 188 | } else { |
163 | - reservation = new Reservation(reservation.cellName, userName, now, minutes); | 189 | + // If minutes are > 0, update the existing cell reservation |
190 | + reservation = new Reservation(reservation.cellName, userName, | ||
191 | + System.currentTimeMillis(), minutes, | ||
192 | + reservation.cellSpec); | ||
164 | } | 193 | } |
165 | 194 | ||
166 | reserveCell(reservation); | 195 | reserveCell(reservation); |
167 | createCell(reservation, sshKey); | 196 | createCell(reservation, sshKey); |
168 | - log(userName, reservation.cellName, "borrowed for " + reservation.duration + " minutes"); | 197 | + log(userName, reservation.cellName, reservation.cellSpec, |
198 | + "borrowed for " + reservation.duration + " minutes"); | ||
169 | return getCellDefinition(reservation.cellName); | 199 | return getCellDefinition(reservation.cellName); |
170 | } | 200 | } |
171 | 201 | ||
172 | /** | 202 | /** |
203 | + * Returns name of an available cell. Cell is chosen based on the load | ||
204 | + * of its hosting server; a random one will be chosen from the set of | ||
205 | + * cells hosted by the least loaded server. | ||
206 | + * | ||
207 | + * @return name of an available cell | ||
208 | + */ | ||
209 | + private String findAvailableCell() { | ||
210 | + Set<String> cells = getAvailableCells(); | ||
211 | + checkState(!cells.isEmpty(), "No cells are presently available"); | ||
212 | + Map<String, ServerInfo> load = Maps.newHashMap(); | ||
213 | + | ||
214 | + cells.stream().map(this::getCellInfo) | ||
215 | + .forEach(info -> load.compute(info.hostName, (k, v) -> v == null ? | ||
216 | + new ServerInfo(info.hostName) : v.bumpLoad(info))); | ||
217 | + | ||
218 | + List<ServerInfo> servers = new ArrayList<>(load.values()); | ||
219 | + servers.sort((a, b) -> a.load - b.load); | ||
220 | + ServerInfo server = servers.get(0); | ||
221 | + return server.cells.get(random.nextInt(server.cells.size())).cellName; | ||
222 | + } | ||
223 | + | ||
224 | + /** | ||
173 | * Returns the specified cell for the specified user and their public access key. | 225 | * Returns the specified cell for the specified user and their public access key. |
174 | * | 226 | * |
175 | * @param userName user name | 227 | * @param userName user name |
... | @@ -181,7 +233,7 @@ class Warden { | ... | @@ -181,7 +233,7 @@ class Warden { |
181 | 233 | ||
182 | unreserveCell(reservation); | 234 | unreserveCell(reservation); |
183 | destroyCell(reservation); | 235 | destroyCell(reservation); |
184 | - log(userName, reservation.cellName, "returned"); | 236 | + log(userName, reservation.cellName, reservation.cellSpec, "returned"); |
185 | } | 237 | } |
186 | 238 | ||
187 | /** | 239 | /** |
... | @@ -229,9 +281,9 @@ class Warden { | ... | @@ -229,9 +281,9 @@ class Warden { |
229 | */ | 281 | */ |
230 | private void createCell(Reservation reservation, String sshKey) { | 282 | private void createCell(Reservation reservation, String sshKey) { |
231 | CellInfo cellInfo = getCellInfo(reservation.cellName); | 283 | CellInfo cellInfo = getCellInfo(reservation.cellName); |
232 | - String cmd = String.format("ssh %s warden/bin/create-cell %s %s %s", | 284 | + String cmd = String.format("ssh %s warden/bin/create-cell %s %s %s %s", |
233 | cellInfo.hostName, cellInfo.cellName, | 285 | cellInfo.hostName, cellInfo.cellName, |
234 | - cellInfo.ipPrefix, sshKey); | 286 | + cellInfo.ipPrefix, reservation.cellSpec, sshKey); |
235 | exec(cmd); | 287 | exec(cmd); |
236 | } | 288 | } |
237 | 289 | ||
... | @@ -242,8 +294,8 @@ class Warden { | ... | @@ -242,8 +294,8 @@ class Warden { |
242 | */ | 294 | */ |
243 | private void destroyCell(Reservation reservation) { | 295 | private void destroyCell(Reservation reservation) { |
244 | CellInfo cellInfo = getCellInfo(reservation.cellName); | 296 | CellInfo cellInfo = getCellInfo(reservation.cellName); |
245 | - exec(String.format("ssh %s warden/bin/destroy-cell %s", | 297 | + exec(String.format("ssh %s warden/bin/destroy-cell %s %s", |
246 | - cellInfo.hostName, cellInfo.cellName)); | 298 | + cellInfo.hostName, cellInfo.cellName, reservation.cellSpec)); |
247 | } | 299 | } |
248 | 300 | ||
249 | /** | 301 | /** |
... | @@ -265,7 +317,7 @@ class Warden { | ... | @@ -265,7 +317,7 @@ class Warden { |
265 | // Executes the specified command. | 317 | // Executes the specified command. |
266 | private String exec(String command) { | 318 | private String exec(String command) { |
267 | try { | 319 | try { |
268 | - Process process = Runtime.getRuntime().exec(command); | 320 | + Process process = Runtime.getRuntime().exec(cmdPrefix + command); |
269 | String output = new String(ByteStreams.toByteArray(process.getInputStream()), UTF_8); | 321 | String output = new String(ByteStreams.toByteArray(process.getInputStream()), UTF_8); |
270 | process.waitFor(TIMEOUT, TimeUnit.SECONDS); | 322 | process.waitFor(TIMEOUT, TimeUnit.SECONDS); |
271 | return process.exitValue() == 0 ? output : null; | 323 | return process.exitValue() == 0 ? output : null; |
... | @@ -275,12 +327,12 @@ class Warden { | ... | @@ -275,12 +327,12 @@ class Warden { |
275 | } | 327 | } |
276 | 328 | ||
277 | // Creates an audit log entry. | 329 | // Creates an audit log entry. |
278 | - private void log(String userName, String cellName, String action) { | 330 | + private void log(String userName, String cellName, String cellSpec, String action) { |
279 | try (FileOutputStream fos = new FileOutputStream(log, true); | 331 | try (FileOutputStream fos = new FileOutputStream(log, true); |
280 | PrintWriter pw = new PrintWriter(fos)) { | 332 | PrintWriter pw = new PrintWriter(fos)) { |
281 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | 333 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
282 | - pw.println(String.format("%s\t%s\t%s\t%s", format.format(new Date()), | 334 | + pw.println(String.format("%s\t%s\t%s-%s\t%s", format.format(new Date()), |
283 | - userName, cellName, action)); | 335 | + userName, cellName, cellSpec, action)); |
284 | pw.flush(); | 336 | pw.flush(); |
285 | } catch (IOException e) { | 337 | } catch (IOException e) { |
286 | throw new IllegalStateException("Unable to log reservation action", e); | 338 | throw new IllegalStateException("Unable to log reservation action", e); |
... | @@ -300,6 +352,23 @@ class Warden { | ... | @@ -300,6 +352,23 @@ class Warden { |
300 | } | 352 | } |
301 | } | 353 | } |
302 | 354 | ||
355 | + // Carrier of cell server information | ||
356 | + private final class ServerInfo { | ||
357 | + final String hostName; | ||
358 | + int load = 0; | ||
359 | + List<CellInfo> cells = Lists.newArrayList(); | ||
360 | + | ||
361 | + private ServerInfo(String hostName) { | ||
362 | + this.hostName = hostName; | ||
363 | + } | ||
364 | + | ||
365 | + private ServerInfo bumpLoad(CellInfo info) { | ||
366 | + cells.add(info); | ||
367 | + load++; // TODO: bump by cell size later | ||
368 | + return this; | ||
369 | + } | ||
370 | + } | ||
371 | + | ||
303 | // Task for re-possessing overdue cells | 372 | // Task for re-possessing overdue cells |
304 | private final class Reposessor extends TimerTask { | 373 | private final class Reposessor extends TimerTask { |
305 | @Override | 374 | @Override | ... | ... |
... | @@ -49,8 +49,8 @@ public class WardenServlet extends HttpServlet { | ... | @@ -49,8 +49,8 @@ public class WardenServlet extends HttpServlet { |
49 | if (reservation != null) { | 49 | if (reservation != null) { |
50 | long expiration = reservation.time + reservation.duration * 60_000; | 50 | long expiration = reservation.time + reservation.duration * 60_000; |
51 | long remaining = (expiration - System.currentTimeMillis()) / 60_000; | 51 | long remaining = (expiration - System.currentTimeMillis()) / 60_000; |
52 | - out.println(String.format("%-10s\t%-10s\t%s\t%s\t%s mins (%s remaining)", | 52 | + out.println(String.format("%-14s\t%-10s\t%s\t%s\t%s mins (%s remaining)", |
53 | - cellName, | 53 | + cellName + "-" + reservation.cellSpec, |
54 | reservation.userName, | 54 | reservation.userName, |
55 | fmt.format(new Date(reservation.time)), | 55 | fmt.format(new Date(reservation.time)), |
56 | fmt.format(new Date(expiration)), | 56 | fmt.format(new Date(expiration)), |
... | @@ -72,8 +72,9 @@ public class WardenServlet extends HttpServlet { | ... | @@ -72,8 +72,9 @@ public class WardenServlet extends HttpServlet { |
72 | String sshKey = new String(ByteStreams.toByteArray(req.getInputStream()), "UTF-8"); | 72 | String sshKey = new String(ByteStreams.toByteArray(req.getInputStream()), "UTF-8"); |
73 | String userName = req.getParameter("user"); | 73 | String userName = req.getParameter("user"); |
74 | String sd = req.getParameter("duration"); | 74 | String sd = req.getParameter("duration"); |
75 | + String spec = req.getParameter("spec"); | ||
75 | int duration = isNullOrEmpty(sd) ? 0 : Integer.parseInt(sd); | 76 | int duration = isNullOrEmpty(sd) ? 0 : Integer.parseInt(sd); |
76 | - String cellDefinition = warden.borrowCell(userName, sshKey, duration); | 77 | + String cellDefinition = warden.borrowCell(userName, sshKey, duration, spec); |
77 | out.println(cellDefinition); | 78 | out.println(cellDefinition); |
78 | } catch (Exception e) { | 79 | } catch (Exception e) { |
79 | resp.setStatus(Response.SC_INTERNAL_SERVER_ERROR); | 80 | resp.setStatus(Response.SC_INTERNAL_SERVER_ERROR); | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +package org.onlab.warden; | ||
18 | + | ||
19 | +import com.google.common.io.Files; | ||
20 | +import org.junit.After; | ||
21 | +import org.junit.Before; | ||
22 | +import org.junit.Test; | ||
23 | +import org.onlab.util.Tools; | ||
24 | + | ||
25 | +import java.io.File; | ||
26 | +import java.io.IOException; | ||
27 | +import java.util.Objects; | ||
28 | + | ||
29 | +import static org.junit.Assert.*; | ||
30 | + | ||
31 | +/** | ||
32 | + * Suite of tests for the cell warden. | ||
33 | + */ | ||
34 | +public class WardenTest { | ||
35 | + | ||
36 | + private Warden warden; | ||
37 | + private File cells; | ||
38 | + private File supportedCells; | ||
39 | + | ||
40 | + @Before | ||
41 | + public void setUp() throws IOException { | ||
42 | + // Setup warden to be tested | ||
43 | + Warden.root = Files.createTempDir(); | ||
44 | + Warden.cmdPrefix = "echo "; | ||
45 | + cells = new File(Warden.root, "cells"); | ||
46 | + supportedCells = new File(cells, "supported"); | ||
47 | + warden = new Warden(); | ||
48 | + | ||
49 | + // Setup test cell information | ||
50 | + createCell("alpha", "foo"); | ||
51 | + createCell("bravo", "foo"); | ||
52 | + createCell("charlie", "foo"); | ||
53 | + createCell("delta", "bar"); | ||
54 | + createCell("echo", "bar"); | ||
55 | + createCell("foxtrot", "bar"); | ||
56 | + | ||
57 | + new File("warden.log").deleteOnExit(); | ||
58 | + } | ||
59 | + | ||
60 | + private void createCell(String cellName, String hostName) throws IOException { | ||
61 | + File cellFile = new File(supportedCells, cellName); | ||
62 | + Files.createParentDirs(cellFile); | ||
63 | + Files.write((hostName + " " + cellName).getBytes(), cellFile); | ||
64 | + } | ||
65 | + | ||
66 | + @After | ||
67 | + public void tearDown() throws IOException { | ||
68 | + Tools.removeDirectory(Warden.root); | ||
69 | + } | ||
70 | + | ||
71 | + @Test | ||
72 | + public void basics() { | ||
73 | + assertEquals("incorrect number of cells", 6, warden.getCells().size()); | ||
74 | + validateSizes(6, 0); | ||
75 | + | ||
76 | + String cellDefinition = warden.borrowCell("dude", "the-key", 0, null); | ||
77 | + assertTrue("incorrect definition", cellDefinition.contains("cell-def")); | ||
78 | + validateSizes(5, 1); | ||
79 | + | ||
80 | + Reservation dudeCell = warden.currentUserReservation("dude"); | ||
81 | + validateCellState(dudeCell); | ||
82 | + | ||
83 | + warden.borrowCell("dolt", "a-key", 0, "4+1"); | ||
84 | + Reservation doltCell = warden.currentUserReservation("dolt"); | ||
85 | + validateCellState(doltCell); | ||
86 | + validateSizes(4, 2); | ||
87 | + | ||
88 | + assertTrue("cells should not be on the same host", | ||
89 | + Objects.equals(warden.getCellHost(dudeCell.cellName), | ||
90 | + warden.getCellHost(doltCell.cellName))); | ||
91 | + | ||
92 | + warden.returnCell("dude"); | ||
93 | + validateSizes(5, 1); | ||
94 | + | ||
95 | + warden.borrowCell("dolt", "a-key", 30, null); | ||
96 | + validateSizes(5, 1); | ||
97 | + | ||
98 | + warden.returnCell("dolt"); | ||
99 | + validateSizes(6, 0); | ||
100 | + } | ||
101 | + | ||
102 | + private void validateSizes(int available, int reserved) { | ||
103 | + assertEquals("incorrect number of available cells", available, | ||
104 | + warden.getAvailableCells().size()); | ||
105 | + assertEquals("incorrect number of reserved cells", reserved, | ||
106 | + warden.getReservedCells().size()); | ||
107 | + } | ||
108 | + | ||
109 | + private void validateCellState(Reservation reservation) { | ||
110 | + assertFalse("cell should not be available", | ||
111 | + warden.getAvailableCells().contains(reservation.cellName)); | ||
112 | + assertTrue("cell should be reserved", | ||
113 | + warden.getReservedCells().contains(reservation.cellName)); | ||
114 | + } | ||
115 | + | ||
116 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment