blob: 2c5349f51e136660c45ccc3e45ff0f90163f7fbf [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Ray Milkey34c95902015-04-15 09:47:53 -07002 * Copyright 2014-2015 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
Brian O'Connor772852a2014-11-17 15:51:19 -080018import com.google.common.collect.Lists;
weibitf32383b2014-10-22 10:17:31 -070019import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070021import org.apache.felix.scr.annotations.Deactivate;
weibitf32383b2014-10-22 10:17:31 -070022import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070024import org.onlab.util.KryoNamespace;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -080025import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.NodeId;
Brian O'Connorabafb502014-12-02 22:26:20 -080027import org.onosproject.core.ApplicationId;
28import org.onosproject.core.CoreService;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -080029import org.onosproject.mastership.MastershipService;
Brian O'Connorabafb502014-12-02 22:26:20 -080030import org.onosproject.net.ConnectPoint;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070031import org.onosproject.net.Device;
Brian O'Connorabafb502014-12-02 22:26:20 -080032import org.onosproject.net.Host;
33import org.onosproject.net.Link;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070034import org.onosproject.net.OduCltPort;
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;
45import org.onosproject.net.intent.OpticalConnectivityIntent;
46import org.onosproject.net.intent.PointToPointIntent;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070047import org.onosproject.net.resource.DeviceResourceService;
48import org.onosproject.net.resource.LinkResourceService;
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;
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070052import org.onosproject.net.topology.TopologyService;
53import org.onosproject.store.serializers.KryoNamespaces;
54import org.onosproject.store.service.Serializer;
55import org.onosproject.store.service.StorageService;
weibitf32383b2014-10-22 10:17:31 -070056import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
Marc De Leenheer16f857b2015-05-05 20:50:24 -070059import java.util.Collections;
Brian O'Connor772852a2014-11-17 15:51:19 -080060import java.util.Iterator;
Marc De Leenheer16f857b2015-05-05 20:50:24 -070061import java.util.LinkedList;
Brian O'Connor772852a2014-11-17 15:51:19 -080062import java.util.List;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -080063import java.util.Map;
Brian O'Connor772852a2014-11-17 15:51:19 -080064import java.util.Set;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -080065import java.util.concurrent.ConcurrentHashMap;
66
Marc De Leenheer16f857b2015-05-05 20:50:24 -070067import static com.google.common.base.Preconditions.checkArgument;
Praseed Balakrishnanc0029652014-11-14 13:38:49 -080068
Marc De Leenheer16f857b2015-05-05 20:50:24 -070069import static com.google.common.base.Preconditions.checkNotNull;
70
weibitf32383b2014-10-22 10:17:31 -070071/**
Marc De Leenheer16f857b2015-05-05 20:50:24 -070072 * OpticalPathProvisioner listens for event notifications from the Intent F/W.
weibitf32383b2014-10-22 10:17:31 -070073 * It generates one or more opticalConnectivityIntent(s) and submits (or withdraws) to Intent F/W
74 * for adding/releasing capacity at the packet layer.
weibitf32383b2014-10-22 10:17:31 -070075 */
76
77@Component(immediate = true)
78public class OpticalPathProvisioner {
79
80 protected static final Logger log = LoggerFactory
81 .getLogger(OpticalPathProvisioner.class);
82
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070083 private static final Serializer SERIALIZER = Serializer.using(
84 new KryoNamespace.Builder().register(KryoNamespaces.API).build());
85
weibitf32383b2014-10-22 10:17:31 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 private IntentService intentService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connor772852a2014-11-17 15:51:19 -080090 protected PathService pathService;
weibitf32383b2014-10-22 10:17:31 -070091
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Marc De Leenheer1afa2a02015-05-13 09:18:07 -070093 protected TopologyService topologyService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
weibitf32383b2014-10-22 10:17:31 -070096 protected CoreService coreService;
97
weibit7e583462014-10-23 10:14:05 -070098 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Praseed Balakrishnanc0029652014-11-14 13:38:49 -080099 protected HostService hostService;
100
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected MastershipService mastershipService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected ClusterService clusterService;
106
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected StorageService storageService;
weibitf32383b2014-10-22 10:17:31 -0700109
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected DeviceService deviceService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected DeviceResourceService deviceResourceService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected LinkResourceService linkResourceService;
118
119 private ApplicationId appId;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800120
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700121 private final Map<ConnectPoint, Map<ConnectPoint, Intent>> intentMap =
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800122 new ConcurrentHashMap<>();
weibitf32383b2014-10-22 10:17:31 -0700123
124 private final InternalOpticalPathProvisioner pathProvisioner = new InternalOpticalPathProvisioner();
125
126 @Activate
127 protected void activate() {
128 intentService.addListener(pathProvisioner);
Brian O'Connorabafb502014-12-02 22:26:20 -0800129 appId = coreService.registerApplication("org.onosproject.optical");
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700130 initOpticalPorts();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700131 log.info("Started");
132 }
133
134 @Deactivate
135 protected void deactivate() {
136 intentService.removeListener(pathProvisioner);
137 log.info("Stopped");
weibitf32383b2014-10-22 10:17:31 -0700138 }
139
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700140 /**
141 * Initialize availability of optical ports.
142 */
143 private void initOpticalPorts() {
144 // TODO: check for existing optical intents
145 return;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800146 }
147
weibitf32383b2014-10-22 10:17:31 -0700148 public class InternalOpticalPathProvisioner implements IntentListener {
149 @Override
150 public void event(IntentEvent event) {
151 switch (event.type()) {
Brian O'Connor7a71d5d2014-12-02 00:12:27 -0800152 case INSTALL_REQ:
weibitf32383b2014-10-22 10:17:31 -0700153 break;
154 case INSTALLED:
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700155 // track h2h & p2p intents using our connectivity
weibitf32383b2014-10-22 10:17:31 -0700156 break;
157 case FAILED:
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700158 log.info("Intent {} failed, calling optical path provisioning app.", event.subject());
Brian O'Connor772852a2014-11-17 15:51:19 -0800159 setupLightpath(event.subject());
weibitf32383b2014-10-22 10:17:31 -0700160 break;
161 case WITHDRAWN:
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700162 log.info("Intent {} withdrawn.", event.subject());
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700163 withdrawIntent(event.subject());
weibitf32383b2014-10-22 10:17:31 -0700164 break;
165 default:
166 break;
167 }
168 }
169
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800170 /**
171 * Registers an intent from src to dst.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700172 *
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800173 * @param src source point
174 * @param dst destination point
175 * @param intent intent to be registered
176 * @return true if intent has not been previously added, false otherwise
177 */
178 private boolean addIntent(ConnectPoint src, ConnectPoint dst, Intent intent) {
179 Map<ConnectPoint, Intent> srcMap = intentMap.get(src);
180 if (srcMap == null) {
181 srcMap = new ConcurrentHashMap<>();
182 intentMap.put(src, srcMap);
183 }
184 if (srcMap.containsKey(dst)) {
185 return false;
186 } else {
187 srcMap.put(dst, intent);
188 return true;
189 }
190 }
191
Brian O'Connor772852a2014-11-17 15:51:19 -0800192 private void setupLightpath(Intent intent) {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700193 checkNotNull(intent);
194
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800195 // TODO change the coordination approach between packet intents and optical intents
196 // Low speed LLDP may cause multiple calls which are not expected
197
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800198 if (!IntentState.FAILED.equals(intentService.getIntentState(intent.key()))) {
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800199 return;
Marc De Leenheerb473b9d2015-02-06 15:21:03 -0800200 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800201
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700202 // Get source and destination based on intent type
203 ConnectPoint src;
204 ConnectPoint dst;
Brian O'Connor772852a2014-11-17 15:51:19 -0800205 if (intent instanceof HostToHostIntent) {
206 HostToHostIntent hostToHostIntent = (HostToHostIntent) intent;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800207
Brian O'Connor772852a2014-11-17 15:51:19 -0800208 Host one = hostService.getHost(hostToHostIntent.one());
209 Host two = hostService.getHost(hostToHostIntent.two());
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800210
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700211 checkNotNull(one);
212 checkNotNull(two);
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800213
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700214 src = one.location();
215 dst = two.location();
Brian O'Connor772852a2014-11-17 15:51:19 -0800216 } else if (intent instanceof PointToPointIntent) {
217 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Marc De Leenheer8b3e80b2015-03-06 14:27:03 -0800218
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700219 src = p2pIntent.ingressPoint();
220 dst = p2pIntent.egressPoint();
Brian O'Connor772852a2014-11-17 15:51:19 -0800221 } else {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700222 log.error("Unsupported intent type: {}", intent.getClass());
223 return;
Brian O'Connor772852a2014-11-17 15:51:19 -0800224 }
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800225
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700226 if (src == null || dst == null) {
227 return;
228 }
229
230 // Ignore if we're not the master for the intent's origin device
231 NodeId localNode = clusterService.getLocalNode().id();
232 NodeId sourceMaster = mastershipService.getMasterFor(src.deviceId());
233 if (!localNode.equals(sourceMaster)) {
234 return;
235 }
236
237 // Generate optical connectivity intents
238 List<Intent> intents = Lists.newArrayList();
239 intents.addAll(getOpticalIntents(src, dst));
240
241 // Submit the intents
Brian O'Connor772852a2014-11-17 15:51:19 -0800242 for (Intent i : intents) {
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700243 intentService.submit(i);
Brian O'Connor772852a2014-11-17 15:51:19 -0800244 }
weibit7e583462014-10-23 10:14:05 -0700245 }
246
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700247 /**
248 * Returns list of cross connection points of missing optical path sections.
249 *
250 * Scans the given multi-layer path and looks for sections that use cross connect links.
251 * The ingress and egress points in the optical layer are returned in a list.
252 *
253 * @param path the multi-layer path
254 * @return list of cross connection points on the optical layer
255 */
256 private List<ConnectPoint> getCrossConnectPoints(Path path) {
257 boolean scanning = false;
258 List<ConnectPoint> connectPoints = new LinkedList<ConnectPoint>();
259
260 for (Link link : path.links()) {
261 if (!isCrossConnectLink(link)) {
262 continue;
263 }
264
265 if (scanning) {
266 connectPoints.add(checkNotNull(link.src()));
267 scanning = false;
268 } else {
269 connectPoints.add(checkNotNull(link.dst()));
270 scanning = true;
271 }
272 }
273
274 return connectPoints;
275 }
276
277 /**
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700278 * Checks if cross connect points are of same type.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700279 *
280 * @param crossConnectPoints list of cross connection points
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700281 * @return true if cross connect point pairs are of same type, false otherwise
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700282 */
283 private boolean checkCrossConnectPoints(List<ConnectPoint> crossConnectPoints) {
284 checkArgument(crossConnectPoints.size() % 2 == 0);
285
286 Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
287
288 while (itr.hasNext()) {
289 // checkArgument at start ensures we'll always have pairs of connect points
290 ConnectPoint src = itr.next();
291 ConnectPoint dst = itr.next();
292
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700293 Device.Type srcType = deviceService.getDevice(src.deviceId()).type();
294 Device.Type dstType = deviceService.getDevice(dst.deviceId()).type();
295
296 // Only support connections between identical port types
297 if (srcType != dstType) {
298 log.warn("Unsupported mix of cross connect points");
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700299 return false;
300 }
301 }
302
303 return true;
304 }
305
306 /**
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700307 * Scans the list of cross connection points and returns a list of optical connectivity intents.
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700308 *
309 * @param crossConnectPoints list of cross connection points
310 * @return list of optical connectivity intents
311 */
312 private List<Intent> getIntents(List<ConnectPoint> crossConnectPoints) {
313 checkArgument(crossConnectPoints.size() % 2 == 0);
314
Sho SHIMIZU79945e82015-05-20 17:20:47 -0700315 List<Intent> intents = new LinkedList<>();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700316 Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
317
318 while (itr.hasNext()) {
319 // checkArgument at start ensures we'll always have pairs of connect points
320 ConnectPoint src = itr.next();
321 ConnectPoint dst = itr.next();
322
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700323 Port srcPort = deviceService.getPort(src.deviceId(), src.port());
324 Port dstPort = deviceService.getPort(dst.deviceId(), dst.port());
325 // Create lightpath
326 // TODO: Ensure src & dst are of type OchPort
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700327 Intent opticalIntent = OpticalConnectivityIntent.builder()
328 .appId(appId)
329 .src(src)
330 .dst(dst)
331 .build();
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700332 intents.add(opticalIntent);
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700333 if (srcPort instanceof OduCltPort && dstPort instanceof OduCltPort) {
334 continue;
335 // also create OTN service
336 }
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700337 }
338
339 return intents;
340 }
341
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700342 /**
343 * Returns list of optical connectivity intents needed to create connectivity
344 * between ingress and egress.
345 *
346 * @param ingress the ingress connect point
347 * @param egress the egress connect point
348 * @return list of optical connectivity intents, empty list if no path was found
349 */
350 private List<Intent> getOpticalIntents(ConnectPoint ingress, ConnectPoint egress) {
Brian O'Connor772852a2014-11-17 15:51:19 -0800351 Set<Path> paths = pathService.getPaths(ingress.deviceId(),
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700352 egress.deviceId(),
353 new OpticalLinkWeight());
Brian O'Connor772852a2014-11-17 15:51:19 -0800354
355 if (paths.isEmpty()) {
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700356 return Collections.emptyList();
weibit7e583462014-10-23 10:14:05 -0700357 }
Brian O'Connor772852a2014-11-17 15:51:19 -0800358
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700359 // Search path with available cross connect points
360 for (Path path : paths) {
361 List<ConnectPoint> crossConnectPoints = getCrossConnectPoints(path);
Brian O'Connor772852a2014-11-17 15:51:19 -0800362
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700363 // Skip to next path if cross connect points are mismatched
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700364 if (!checkCrossConnectPoints(crossConnectPoints)) {
365 continue;
Brian O'Connor772852a2014-11-17 15:51:19 -0800366 }
367
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700368 return getIntents(crossConnectPoints);
Brian O'Connor772852a2014-11-17 15:51:19 -0800369 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800370
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700371 return Collections.emptyList();
Brian O'Connor772852a2014-11-17 15:51:19 -0800372 }
373
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700374 /**
375 * Link weight function that emphasizes re-use of packet links.
376 */
377 private class OpticalLinkWeight implements LinkWeight {
378 @Override
379 public double weight(TopologyEdge edge) {
380 // Ignore inactive links
381 if (edge.link().state() == Link.State.INACTIVE) {
382 return -1;
383 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800384
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700385 // TODO: Ignore cross connect links with used ports
386
387 // Transport links have highest weight
388 if (edge.link().type() == Link.Type.OPTICAL) {
389 return 1000;
390 }
391
392 // Packet links
393 return 1;
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800394 }
weibitf32383b2014-10-22 10:17:31 -0700395 }
Brian O'Connorc7bdd8c2014-12-08 01:29:53 -0800396
Marc De Leenheer1afa2a02015-05-13 09:18:07 -0700397 /**
398 * Handle withdrawn intent on each network layer.
399 *
400 * @param intent the withdrawn intent
401 */
402 private void withdrawIntent(Intent intent) {
403 if (intent instanceof OpticalConnectivityIntent) {
404 deviceResourceService.releasePorts(intent.id());
405 linkResourceService.releaseResources(linkResourceService.getAllocations(intent.id()));
406 }
407 // TODO: add other layers
408 }
Brian O'Connor772852a2014-11-17 15:51:19 -0800409 }
weibitf32383b2014-10-22 10:17:31 -0700410
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700411 /**
412 * Verifies if given link is cross-connect between packet and optical layer.
413 *
414 * @param link the link
415 * @return true if the link is a cross-connect link
416 */
417 public static boolean isCrossConnectLink(Link link) {
418 if (link.type() != Link.Type.OPTICAL) {
419 return false;
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800420 }
Marc De Leenheer16f857b2015-05-05 20:50:24 -0700421
422 checkNotNull(link.annotations());
423 checkNotNull(link.annotations().value("optical.type"));
424
425 if (link.annotations().value("optical.type").equals("cross-connect")) {
426 return true;
427 }
428
429 return false;
Brian O'Connor772852a2014-11-17 15:51:19 -0800430 }
Praseed Balakrishnanc0029652014-11-14 13:38:49 -0800431
weibitf32383b2014-10-22 10:17:31 -0700432}