blob: 7f472adfd49d062bd89d5dfbd99e983f8203a0d1 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2014-present Open Networking Foundation
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.provider.of.flow.impl;
alshabib1cc04f72014-09-16 16:09:58 -070017
Thomas Vachuska75aaa672015-04-29 12:24:43 -070018import com.google.common.cache.Cache;
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.RemovalCause;
21import com.google.common.cache.RemovalNotification;
Madan Jampani84382b92016-06-22 08:26:49 -070022import com.google.common.collect.ImmutableSet;
23import com.google.common.collect.Lists;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070024import com.google.common.collect.Maps;
25import com.google.common.collect.Sets;
Madan Jampani84382b92016-06-22 08:26:49 -070026
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -070027import io.netty.buffer.ByteBuf;
28import io.netty.buffer.Unpooled;
alshabib1cc04f72014-09-16 16:09:58 -070029import org.apache.felix.scr.annotations.Activate;
30import org.apache.felix.scr.annotations.Component;
31import org.apache.felix.scr.annotations.Deactivate;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070032import org.apache.felix.scr.annotations.Modified;
33import org.apache.felix.scr.annotations.Property;
alshabib1cc04f72014-09-16 16:09:58 -070034import org.apache.felix.scr.annotations.Reference;
35import org.apache.felix.scr.annotations.ReferenceCardinality;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070036import org.onosproject.cfg.ComponentConfigService;
Brian O'Connorabafb502014-12-02 22:26:20 -080037import org.onosproject.core.ApplicationId;
38import org.onosproject.net.DeviceId;
Jonathan Hart3c259162015-10-21 21:31:19 -070039import org.onosproject.net.driver.DriverService;
Brian O'Connorabafb502014-12-02 22:26:20 -080040import org.onosproject.net.flow.CompletedBatchOperation;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070041import org.onosproject.net.flow.DefaultTableStatisticsEntry;
Brian O'Connorabafb502014-12-02 22:26:20 -080042import org.onosproject.net.flow.FlowEntry;
43import org.onosproject.net.flow.FlowRule;
Ray Milkey7bf273c2017-09-27 16:15:15 -070044import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
45import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -070046import org.onosproject.net.flow.FlowRuleExtPayLoad;
Brian O'Connorabafb502014-12-02 22:26:20 -080047import org.onosproject.net.flow.FlowRuleProvider;
48import org.onosproject.net.flow.FlowRuleProviderRegistry;
49import org.onosproject.net.flow.FlowRuleProviderService;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070050import org.onosproject.net.flow.TableStatisticsEntry;
Brian O'Connorabafb502014-12-02 22:26:20 -080051import org.onosproject.net.provider.AbstractProvider;
52import org.onosproject.net.provider.ProviderId;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070053import org.onosproject.net.statistic.DefaultLoad;
Brian O'Connorabafb502014-12-02 22:26:20 -080054import org.onosproject.openflow.controller.Dpid;
55import org.onosproject.openflow.controller.OpenFlowController;
56import org.onosproject.openflow.controller.OpenFlowEventListener;
57import org.onosproject.openflow.controller.OpenFlowSwitch;
58import org.onosproject.openflow.controller.OpenFlowSwitchListener;
59import org.onosproject.openflow.controller.RoleState;
jcc3d4e14a2015-04-21 11:32:05 +080060import org.onosproject.openflow.controller.ThirdPartyMessage;
Thomas Vachuska95caba32016-04-04 10:42:05 -070061import org.onosproject.provider.of.flow.util.FlowEntryBuilder;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070062import org.osgi.service.component.ComponentContext;
Thomas Vachuska3358af22015-05-19 18:40:34 -070063import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
alshabib902d41b2014-10-07 16:52:05 -070064import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
65import org.projectfloodlight.openflow.protocol.OFErrorMsg;
Cem Türker3baff672017-10-12 15:09:01 +030066import org.projectfloodlight.openflow.protocol.OFFlowLightweightStatsReply;
alshabib193525b2014-10-08 18:58:03 -070067import org.projectfloodlight.openflow.protocol.OFFlowMod;
alshabib8f1cf4a2014-09-17 14:44:48 -070068import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
alshabib5c370ff2014-09-18 10:12:14 -070069import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
alshabib8f1cf4a2014-09-17 14:44:48 -070070import org.projectfloodlight.openflow.protocol.OFMessage;
71import org.projectfloodlight.openflow.protocol.OFPortStatus;
alshabib5c370ff2014-09-18 10:12:14 -070072import org.projectfloodlight.openflow.protocol.OFStatsReply;
sangho89bf6fb2015-02-09 09:33:13 -080073import org.projectfloodlight.openflow.protocol.OFStatsType;
Jonathan Hart3c259162015-10-21 21:31:19 -070074import org.projectfloodlight.openflow.protocol.OFTableStatsEntry;
75import org.projectfloodlight.openflow.protocol.OFTableStatsReply;
Prince Pereira141ed812016-09-02 19:03:18 +053076import org.projectfloodlight.openflow.protocol.OFType;
77import org.projectfloodlight.openflow.protocol.OFVersion;
Prince Pereira788797e2016-08-10 11:24:14 +053078import org.projectfloodlight.openflow.protocol.errormsg.OFBadActionErrorMsg;
79import org.projectfloodlight.openflow.protocol.errormsg.OFBadInstructionErrorMsg;
80import org.projectfloodlight.openflow.protocol.errormsg.OFBadMatchErrorMsg;
Thomas Vachuska3358af22015-05-19 18:40:34 -070081import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
alshabib193525b2014-10-08 18:58:03 -070082import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
Prince Pereira141ed812016-09-02 19:03:18 +053083import org.projectfloodlight.openflow.types.U16;
84import org.projectfloodlight.openflow.types.U64;
alshabib1cc04f72014-09-16 16:09:58 -070085import org.slf4j.Logger;
86
Thomas Vachuska75aaa672015-04-29 12:24:43 -070087import java.util.Collections;
88import java.util.Dictionary;
89import java.util.List;
90import java.util.Map;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070091import java.util.Objects;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070092import java.util.Optional;
93import java.util.Set;
94import java.util.Timer;
95import java.util.concurrent.TimeUnit;
96import java.util.stream.Collectors;
97
ssyoon9030fbcd92015-08-17 10:42:07 +090098import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070099import static com.google.common.base.Strings.isNullOrEmpty;
100import static org.onlab.util.Tools.get;
101import static org.slf4j.LoggerFactory.getLogger;
alshabibeec3a062014-09-17 18:01:26 -0700102
alshabib1cc04f72014-09-16 16:09:58 -0700103/**
jcc3d4e14a2015-04-21 11:32:05 +0800104 * Provider which uses an OpenFlow controller to detect network end-station
105 * hosts.
alshabib1cc04f72014-09-16 16:09:58 -0700106 */
107@Component(immediate = true)
jcc3d4e14a2015-04-21 11:32:05 +0800108public class OpenFlowRuleProvider extends AbstractProvider
109 implements FlowRuleProvider {
alshabib1cc04f72014-09-16 16:09:58 -0700110
111 private final Logger log = getLogger(getClass());
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected FlowRuleProviderRegistry providerRegistry;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected OpenFlowController controller;
118
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected ComponentConfigService cfgService;
121
Jonathan Hart3c259162015-10-21 21:31:19 -0700122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected DriverService driverService;
124
ssyoon9030fbcd92015-08-17 10:42:07 +0900125 private static final int DEFAULT_POLL_FREQUENCY = 5;
Prince Pereira141ed812016-09-02 19:03:18 +0530126 private static final int MIN_EXPECTED_BYTE_LEN = 56;
127 private static final int SKIP_BYTES = 4;
128 private static final boolean DEFAULT_ADAPTIVE_FLOW_SAMPLING = false;
129
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700130 @Property(name = "flowPollFrequency", intValue = DEFAULT_POLL_FREQUENCY,
131 label = "Frequency (in seconds) for polling flow statistics")
132 private int flowPollFrequency = DEFAULT_POLL_FREQUENCY;
133
ssyoon9030fbcd92015-08-17 10:42:07 +0900134 @Property(name = "adaptiveFlowSampling", boolValue = DEFAULT_ADAPTIVE_FLOW_SAMPLING,
135 label = "Adaptive Flow Sampling is on or off")
136 private boolean adaptiveFlowSampling = DEFAULT_ADAPTIVE_FLOW_SAMPLING;
137
alshabib1cc04f72014-09-16 16:09:58 -0700138 private FlowRuleProviderService providerService;
139
alshabibeec3a062014-09-17 18:01:26 -0700140 private final InternalFlowProvider listener = new InternalFlowProvider();
141
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800142 private Cache<Long, InternalCacheEntry> pendingBatches;
alshabib193525b2014-10-08 18:58:03 -0700143
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900144 private final Timer timer = new Timer("onos-openflow-collector");
145
Cem Türker3baff672017-10-12 15:09:01 +0300146
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900147 // Old simple collector set
Madan Jampani6b266102016-06-23 00:56:36 -0700148 private final Map<Dpid, FlowStatsCollector> simpleCollectors = Maps.newConcurrentMap();
ssyoon9030fbcd92015-08-17 10:42:07 +0900149
150 // NewAdaptiveFlowStatsCollector Set
Madan Jampani6b266102016-06-23 00:56:36 -0700151 private final Map<Dpid, NewAdaptiveFlowStatsCollector> afsCollectors = Maps.newConcurrentMap();
152 private final Map<Dpid, TableStatisticsCollector> tableStatsCollectors = Maps.newConcurrentMap();
alshabib3d643ec2014-10-22 18:33:00 -0700153
alshabib1cc04f72014-09-16 16:09:58 -0700154 /**
155 * Creates an OpenFlow host provider.
156 */
157 public OpenFlowRuleProvider() {
Brian O'Connorabafb502014-12-02 22:26:20 -0800158 super(new ProviderId("of", "org.onosproject.provider.openflow"));
alshabib1cc04f72014-09-16 16:09:58 -0700159 }
160
161 @Activate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700162 protected void activate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700163 cfgService.registerProperties(getClass());
alshabib1cc04f72014-09-16 16:09:58 -0700164 providerService = providerRegistry.register(this);
alshabibeec3a062014-09-17 18:01:26 -0700165 controller.addListener(listener);
166 controller.addEventListener(listener);
alshabib3d643ec2014-10-22 18:33:00 -0700167
Antonio Marsico1c5ae1f2015-12-15 15:31:56 +0100168 modified(context);
169
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700170 pendingBatches = createBatchCache();
ssyoon9030fbcd92015-08-17 10:42:07 +0900171
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700172 createCollectors();
alshabib3d643ec2014-10-22 18:33:00 -0700173
ssyoon9030fbcd92015-08-17 10:42:07 +0900174 log.info("Started with flowPollFrequency = {}, adaptiveFlowSampling = {}",
175 flowPollFrequency, adaptiveFlowSampling);
alshabib1cc04f72014-09-16 16:09:58 -0700176 }
177
178 @Deactivate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700179 protected void deactivate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700180 cfgService.unregisterProperties(getClass(), false);
181 stopCollectors();
alshabib1cc04f72014-09-16 16:09:58 -0700182 providerRegistry.unregister(this);
183 providerService = null;
184
185 log.info("Stopped");
186 }
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800187
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700188 @Modified
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700189 protected void modified(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700190 Dictionary<?, ?> properties = context.getProperties();
191 int newFlowPollFrequency;
192 try {
193 String s = get(properties, "flowPollFrequency");
194 newFlowPollFrequency = isNullOrEmpty(s) ? flowPollFrequency : Integer.parseInt(s.trim());
195
196 } catch (NumberFormatException | ClassCastException e) {
197 newFlowPollFrequency = flowPollFrequency;
198 }
199
200 if (newFlowPollFrequency != flowPollFrequency) {
201 flowPollFrequency = newFlowPollFrequency;
202 adjustRate();
203 }
204
205 log.info("Settings: flowPollFrequency={}", flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900206
207 boolean newAdaptiveFlowSampling;
208 String s = get(properties, "adaptiveFlowSampling");
209 newAdaptiveFlowSampling = isNullOrEmpty(s) ? adaptiveFlowSampling : Boolean.parseBoolean(s.trim());
210
211 if (newAdaptiveFlowSampling != adaptiveFlowSampling) {
212 // stop previous collector
213 stopCollectors();
214 adaptiveFlowSampling = newAdaptiveFlowSampling;
215 // create new collectors
216 createCollectors();
217 }
218
219 log.info("Settings: adaptiveFlowSampling={}", adaptiveFlowSampling);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700220 }
221
222 private Cache<Long, InternalCacheEntry> createBatchCache() {
223 return CacheBuilder.newBuilder()
224 .expireAfterWrite(10, TimeUnit.SECONDS)
225 .removalListener((RemovalNotification<Long, InternalCacheEntry> notification) -> {
226 if (notification.getCause() == RemovalCause.EXPIRED) {
227 providerService.batchOperationCompleted(notification.getKey(),
228 notification.getValue().failedCompletion());
229 }
230 }).build();
231 }
232
233 private void createCollectors() {
234 controller.getSwitches().forEach(this::createCollector);
235 }
236
237 private void createCollector(OpenFlowSwitch sw) {
Kavitha Alagesan6704df32016-08-18 15:15:31 +0530238 if (sw == null) {
239 return;
240 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900241 if (adaptiveFlowSampling) {
242 // NewAdaptiveFlowStatsCollector Constructor
Charles Chan14967c22015-12-07 11:11:50 -0800243 NewAdaptiveFlowStatsCollector fsc =
244 new NewAdaptiveFlowStatsCollector(driverService, sw, flowPollFrequency);
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700245 stopCollectorIfNeeded(afsCollectors.put(new Dpid(sw.getId()), fsc));
Palash Kalaa439afe2017-05-16 14:53:15 +0900246 fsc.start();
ssyoon9030fbcd92015-08-17 10:42:07 +0900247 } else {
248 FlowStatsCollector fsc = new FlowStatsCollector(timer, sw, flowPollFrequency);
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700249 stopCollectorIfNeeded(simpleCollectors.put(new Dpid(sw.getId()), fsc));
Palash Kalaa439afe2017-05-16 14:53:15 +0900250 fsc.start();
ssyoon9030fbcd92015-08-17 10:42:07 +0900251 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700252 TableStatisticsCollector tsc = new TableStatisticsCollector(timer, sw, flowPollFrequency);
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700253 stopCollectorIfNeeded(tableStatsCollectors.put(new Dpid(sw.getId()), tsc));
Palash Kalaa439afe2017-05-16 14:53:15 +0900254 tsc.start();
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700255 }
256
257 private void stopCollectorIfNeeded(SwitchDataCollector collector) {
258 if (collector != null) {
259 collector.stop();
260 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700261 }
262
263 private void stopCollectors() {
ssyoon9030fbcd92015-08-17 10:42:07 +0900264 if (adaptiveFlowSampling) {
265 // NewAdaptiveFlowStatsCollector Destructor
266 afsCollectors.values().forEach(NewAdaptiveFlowStatsCollector::stop);
267 afsCollectors.clear();
268 } else {
269 simpleCollectors.values().forEach(FlowStatsCollector::stop);
270 simpleCollectors.clear();
271 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700272 tableStatsCollectors.values().forEach(TableStatisticsCollector::stop);
273 tableStatsCollectors.clear();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700274 }
275
276 private void adjustRate() {
277 DefaultLoad.setPollInterval(flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900278 if (adaptiveFlowSampling) {
279 // NewAdaptiveFlowStatsCollector calAndPollInterval
280 afsCollectors.values().forEach(fsc -> fsc.adjustCalAndPollInterval(flowPollFrequency));
281 } else {
282 simpleCollectors.values().forEach(fsc -> fsc.adjustPollInterval(flowPollFrequency));
283 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700284 tableStatsCollectors.values().forEach(tsc -> tsc.adjustPollInterval(flowPollFrequency));
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700285 }
286
alshabib1cc04f72014-09-16 16:09:58 -0700287 @Override
288 public void applyFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800289 for (FlowRule flowRule : flowRules) {
290 applyRule(flowRule);
alshabib35edb1a2014-09-16 17:44:44 -0700291 }
alshabib1cc04f72014-09-16 16:09:58 -0700292 }
293
alshabib35edb1a2014-09-16 17:44:44 -0700294 private void applyRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900295 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
296 OpenFlowSwitch sw = controller.getSwitch(dpid);
297
Ray Milkey0ae473d2016-04-04 10:56:47 -0700298 if (sw == null) {
299 return;
300 }
301
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700302 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
303 if (hasPayload(flowRuleExtPayLoad)) {
304 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800305 sw.sendMsg(msg);
306 return;
307 }
alshabibbdcbb102015-04-22 14:16:38 -0700308 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700309 Optional.empty(), Optional.of(driverService)).buildFlowAdd());
alshabib35edb1a2014-09-16 17:44:44 -0700310 }
311
alshabib1cc04f72014-09-16 16:09:58 -0700312 @Override
313 public void removeFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800314 for (FlowRule flowRule : flowRules) {
315 removeRule(flowRule);
alshabib219ebaa2014-09-22 15:41:24 -0700316 }
alshabib1cc04f72014-09-16 16:09:58 -0700317 }
318
alshabib219ebaa2014-09-22 15:41:24 -0700319 private void removeRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900320 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
321 OpenFlowSwitch sw = controller.getSwitch(dpid);
322
Ray Milkey0ae473d2016-04-04 10:56:47 -0700323 if (sw == null) {
324 return;
325 }
326
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700327 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
328 if (hasPayload(flowRuleExtPayLoad)) {
329 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800330 sw.sendMsg(msg);
331 return;
332 }
alshabibbdcbb102015-04-22 14:16:38 -0700333 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700334 Optional.empty(), Optional.of(driverService)).buildFlowDel());
alshabib219ebaa2014-09-22 15:41:24 -0700335 }
336
alshabiba68eb962014-09-24 20:34:13 -0700337 @Override
338 public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
339 // TODO: optimize using the ApplicationId
340 removeFlowRule(flowRules);
341 }
342
alshabib193525b2014-10-08 18:58:03 -0700343 @Override
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800344 public void executeBatch(FlowRuleBatchOperation batch) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900345 checkNotNull(batch);
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800346
ssyoon9030fbcd92015-08-17 10:42:07 +0900347 Dpid dpid = Dpid.dpid(batch.deviceId().uri());
348 OpenFlowSwitch sw = controller.getSwitch(dpid);
Madan Jampani84382b92016-06-22 08:26:49 -0700349
350 // If switch no longer exists, simply return.
351 if (sw == null) {
352 Set<FlowRule> failures = ImmutableSet.copyOf(Lists.transform(batch.getOperations(), e -> e.target()));
353 providerService.batchOperationCompleted(batch.id(),
354 new CompletedBatchOperation(false, failures, batch.deviceId()));
355 return;
356 }
357 pendingBatches.put(batch.id(), new InternalCacheEntry(batch));
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800358 OFFlowMod mod;
alshabib193525b2014-10-08 18:58:03 -0700359 for (FlowRuleBatchEntry fbe : batch.getOperations()) {
jcc3d4e14a2015-04-21 11:32:05 +0800360 // flow is the third party privacy flow
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700361
362 FlowRuleExtPayLoad flowRuleExtPayLoad = fbe.target().payLoad();
363 if (hasPayload(flowRuleExtPayLoad)) {
364 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800365 sw.sendMsg(msg);
366 continue;
367 }
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700368 FlowModBuilder builder =
Jonathan Hart3c259162015-10-21 21:31:19 -0700369 FlowModBuilder.builder(fbe.target(), sw.factory(),
370 Optional.of(batch.id()), Optional.of(driverService));
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700371 NewAdaptiveFlowStatsCollector collector = afsCollectors.get(dpid);
Sho SHIMIZUaba9d002015-01-29 14:51:04 -0800372 switch (fbe.operator()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700373 case ADD:
374 mod = builder.buildFlowAdd();
375 break;
376 case REMOVE:
377 mod = builder.buildFlowDel();
378 break;
379 case MODIFY:
380 mod = builder.buildFlowMod();
381 break;
382 default:
383 log.error("Unsupported batch operation {}; skipping flowmod {}",
ssyoon9030fbcd92015-08-17 10:42:07 +0900384 fbe.operator(), fbe);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700385 continue;
jcc3d4e14a2015-04-21 11:32:05 +0800386 }
Saurav Das3ea46622015-04-22 14:01:34 -0700387 sw.sendMsg(mod);
alshabib193525b2014-10-08 18:58:03 -0700388 }
jcc3d4e14a2015-04-21 11:32:05 +0800389 OFBarrierRequest.Builder builder = sw.factory().buildBarrierRequest()
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800390 .setXid(batch.id());
391 sw.sendMsg(builder.build());
alshabib193525b2014-10-08 18:58:03 -0700392 }
393
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700394 private boolean hasPayload(FlowRuleExtPayLoad flowRuleExtPayLoad) {
395 return flowRuleExtPayLoad != null &&
396 flowRuleExtPayLoad.payLoad() != null &&
397 flowRuleExtPayLoad.payLoad().length > 0;
398 }
399
alshabib8f1cf4a2014-09-17 14:44:48 -0700400 private class InternalFlowProvider
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800401 implements OpenFlowSwitchListener, OpenFlowEventListener {
alshabib8f1cf4a2014-09-17 14:44:48 -0700402
alshabib8f1cf4a2014-09-17 14:44:48 -0700403 @Override
404 public void switchAdded(Dpid dpid) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700405 createCollector(controller.getSwitch(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700406 }
407
408 @Override
409 public void switchRemoved(Dpid dpid) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900410 if (adaptiveFlowSampling) {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700411 stopCollectorIfNeeded(afsCollectors.remove(dpid));
ssyoon9030fbcd92015-08-17 10:42:07 +0900412 } else {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700413 stopCollectorIfNeeded(simpleCollectors.remove(dpid));
alshabibdfc7afb2014-10-21 20:13:27 -0700414 }
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700415 stopCollectorIfNeeded(tableStatsCollectors.remove(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700416 }
417
418 @Override
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700419 public void switchChanged(Dpid dpid) {
420 }
421
422 @Override
alshabib8f1cf4a2014-09-17 14:44:48 -0700423 public void portChanged(Dpid dpid, OFPortStatus status) {
jcc3d4e14a2015-04-21 11:32:05 +0800424 // TODO: Decide whether to evict flows internal store.
alshabib8f1cf4a2014-09-17 14:44:48 -0700425 }
426
427 @Override
428 public void handleMessage(Dpid dpid, OFMessage msg) {
Ray Milkeyada9e2d2016-04-05 16:42:35 -0700429 if (providerService == null) {
430 // We are shutting down, nothing to be done
431 return;
432 }
Jonathan Harte4e74f02016-03-03 12:57:40 -0800433 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700434 switch (msg.getType()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700435 case FLOW_REMOVED:
436 OFFlowRemoved removed = (OFFlowRemoved) msg;
alshabib6b5cfec2014-09-18 17:42:18 -0700437
Jonathan Harte4e74f02016-03-03 12:57:40 -0800438 FlowEntry fr = new FlowEntryBuilder(deviceId, removed, driverService).build();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700439 providerService.flowRemoved(fr);
440 break;
441 case STATS_REPLY:
442 if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) {
443 pushFlowMetrics(dpid, (OFFlowStatsReply) msg);
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700444 } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.TABLE) {
445 pushTableStatistics(dpid, (OFTableStatsReply) msg);
Cem Türker3baff672017-10-12 15:09:01 +0300446 } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW_LIGHTWEIGHT) {
447 pushFlowLightWeightMetrics(dpid, (OFFlowLightweightStatsReply) msg);
sangho89bf6fb2015-02-09 09:33:13 -0800448 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700449 break;
450 case BARRIER_REPLY:
451 try {
Thomas Vachuska3358af22015-05-19 18:40:34 -0700452 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800453 if (entry != null) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700454 providerService
455 .batchOperationCompleted(msg.getXid(),
456 entry.completed());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800457 } else {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700458 log.warn("Received unknown Barrier Reply: {}",
459 msg.getXid());
460 }
461 } finally {
462 pendingBatches.invalidate(msg.getXid());
463 }
464 break;
465 case ERROR:
Thomas Vachuska3358af22015-05-19 18:40:34 -0700466 // TODO: This needs to get suppressed in a better way.
467 if (msg instanceof OFBadRequestErrorMsg &&
468 ((OFBadRequestErrorMsg) msg).getCode() == OFBadRequestCode.BAD_TYPE) {
469 log.debug("Received error message {} from {}", msg, dpid);
470 } else {
471 log.warn("Received error message {} from {}", msg, dpid);
472 }
Prince Pereira788797e2016-08-10 11:24:14 +0530473 handleErrorMsg(deviceId, msg);
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800474 break;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700475 default:
476 log.debug("Unhandled message type: {}", msg.getType());
alshabib8f1cf4a2014-09-17 14:44:48 -0700477 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700478 }
479
Prince Pereira788797e2016-08-10 11:24:14 +0530480 private void handleErrorMsg(DeviceId deviceId, OFMessage msg) {
Prince Pereira141ed812016-09-02 19:03:18 +0530481 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Prince Pereira788797e2016-08-10 11:24:14 +0530482 OFErrorMsg error = (OFErrorMsg) msg;
483 OFMessage ofMessage = null;
484 switch (error.getErrType()) {
485 case BAD_ACTION:
486 OFBadActionErrorMsg baErrorMsg = (OFBadActionErrorMsg) error;
487 if (baErrorMsg.getData().getParsedMessage().isPresent()) {
488 ofMessage = baErrorMsg.getData().getParsedMessage().get();
489 }
490 break;
491 case BAD_INSTRUCTION:
492 OFBadInstructionErrorMsg biErrorMsg = (OFBadInstructionErrorMsg) error;
493 if (biErrorMsg.getData().getParsedMessage().isPresent()) {
494 ofMessage = biErrorMsg.getData().getParsedMessage().get();
495 }
496 break;
497 case BAD_MATCH:
498 OFBadMatchErrorMsg bmErrorMsg = (OFBadMatchErrorMsg) error;
499 if (bmErrorMsg.getData().getParsedMessage().isPresent()) {
500 ofMessage = bmErrorMsg.getData().getParsedMessage().get();
501 }
502 break;
503 case FLOW_MOD_FAILED:
504 OFFlowModFailedErrorMsg fmFailed = (OFFlowModFailedErrorMsg) error;
505 if (fmFailed.getData().getParsedMessage().isPresent()) {
506 ofMessage = fmFailed.getData().getParsedMessage().get();
507 }
508 break;
509 default:
510 // Do nothing.
511 return;
512 }
Prince Pereira141ed812016-09-02 19:03:18 +0530513
Prince Pereira788797e2016-08-10 11:24:14 +0530514 if (ofMessage != null) {
Prince Pereira141ed812016-09-02 19:03:18 +0530515
Prince Pereira788797e2016-08-10 11:24:14 +0530516 if (entry != null) {
517 OFFlowMod ofFlowMod = (OFFlowMod) ofMessage;
518 entry.appendFailure(new FlowEntryBuilder(deviceId, ofFlowMod, driverService).build());
519 } else {
520 log.error("No matching batch for this error: {}", error);
521 }
Prince Pereira141ed812016-09-02 19:03:18 +0530522
Prince Pereira788797e2016-08-10 11:24:14 +0530523 } else {
Prince Pereira141ed812016-09-02 19:03:18 +0530524
525 U64 cookieId = readCookieIdFromOFErrorMsg(error, msg.getVersion());
526
527 if (cookieId != null) {
528 long flowId = cookieId.getValue();
529
530 if (entry != null) {
531 for (FlowRuleBatchEntry fbEntry : entry.operation.getOperations()) {
532 if (fbEntry.target().id().value() == flowId) {
533 entry.appendFailure(fbEntry.target());
534 break;
535 }
536 }
537 } else {
538 log.error("No matching batch for this error: {}", error);
539 }
540
541 } else {
542 log.error("Flow installation failed but switch " +
543 "didn't tell us which one.");
544 }
Prince Pereira788797e2016-08-10 11:24:14 +0530545 }
546 }
547
Prince Pereira141ed812016-09-02 19:03:18 +0530548 /**
549 * Reading cookieId from OFErrorMsg.
550 *
551 * Loxigen OpenFlow API failed in parsing error messages because of
552 * 64 byte data truncation based on OpenFlow specs. The method written
553 * is a workaround to extract the cookieId from the packet till the
554 * issue is resolved in Loxigen OpenFlow code.
555 * Ref: https://groups.google.com/a/onosproject.org/forum/#!topic
556 * /onos-dev/_KwlHZDllLE
557 *
558 * @param msg OF error message
559 * @param ofVersion Openflow version
560 * @return cookieId
561 */
562 private U64 readCookieIdFromOFErrorMsg(OFErrorMsg msg,
563 OFVersion ofVersion) {
564
565 if (ofVersion.wireVersion < OFVersion.OF_13.wireVersion) {
566 log.debug("Unhandled error msg with OF version {} " +
567 "which is less than {}",
568 ofVersion, OFVersion.OF_13);
569 return null;
570 }
571
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700572 ByteBuf bb = Unpooled.wrappedBuffer(msg.getData().getData());
Prince Pereira141ed812016-09-02 19:03:18 +0530573
574 if (bb.readableBytes() < MIN_EXPECTED_BYTE_LEN) {
575 log.debug("Wrong length: Expected to be >= {}, was: {}",
576 MIN_EXPECTED_BYTE_LEN, bb.readableBytes());
577 return null;
578 }
579
580 byte ofVer = bb.readByte();
581
582 if (ofVer != ofVersion.wireVersion) {
583 log.debug("Wrong version: Expected={}, got={}",
584 ofVersion.wireVersion, ofVer);
585 return null;
586 }
587
588 byte type = bb.readByte();
589
590 if (type != OFType.FLOW_MOD.ordinal()) {
591 log.debug("Wrong type: Expected={}, got={}",
592 OFType.FLOW_MOD.ordinal(), type);
593 return null;
594 }
595
596 int length = U16.f(bb.readShort());
597
598 if (length < MIN_EXPECTED_BYTE_LEN) {
599 log.debug("Wrong length: Expected to be >= {}, was: {}",
600 MIN_EXPECTED_BYTE_LEN, length);
601 return null;
602 }
603
604 bb.skipBytes(SKIP_BYTES);
605 return U64.ofRaw(bb.readLong());
606 }
607
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700608 @Override
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700609 public void receivedRoleReply(Dpid dpid, RoleState requested,
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800610 RoleState response) {
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700611 // Do nothing here for now.
612 }
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700613
sangho89bf6fb2015-02-09 09:33:13 -0800614 private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies) {
alshabib64def642014-12-02 23:27:37 -0800615
alshabib54ce5892014-09-23 17:50:51 -0700616 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900617 NewAdaptiveFlowStatsCollector afsc = afsCollectors.get(dpid);
alshabib54ce5892014-09-23 17:50:51 -0700618
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900619 if (adaptiveFlowSampling && afsc != null) {
620 List<FlowEntry> flowEntries = replies.getEntries().stream()
621 .map(entry -> new FlowEntryBuilder(did, entry, driverService).withSetAfsc(afsc).build())
622 .collect(Collectors.toList());
alshabib54ce5892014-09-23 17:50:51 -0700623
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900624 // Check that OFFlowStatsReply Xid is same with the one of OFFlowStatsRequest?
625 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
626 log.debug("OpenFlowRuleProvider:pushFlowMetrics, flowMissingXid={}, "
627 + "OFFlowStatsReply Xid={}, for {}",
628 afsc.getFlowMissingXid(), replies.getXid(), dpid);
629 if (afsc.getFlowMissingXid() == replies.getXid()) {
630 // call entire flow stats update with flowMissing synchronization.
631 // used existing pushFlowMetrics
632 providerService.pushFlowMetrics(did, flowEntries);
ssyoon9030fbcd92015-08-17 10:42:07 +0900633 }
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900634 // reset flowMissingXid to NO_FLOW_MISSING_XID
635 afsc.setFlowMissingXid(NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID);
636 } else {
637 // call individual flow stats update
638 providerService.pushFlowMetricsWithoutFlowMissing(did, flowEntries);
ssyoon9030fbcd92015-08-17 10:42:07 +0900639 }
640 } else {
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900641 List<FlowEntry> flowEntries = replies.getEntries().stream()
642 .map(entry -> new FlowEntryBuilder(did, entry, driverService).build())
643 .collect(Collectors.toList());
644
ssyoon9030fbcd92015-08-17 10:42:07 +0900645 // call existing entire flow stats update with flowMissing synchronization
646 providerService.pushFlowMetrics(did, flowEntries);
647 }
alshabib5c370ff2014-09-18 10:12:14 -0700648 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700649
650 private void pushTableStatistics(Dpid dpid, OFTableStatsReply replies) {
651
652 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
653 List<TableStatisticsEntry> tableStatsEntries = replies.getEntries().stream()
654 .map(entry -> buildTableStatistics(did, entry))
655 .filter(Objects::nonNull)
656 .collect(Collectors.toList());
657 providerService.pushTableStatistics(did, tableStatsEntries);
658 }
659
Cem Türker3baff672017-10-12 15:09:01 +0300660 private void pushFlowLightWeightMetrics(Dpid dpid, OFFlowLightweightStatsReply replies) {
661
662 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
663 NewAdaptiveFlowStatsCollector afsc = afsCollectors.get(dpid);
664 if (adaptiveFlowSampling && afsc != null) {
665 List<FlowEntry> flowEntries = replies.getEntries().stream()
666 .map(entry -> new FlowEntryBuilder(did, entry, driverService).withSetAfsc(afsc).build())
667 .collect(Collectors.toList());
668
669 // Check that OFFlowStatsReply Xid is same with the one of OFFlowStatsRequest?
670 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
671 log.debug("OpenFlowRuleProvider:pushFlowMetrics, flowMissingXid={}, "
672 + "OFFlowStatsReply Xid={}, for {}",
673 afsc.getFlowMissingXid(), replies.getXid(), dpid);
674 if (afsc.getFlowMissingXid() == replies.getXid()) {
675 // call entire flow stats update with flowMissing synchronization.
676 // used existing pushFlowMetrics
677 providerService.pushFlowMetrics(did, flowEntries);
678 }
679 // reset flowMissingXid to NO_FLOW_MISSING_XID
680 afsc.setFlowMissingXid(NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID);
681 } else {
682 // call individual flow stats update
683 providerService.pushFlowMetricsWithoutFlowMissing(did, flowEntries);
684 }
685 } else {
686 List<FlowEntry> flowEntries = replies.getEntries().stream()
687 .map(entry -> new FlowEntryBuilder(did, entry, driverService).build())
688 .collect(Collectors.toList());
689 // call existing entire flow stats update with flowMissing synchronization
690 providerService.pushFlowMetrics(did, flowEntries);
691 }
692 }
693
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700694 private TableStatisticsEntry buildTableStatistics(DeviceId deviceId,
695 OFTableStatsEntry ofEntry) {
696 TableStatisticsEntry entry = null;
697 if (ofEntry != null) {
698 entry = new DefaultTableStatisticsEntry(deviceId,
699 ofEntry.getTableId().getValue(),
700 ofEntry.getActiveCount(),
701 ofEntry.getLookupCount().getValue(),
702 ofEntry.getMatchedCount().getValue());
703 }
704
705 return entry;
706
707 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700708 }
alshabib1cc04f72014-09-16 16:09:58 -0700709
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800710 /**
jcc3d4e14a2015-04-21 11:32:05 +0800711 * The internal cache entry holding the original request as well as
712 * accumulating the any failures along the way.
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700713 * <p/>
jcc3d4e14a2015-04-21 11:32:05 +0800714 * If this entry is evicted from the cache then the entire operation is
715 * considered failed. Otherwise, only the failures reported by the device
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800716 * will be propagated up.
717 */
718 private class InternalCacheEntry {
alshabib902d41b2014-10-07 16:52:05 -0700719
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800720 private final FlowRuleBatchOperation operation;
721 private final Set<FlowRule> failures = Sets.newConcurrentHashSet();
alshabib193525b2014-10-08 18:58:03 -0700722
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800723 public InternalCacheEntry(FlowRuleBatchOperation operation) {
724 this.operation = operation;
alshabib902d41b2014-10-07 16:52:05 -0700725 }
726
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800727 /**
728 * Appends a failed rule to the set of failed items.
jcc3d4e14a2015-04-21 11:32:05 +0800729 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800730 * @param rule the failed rule
731 */
732 public void appendFailure(FlowRule rule) {
733 failures.add(rule);
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800734 }
735
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800736 /**
737 * Fails the entire batch and returns the failed operation.
jcc3d4e14a2015-04-21 11:32:05 +0800738 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800739 * @return the failed operation
740 */
741 public CompletedBatchOperation failedCompletion() {
742 Set<FlowRule> fails = operation.getOperations().stream()
743 .map(op -> op.target()).collect(Collectors.toSet());
jcc3d4e14a2015-04-21 11:32:05 +0800744 return new CompletedBatchOperation(false,
745 Collections
746 .unmodifiableSet(fails),
747 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700748 }
749
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800750 /**
751 * Returns the completed operation and whether the batch suceeded.
jcc3d4e14a2015-04-21 11:32:05 +0800752 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800753 * @return the completed operation
754 */
755 public CompletedBatchOperation completed() {
jcc3d4e14a2015-04-21 11:32:05 +0800756 return new CompletedBatchOperation(
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700757 failures.isEmpty(),
758 Collections
759 .unmodifiableSet(failures),
760 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700761 }
alshabib902d41b2014-10-07 16:52:05 -0700762 }
alshabiba68eb962014-09-24 20:34:13 -0700763
alshabib1cc04f72014-09-16 16:09:58 -0700764}