blob: 17a949811794a131a3ddd771c92032f612d63236 [file] [log] [blame]
alshabibab984662014-12-04 18:56:18 -08001/*
2 * Copyright 2014 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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.net.intent.impl;
Brian O'Connor427a1762014-11-19 18:40:32 -080017
Ray Milkeye9a3e222014-12-03 16:46:06 -080018import java.util.Collection;
Sho SHIMIZUd7d18002015-01-21 14:37:14 -080019import java.util.Collections;
Ray Milkeye9a3e222014-12-03 16:46:06 -080020import java.util.List;
21import java.util.Map;
22import java.util.Set;
23import java.util.concurrent.CountDownLatch;
24import java.util.concurrent.TimeUnit;
25import java.util.concurrent.atomic.AtomicLong;
alshabiba9819bf2014-11-30 18:15:52 -080026
Brian O'Connor427a1762014-11-19 18:40:32 -080027import org.hamcrest.Description;
Brian O'Connor427a1762014-11-19 18:40:32 -080028import org.hamcrest.TypeSafeMatcher;
29import org.junit.After;
30import org.junit.Before;
31import org.junit.Test;
Brian O'Connorabafb502014-12-02 22:26:20 -080032import org.onosproject.TestApplicationId;
33import org.onosproject.core.ApplicationId;
34import org.onosproject.core.impl.TestCoreManager;
35import org.onosproject.event.impl.TestEventDispatcher;
36import org.onosproject.net.NetworkResource;
37import org.onosproject.net.flow.FlowRule;
38import org.onosproject.net.flow.FlowRuleBatchEntry;
39import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
40import org.onosproject.net.flow.FlowRuleBatchOperation;
41import org.onosproject.net.intent.Intent;
42import org.onosproject.net.intent.IntentCompiler;
43import org.onosproject.net.intent.IntentEvent;
44import org.onosproject.net.intent.IntentEvent.Type;
45import org.onosproject.net.intent.IntentExtensionService;
46import org.onosproject.net.intent.IntentId;
47import org.onosproject.net.intent.IntentInstaller;
48import org.onosproject.net.intent.IntentListener;
49import org.onosproject.net.intent.IntentService;
50import org.onosproject.net.intent.IntentState;
51import org.onosproject.net.intent.IntentTestsMocks;
52import org.onosproject.net.resource.LinkResourceAllocations;
53import org.onosproject.store.trivial.impl.SimpleIntentBatchQueue;
54import org.onosproject.store.trivial.impl.SimpleIntentStore;
Brian O'Connor427a1762014-11-19 18:40:32 -080055
Ray Milkeye9a3e222014-12-03 16:46:06 -080056import com.google.common.collect.HashMultimap;
57import com.google.common.collect.Lists;
58import com.google.common.collect.Maps;
59import com.google.common.collect.Multimap;
60import com.google.common.collect.Sets;
Brian O'Connor427a1762014-11-19 18:40:32 -080061
Ray Milkeye9a3e222014-12-03 16:46:06 -080062import static org.hamcrest.MatcherAssert.assertThat;
63import static org.hamcrest.Matchers.hasSize;
Brian O'Connor427a1762014-11-19 18:40:32 -080064import static org.junit.Assert.assertEquals;
Ray Milkeye9a3e222014-12-03 16:46:06 -080065import static org.junit.Assert.assertNotNull;
Brian O'Connor427a1762014-11-19 18:40:32 -080066import static org.junit.Assert.assertTrue;
Brian O'Connor427a1762014-11-19 18:40:32 -080067import static org.onlab.util.Tools.delay;
Ray Milkeye9a3e222014-12-03 16:46:06 -080068import static org.onosproject.net.intent.IntentState.FAILED;
69import static org.onosproject.net.intent.IntentState.INSTALLED;
70import static org.onosproject.net.intent.IntentState.WITHDRAWN;
Brian O'Connor427a1762014-11-19 18:40:32 -080071
72/**
73 * Test intent manager and transitions.
74 *
75 * TODO implement the following tests:
76 * - {submit, withdraw, update, replace} intent
Sho SHIMIZU34660962015-01-22 17:58:44 -080077 * - {submit, update, recompiling} intent with failed compilation
Brian O'Connor427a1762014-11-19 18:40:32 -080078 * - failed reservation
79 * - push timeout recovery
80 * - failed items recovery
81 *
82 * in general, verify intents store, flow store, and work queue
83 */
84public class IntentManagerTest {
85
86 private static final ApplicationId APPID = new TestApplicationId("manager-test");
87
88 private IntentManager manager;
89 private MockFlowRuleService flowRuleService;
90
91 protected IntentService service;
92 protected IntentExtensionService extensionService;
93 protected TestListener listener = new TestListener();
94 protected TestIntentCompiler compiler = new TestIntentCompiler();
95 protected TestIntentInstaller installer = new TestIntentInstaller();
96
Ray Milkeye9a3e222014-12-03 16:46:06 -080097 private static class TestListener implements IntentListener {
98 final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create();
99 Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap();
100
101 @Override
102 public void event(IntentEvent event) {
103 events.put(event.type(), event);
104 if (latchMap.containsKey(event.type())) {
105 latchMap.get(event.type()).countDown();
106 }
107 }
108
109 public int getCounts(IntentEvent.Type type) {
110 return events.get(type).size();
111 }
112
113 public void setLatch(int count, IntentEvent.Type type) {
114 latchMap.put(type, new CountDownLatch(count));
115 }
116
117 public void await(IntentEvent.Type type) {
118 try {
119 assertTrue("Timed out waiting for: " + type,
120 latchMap.get(type).await(5, TimeUnit.SECONDS));
121 } catch (InterruptedException e) {
122 e.printStackTrace();
123 }
124 }
125 }
126
127 private static class TestIntentTracker implements ObjectiveTrackerService {
128 private TopologyChangeDelegate delegate;
129 @Override
130 public void setDelegate(TopologyChangeDelegate delegate) {
131 this.delegate = delegate;
132 }
133
134 @Override
135 public void unsetDelegate(TopologyChangeDelegate delegate) {
136 if (delegate.equals(this.delegate)) {
137 this.delegate = null;
138 }
139 }
140
141 @Override
142 public void addTrackedResources(IntentId intentId, Collection<NetworkResource> resources) {
143 //TODO
144 }
145
146 @Override
147 public void removeTrackedResources(IntentId intentId, Collection<NetworkResource> resources) {
148 //TODO
149 }
150 }
151
152 private static class MockIntent extends Intent {
153 private static AtomicLong counter = new AtomicLong(0);
154
155 private final Long number;
156 // Nothing new here
157 public MockIntent(Long number) {
Sho SHIMIZUd7d18002015-01-21 14:37:14 -0800158 super(APPID, Collections.emptyList());
Ray Milkeye9a3e222014-12-03 16:46:06 -0800159 this.number = number;
160 }
161
162 public Long number() {
163 return number;
164 }
165
166 public static Long nextId() {
167 return counter.getAndIncrement();
168 }
169 }
170
171 private static class MockInstallableIntent extends MockIntent {
172 public MockInstallableIntent(Long number) {
173 super(number);
174 }
175
176 @Override
177 public boolean isInstallable() {
178 return true;
179 }
180 }
181
182 private static class TestIntentCompiler implements IntentCompiler<MockIntent> {
183 @Override
184 public List<Intent> compile(MockIntent intent, List<Intent> installable,
185 Set<LinkResourceAllocations> resources) {
186 return Lists.newArrayList(new MockInstallableIntent(intent.number()));
187 }
188 }
189
190 private static class TestIntentCompilerError implements IntentCompiler<MockIntent> {
191 @Override
192 public List<Intent> compile(MockIntent intent, List<Intent> installable,
193 Set<LinkResourceAllocations> resources) {
194 throw new IntentCompilationException("Compilation always fails");
195 }
196 }
197
198 private static class TestIntentInstaller implements IntentInstaller<MockInstallableIntent> {
199 @Override
200 public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) {
201 FlowRule fr = new IntentTestsMocks.MockFlowRule(intent.number().intValue());
202 List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
203 rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, fr));
204 return Lists.newArrayList(new FlowRuleBatchOperation(rules));
205 }
206
207 @Override
208 public List<FlowRuleBatchOperation> uninstall(MockInstallableIntent intent) {
209 FlowRule fr = new IntentTestsMocks.MockFlowRule(intent.number().intValue());
210 List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
211 rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, fr));
212 return Lists.newArrayList(new FlowRuleBatchOperation(rules));
213 }
214
215 @Override
216 public List<FlowRuleBatchOperation> replace(MockInstallableIntent oldIntent, MockInstallableIntent newIntent) {
217 FlowRule fr = new IntentTestsMocks.MockFlowRule(oldIntent.number().intValue());
218 FlowRule fr2 = new IntentTestsMocks.MockFlowRule(newIntent.number().intValue());
219 List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
220 rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, fr));
221 rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, fr2));
222 return Lists.newArrayList(new FlowRuleBatchOperation(rules));
223 }
224 }
225
226 private static class TestIntentErrorInstaller implements IntentInstaller<MockInstallableIntent> {
227 @Override
228 public List<FlowRuleBatchOperation> install(MockInstallableIntent intent) {
229 throw new IntentInstallationException("install() always fails");
230 }
231
232 @Override
233 public List<FlowRuleBatchOperation> uninstall(MockInstallableIntent intent) {
234 throw new IntentRemovalException("uninstall() always fails");
235 }
236
237 @Override
238 public List<FlowRuleBatchOperation> replace(MockInstallableIntent oldIntent, MockInstallableIntent newIntent) {
239 throw new IntentInstallationException("replace() always fails");
240 }
241 }
242
243 /**
244 * Hamcrest matcher to check that a conllection of Intents contains an
245 * Intent with the specified Intent Id.
246 */
247 public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> {
248 private final IntentId id;
249
250 public EntryForIntentMatcher(IntentId idValue) {
251 id = idValue;
252 }
253
254 @Override
255 public boolean matchesSafely(Collection<Intent> intents) {
256 for (Intent intent : intents) {
257 if (intent.id().equals(id)) {
258 return true;
259 }
260 }
261 return false;
262 }
263
264 @Override
265 public void describeTo(Description description) {
266 description.appendText("an intent with id \" ").
267 appendText(id.toString()).
268 appendText("\"");
269 }
270 }
271
272 private static EntryForIntentMatcher hasIntentWithId(IntentId id) {
273 return new EntryForIntentMatcher(id);
274 }
275
Brian O'Connor427a1762014-11-19 18:40:32 -0800276 @Before
277 public void setUp() {
278 manager = new IntentManager();
279 flowRuleService = new MockFlowRuleService();
280 manager.store = new SimpleIntentStore();
281 manager.batchService = new SimpleIntentBatchQueue();
282 manager.eventDispatcher = new TestEventDispatcher();
283 manager.trackerService = new TestIntentTracker();
284 manager.flowRuleService = flowRuleService;
Brian O'Connor520c0522014-11-23 23:50:47 -0800285 manager.coreService = new TestCoreManager();
Brian O'Connor427a1762014-11-19 18:40:32 -0800286 service = manager;
287 extensionService = manager;
288
289 manager.activate();
290 service.addListener(listener);
291 extensionService.registerCompiler(MockIntent.class, compiler);
292 extensionService.registerInstaller(MockInstallableIntent.class, installer);
293
294 assertTrue("store should be empty",
295 Sets.newHashSet(service.getIntents()).isEmpty());
296 assertEquals(0L, flowRuleService.getFlowRuleCount());
297 }
298
299 @After
300 public void tearDown() {
301 // verify that all intents are parked and the batch operation is unblocked
302 Set<IntentState> parked = Sets.newHashSet(INSTALLED, WITHDRAWN, FAILED);
303 for (Intent i : service.getIntents()) {
304 IntentState state = service.getIntentState(i.id());
305 assertTrue("Intent " + i.id() + " is in invalid state " + state,
306 parked.contains(state));
307 }
308 //the batch has not yet been removed when we receive the last event
309 // FIXME: this doesn't guarantee to avoid the race
310 for (int tries = 0; tries < 10; tries++) {
Brian O'Connor86f6f7f2014-12-01 17:02:45 -0800311 if (manager.batchService.getPendingOperations().isEmpty()) {
Brian O'Connor427a1762014-11-19 18:40:32 -0800312 break;
313 }
314 delay(10);
315 }
316 assertTrue("There are still pending batch operations.",
317 manager.batchService.getPendingOperations().isEmpty());
Brian O'Connor427a1762014-11-19 18:40:32 -0800318
319 extensionService.unregisterCompiler(MockIntent.class);
320 extensionService.unregisterInstaller(MockInstallableIntent.class);
321 service.removeListener(listener);
322 manager.deactivate();
323 // TODO null the other refs?
324 }
325
326 @Test
327 public void submitIntent() {
328 flowRuleService.setFuture(true);
329
Brian O'Connor7a71d5d2014-12-02 00:12:27 -0800330 listener.setLatch(1, Type.INSTALL_REQ);
Brian O'Connor427a1762014-11-19 18:40:32 -0800331 listener.setLatch(1, Type.INSTALLED);
332 Intent intent = new MockIntent(MockIntent.nextId());
333 service.submit(intent);
Brian O'Connor7a71d5d2014-12-02 00:12:27 -0800334 listener.await(Type.INSTALL_REQ);
Brian O'Connor427a1762014-11-19 18:40:32 -0800335 listener.await(Type.INSTALLED);
336 assertEquals(1L, service.getIntentCount());
337 assertEquals(1L, flowRuleService.getFlowRuleCount());
338 }
339
340 @Test
341 public void withdrawIntent() {
342 flowRuleService.setFuture(true);
343
344 listener.setLatch(1, Type.INSTALLED);
345 Intent intent = new MockIntent(MockIntent.nextId());
346 service.submit(intent);
347 listener.await(Type.INSTALLED);
348 assertEquals(1L, service.getIntentCount());
349 assertEquals(1L, flowRuleService.getFlowRuleCount());
350
351 listener.setLatch(1, Type.WITHDRAWN);
352 service.withdraw(intent);
353 listener.await(Type.WITHDRAWN);
Thomas Vachuskae4b6bb22014-11-25 17:09:43 -0800354 delay(10); //FIXME this is a race
355 assertEquals(0L, service.getIntentCount());
Brian O'Connor427a1762014-11-19 18:40:32 -0800356 assertEquals(0L, flowRuleService.getFlowRuleCount());
357 }
358
359 @Test
360 public void stressSubmitWithdraw() {
361 flowRuleService.setFuture(true);
362
363 int count = 500;
364
365 listener.setLatch(count, Type.INSTALLED);
366 listener.setLatch(count, Type.WITHDRAWN);
367
368 Intent intent = new MockIntent(MockIntent.nextId());
369 for (int i = 0; i < count; i++) {
370 service.submit(intent);
371 service.withdraw(intent);
372 }
373
374 listener.await(Type.INSTALLED);
375 listener.await(Type.WITHDRAWN);
Thomas Vachuskae4b6bb22014-11-25 17:09:43 -0800376 delay(10); //FIXME this is a race
377 assertEquals(0L, service.getIntentCount());
Brian O'Connor427a1762014-11-19 18:40:32 -0800378 assertEquals(0L, flowRuleService.getFlowRuleCount());
379 }
380
381 @Test
382 public void replaceIntent() {
383 flowRuleService.setFuture(true);
384
385 MockIntent intent = new MockIntent(MockIntent.nextId());
386 listener.setLatch(1, Type.INSTALLED);
387 service.submit(intent);
388 listener.await(Type.INSTALLED);
389 assertEquals(1L, service.getIntentCount());
390 assertEquals(1L, manager.flowRuleService.getFlowRuleCount());
391
392 MockIntent intent2 = new MockIntent(MockIntent.nextId());
393 listener.setLatch(1, Type.WITHDRAWN);
Brian O'Connor7a71d5d2014-12-02 00:12:27 -0800394 listener.setLatch(1, Type.INSTALL_REQ);
Brian O'Connor427a1762014-11-19 18:40:32 -0800395 listener.setLatch(1, Type.INSTALLED);
396 service.replace(intent.id(), intent2);
397 listener.await(Type.WITHDRAWN);
398 listener.await(Type.INSTALLED);
Thomas Vachuskae4b6bb22014-11-25 17:09:43 -0800399 delay(10); //FIXME this is a race
400 assertEquals(1L, service.getIntentCount());
Brian O'Connor427a1762014-11-19 18:40:32 -0800401 assertEquals(1L, manager.flowRuleService.getFlowRuleCount());
402 assertEquals(intent2.number().intValue(),
403 flowRuleService.flows.iterator().next().priority());
404 }
405
Ray Milkey93508c22014-12-02 11:35:56 -0800406 /**
407 * Tests for proper behavior of installation of an intent that triggers
408 * a compilation error.
409 */
410 @Test
411 public void errorIntentCompile() {
412 final TestIntentCompilerError errorCompiler = new TestIntentCompilerError();
413 extensionService.registerCompiler(MockIntent.class, errorCompiler);
414 MockIntent intent = new MockIntent(MockIntent.nextId());
415 listener.setLatch(1, Type.INSTALL_REQ);
416 listener.setLatch(1, Type.FAILED);
417 service.submit(intent);
418 listener.await(Type.INSTALL_REQ);
419 listener.await(Type.FAILED);
420 }
421
422 /**
423 * Tests handling a future that contains an error as a result of
424 * installing an intent.
425 */
426 @Test
427 public void errorIntentInstallFromFlows() {
428 final Long id = MockIntent.nextId();
429 flowRuleService.setFuture(false, 1);
430 MockIntent intent = new MockIntent(id);
431 listener.setLatch(1, Type.FAILED);
432 listener.setLatch(1, Type.INSTALL_REQ);
433 service.submit(intent);
434 listener.await(Type.INSTALL_REQ);
435 delay(10); // need to make sure we have some failed futures returned first
436 flowRuleService.setFuture(true, 0);
437 listener.await(Type.FAILED);
438 }
439
440 /**
441 * Tests handling of an error that is generated by the intent installer.
442 */
443 @Test
444 public void errorIntentInstallFromInstaller() {
445 final TestIntentErrorInstaller errorInstaller = new TestIntentErrorInstaller();
446 extensionService.registerInstaller(MockInstallableIntent.class, errorInstaller);
447 MockIntent intent = new MockIntent(MockIntent.nextId());
448 listener.setLatch(1, Type.INSTALL_REQ);
449 listener.setLatch(1, Type.FAILED);
450 service.submit(intent);
451 listener.await(Type.INSTALL_REQ);
452 listener.await(Type.FAILED);
Ray Milkey93508c22014-12-02 11:35:56 -0800453 }
454
Brian O'Connor427a1762014-11-19 18:40:32 -0800455 /**
Ray Milkeye9a3e222014-12-03 16:46:06 -0800456 * Tests handling a future that contains an unresolvable error as a result of
457 * installing an intent.
Brian O'Connor427a1762014-11-19 18:40:32 -0800458 */
Ray Milkeye9a3e222014-12-03 16:46:06 -0800459 @Test
460 public void errorIntentInstallNeverTrue() {
461 final Long id = MockIntent.nextId();
462 flowRuleService.setFuture(false, 1);
463 MockIntent intent = new MockIntent(id);
464 listener.setLatch(1, Type.WITHDRAWN);
465 listener.setLatch(1, Type.INSTALL_REQ);
466 service.submit(intent);
467 listener.await(Type.INSTALL_REQ);
468 // The delay here forces the retry loop in the intent manager to time out
469 delay(100);
470 flowRuleService.setFuture(false, 1);
471 service.withdraw(intent);
472 listener.await(Type.WITHDRAWN);
473 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800474
Ray Milkeye9a3e222014-12-03 16:46:06 -0800475 /**
476 * Tests that a compiler for a subclass of an intent that already has a
477 * compiler is automatically added.
478 */
479 @Test
480 public void intentSubclassCompile() {
481 class MockIntentSubclass extends MockIntent {
482 public MockIntentSubclass(Long number) {
483 super(number);
484 }
485 }
486 flowRuleService.setFuture(true);
487
488 listener.setLatch(1, Type.INSTALL_REQ);
489 listener.setLatch(1, Type.INSTALLED);
490 Intent intent = new MockIntentSubclass(MockIntent.nextId());
491 service.submit(intent);
492 listener.await(Type.INSTALL_REQ);
493 listener.await(Type.INSTALLED);
494 assertEquals(1L, service.getIntentCount());
495 assertEquals(1L, flowRuleService.getFlowRuleCount());
496
497 final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers =
498 extensionService.getCompilers();
499 assertEquals(2, compilers.size());
500 assertNotNull(compilers.get(MockIntentSubclass.class));
501 assertNotNull(compilers.get(MockIntent.class));
502 }
503
504 /**
505 * Tests an intent with no compiler.
506 */
507 @Test
508 public void intentWithoutCompiler() {
509 class IntentNoCompiler extends Intent {
510 IntentNoCompiler() {
Sho SHIMIZUd7d18002015-01-21 14:37:14 -0800511 super(APPID, Collections.emptyList());
Ray Milkeye9a3e222014-12-03 16:46:06 -0800512 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800513 }
514
Ray Milkeye9a3e222014-12-03 16:46:06 -0800515 Intent intent = new IntentNoCompiler();
516 listener.setLatch(1, Type.INSTALL_REQ);
517 listener.setLatch(1, Type.FAILED);
518 service.submit(intent);
519 listener.await(Type.INSTALL_REQ);
520 listener.await(Type.FAILED);
521 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800522
Ray Milkeye9a3e222014-12-03 16:46:06 -0800523 /**
524 * Tests an intent with no installer.
525 */
526 @Test
527 public void intentWithoutInstaller() {
528
529 extensionService.unregisterInstaller(MockInstallableIntent.class);
530
531 MockIntent intent = new MockIntent(MockIntent.nextId());
532 listener.setLatch(1, Type.INSTALL_REQ);
533 listener.setLatch(1, Type.FAILED);
534 service.submit(intent);
535 listener.await(Type.INSTALL_REQ);
536 listener.await(Type.FAILED);
537 }
538
539 /**
540 * Tests that the intent fetching methods are correct.
541 */
542 @Test
543 public void testIntentFetching() {
544 List<Intent> intents;
545
546 flowRuleService.setFuture(true);
547
548 intents = Lists.newArrayList(service.getIntents());
549 assertThat(intents, hasSize(0));
550
551 final MockIntent intent1 = new MockIntent(MockIntent.nextId());
552 final MockIntent intent2 = new MockIntent(MockIntent.nextId());
553
554 listener.setLatch(2, Type.INSTALL_REQ);
555 listener.setLatch(2, Type.INSTALLED);
556 service.submit(intent1);
557 service.submit(intent2);
558 listener.await(Type.INSTALL_REQ);
559 listener.await(Type.INSTALL_REQ);
560 listener.await(Type.INSTALLED);
561 listener.await(Type.INSTALLED);
562
563 intents = Lists.newArrayList(service.getIntents());
564 assertThat(intents, hasSize(2));
565
566 assertThat(intents, hasIntentWithId(intent1.id()));
567 assertThat(intents, hasIntentWithId(intent2.id()));
Brian O'Connor427a1762014-11-19 18:40:32 -0800568 }
569}