Thomas Vachuska | 781d18b | 2014-10-27 10:31:25 -0700 | [diff] [blame] | 1 | /* |
Thomas Vachuska | 4f1a60c | 2014-10-28 13:39:07 -0700 | [diff] [blame] | 2 | * Copyright 2014 Open Networking Laboratory |
Thomas Vachuska | 781d18b | 2014-10-27 10:31:25 -0700 | [diff] [blame] | 3 | * |
Thomas Vachuska | 4f1a60c | 2014-10-28 13:39:07 -0700 | [diff] [blame] | 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 |
Thomas Vachuska | 781d18b | 2014-10-27 10:31:25 -0700 | [diff] [blame] | 7 | * |
Thomas Vachuska | 4f1a60c | 2014-10-28 13:39:07 -0700 | [diff] [blame] | 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. |
Thomas Vachuska | 781d18b | 2014-10-27 10:31:25 -0700 | [diff] [blame] | 15 | */ |
Brian O'Connor | abafb50 | 2014-12-02 22:26:20 -0800 | [diff] [blame] | 16 | package org.onosproject.optical.cfg; |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 17 | |
Jian Li | a5a312b | 2016-01-15 17:59:12 -0800 | [diff] [blame] | 18 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| 19 | import com.fasterxml.jackson.core.JsonParseException; |
| 20 | import com.fasterxml.jackson.databind.JsonMappingException; |
| 21 | import com.fasterxml.jackson.databind.JsonNode; |
| 22 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 23 | import org.onlab.packet.ChassisId; |
Brian O'Connor | abafb50 | 2014-12-02 22:26:20 -0800 | [diff] [blame] | 24 | import org.onosproject.net.ConnectPoint; |
| 25 | import org.onosproject.net.DefaultAnnotations; |
| 26 | import org.onosproject.net.Device; |
| 27 | import org.onosproject.net.DeviceId; |
| 28 | import org.onosproject.net.Link; |
| 29 | import org.onosproject.net.MastershipRole; |
| 30 | import org.onosproject.net.PortNumber; |
| 31 | import org.onosproject.net.device.DefaultDeviceDescription; |
| 32 | import org.onosproject.net.device.DeviceDescription; |
| 33 | import org.onosproject.net.device.DeviceProvider; |
| 34 | import org.onosproject.net.device.DeviceProviderRegistry; |
| 35 | import org.onosproject.net.device.DeviceProviderService; |
| 36 | import org.onosproject.net.link.DefaultLinkDescription; |
| 37 | import org.onosproject.net.link.LinkProvider; |
| 38 | import org.onosproject.net.link.LinkProviderRegistry; |
| 39 | import org.onosproject.net.link.LinkProviderService; |
| 40 | import org.onosproject.net.provider.AbstractProvider; |
| 41 | import org.onosproject.net.provider.ProviderId; |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 42 | import org.slf4j.Logger; |
| 43 | import org.slf4j.LoggerFactory; |
| 44 | |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 45 | import java.io.File; |
| 46 | import java.io.IOException; |
| 47 | import java.util.ArrayList; |
| 48 | import java.util.Iterator; |
| 49 | import java.util.List; |
| 50 | import java.util.Map; |
| 51 | import java.util.Set; |
| 52 | |
Brian O'Connor | abafb50 | 2014-12-02 22:26:20 -0800 | [diff] [blame] | 53 | import static org.onosproject.net.DeviceId.deviceId; |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 54 | |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 55 | /** |
| 56 | * OpticalConfigProvider emulates the SB network provider for optical switches, |
| 57 | * optical links and any other state that needs to be configured for correct network |
| 58 | * operations. |
| 59 | * |
Sho SHIMIZU | be63b23 | 2015-06-30 10:57:58 -0700 | [diff] [blame] | 60 | * @deprecated in Cardinal Release |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 61 | */ |
Thomas Vachuska | 112c703 | 2014-11-16 11:05:14 -0800 | [diff] [blame] | 62 | @Deprecated |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 63 | @JsonIgnoreProperties(ignoreUnknown = true) |
Praseed Balakrishnan | 0bb2d7d | 2014-11-06 12:29:09 -0800 | [diff] [blame] | 64 | //@Component(immediate = true) |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 65 | public class OpticalConfigProvider extends AbstractProvider implements DeviceProvider, LinkProvider { |
| 66 | |
| 67 | protected static final Logger log = LoggerFactory |
| 68 | .getLogger(OpticalConfigProvider.class); |
| 69 | |
| 70 | // TODO: fix hard coded file path later. |
| 71 | private static final String DEFAULT_CONFIG_FILE = |
Marc De Leenheer | 631ffce | 2014-10-28 16:29:07 -0700 | [diff] [blame] | 72 | "config/demo-3-roadm-2-ps.json"; |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 73 | private String configFileName = DEFAULT_CONFIG_FILE; |
| 74 | |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 75 | // @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 76 | protected LinkProviderRegistry linkProviderRegistry; |
| 77 | |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 78 | // @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 79 | protected DeviceProviderRegistry deviceProviderRegistry; |
| 80 | |
| 81 | private static final String OPTICAL_ANNOTATION = "optical."; |
| 82 | |
| 83 | private LinkProviderService linkProviderService; |
| 84 | private DeviceProviderService deviceProviderService; |
| 85 | |
| 86 | private static final List<Roadm> RAW_ROADMS = new ArrayList<>(); |
| 87 | private static final List<WdmLink> RAW_WDMLINKS = new ArrayList<>(); |
| 88 | private static final List<PktOptLink> RAW_PKTOPTLINKS = new ArrayList<>(); |
| 89 | |
| 90 | private static final String ROADM = "Roadm"; |
| 91 | private static final String WDM_LINK = "wdmLink"; |
| 92 | private static final String PKT_OPT_LINK = "pktOptLink"; |
| 93 | |
| 94 | protected OpticalNetworkConfig opticalNetworkConfig; |
| 95 | |
| 96 | public OpticalConfigProvider() { |
Brian O'Connor | abafb50 | 2014-12-02 22:26:20 -0800 | [diff] [blame] | 97 | super(new ProviderId("optical", "org.onosproject.provider" + |
Praseed Balakrishnan | 69d95be | 2014-10-22 13:55:05 -0700 | [diff] [blame] | 98 | ".opticalConfig")); |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 99 | } |
| 100 | |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 101 | // @Activate |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 102 | protected void activate() { |
| 103 | linkProviderService = linkProviderRegistry.register(this); |
| 104 | deviceProviderService = deviceProviderRegistry.register(this); |
| 105 | log.info("Starting optical network configuration process..."); |
| 106 | log.info("Optical config file set to {}", configFileName); |
| 107 | |
| 108 | loadOpticalConfig(); |
| 109 | parseOpticalConfig(); |
| 110 | publishOpticalConfig(); |
| 111 | } |
| 112 | |
Thomas Vachuska | badb93f | 2014-11-15 23:51:17 -0800 | [diff] [blame] | 113 | // @Deactivate |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 114 | protected void deactivate() { |
| 115 | linkProviderRegistry.unregister(this); |
| 116 | linkProviderService = null; |
| 117 | deviceProviderRegistry.unregister(this); |
| 118 | deviceProviderService = null; |
| 119 | RAW_ROADMS.clear(); |
| 120 | RAW_WDMLINKS.clear(); |
| 121 | RAW_PKTOPTLINKS.clear(); |
| 122 | log.info("Stopped"); |
| 123 | } |
| 124 | |
| 125 | private void loadOpticalConfig() { |
| 126 | ObjectMapper mapper = new ObjectMapper(); |
| 127 | opticalNetworkConfig = new OpticalNetworkConfig(); |
| 128 | try { |
| 129 | opticalNetworkConfig = mapper.readValue(new File(configFileName), OpticalNetworkConfig.class); |
| 130 | } catch (JsonParseException e) { |
| 131 | String err = String.format("JsonParseException while loading network " |
| 132 | + "config from file: %s: %s", configFileName, e.getMessage()); |
| 133 | log.error(err, e); |
| 134 | } catch (JsonMappingException e) { |
| 135 | String err = String.format( |
| 136 | "JsonMappingException while loading network config " |
| 137 | + "from file: %s: %s", configFileName, e.getMessage()); |
| 138 | log.error(err, e); |
| 139 | } catch (IOException e) { |
| 140 | String err = String.format("IOException while loading network config " |
| 141 | + "from file: %s %s", configFileName, e.getMessage()); |
| 142 | log.error(err, e); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | private void parseOpticalConfig() { |
| 147 | List<OpticalSwitchDescription> swList = opticalNetworkConfig.getOpticalSwitches(); |
| 148 | List<OpticalLinkDescription> lkList = opticalNetworkConfig.getOpticalLinks(); |
| 149 | |
| 150 | for (OpticalSwitchDescription sw : swList) { |
| 151 | String swtype = sw.getType(); |
| 152 | boolean allow = sw.isAllowed(); |
| 153 | if (swtype.equals(ROADM) && allow) { |
| 154 | int regNum = 0; |
| 155 | Set<Map.Entry<String, JsonNode>> m = sw.params.entrySet(); |
| 156 | for (Map.Entry<String, JsonNode> e : m) { |
| 157 | String key = e.getKey(); |
| 158 | JsonNode j = e.getValue(); |
| 159 | if (key.equals("numRegen")) { |
| 160 | regNum = j.asInt(); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | Roadm newRoadm = new Roadm(); |
| 165 | newRoadm.setName(sw.name); |
| 166 | newRoadm.setNodeId(sw.nodeDpid); |
| 167 | newRoadm.setLongtitude(sw.longitude); |
| 168 | newRoadm.setLatitude(sw.latitude); |
| 169 | newRoadm.setRegenNum(regNum); |
| 170 | |
| 171 | RAW_ROADMS.add(newRoadm); |
| 172 | log.info(newRoadm.toString()); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | for (OpticalLinkDescription lk : lkList) { |
| 177 | String lktype = lk.getType(); |
| 178 | switch (lktype) { |
| 179 | case WDM_LINK: |
| 180 | WdmLink newWdmLink = new WdmLink(); |
| 181 | newWdmLink.setSrcNodeId(lk.getNodeDpid1()); |
| 182 | newWdmLink.setSnkNodeId(lk.getNodeDpid2()); |
| 183 | newWdmLink.setAdminWeight(1000); // default weight for each WDM link. |
| 184 | Set<Map.Entry<String, JsonNode>> m = lk.params.entrySet(); |
| 185 | for (Map.Entry<String, JsonNode> e : m) { |
| 186 | String key = e.getKey(); |
| 187 | JsonNode j = e.getValue(); |
| 188 | if (key.equals("nodeName1")) { |
| 189 | newWdmLink.setSrcNodeName(j.asText()); |
| 190 | } else if (key.equals("nodeName2")) { |
| 191 | newWdmLink.setSnkNodeName(j.asText()); |
| 192 | } else if (key.equals("port1")) { |
| 193 | newWdmLink.setSrcPort(j.asInt()); |
| 194 | } else if (key.equals("port2")) { |
| 195 | newWdmLink.setSnkPort(j.asInt()); |
| 196 | } else if (key.equals("distKms")) { |
| 197 | newWdmLink.setDistance(j.asDouble()); |
| 198 | } else if (key.equals("numWaves")) { |
| 199 | newWdmLink.setWavelengthNumber(j.asInt()); |
| 200 | } else { |
| 201 | log.error("error found"); |
| 202 | // TODO add exception processing; |
| 203 | } |
| 204 | } |
| 205 | RAW_WDMLINKS.add(newWdmLink); |
| 206 | log.info(newWdmLink.toString()); |
| 207 | |
| 208 | break; |
| 209 | |
| 210 | case PKT_OPT_LINK: |
| 211 | PktOptLink newPktOptLink = new PktOptLink(); |
| 212 | newPktOptLink.setSrcNodeId(lk.getNodeDpid1()); |
| 213 | newPktOptLink.setSnkNodeId(lk.getNodeDpid2()); |
| 214 | newPktOptLink.setAdminWeight(10); // default weight for each packet-optical link. |
| 215 | Set<Map.Entry<String, JsonNode>> ptm = lk.params.entrySet(); |
| 216 | for (Map.Entry<String, JsonNode> e : ptm) { |
| 217 | String key = e.getKey(); |
| 218 | JsonNode j = e.getValue(); |
| 219 | if (key.equals("nodeName1")) { |
| 220 | newPktOptLink.setSrcNodeName(j.asText()); |
| 221 | } else if (key.equals("nodeName2")) { |
| 222 | newPktOptLink.setSnkNodeName(j.asText()); |
| 223 | } else if (key.equals("port1")) { |
| 224 | newPktOptLink.setSrcPort(j.asInt()); |
| 225 | } else if (key.equals("port2")) { |
| 226 | newPktOptLink.setSnkPort(j.asInt()); |
| 227 | } else if (key.equals("bandWidth")) { |
| 228 | newPktOptLink.setBandwdith(j.asDouble()); |
| 229 | } else { |
| 230 | log.error("error found"); |
| 231 | // TODO add exception processing; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | RAW_PKTOPTLINKS.add(newPktOptLink); |
| 236 | log.info(newPktOptLink.toString()); |
| 237 | break; |
| 238 | default: |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | private void publishOpticalConfig() { |
| 244 | if (deviceProviderService == null || linkProviderService == null) { |
| 245 | return; |
| 246 | } |
| 247 | |
| 248 | // Discover the optical ROADM objects |
| 249 | Iterator<Roadm> iterWdmNode = RAW_ROADMS.iterator(); |
| 250 | while (iterWdmNode.hasNext()) { |
| 251 | Roadm value = iterWdmNode.next(); |
| 252 | DeviceId did = deviceId("of:" + value.getNodeId().replace(":", "")); |
Praseed Balakrishnan | 69d95be | 2014-10-22 13:55:05 -0700 | [diff] [blame] | 253 | ChassisId cid = new ChassisId(); |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 254 | DefaultAnnotations extendedAttributes = DefaultAnnotations.builder() |
| 255 | .set(OPTICAL_ANNOTATION + "switchType", "ROADM") |
| 256 | .set(OPTICAL_ANNOTATION + "switchName", value.getName()) |
| 257 | .set(OPTICAL_ANNOTATION + "latitude", Double.toString(value.getLatitude())) |
| 258 | .set(OPTICAL_ANNOTATION + "longtitude", Double.toString(value.getLongtitude())) |
| 259 | .set(OPTICAL_ANNOTATION + "regNum", Integer.toString(value.getRegenNum())) |
| 260 | .build(); |
| 261 | |
| 262 | DeviceDescription description = |
| 263 | new DefaultDeviceDescription(did.uri(), |
| 264 | Device.Type.SWITCH, |
| 265 | "", |
| 266 | "", |
| 267 | "", |
| 268 | "", |
| 269 | cid, |
| 270 | extendedAttributes); |
| 271 | deviceProviderService.deviceConnected(did, description); |
| 272 | } |
| 273 | |
| 274 | // Discover the optical WDM link objects |
| 275 | Iterator<WdmLink> iterWdmlink = RAW_WDMLINKS.iterator(); |
| 276 | while (iterWdmlink.hasNext()) { |
| 277 | WdmLink value = iterWdmlink.next(); |
| 278 | |
| 279 | DeviceId srcNodeId = deviceId("of:" + value.getSrcNodeId().replace(":", "")); |
| 280 | DeviceId snkNodeId = deviceId("of:" + value.getSnkNodeId().replace(":", "")); |
| 281 | |
| 282 | PortNumber srcPort = PortNumber.portNumber(value.getSrcPort()); |
| 283 | PortNumber snkPort = PortNumber.portNumber(value.getSnkPort()); |
| 284 | |
| 285 | ConnectPoint srcPoint = new ConnectPoint(srcNodeId, srcPort); |
| 286 | ConnectPoint snkPoint = new ConnectPoint(snkNodeId, snkPort); |
| 287 | |
| 288 | DefaultAnnotations extendedAttributes = DefaultAnnotations.builder() |
| 289 | .set(OPTICAL_ANNOTATION + "linkType", "WDM") |
| 290 | .set(OPTICAL_ANNOTATION + "distance", Double.toString(value.getDistance())) |
| 291 | .set(OPTICAL_ANNOTATION + "cost", Double.toString(value.getDistance())) |
| 292 | .set(OPTICAL_ANNOTATION + "adminWeight", Double.toString(value.getAdminWeight())) |
| 293 | .set(OPTICAL_ANNOTATION + "wavelengthNum", Integer.toString(value.getWavelengthNumber())) |
| 294 | .build(); |
| 295 | |
| 296 | DefaultLinkDescription linkDescription = |
| 297 | new DefaultLinkDescription(srcPoint, |
| 298 | snkPoint, |
Thomas Vachuska | 0e752bd | 2014-10-22 22:33:41 -0700 | [diff] [blame] | 299 | Link.Type.OPTICAL, |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 300 | extendedAttributes); |
| 301 | |
| 302 | linkProviderService.linkDetected(linkDescription); |
| 303 | log.info(String.format("WDM link: %s : %s", |
| 304 | linkDescription.src().toString(), linkDescription.dst().toString())); |
weibit | aca1460 | 2014-10-24 10:26:26 -0700 | [diff] [blame] | 305 | |
| 306 | |
| 307 | DefaultLinkDescription linkDescriptionReverse = |
| 308 | new DefaultLinkDescription(snkPoint, |
| 309 | srcPoint, |
| 310 | Link.Type.OPTICAL, |
| 311 | extendedAttributes); |
| 312 | |
| 313 | linkProviderService.linkDetected(linkDescriptionReverse); |
| 314 | log.info(String.format("WDM link: %s : %s", |
| 315 | linkDescriptionReverse.src().toString(), linkDescriptionReverse.dst().toString())); |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 316 | } |
| 317 | |
| 318 | // Discover the packet optical link objects |
| 319 | Iterator<PktOptLink> iterPktOptlink = RAW_PKTOPTLINKS.iterator(); |
| 320 | while (iterPktOptlink.hasNext()) { |
| 321 | PktOptLink value = iterPktOptlink.next(); |
| 322 | DeviceId srcNodeId = deviceId("of:" + value.getSrcNodeId().replace(":", "")); |
| 323 | DeviceId snkNodeId = deviceId("of:" + value.getSnkNodeId().replace(":", "")); |
| 324 | |
| 325 | PortNumber srcPort = PortNumber.portNumber(value.getSrcPort()); |
| 326 | PortNumber snkPort = PortNumber.portNumber(value.getSnkPort()); |
| 327 | |
| 328 | ConnectPoint srcPoint = new ConnectPoint(srcNodeId, srcPort); |
| 329 | ConnectPoint snkPoint = new ConnectPoint(snkNodeId, snkPort); |
| 330 | |
| 331 | DefaultAnnotations extendedAttributes = DefaultAnnotations.builder() |
| 332 | .set(OPTICAL_ANNOTATION + "linkType", "PktOptLink") |
| 333 | .set(OPTICAL_ANNOTATION + "bandwidth", Double.toString(value.getBandwidth())) |
| 334 | .set(OPTICAL_ANNOTATION + "cost", Double.toString(value.getBandwidth())) |
| 335 | .set(OPTICAL_ANNOTATION + "adminWeight", Double.toString(value.getAdminWeight())) |
| 336 | .build(); |
| 337 | |
| 338 | DefaultLinkDescription linkDescription = |
| 339 | new DefaultLinkDescription(srcPoint, |
| 340 | snkPoint, |
Thomas Vachuska | 0e752bd | 2014-10-22 22:33:41 -0700 | [diff] [blame] | 341 | Link.Type.OPTICAL, |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 342 | extendedAttributes); |
| 343 | |
| 344 | linkProviderService.linkDetected(linkDescription); |
| 345 | log.info(String.format("Packet-optical link: %s : %s", |
| 346 | linkDescription.src().toString(), linkDescription.dst().toString())); |
weibit | aca1460 | 2014-10-24 10:26:26 -0700 | [diff] [blame] | 347 | |
| 348 | DefaultLinkDescription linkDescriptionReverse = |
| 349 | new DefaultLinkDescription(snkPoint, |
| 350 | srcPoint, |
| 351 | Link.Type.OPTICAL, |
| 352 | extendedAttributes); |
| 353 | |
| 354 | linkProviderService.linkDetected(linkDescriptionReverse); |
| 355 | log.info(String.format("Packet-optical link: %s : %s", |
| 356 | linkDescriptionReverse.src().toString(), linkDescriptionReverse.dst().toString())); |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 357 | } |
| 358 | |
| 359 | } |
| 360 | |
| 361 | @Override |
Ayaka Koshibe | 78bcbc1 | 2014-11-19 14:28:58 -0800 | [diff] [blame] | 362 | public void triggerProbe(DeviceId deviceId) { |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 363 | // TODO We may want to consider re-reading config files and publishing them based on this event. |
| 364 | } |
| 365 | |
| 366 | @Override |
Yuta HIGUCHI | 5481532 | 2014-10-31 23:17:08 -0700 | [diff] [blame] | 367 | public void roleChanged(DeviceId device, MastershipRole newRole) { |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 368 | } |
| 369 | |
Ayaka Koshibe | e60d452 | 2014-10-28 15:07:00 -0700 | [diff] [blame] | 370 | @Override |
Yuta HIGUCHI | 5481532 | 2014-10-31 23:17:08 -0700 | [diff] [blame] | 371 | public boolean isReachable(DeviceId device) { |
Ayaka Koshibe | e60d452 | 2014-10-28 15:07:00 -0700 | [diff] [blame] | 372 | return false; |
| 373 | } |
weibit | 38c42ed | 2014-10-09 19:03:54 -0700 | [diff] [blame] | 374 | } |