blob: 9ef1a1f0f2f29bd7f97992a7bdcc4a958364a229 [file] [log] [blame]
Jonathan Hartaa380972014-04-03 10:24:46 -07001package net.onrc.onos.core.intent.runtime;
Toshio Koide4f308732014-02-18 15:19:48 -08002
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.HashMap;
Toshio Koide798bc1b2014-02-20 14:02:40 -08006import java.util.HashSet;
Toshio Koidedf2eab92014-02-20 11:24:59 -08007import java.util.Iterator;
Toshio Koidebf875662014-02-24 12:19:15 -08008import java.util.LinkedList;
Toshio Koide4f308732014-02-18 15:19:48 -08009import java.util.Map;
Toshio Koide066506e2014-02-20 19:52:09 -080010import java.util.Map.Entry;
Toshio Koide93be5d62014-02-23 19:30:57 -080011import java.util.concurrent.locks.ReentrantLock;
Toshio Koide4f308732014-02-18 15:19:48 -080012
13import net.floodlightcontroller.core.module.FloodlightModuleContext;
14import net.floodlightcontroller.core.module.FloodlightModuleException;
15import net.floodlightcontroller.core.module.IFloodlightModule;
16import net.floodlightcontroller.core.module.IFloodlightService;
Jonathan Hart6df90172014-04-03 10:13:11 -070017import net.onrc.onos.core.datagrid.IDatagridService;
18import net.onrc.onos.core.datagrid.IEventChannel;
19import net.onrc.onos.core.datagrid.IEventChannelListener;
Jonathan Hartaa380972014-04-03 10:24:46 -070020import net.onrc.onos.core.intent.Intent;
Jonathan Harta99ec672014-04-03 11:30:34 -070021import net.onrc.onos.core.intent.Intent.IntentState;
Jonathan Hartaa380972014-04-03 10:24:46 -070022import net.onrc.onos.core.intent.IntentMap;
23import net.onrc.onos.core.intent.IntentOperation;
Jonathan Harta99ec672014-04-03 11:30:34 -070024import net.onrc.onos.core.intent.IntentOperation.Operator;
Jonathan Hartaa380972014-04-03 10:24:46 -070025import net.onrc.onos.core.intent.IntentOperationList;
26import net.onrc.onos.core.intent.PathIntent;
27import net.onrc.onos.core.intent.PathIntentMap;
28import net.onrc.onos.core.intent.ShortestPathIntent;
Jonathan Hartdeda0ba2014-04-03 11:14:12 -070029import net.onrc.onos.core.registry.IControllerRegistryService;
Jonathan Hart472062d2014-04-03 10:56:48 -070030import net.onrc.onos.core.topology.DeviceEvent;
31import net.onrc.onos.core.topology.INetworkGraphListener;
32import net.onrc.onos.core.topology.INetworkGraphService;
33import net.onrc.onos.core.topology.LinkEvent;
34import net.onrc.onos.core.topology.PortEvent;
35import net.onrc.onos.core.topology.SwitchEvent;
Toshio Koide4f308732014-02-18 15:19:48 -080036
Jonathan Harta99ec672014-04-03 11:30:34 -070037import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39
Toshio Koide0c9106d2014-02-19 15:26:38 -080040/**
41 * @author Toshio Koide (t-koide@onlab.us)
42 */
Toshio Koide066506e2014-02-20 19:52:09 -080043public class PathCalcRuntimeModule implements IFloodlightModule, IPathCalcRuntimeService, INetworkGraphListener, IEventChannelListener<Long, IntentStateList> {
Pavlin Radoslavovfee80982014-04-10 12:12:04 -070044 static class PerfLog {
Ray Milkey269ffb92014-04-03 14:43:30 -070045 private String step;
46 private long time;
Toshio Koidebf875662014-02-24 12:19:15 -080047
Ray Milkey269ffb92014-04-03 14:43:30 -070048 public PerfLog(String step) {
49 this.step = step;
50 this.time = System.nanoTime();
51 }
Toshio Koidebf875662014-02-24 12:19:15 -080052
Ray Milkey269ffb92014-04-03 14:43:30 -070053 public void logThis() {
54 log.error("Time:{}, Step:{}", time, step);
55 }
56 }
Toshio Koidebf875662014-02-24 12:19:15 -080057
Pavlin Radoslavov53ad5e32014-04-10 14:24:26 -070058 static class PerfLogger {
Ray Milkey269ffb92014-04-03 14:43:30 -070059 private LinkedList<PerfLog> logData = new LinkedList<>();
Toshio Koidebf875662014-02-24 12:19:15 -080060
Ray Milkey269ffb92014-04-03 14:43:30 -070061 public PerfLogger(String logPhase) {
62 log("start_" + logPhase);
63 }
Toshio Koidebf875662014-02-24 12:19:15 -080064
Ray Milkey269ffb92014-04-03 14:43:30 -070065 public void log(String step) {
66 logData.add(new PerfLog(step));
67 }
Toshio Koide27ffd412014-02-18 19:15:27 -080068
Ray Milkey269ffb92014-04-03 14:43:30 -070069 public void flushLog() {
70 log("finish");
71 for (PerfLog log : logData) {
72 log.logThis();
73 }
74 logData.clear();
75 }
76 }
Toshio Koide4f308732014-02-18 15:19:48 -080077
Ray Milkey269ffb92014-04-03 14:43:30 -070078 private PathCalcRuntime runtime;
79 private IDatagridService datagridService;
80 private INetworkGraphService networkGraphService;
81 private IntentMap highLevelIntents;
82 private PathIntentMap pathIntents;
83 private IControllerRegistryService controllerRegistry;
84 private PersistIntent persistIntent;
Toshio Koide798bc1b2014-02-20 14:02:40 -080085
Ray Milkey269ffb92014-04-03 14:43:30 -070086 private IEventChannel<Long, IntentOperationList> opEventChannel;
87 private final ReentrantLock lock = new ReentrantLock();
88 private HashSet<LinkEvent> unmatchedLinkEvents = new HashSet<>();
89 private static final String INTENT_OP_EVENT_CHANNEL_NAME = "onos.pathintent";
90 private static final String INTENT_STATE_EVENT_CHANNEL_NAME = "onos.pathintent_state";
91 private static final Logger log = LoggerFactory.getLogger(PathCalcRuntimeModule.class);
Toshio Koidea10c0372014-02-20 17:28:10 -080092
Ray Milkey269ffb92014-04-03 14:43:30 -070093 // ================================================================================
94 // private methods
95 // ================================================================================
96
97 private void reroutePaths(Collection<Intent> oldPaths) {
Ray Milkeyb29e6262014-04-09 16:02:14 -070098 if (oldPaths == null || oldPaths.isEmpty()) {
Ray Milkey269ffb92014-04-03 14:43:30 -070099 return;
Ray Milkeyb29e6262014-04-09 16:02:14 -0700100 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700101
102 IntentOperationList reroutingOperation = new IntentOperationList();
103 for (Intent intent : oldPaths) {
104 PathIntent pathIntent = (PathIntent) intent;
Ray Milkeyb29e6262014-04-09 16:02:14 -0700105 if (pathIntent.isPathFrozen()) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700106 continue;
Ray Milkeyb29e6262014-04-09 16:02:14 -0700107 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700108 if (pathIntent.getState().equals(IntentState.INST_ACK) && // XXX: path intents in flight
109 !reroutingOperation.contains(pathIntent.getParentIntent())) {
110 reroutingOperation.add(Operator.ADD, pathIntent.getParentIntent());
111 }
112 }
113 executeIntentOperations(reroutingOperation);
114 }
Toshio Koidea94060f2014-02-21 22:58:32 -0800115
Toshio Koide27ffd412014-02-18 19:15:27 -0800116
Ray Milkey269ffb92014-04-03 14:43:30 -0700117 // ================================================================================
118 // IFloodlightModule implementations
119 // ================================================================================
Toshio Koide798bc1b2014-02-20 14:02:40 -0800120
Ray Milkey269ffb92014-04-03 14:43:30 -0700121 @Override
122 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
123 Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(1);
124 l.add(IPathCalcRuntimeService.class);
125 return l;
126 }
Toshio Koide4f308732014-02-18 15:19:48 -0800127
Ray Milkey269ffb92014-04-03 14:43:30 -0700128 @Override
129 public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
130 Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<>();
131 m.put(IPathCalcRuntimeService.class, this);
132 return m;
133 }
Toshio Koide4f308732014-02-18 15:19:48 -0800134
Ray Milkey269ffb92014-04-03 14:43:30 -0700135 @Override
136 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
137 Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(2);
138 l.add(IDatagridService.class);
139 l.add(INetworkGraphService.class);
140 return l;
141 }
Toshio Koide4f308732014-02-18 15:19:48 -0800142
Ray Milkey269ffb92014-04-03 14:43:30 -0700143 @Override
144 public void init(FloodlightModuleContext context) throws FloodlightModuleException {
145 datagridService = context.getServiceImpl(IDatagridService.class);
146 networkGraphService = context.getServiceImpl(INetworkGraphService.class);
147 controllerRegistry = context.getServiceImpl(IControllerRegistryService.class);
148 }
Toshio Koide4f308732014-02-18 15:19:48 -0800149
Ray Milkey269ffb92014-04-03 14:43:30 -0700150 @Override
151 public void startUp(FloodlightModuleContext context) {
152 highLevelIntents = new IntentMap();
153 runtime = new PathCalcRuntime(networkGraphService.getNetworkGraph());
154 pathIntents = new PathIntentMap();
155 opEventChannel = datagridService.createChannel(INTENT_OP_EVENT_CHANNEL_NAME, Long.class, IntentOperationList.class);
156 datagridService.addListener(INTENT_STATE_EVENT_CHANNEL_NAME, this, Long.class, IntentStateList.class);
157 networkGraphService.registerNetworkGraphListener(this);
Pavlin Radoslavov0294e052014-04-10 13:36:45 -0700158 persistIntent = new PersistIntent(controllerRegistry);
Ray Milkey269ffb92014-04-03 14:43:30 -0700159 }
Toshio Koide27ffd412014-02-18 19:15:27 -0800160
Ray Milkey269ffb92014-04-03 14:43:30 -0700161 // ================================================================================
162 // IPathCalcRuntimeService implementations
163 // ================================================================================
Toshio Koide798bc1b2014-02-20 14:02:40 -0800164
Ray Milkey269ffb92014-04-03 14:43:30 -0700165 @Override
166 public IntentOperationList executeIntentOperations(IntentOperationList list) {
Ray Milkeyb29e6262014-04-09 16:02:14 -0700167 if (list == null || list.size() == 0) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700168 return null;
Ray Milkeyb29e6262014-04-09 16:02:14 -0700169 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700170 PerfLogger p = new PerfLogger("executeIntentOperations_" + list.get(0).operator);
Toshio Koide275d8142014-02-24 16:41:52 -0800171
Ray Milkey269ffb92014-04-03 14:43:30 -0700172 lock.lock(); // TODO optimize locking using smaller steps
173 try {
174 // update the map of high-level intents
175 p.log("begin_updateInMemoryIntents");
176 highLevelIntents.executeOperations(list);
Toshio Koidedf2eab92014-02-20 11:24:59 -0800177
Ray Milkey269ffb92014-04-03 14:43:30 -0700178 // change states of high-level intents
179 IntentStateList states = new IntentStateList();
180 for (IntentOperation op : list) {
181 switch (op.operator) {
182 case ADD:
183 switch (op.intent.getState()) {
184 case CREATED:
185 states.put(op.intent.getId(), IntentState.INST_REQ);
186 break;
187 case INST_ACK:
188 states.put(op.intent.getId(), IntentState.REROUTE_REQ);
189 break;
190 default:
191 break;
192 }
193 break;
194 case REMOVE:
195 switch (op.intent.getState()) {
196 case CREATED:
197 states.put(op.intent.getId(), IntentState.DEL_REQ);
198 break;
199 default:
200 break;
201 }
202 break;
203 default:
204 break;
205 }
206 }
207 highLevelIntents.changeStates(states);
208 p.log("end_updateInMemoryIntents");
Toshio Koidedf2eab92014-02-20 11:24:59 -0800209
Ray Milkey269ffb92014-04-03 14:43:30 -0700210 // calculate path-intents (low-level operations)
211 p.log("begin_calcPathIntents");
212 IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, highLevelIntents, pathIntents);
213 p.log("end_calcPathIntents");
Toshio Koide600ae5f2014-02-20 18:42:00 -0800214
Ray Milkey269ffb92014-04-03 14:43:30 -0700215 // persist calculated low-level operations into data store
216 p.log("begin_persistPathIntents");
217 long key = persistIntent.getKey();
218 persistIntent.persistIfLeader(key, pathIntentOperations);
219 p.log("end_persistPathIntents");
Toshio Koide93be5d62014-02-23 19:30:57 -0800220
Ray Milkey269ffb92014-04-03 14:43:30 -0700221 // remove error-intents and reflect them to high-level intents
222 p.log("begin_removeErrorIntents");
223 states.clear();
224 Iterator<IntentOperation> i = pathIntentOperations.iterator();
225 while (i.hasNext()) {
226 IntentOperation op = i.next();
227 if (op.operator.equals(Operator.ERROR)) {
228 states.put(op.intent.getId(), IntentState.INST_NACK);
229 i.remove();
230 }
231 }
232 highLevelIntents.changeStates(states);
233 p.log("end_removeErrorIntents");
Toshio Koidea94060f2014-02-21 22:58:32 -0800234
Ray Milkey269ffb92014-04-03 14:43:30 -0700235 // update the map of path intents and publish the path operations
236 p.log("begin_updateInMemoryPathIntents");
237 pathIntents.executeOperations(pathIntentOperations);
238 p.log("end_updateInMemoryPathIntents");
Toshio Koide93be5d62014-02-23 19:30:57 -0800239
Ray Milkey269ffb92014-04-03 14:43:30 -0700240 // XXX Demo special: add a complete path to remove operation
241 p.log("begin_addPathToRemoveOperation");
242 for (IntentOperation op : pathIntentOperations) {
243 if (op.operator.equals(Operator.REMOVE)) {
244 op.intent = pathIntents.getIntent(op.intent.getId());
245 }
246 if (op.intent instanceof PathIntent) {
247 log.debug("operation: {}, intent:{}", op.operator, op.intent);
248 }
249 }
250 p.log("end_addPathToRemoveOperation");
Toshio Koide93be5d62014-02-23 19:30:57 -0800251
Ray Milkey269ffb92014-04-03 14:43:30 -0700252 // send notification
253 p.log("begin_sendNotification");
254 // XXX: Send notifications using the same key every time
255 // and receive them by entryAdded() and entryUpdated()
256 opEventChannel.addEntry(0L, pathIntentOperations);
257 p.log("end_sendNotification");
258 //opEventChannel.removeEntry(key);
259 return pathIntentOperations;
260 } finally {
Ray Milkey269ffb92014-04-03 14:43:30 -0700261 lock.unlock();
Pavlin Radoslavovc68974f2014-04-09 17:28:38 -0700262 p.flushLog();
Ray Milkey269ffb92014-04-03 14:43:30 -0700263 }
264 }
Toshio Koide27ffd412014-02-18 19:15:27 -0800265
Ray Milkey269ffb92014-04-03 14:43:30 -0700266 @Override
267 public IntentMap getHighLevelIntents() {
268 return highLevelIntents;
269 }
Toshio Koide27ffd412014-02-18 19:15:27 -0800270
Ray Milkey269ffb92014-04-03 14:43:30 -0700271 @Override
272 public IntentMap getPathIntents() {
273 return pathIntents;
274 }
Toshio Koide27ffd412014-02-18 19:15:27 -0800275
Ray Milkey269ffb92014-04-03 14:43:30 -0700276 @Override
277 public void purgeIntents() {
278 highLevelIntents.purge();
279 pathIntents.purge();
280 }
Toshio Koide0c9106d2014-02-19 15:26:38 -0800281
Ray Milkey269ffb92014-04-03 14:43:30 -0700282 // ================================================================================
283 // INetworkGraphListener implementations
284 // ================================================================================
Toshio Koide798bc1b2014-02-20 14:02:40 -0800285
Ray Milkey269ffb92014-04-03 14:43:30 -0700286 @Override
287 public void networkGraphEvents(Collection<SwitchEvent> addedSwitchEvents,
288 Collection<SwitchEvent> removedSwitchEvents,
289 Collection<PortEvent> addedPortEvents,
290 Collection<PortEvent> removedPortEvents,
291 Collection<LinkEvent> addedLinkEvents,
292 Collection<LinkEvent> removedLinkEvents,
293 Collection<DeviceEvent> addedDeviceEvents,
294 Collection<DeviceEvent> removedDeviceEvents) {
Toshio Koidea9078af2014-02-21 16:57:04 -0800295
Ray Milkey269ffb92014-04-03 14:43:30 -0700296 PerfLogger p = new PerfLogger("networkGraphEvents");
297 HashSet<Intent> affectedPaths = new HashSet<>();
Toshio Koide93797dc2014-02-27 23:54:26 -0800298
Ray Milkey269ffb92014-04-03 14:43:30 -0700299 boolean rerouteAll = false;
300 for (LinkEvent le : addedLinkEvents) {
301 LinkEvent rev = new LinkEvent(le.getDst().getDpid(), le.getDst().getNumber(), le.getSrc().getDpid(), le.getSrc().getNumber());
302 if (unmatchedLinkEvents.contains(rev)) {
303 rerouteAll = true;
304 unmatchedLinkEvents.remove(rev);
305 log.debug("Found matched LinkEvent: {} {}", rev, le);
306 } else {
307 unmatchedLinkEvents.add(le);
308 log.debug("Adding unmatched LinkEvent: {}", le);
309 }
310 }
311 for (LinkEvent le : removedLinkEvents) {
312 if (unmatchedLinkEvents.contains(le)) {
313 unmatchedLinkEvents.remove(le);
314 log.debug("Removing LinkEvent: {}", le);
315 }
316 }
317 if (unmatchedLinkEvents.size() > 0) {
318 log.debug("Unmatched link events: {} events", unmatchedLinkEvents.size());
319 }
Toshio Koidea9078af2014-02-21 16:57:04 -0800320
Ray Milkey7f1567c2014-04-08 13:53:32 -0700321 if (rerouteAll) { //addedLinkEvents.size() > 0) { // ||
Ray Milkey269ffb92014-04-03 14:43:30 -0700322// addedPortEvents.size() > 0 ||
323// addedSwitchEvents.size() > 0) {
324 p.log("begin_getAllIntents");
325 affectedPaths.addAll(getPathIntents().getAllIntents());
326 p.log("end_getAllIntents");
327 } else if (removedSwitchEvents.size() > 0 ||
328 removedLinkEvents.size() > 0 ||
329 removedPortEvents.size() > 0) {
330 p.log("begin_getIntentsByLink");
Ray Milkeyb29e6262014-04-09 16:02:14 -0700331 for (LinkEvent linkEvent : removedLinkEvents) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700332 affectedPaths.addAll(pathIntents.getIntentsByLink(linkEvent));
Ray Milkeyb29e6262014-04-09 16:02:14 -0700333 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700334 p.log("end_getIntentsByLink");
Toshio Koidea9078af2014-02-21 16:57:04 -0800335
Ray Milkey269ffb92014-04-03 14:43:30 -0700336 p.log("begin_getIntentsByPort");
Ray Milkeyb29e6262014-04-09 16:02:14 -0700337 for (PortEvent portEvent : removedPortEvents) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700338 affectedPaths.addAll(pathIntents.getIntentsByPort(portEvent.getDpid(), portEvent.getNumber()));
Ray Milkeyb29e6262014-04-09 16:02:14 -0700339 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700340 p.log("end_getIntentsByPort");
Toshio Koidea9078af2014-02-21 16:57:04 -0800341
Ray Milkey269ffb92014-04-03 14:43:30 -0700342 p.log("begin_getIntentsByDpid");
Ray Milkeyb29e6262014-04-09 16:02:14 -0700343 for (SwitchEvent switchEvent : removedSwitchEvents) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700344 affectedPaths.addAll(pathIntents.getIntentsByDpid(switchEvent.getDpid()));
Ray Milkeyb29e6262014-04-09 16:02:14 -0700345 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700346 p.log("end_getIntentsByDpid");
347 }
348 p.log("begin_reroutePaths");
349 reroutePaths(affectedPaths);
350 p.log("end_reroutePaths");
351 p.flushLog();
352 }
Toshio Koide066506e2014-02-20 19:52:09 -0800353
Ray Milkey269ffb92014-04-03 14:43:30 -0700354 // ================================================================================
355 // IEventChannelListener implementations
356 // ================================================================================
Toshio Koide066506e2014-02-20 19:52:09 -0800357
Ray Milkey269ffb92014-04-03 14:43:30 -0700358 @Override
359 public void entryAdded(IntentStateList value) {
360 entryUpdated(value);
361 }
Toshio Koide066506e2014-02-20 19:52:09 -0800362
Ray Milkey269ffb92014-04-03 14:43:30 -0700363 @Override
364 public void entryRemoved(IntentStateList value) {
365 // do nothing
366 }
Toshio Koide066506e2014-02-20 19:52:09 -0800367
Ray Milkey269ffb92014-04-03 14:43:30 -0700368 @Override
369 public void entryUpdated(IntentStateList value) {
370 // TODO draw state transition diagram in multiple ONOS instances and update this method
371 PerfLogger p = new PerfLogger("entryUpdated");
372 lock.lock(); // TODO optimize locking using smaller steps
373 try {
374 // reflect state changes of path-level intent into application-level intents
375 p.log("begin_changeStateByNotification");
376 IntentStateList highLevelIntentStates = new IntentStateList();
377 IntentStateList pathIntentStates = new IntentStateList();
378 for (Entry<String, IntentState> entry : value.entrySet()) {
379 PathIntent pathIntent = (PathIntent) pathIntents.getIntent(entry.getKey());
Ray Milkeyb29e6262014-04-09 16:02:14 -0700380 if (pathIntent == null) {
381 continue;
382 }
Toshio Koide8315d7d2014-02-21 22:58:32 -0800383
Ray Milkey269ffb92014-04-03 14:43:30 -0700384 Intent parentIntent = pathIntent.getParentIntent();
385 if (parentIntent == null ||
386 !(parentIntent instanceof ShortestPathIntent) ||
Ray Milkeyb29e6262014-04-09 16:02:14 -0700387 !((ShortestPathIntent) parentIntent).getPathIntentId().equals(pathIntent.getId())) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700388 continue;
Ray Milkeyb29e6262014-04-09 16:02:14 -0700389 }
Toshio Koide066506e2014-02-20 19:52:09 -0800390
Ray Milkey269ffb92014-04-03 14:43:30 -0700391 IntentState state = entry.getValue();
392 switch (state) {
393 //case INST_REQ:
394 case INST_ACK:
395 case INST_NACK:
396 //case DEL_REQ:
397 case DEL_ACK:
398 case DEL_PENDING:
399 highLevelIntentStates.put(parentIntent.getId(), state);
400 pathIntentStates.put(entry.getKey(), entry.getValue());
401 break;
402 default:
403 break;
404 }
405 }
406 highLevelIntents.changeStates(highLevelIntentStates);
407 pathIntents.changeStates(pathIntentStates);
408 p.log("end_changeStateByNotification");
409 } finally {
Ray Milkey269ffb92014-04-03 14:43:30 -0700410 lock.unlock();
Pavlin Radoslavovc68974f2014-04-09 17:28:38 -0700411 p.flushLog();
Ray Milkey269ffb92014-04-03 14:43:30 -0700412 }
413 }
Toshio Koidea9078af2014-02-21 16:57:04 -0800414}