blob: 273e0bb8ebd3cdb74662e6af5187289e981583e5 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.optical;
weibitf32383b2014-10-22 10:17:31 -070017
weibitf32383b2014-10-22 10:17:31 -070018import org.apache.felix.scr.annotations.Activate;
19import org.apache.felix.scr.annotations.Component;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070020import org.apache.felix.scr.annotations.Deactivate;
weibitf32383b2014-10-22 10:17:31 -070021import org.apache.felix.scr.annotations.Reference;
22import org.apache.felix.scr.annotations.ReferenceCardinality;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -080023import org.onosproject.cluster.ClusterService;
24import org.onosproject.cluster.NodeId;
Brian O'Connorabafb502014-12-02 22:26:20 -080025import org.onosproject.core.ApplicationId;
26import org.onosproject.core.CoreService;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -080027import org.onosproject.mastership.MastershipService;
Toru Furusawa72ee30c2016-01-08 13:29:04 -080028import org.onosproject.net.CltSignalType;
Brian O'Connorabafb502014-12-02 22:26:20 -080029import org.onosproject.net.ConnectPoint;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070030import org.onosproject.net.Device;
Brian O'Connorabafb502014-12-02 22:26:20 -080031import org.onosproject.net.Host;
32import org.onosproject.net.Link;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070033import org.onosproject.net.OduCltPort;
Marc De Leenheer8c2caac2015-05-28 16:37:33 -070034import org.onosproject.net.OduSignalType;
Brian O'Connorabafb502014-12-02 22:26:20 -080035import org.onosproject.net.Path;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070036import org.onosproject.net.Port;
37import org.onosproject.net.device.DeviceService;
Brian O'Connorabafb502014-12-02 22:26:20 -080038import org.onosproject.net.host.HostService;
39import org.onosproject.net.intent.HostToHostIntent;
40import org.onosproject.net.intent.Intent;
41import org.onosproject.net.intent.IntentEvent;
42import org.onosproject.net.intent.IntentListener;
Brian O'Connorabafb502014-12-02 22:26:20 -080043import org.onosproject.net.intent.IntentService;
44import org.onosproject.net.intent.IntentState;
Marc De Leenheer8c2caac2015-05-28 16:37:33 -070045import org.onosproject.net.intent.OpticalCircuitIntent;
Brian O'Connorabafb502014-12-02 22:26:20 -080046import org.onosproject.net.intent.OpticalConnectivityIntent;
47import org.onosproject.net.intent.PointToPointIntent;
HIGUCHI Yuta34a3f692016-01-09 21:08:57 -080048import org.onosproject.net.optical.OchPort;
Brian O'Connorabafb502014-12-02 22:26:20 -080049import org.onosproject.net.topology.LinkWeight;
50import org.onosproject.net.topology.PathService;
51import org.onosproject.net.topology.TopologyEdge;
weibitf32383b2014-10-22 10:17:31 -070052import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54
Marc De Leenheer16f857b2015-05-05 20:50:24 -070055import java.util.Collections;
Brian O'Connor772852a2014-11-17 15:51:19 -080056import java.util.Iterator;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070057import java.util.LinkedList;
Brian O'Connor772852a2014-11-17 15:51:19 -080058import java.util.List;
59import java.util.Set;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -080060
Marc De Leenheer16f857b2015-05-05 20:50:24 -070061import static com.google.common.base.Preconditions.checkArgument;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070062import static com.google.common.base.Preconditions.checkNotNull;
HIGUCHI Yuta34a3f692016-01-09 21:08:57 -080063import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070064
weibitf32383b2014-10-22 10:17:31 -070065/**
Marc De Leenheer16f857b2015-05-05 20:50:24 -070066 * OpticalPathProvisioner listens for event notifications from the Intent F/W.
weibitf32383b2014-10-22 10:17:31 -070067 * It generates one or more opticalConnectivityIntent(s) and submits (or withdraws) to Intent F/W
68 * for adding/releasing capacity at the packet layer.
weibitf32383b2014-10-22 10:17:31 -070069 */
70
71@Component(immediate = true)
72public class OpticalPathProvisioner {
73
74 protected static final Logger log = LoggerFactory
75 .getLogger(OpticalPathProvisioner.class);
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 private IntentService intentService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connor772852a2014-11-17 15:51:19 -080081 protected PathService pathService;
weibitf32383b2014-10-22 10:17:31 -070082
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected CoreService coreService;
85
weibit7e583462014-10-23 10:14:05 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Praseed Balakrishnanc0029652014-11-14 13:38:49 -080087 protected HostService hostService;
88
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -080089 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected MastershipService mastershipService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected ClusterService clusterService;
94
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070095 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070096 protected DeviceService deviceService;
97
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070098 private ApplicationId appId;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -080099
weibitf32383b2014-10-22 10:17:31 -0700100 private final InternalOpticalPathProvisioner pathProvisioner = new InternalOpticalPathProvisioner();
101
102 @Activate
103 protected void activate() {
HIGUCHI Yuta34a3f692016-01-09 21:08:57 -0800104 deviceService = opticalView(deviceService);
weibitf32383b2014-10-22 10:17:31 -0700105 intentService.addListener(pathProvisioner);
Brian O'Connorabafb502014-12-02 22:26:20 -0800106 appId = coreService.registerApplication("org.onosproject.optical");
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700107 initOpticalPorts();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700108 log.info("Started");
109 }
110
111 @Deactivate
112 protected void deactivate() {
113 intentService.removeListener(pathProvisioner);
114 log.info("Stopped");
weibitf32383b2014-10-22 10:17:31 -0700115 }
116
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700117 /**
118 * Initialize availability of optical ports.
119 */
120 private void initOpticalPorts() {
121 // TODO: check for existing optical intents
122 return;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800123 }
124
weibitf32383b2014-10-22 10:17:31 -0700125 public class InternalOpticalPathProvisioner implements IntentListener {
126 @Override
127 public void event(IntentEvent event) {
128 switch (event.type()) {
Brian O'Connor7a71d5d2014-12-02 00:12:27 -0800129 case INSTALL_REQ:
weibitf32383b2014-10-22 10:17:31 -0700130 break;
131 case INSTALLED:
132 break;
133 case FAILED:
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700134 log.info("Intent {} failed, calling optical path provisioning app.", event.subject());
Brian O'Connor772852a2014-11-17 15:51:19 -0800135 setupLightpath(event.subject());
weibitf32383b2014-10-22 10:17:31 -0700136 break;
weibitf32383b2014-10-22 10:17:31 -0700137 default:
138 break;
139 }
140 }
141
Brian O'Connor772852a2014-11-17 15:51:19 -0800142 private void setupLightpath(Intent intent) {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700143 checkNotNull(intent);
144
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800145 // TODO change the coordination approach between packet intents and optical intents
146 // Low speed LLDP may cause multiple calls which are not expected
147
Sho SHIMIZU0a9c9c42015-07-01 15:29:16 -0700148 if (intentService.getIntentState(intent.key()) != IntentState.FAILED) {
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800149 return;
Marc De Leenheerb473b9d2015-02-06 15:21:03 -0800150 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800151
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700152 // Get source and destination based on intent type
153 ConnectPoint src;
154 ConnectPoint dst;
Brian O'Connor772852a2014-11-17 15:51:19 -0800155 if (intent instanceof HostToHostIntent) {
156 HostToHostIntent hostToHostIntent = (HostToHostIntent) intent;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800157
Brian O'Connor772852a2014-11-17 15:51:19 -0800158 Host one = hostService.getHost(hostToHostIntent.one());
159 Host two = hostService.getHost(hostToHostIntent.two());
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800160
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700161 checkNotNull(one);
162 checkNotNull(two);
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800163
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700164 src = one.location();
165 dst = two.location();
Brian O'Connor772852a2014-11-17 15:51:19 -0800166 } else if (intent instanceof PointToPointIntent) {
167 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800168
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700169 src = p2pIntent.ingressPoint();
170 dst = p2pIntent.egressPoint();
Brian O'Connor772852a2014-11-17 15:51:19 -0800171 } else {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700172 return;
Brian O'Connor772852a2014-11-17 15:51:19 -0800173 }
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800174
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700175 if (src == null || dst == null) {
176 return;
177 }
178
179 // Ignore if we're not the master for the intent's origin device
180 NodeId localNode = clusterService.getLocalNode().id();
181 NodeId sourceMaster = mastershipService.getMasterFor(src.deviceId());
182 if (!localNode.equals(sourceMaster)) {
183 return;
184 }
185
186 // Generate optical connectivity intents
Sho SHIMIZUfb446fe2015-05-21 14:45:23 -0700187 List<Intent> intents = getOpticalIntents(src, dst);
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700188
189 // Submit the intents
Brian O'Connor772852a2014-11-17 15:51:19 -0800190 for (Intent i : intents) {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700191 intentService.submit(i);
Sho SHIMIZUb5d38412015-05-21 11:12:58 -0700192 log.debug("Submitted an intent: {}", i);
Brian O'Connor772852a2014-11-17 15:51:19 -0800193 }
weibit7e583462014-10-23 10:14:05 -0700194 }
195
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700196 /**
197 * Returns list of cross connection points of missing optical path sections.
198 *
199 * Scans the given multi-layer path and looks for sections that use cross connect links.
200 * The ingress and egress points in the optical layer are returned in a list.
201 *
202 * @param path the multi-layer path
203 * @return list of cross connection points on the optical layer
204 */
205 private List<ConnectPoint> getCrossConnectPoints(Path path) {
206 boolean scanning = false;
Sho SHIMIZU819ee292015-07-01 09:36:21 -0700207 List<ConnectPoint> connectPoints = new LinkedList<>();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700208
209 for (Link link : path.links()) {
210 if (!isCrossConnectLink(link)) {
211 continue;
212 }
213
214 if (scanning) {
215 connectPoints.add(checkNotNull(link.src()));
216 scanning = false;
217 } else {
218 connectPoints.add(checkNotNull(link.dst()));
219 scanning = true;
220 }
221 }
222
223 return connectPoints;
224 }
225
226 /**
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700227 * Checks if cross connect points are of same type.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700228 *
229 * @param crossConnectPoints list of cross connection points
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700230 * @return true if cross connect point pairs are of same type, false otherwise
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700231 */
232 private boolean checkCrossConnectPoints(List<ConnectPoint> crossConnectPoints) {
233 checkArgument(crossConnectPoints.size() % 2 == 0);
234
235 Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
236
237 while (itr.hasNext()) {
238 // checkArgument at start ensures we'll always have pairs of connect points
239 ConnectPoint src = itr.next();
240 ConnectPoint dst = itr.next();
241
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700242 Device.Type srcType = deviceService.getDevice(src.deviceId()).type();
243 Device.Type dstType = deviceService.getDevice(dst.deviceId()).type();
244
245 // Only support connections between identical port types
246 if (srcType != dstType) {
247 log.warn("Unsupported mix of cross connect points");
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700248 return false;
249 }
250 }
251
252 return true;
253 }
254
255 /**
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700256 * Scans the list of cross connection points and returns a list of optical connectivity intents.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700257 *
258 * @param crossConnectPoints list of cross connection points
259 * @return list of optical connectivity intents
260 */
261 private List<Intent> getIntents(List<ConnectPoint> crossConnectPoints) {
262 checkArgument(crossConnectPoints.size() % 2 == 0);
263
Sho SHIMIZU79945e82015-05-20 17:20:47 -0700264 List<Intent> intents = new LinkedList<>();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700265 Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
266
267 while (itr.hasNext()) {
268 // checkArgument at start ensures we'll always have pairs of connect points
269 ConnectPoint src = itr.next();
270 ConnectPoint dst = itr.next();
271
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700272 Port srcPort = deviceService.getPort(src.deviceId(), src.port());
273 Port dstPort = deviceService.getPort(dst.deviceId(), dst.port());
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700274
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700275 if (srcPort instanceof OduCltPort && dstPort instanceof OduCltPort) {
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700276 // Create OTN circuit
277 Intent circuitIntent = OpticalCircuitIntent.builder()
278 .appId(appId)
279 .src(src)
280 .dst(dst)
Toru Furusawa72ee30c2016-01-08 13:29:04 -0800281 .signalType(CltSignalType.CLT_10GBE)
Marc De Leenheer4a1c1fa2015-06-01 18:08:56 -0700282 .bidirectional(true)
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700283 .build();
284 intents.add(circuitIntent);
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700285 } else if (srcPort instanceof OchPort && dstPort instanceof OchPort) {
286 // Create lightpath
287 // FIXME: hardcoded ODU signal type
288 Intent opticalIntent = OpticalConnectivityIntent.builder()
289 .appId(appId)
290 .src(src)
291 .dst(dst)
292 .signalType(OduSignalType.ODU4)
Marc De Leenheer4a1c1fa2015-06-01 18:08:56 -0700293 .bidirectional(true)
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700294 .build();
295 intents.add(opticalIntent);
Marc De Leenheer8c2caac2015-05-28 16:37:33 -0700296 } else {
297 log.warn("Unsupported cross connect point types {} {}", srcPort.type(), dstPort.type());
298 return Collections.emptyList();
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700299 }
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700300 }
301
302 return intents;
303 }
304
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700305 /**
306 * Returns list of optical connectivity intents needed to create connectivity
307 * between ingress and egress.
308 *
309 * @param ingress the ingress connect point
310 * @param egress the egress connect point
311 * @return list of optical connectivity intents, empty list if no path was found
312 */
313 private List<Intent> getOpticalIntents(ConnectPoint ingress, ConnectPoint egress) {
Brian O'Connor772852a2014-11-17 15:51:19 -0800314 Set<Path> paths = pathService.getPaths(ingress.deviceId(),
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700315 egress.deviceId(),
316 new OpticalLinkWeight());
Brian O'Connor772852a2014-11-17 15:51:19 -0800317
318 if (paths.isEmpty()) {
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700319 return Collections.emptyList();
weibit7e583462014-10-23 10:14:05 -0700320 }
Brian O'Connor772852a2014-11-17 15:51:19 -0800321
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700322 // Search path with available cross connect points
323 for (Path path : paths) {
324 List<ConnectPoint> crossConnectPoints = getCrossConnectPoints(path);
Brian O'Connor772852a2014-11-17 15:51:19 -0800325
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700326 // Skip to next path if cross connect points are mismatched
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700327 if (!checkCrossConnectPoints(crossConnectPoints)) {
328 continue;
Brian O'Connor772852a2014-11-17 15:51:19 -0800329 }
330
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700331 return getIntents(crossConnectPoints);
Brian O'Connor772852a2014-11-17 15:51:19 -0800332 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800333
Marc De Leenheer234fa822015-11-18 18:24:57 -0800334 log.warn("Unable to find multi-layer path.");
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700335 return Collections.emptyList();
Brian O'Connor772852a2014-11-17 15:51:19 -0800336 }
337
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700338 /**
339 * Link weight function that emphasizes re-use of packet links.
340 */
341 private class OpticalLinkWeight implements LinkWeight {
342 @Override
343 public double weight(TopologyEdge edge) {
344 // Ignore inactive links
345 if (edge.link().state() == Link.State.INACTIVE) {
346 return -1;
347 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800348
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700349 // TODO: Ignore cross connect links with used ports
350
351 // Transport links have highest weight
352 if (edge.link().type() == Link.Type.OPTICAL) {
353 return 1000;
354 }
355
356 // Packet links
357 return 1;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800358 }
weibitf32383b2014-10-22 10:17:31 -0700359 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800360
Brian O'Connor772852a2014-11-17 15:51:19 -0800361 }
weibitf32383b2014-10-22 10:17:31 -0700362
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700363 /**
Marc De Leenheer234fa822015-11-18 18:24:57 -0800364 * Verifies if given device type is in packet layer, i.e., ROADM, OTN or ROADM_OTN device.
365 *
366 * @param type device type
367 * @return true if in packet layer, false otherwise
368 */
369 private boolean isPacketLayer(Device.Type type) {
Ayaka Koshibe31980ab2016-01-21 00:11:16 -0800370 return type == Device.Type.SWITCH || type == Device.Type.ROUTER || type == Device.Type.VIRTUAL;
Marc De Leenheer234fa822015-11-18 18:24:57 -0800371 }
372
373 /**
374 * Verifies if given device type is in packet layer, i.e., switch or router device.
375 *
376 * @param type device type
377 * @return true if in packet layer, false otherwise
378 */
379 private boolean isTransportLayer(Device.Type type) {
380 return type == Device.Type.ROADM || type == Device.Type.OTN || type == Device.Type.ROADM_OTN;
381 }
382
383 /**
384 * Verifies if given link forms a cross-connection between packet and optical layer.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700385 *
386 * @param link the link
Marc De Leenheer234fa822015-11-18 18:24:57 -0800387 * @return true if the link is a cross-connect link, false otherwise
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700388 */
Marc De Leenheer234fa822015-11-18 18:24:57 -0800389 private boolean isCrossConnectLink(Link link) {
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700390 if (link.type() != Link.Type.OPTICAL) {
391 return false;
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800392 }
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700393
Marc De Leenheer234fa822015-11-18 18:24:57 -0800394 Device.Type src = deviceService.getDevice(link.src().deviceId()).type();
395 Device.Type dst = deviceService.getDevice(link.dst().deviceId()).type();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700396
Marc De Leenheer234fa822015-11-18 18:24:57 -0800397 return src != dst &&
398 ((isPacketLayer(src) && isTransportLayer(dst)) || (isPacketLayer(dst) && isTransportLayer(src)));
Brian O'Connor772852a2014-11-17 15:51:19 -0800399 }
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800400
weibitf32383b2014-10-22 10:17:31 -0700401}