blob: 9c9f1546c4d237b1bcafe33a5665609d6fd45920 [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.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
alshabib1cc04f72014-09-16 16:09:58 -070027import org.apache.felix.scr.annotations.Activate;
28import org.apache.felix.scr.annotations.Component;
29import org.apache.felix.scr.annotations.Deactivate;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070030import org.apache.felix.scr.annotations.Modified;
31import org.apache.felix.scr.annotations.Property;
alshabib1cc04f72014-09-16 16:09:58 -070032import org.apache.felix.scr.annotations.Reference;
33import org.apache.felix.scr.annotations.ReferenceCardinality;
Prince Pereira141ed812016-09-02 19:03:18 +053034import org.jboss.netty.buffer.ChannelBuffer;
35import org.jboss.netty.buffer.ChannelBuffers;
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;
44import org.onosproject.net.flow.FlowRuleBatchEntry;
Brian O'Connor72cb19a2015-01-16 16:14:41 -080045import org.onosproject.net.flow.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;
alshabib193525b2014-10-08 18:58:03 -070066import org.projectfloodlight.openflow.protocol.OFFlowMod;
alshabib8f1cf4a2014-09-17 14:44:48 -070067import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
alshabib5c370ff2014-09-18 10:12:14 -070068import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
alshabib8f1cf4a2014-09-17 14:44:48 -070069import org.projectfloodlight.openflow.protocol.OFMessage;
70import org.projectfloodlight.openflow.protocol.OFPortStatus;
alshabib5c370ff2014-09-18 10:12:14 -070071import org.projectfloodlight.openflow.protocol.OFStatsReply;
sangho89bf6fb2015-02-09 09:33:13 -080072import org.projectfloodlight.openflow.protocol.OFStatsType;
Jonathan Hart3c259162015-10-21 21:31:19 -070073import org.projectfloodlight.openflow.protocol.OFTableStatsEntry;
74import org.projectfloodlight.openflow.protocol.OFTableStatsReply;
Prince Pereira141ed812016-09-02 19:03:18 +053075import org.projectfloodlight.openflow.protocol.OFType;
76import org.projectfloodlight.openflow.protocol.OFVersion;
Prince Pereira788797e2016-08-10 11:24:14 +053077import org.projectfloodlight.openflow.protocol.errormsg.OFBadActionErrorMsg;
78import org.projectfloodlight.openflow.protocol.errormsg.OFBadInstructionErrorMsg;
79import org.projectfloodlight.openflow.protocol.errormsg.OFBadMatchErrorMsg;
Thomas Vachuska3358af22015-05-19 18:40:34 -070080import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
alshabib193525b2014-10-08 18:58:03 -070081import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
Prince Pereira141ed812016-09-02 19:03:18 +053082import org.projectfloodlight.openflow.types.U16;
83import org.projectfloodlight.openflow.types.U64;
alshabib1cc04f72014-09-16 16:09:58 -070084import org.slf4j.Logger;
85
Thomas Vachuska75aaa672015-04-29 12:24:43 -070086import java.util.Collections;
87import java.util.Dictionary;
88import java.util.List;
89import java.util.Map;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070090import java.util.Objects;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070091import java.util.Optional;
92import java.util.Set;
93import java.util.Timer;
94import java.util.concurrent.TimeUnit;
95import java.util.stream.Collectors;
96
ssyoon9030fbcd92015-08-17 10:42:07 +090097import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070098import static com.google.common.base.Strings.isNullOrEmpty;
99import static org.onlab.util.Tools.get;
100import static org.slf4j.LoggerFactory.getLogger;
alshabibeec3a062014-09-17 18:01:26 -0700101
alshabib1cc04f72014-09-16 16:09:58 -0700102/**
jcc3d4e14a2015-04-21 11:32:05 +0800103 * Provider which uses an OpenFlow controller to detect network end-station
104 * hosts.
alshabib1cc04f72014-09-16 16:09:58 -0700105 */
106@Component(immediate = true)
jcc3d4e14a2015-04-21 11:32:05 +0800107public class OpenFlowRuleProvider extends AbstractProvider
108 implements FlowRuleProvider {
alshabib1cc04f72014-09-16 16:09:58 -0700109
110 private final Logger log = getLogger(getClass());
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected FlowRuleProviderRegistry providerRegistry;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected OpenFlowController controller;
117
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected ComponentConfigService cfgService;
120
Jonathan Hart3c259162015-10-21 21:31:19 -0700121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected DriverService driverService;
123
ssyoon9030fbcd92015-08-17 10:42:07 +0900124 private static final int DEFAULT_POLL_FREQUENCY = 5;
Prince Pereira141ed812016-09-02 19:03:18 +0530125 private static final int MIN_EXPECTED_BYTE_LEN = 56;
126 private static final int SKIP_BYTES = 4;
127 private static final boolean DEFAULT_ADAPTIVE_FLOW_SAMPLING = false;
128
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700129 @Property(name = "flowPollFrequency", intValue = DEFAULT_POLL_FREQUENCY,
130 label = "Frequency (in seconds) for polling flow statistics")
131 private int flowPollFrequency = DEFAULT_POLL_FREQUENCY;
132
ssyoon9030fbcd92015-08-17 10:42:07 +0900133 @Property(name = "adaptiveFlowSampling", boolValue = DEFAULT_ADAPTIVE_FLOW_SAMPLING,
134 label = "Adaptive Flow Sampling is on or off")
135 private boolean adaptiveFlowSampling = DEFAULT_ADAPTIVE_FLOW_SAMPLING;
136
alshabib1cc04f72014-09-16 16:09:58 -0700137 private FlowRuleProviderService providerService;
138
alshabibeec3a062014-09-17 18:01:26 -0700139 private final InternalFlowProvider listener = new InternalFlowProvider();
140
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800141 private Cache<Long, InternalCacheEntry> pendingBatches;
alshabib193525b2014-10-08 18:58:03 -0700142
Victor Silvaff5871b2016-10-04 18:08:47 -0300143 private final Timer timer = new Timer("onos-openflow-flowstats-collector");
Madan Jampani6b266102016-06-23 00:56:36 -0700144 private final Map<Dpid, FlowStatsCollector> simpleCollectors = Maps.newConcurrentMap();
ssyoon9030fbcd92015-08-17 10:42:07 +0900145
146 // NewAdaptiveFlowStatsCollector Set
Madan Jampani6b266102016-06-23 00:56:36 -0700147 private final Map<Dpid, NewAdaptiveFlowStatsCollector> afsCollectors = Maps.newConcurrentMap();
148 private final Map<Dpid, TableStatisticsCollector> tableStatsCollectors = Maps.newConcurrentMap();
alshabib3d643ec2014-10-22 18:33:00 -0700149
alshabib1cc04f72014-09-16 16:09:58 -0700150 /**
151 * Creates an OpenFlow host provider.
152 */
153 public OpenFlowRuleProvider() {
Brian O'Connorabafb502014-12-02 22:26:20 -0800154 super(new ProviderId("of", "org.onosproject.provider.openflow"));
alshabib1cc04f72014-09-16 16:09:58 -0700155 }
156
157 @Activate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700158 protected void activate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700159 cfgService.registerProperties(getClass());
alshabib1cc04f72014-09-16 16:09:58 -0700160 providerService = providerRegistry.register(this);
alshabibeec3a062014-09-17 18:01:26 -0700161 controller.addListener(listener);
162 controller.addEventListener(listener);
alshabib3d643ec2014-10-22 18:33:00 -0700163
Antonio Marsico1c5ae1f2015-12-15 15:31:56 +0100164 modified(context);
165
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700166 pendingBatches = createBatchCache();
ssyoon9030fbcd92015-08-17 10:42:07 +0900167
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700168 createCollectors();
alshabib3d643ec2014-10-22 18:33:00 -0700169
ssyoon9030fbcd92015-08-17 10:42:07 +0900170 log.info("Started with flowPollFrequency = {}, adaptiveFlowSampling = {}",
171 flowPollFrequency, adaptiveFlowSampling);
alshabib1cc04f72014-09-16 16:09:58 -0700172 }
173
174 @Deactivate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700175 protected void deactivate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700176 cfgService.unregisterProperties(getClass(), false);
177 stopCollectors();
alshabib1cc04f72014-09-16 16:09:58 -0700178 providerRegistry.unregister(this);
179 providerService = null;
180
181 log.info("Stopped");
182 }
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800183
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700184 @Modified
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700185 protected void modified(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700186 Dictionary<?, ?> properties = context.getProperties();
187 int newFlowPollFrequency;
188 try {
189 String s = get(properties, "flowPollFrequency");
190 newFlowPollFrequency = isNullOrEmpty(s) ? flowPollFrequency : Integer.parseInt(s.trim());
191
192 } catch (NumberFormatException | ClassCastException e) {
193 newFlowPollFrequency = flowPollFrequency;
194 }
195
196 if (newFlowPollFrequency != flowPollFrequency) {
197 flowPollFrequency = newFlowPollFrequency;
198 adjustRate();
199 }
200
201 log.info("Settings: flowPollFrequency={}", flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900202
203 boolean newAdaptiveFlowSampling;
204 String s = get(properties, "adaptiveFlowSampling");
205 newAdaptiveFlowSampling = isNullOrEmpty(s) ? adaptiveFlowSampling : Boolean.parseBoolean(s.trim());
206
207 if (newAdaptiveFlowSampling != adaptiveFlowSampling) {
208 // stop previous collector
209 stopCollectors();
210 adaptiveFlowSampling = newAdaptiveFlowSampling;
211 // create new collectors
212 createCollectors();
213 }
214
215 log.info("Settings: adaptiveFlowSampling={}", adaptiveFlowSampling);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700216 }
217
218 private Cache<Long, InternalCacheEntry> createBatchCache() {
219 return CacheBuilder.newBuilder()
220 .expireAfterWrite(10, TimeUnit.SECONDS)
221 .removalListener((RemovalNotification<Long, InternalCacheEntry> notification) -> {
222 if (notification.getCause() == RemovalCause.EXPIRED) {
223 providerService.batchOperationCompleted(notification.getKey(),
224 notification.getValue().failedCompletion());
225 }
226 }).build();
227 }
228
229 private void createCollectors() {
230 controller.getSwitches().forEach(this::createCollector);
231 }
232
233 private void createCollector(OpenFlowSwitch sw) {
Kavitha Alagesan6704df32016-08-18 15:15:31 +0530234 if (sw == null) {
235 return;
236 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900237 if (adaptiveFlowSampling) {
238 // NewAdaptiveFlowStatsCollector Constructor
Charles Chan14967c22015-12-07 11:11:50 -0800239 NewAdaptiveFlowStatsCollector fsc =
240 new NewAdaptiveFlowStatsCollector(driverService, sw, flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900241 fsc.start();
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700242 stopCollectorIfNeeded(afsCollectors.put(new Dpid(sw.getId()), fsc));
ssyoon9030fbcd92015-08-17 10:42:07 +0900243 } else {
244 FlowStatsCollector fsc = new FlowStatsCollector(timer, sw, flowPollFrequency);
245 fsc.start();
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700246 stopCollectorIfNeeded(simpleCollectors.put(new Dpid(sw.getId()), fsc));
ssyoon9030fbcd92015-08-17 10:42:07 +0900247 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700248 TableStatisticsCollector tsc = new TableStatisticsCollector(timer, sw, flowPollFrequency);
249 tsc.start();
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700250 stopCollectorIfNeeded(tableStatsCollectors.put(new Dpid(sw.getId()), tsc));
251 }
252
253 private void stopCollectorIfNeeded(SwitchDataCollector collector) {
254 if (collector != null) {
255 collector.stop();
256 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700257 }
258
259 private void stopCollectors() {
ssyoon9030fbcd92015-08-17 10:42:07 +0900260 if (adaptiveFlowSampling) {
261 // NewAdaptiveFlowStatsCollector Destructor
262 afsCollectors.values().forEach(NewAdaptiveFlowStatsCollector::stop);
263 afsCollectors.clear();
264 } else {
265 simpleCollectors.values().forEach(FlowStatsCollector::stop);
266 simpleCollectors.clear();
267 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700268 tableStatsCollectors.values().forEach(TableStatisticsCollector::stop);
269 tableStatsCollectors.clear();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700270 }
271
272 private void adjustRate() {
273 DefaultLoad.setPollInterval(flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900274 if (adaptiveFlowSampling) {
275 // NewAdaptiveFlowStatsCollector calAndPollInterval
276 afsCollectors.values().forEach(fsc -> fsc.adjustCalAndPollInterval(flowPollFrequency));
277 } else {
278 simpleCollectors.values().forEach(fsc -> fsc.adjustPollInterval(flowPollFrequency));
279 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700280 tableStatsCollectors.values().forEach(tsc -> tsc.adjustPollInterval(flowPollFrequency));
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700281 }
282
alshabib1cc04f72014-09-16 16:09:58 -0700283 @Override
284 public void applyFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800285 for (FlowRule flowRule : flowRules) {
286 applyRule(flowRule);
alshabib35edb1a2014-09-16 17:44:44 -0700287 }
alshabib1cc04f72014-09-16 16:09:58 -0700288 }
289
alshabib35edb1a2014-09-16 17:44:44 -0700290 private void applyRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900291 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
292 OpenFlowSwitch sw = controller.getSwitch(dpid);
293
Ray Milkey0ae473d2016-04-04 10:56:47 -0700294 if (sw == null) {
295 return;
296 }
297
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700298 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
299 if (hasPayload(flowRuleExtPayLoad)) {
300 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800301 sw.sendMsg(msg);
302 return;
303 }
alshabibbdcbb102015-04-22 14:16:38 -0700304 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700305 Optional.empty(), Optional.of(driverService)).buildFlowAdd());
ssyoon9030fbcd92015-08-17 10:42:07 +0900306
307 if (adaptiveFlowSampling) {
308 // Add TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700309 NewAdaptiveFlowStatsCollector collector = afsCollectors.get(dpid);
310 if (collector != null) {
311 collector.addWithFlowRule(flowRule);
312 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900313 }
alshabib35edb1a2014-09-16 17:44:44 -0700314 }
315
alshabib1cc04f72014-09-16 16:09:58 -0700316 @Override
317 public void removeFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800318 for (FlowRule flowRule : flowRules) {
319 removeRule(flowRule);
alshabib219ebaa2014-09-22 15:41:24 -0700320 }
alshabib1cc04f72014-09-16 16:09:58 -0700321 }
322
alshabib219ebaa2014-09-22 15:41:24 -0700323 private void removeRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900324 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
325 OpenFlowSwitch sw = controller.getSwitch(dpid);
326
Ray Milkey0ae473d2016-04-04 10:56:47 -0700327 if (sw == null) {
328 return;
329 }
330
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700331 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
332 if (hasPayload(flowRuleExtPayLoad)) {
333 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800334 sw.sendMsg(msg);
335 return;
336 }
alshabibbdcbb102015-04-22 14:16:38 -0700337 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700338 Optional.empty(), Optional.of(driverService)).buildFlowDel());
ssyoon9030fbcd92015-08-17 10:42:07 +0900339
340 if (adaptiveFlowSampling) {
341 // Remove TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700342 NewAdaptiveFlowStatsCollector collector = afsCollectors.get(dpid);
343 if (collector != null) {
344 collector.removeFlows(flowRule);
345 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900346 }
alshabib219ebaa2014-09-22 15:41:24 -0700347 }
348
alshabiba68eb962014-09-24 20:34:13 -0700349 @Override
350 public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
351 // TODO: optimize using the ApplicationId
352 removeFlowRule(flowRules);
353 }
354
alshabib193525b2014-10-08 18:58:03 -0700355 @Override
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800356 public void executeBatch(FlowRuleBatchOperation batch) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900357 checkNotNull(batch);
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800358
ssyoon9030fbcd92015-08-17 10:42:07 +0900359 Dpid dpid = Dpid.dpid(batch.deviceId().uri());
360 OpenFlowSwitch sw = controller.getSwitch(dpid);
Madan Jampani84382b92016-06-22 08:26:49 -0700361
362 // If switch no longer exists, simply return.
363 if (sw == null) {
364 Set<FlowRule> failures = ImmutableSet.copyOf(Lists.transform(batch.getOperations(), e -> e.target()));
365 providerService.batchOperationCompleted(batch.id(),
366 new CompletedBatchOperation(false, failures, batch.deviceId()));
367 return;
368 }
369 pendingBatches.put(batch.id(), new InternalCacheEntry(batch));
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800370 OFFlowMod mod;
alshabib193525b2014-10-08 18:58:03 -0700371 for (FlowRuleBatchEntry fbe : batch.getOperations()) {
jcc3d4e14a2015-04-21 11:32:05 +0800372 // flow is the third party privacy flow
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700373
374 FlowRuleExtPayLoad flowRuleExtPayLoad = fbe.target().payLoad();
375 if (hasPayload(flowRuleExtPayLoad)) {
376 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800377 sw.sendMsg(msg);
378 continue;
379 }
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700380 FlowModBuilder builder =
Jonathan Hart3c259162015-10-21 21:31:19 -0700381 FlowModBuilder.builder(fbe.target(), sw.factory(),
382 Optional.of(batch.id()), Optional.of(driverService));
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700383 NewAdaptiveFlowStatsCollector collector = afsCollectors.get(dpid);
Sho SHIMIZUaba9d002015-01-29 14:51:04 -0800384 switch (fbe.operator()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700385 case ADD:
386 mod = builder.buildFlowAdd();
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700387 if (adaptiveFlowSampling && collector != null) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900388 // Add TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700389 collector.addWithFlowRule(fbe.target());
ssyoon9030fbcd92015-08-17 10:42:07 +0900390 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700391 break;
392 case REMOVE:
393 mod = builder.buildFlowDel();
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700394 if (adaptiveFlowSampling && collector != null) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900395 // Remove TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700396 collector.removeFlows(fbe.target());
ssyoon9030fbcd92015-08-17 10:42:07 +0900397 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700398 break;
399 case MODIFY:
400 mod = builder.buildFlowMod();
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700401 if (adaptiveFlowSampling && collector != null) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900402 // Add or Update TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
403 // afsCollectors.get(dpid).addWithFlowRule(fbe.target()); //check if add is good or not
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700404 collector.addOrUpdateFlows((FlowEntry) fbe.target());
ssyoon9030fbcd92015-08-17 10:42:07 +0900405 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700406 break;
407 default:
408 log.error("Unsupported batch operation {}; skipping flowmod {}",
ssyoon9030fbcd92015-08-17 10:42:07 +0900409 fbe.operator(), fbe);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700410 continue;
jcc3d4e14a2015-04-21 11:32:05 +0800411 }
Saurav Das3ea46622015-04-22 14:01:34 -0700412 sw.sendMsg(mod);
alshabib193525b2014-10-08 18:58:03 -0700413 }
jcc3d4e14a2015-04-21 11:32:05 +0800414 OFBarrierRequest.Builder builder = sw.factory().buildBarrierRequest()
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800415 .setXid(batch.id());
416 sw.sendMsg(builder.build());
alshabib193525b2014-10-08 18:58:03 -0700417 }
418
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700419 private boolean hasPayload(FlowRuleExtPayLoad flowRuleExtPayLoad) {
420 return flowRuleExtPayLoad != null &&
421 flowRuleExtPayLoad.payLoad() != null &&
422 flowRuleExtPayLoad.payLoad().length > 0;
423 }
424
alshabib8f1cf4a2014-09-17 14:44:48 -0700425 private class InternalFlowProvider
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800426 implements OpenFlowSwitchListener, OpenFlowEventListener {
alshabib8f1cf4a2014-09-17 14:44:48 -0700427
alshabib8f1cf4a2014-09-17 14:44:48 -0700428 @Override
429 public void switchAdded(Dpid dpid) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700430 createCollector(controller.getSwitch(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700431 }
432
433 @Override
434 public void switchRemoved(Dpid dpid) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900435 if (adaptiveFlowSampling) {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700436 stopCollectorIfNeeded(afsCollectors.remove(dpid));
ssyoon9030fbcd92015-08-17 10:42:07 +0900437 } else {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700438 stopCollectorIfNeeded(simpleCollectors.remove(dpid));
alshabibdfc7afb2014-10-21 20:13:27 -0700439 }
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700440 stopCollectorIfNeeded(tableStatsCollectors.remove(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700441 }
442
443 @Override
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700444 public void switchChanged(Dpid dpid) {
445 }
446
447 @Override
alshabib8f1cf4a2014-09-17 14:44:48 -0700448 public void portChanged(Dpid dpid, OFPortStatus status) {
jcc3d4e14a2015-04-21 11:32:05 +0800449 // TODO: Decide whether to evict flows internal store.
alshabib8f1cf4a2014-09-17 14:44:48 -0700450 }
451
452 @Override
453 public void handleMessage(Dpid dpid, OFMessage msg) {
Ray Milkeyada9e2d2016-04-05 16:42:35 -0700454 if (providerService == null) {
455 // We are shutting down, nothing to be done
456 return;
457 }
Jonathan Harte4e74f02016-03-03 12:57:40 -0800458 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700459 switch (msg.getType()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700460 case FLOW_REMOVED:
461 OFFlowRemoved removed = (OFFlowRemoved) msg;
alshabib6b5cfec2014-09-18 17:42:18 -0700462
Jonathan Harte4e74f02016-03-03 12:57:40 -0800463 FlowEntry fr = new FlowEntryBuilder(deviceId, removed, driverService).build();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700464 providerService.flowRemoved(fr);
ssyoon9030fbcd92015-08-17 10:42:07 +0900465
466 if (adaptiveFlowSampling) {
467 // Removed TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700468 NewAdaptiveFlowStatsCollector collector = afsCollectors.get(dpid);
469 if (collector != null) {
470 collector.flowRemoved(fr);
471 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900472 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700473 break;
474 case STATS_REPLY:
475 if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) {
476 pushFlowMetrics(dpid, (OFFlowStatsReply) msg);
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700477 } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.TABLE) {
478 pushTableStatistics(dpid, (OFTableStatsReply) msg);
sangho89bf6fb2015-02-09 09:33:13 -0800479 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700480 break;
481 case BARRIER_REPLY:
482 try {
Thomas Vachuska3358af22015-05-19 18:40:34 -0700483 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800484 if (entry != null) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700485 providerService
486 .batchOperationCompleted(msg.getXid(),
487 entry.completed());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800488 } else {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700489 log.warn("Received unknown Barrier Reply: {}",
490 msg.getXid());
491 }
492 } finally {
493 pendingBatches.invalidate(msg.getXid());
494 }
495 break;
496 case ERROR:
Thomas Vachuska3358af22015-05-19 18:40:34 -0700497 // TODO: This needs to get suppressed in a better way.
498 if (msg instanceof OFBadRequestErrorMsg &&
499 ((OFBadRequestErrorMsg) msg).getCode() == OFBadRequestCode.BAD_TYPE) {
500 log.debug("Received error message {} from {}", msg, dpid);
501 } else {
502 log.warn("Received error message {} from {}", msg, dpid);
503 }
Prince Pereira788797e2016-08-10 11:24:14 +0530504 handleErrorMsg(deviceId, msg);
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800505 break;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700506 default:
507 log.debug("Unhandled message type: {}", msg.getType());
alshabib8f1cf4a2014-09-17 14:44:48 -0700508 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700509 }
510
Prince Pereira788797e2016-08-10 11:24:14 +0530511 private void handleErrorMsg(DeviceId deviceId, OFMessage msg) {
Prince Pereira141ed812016-09-02 19:03:18 +0530512 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Prince Pereira788797e2016-08-10 11:24:14 +0530513 OFErrorMsg error = (OFErrorMsg) msg;
514 OFMessage ofMessage = null;
515 switch (error.getErrType()) {
516 case BAD_ACTION:
517 OFBadActionErrorMsg baErrorMsg = (OFBadActionErrorMsg) error;
518 if (baErrorMsg.getData().getParsedMessage().isPresent()) {
519 ofMessage = baErrorMsg.getData().getParsedMessage().get();
520 }
521 break;
522 case BAD_INSTRUCTION:
523 OFBadInstructionErrorMsg biErrorMsg = (OFBadInstructionErrorMsg) error;
524 if (biErrorMsg.getData().getParsedMessage().isPresent()) {
525 ofMessage = biErrorMsg.getData().getParsedMessage().get();
526 }
527 break;
528 case BAD_MATCH:
529 OFBadMatchErrorMsg bmErrorMsg = (OFBadMatchErrorMsg) error;
530 if (bmErrorMsg.getData().getParsedMessage().isPresent()) {
531 ofMessage = bmErrorMsg.getData().getParsedMessage().get();
532 }
533 break;
534 case FLOW_MOD_FAILED:
535 OFFlowModFailedErrorMsg fmFailed = (OFFlowModFailedErrorMsg) error;
536 if (fmFailed.getData().getParsedMessage().isPresent()) {
537 ofMessage = fmFailed.getData().getParsedMessage().get();
538 }
539 break;
540 default:
541 // Do nothing.
542 return;
543 }
Prince Pereira141ed812016-09-02 19:03:18 +0530544
Prince Pereira788797e2016-08-10 11:24:14 +0530545 if (ofMessage != null) {
Prince Pereira141ed812016-09-02 19:03:18 +0530546
Prince Pereira788797e2016-08-10 11:24:14 +0530547 if (entry != null) {
548 OFFlowMod ofFlowMod = (OFFlowMod) ofMessage;
549 entry.appendFailure(new FlowEntryBuilder(deviceId, ofFlowMod, driverService).build());
550 } else {
551 log.error("No matching batch for this error: {}", error);
552 }
Prince Pereira141ed812016-09-02 19:03:18 +0530553
Prince Pereira788797e2016-08-10 11:24:14 +0530554 } else {
Prince Pereira141ed812016-09-02 19:03:18 +0530555
556 U64 cookieId = readCookieIdFromOFErrorMsg(error, msg.getVersion());
557
558 if (cookieId != null) {
559 long flowId = cookieId.getValue();
560
561 if (entry != null) {
562 for (FlowRuleBatchEntry fbEntry : entry.operation.getOperations()) {
563 if (fbEntry.target().id().value() == flowId) {
564 entry.appendFailure(fbEntry.target());
565 break;
566 }
567 }
568 } else {
569 log.error("No matching batch for this error: {}", error);
570 }
571
572 } else {
573 log.error("Flow installation failed but switch " +
574 "didn't tell us which one.");
575 }
Prince Pereira788797e2016-08-10 11:24:14 +0530576 }
577 }
578
Prince Pereira141ed812016-09-02 19:03:18 +0530579 /**
580 * Reading cookieId from OFErrorMsg.
581 *
582 * Loxigen OpenFlow API failed in parsing error messages because of
583 * 64 byte data truncation based on OpenFlow specs. The method written
584 * is a workaround to extract the cookieId from the packet till the
585 * issue is resolved in Loxigen OpenFlow code.
586 * Ref: https://groups.google.com/a/onosproject.org/forum/#!topic
587 * /onos-dev/_KwlHZDllLE
588 *
589 * @param msg OF error message
590 * @param ofVersion Openflow version
591 * @return cookieId
592 */
593 private U64 readCookieIdFromOFErrorMsg(OFErrorMsg msg,
594 OFVersion ofVersion) {
595
596 if (ofVersion.wireVersion < OFVersion.OF_13.wireVersion) {
597 log.debug("Unhandled error msg with OF version {} " +
598 "which is less than {}",
599 ofVersion, OFVersion.OF_13);
600 return null;
601 }
602
603 ChannelBuffer bb = ChannelBuffers.wrappedBuffer(
604 msg.getData().getData());
605
606 if (bb.readableBytes() < MIN_EXPECTED_BYTE_LEN) {
607 log.debug("Wrong length: Expected to be >= {}, was: {}",
608 MIN_EXPECTED_BYTE_LEN, bb.readableBytes());
609 return null;
610 }
611
612 byte ofVer = bb.readByte();
613
614 if (ofVer != ofVersion.wireVersion) {
615 log.debug("Wrong version: Expected={}, got={}",
616 ofVersion.wireVersion, ofVer);
617 return null;
618 }
619
620 byte type = bb.readByte();
621
622 if (type != OFType.FLOW_MOD.ordinal()) {
623 log.debug("Wrong type: Expected={}, got={}",
624 OFType.FLOW_MOD.ordinal(), type);
625 return null;
626 }
627
628 int length = U16.f(bb.readShort());
629
630 if (length < MIN_EXPECTED_BYTE_LEN) {
631 log.debug("Wrong length: Expected to be >= {}, was: {}",
632 MIN_EXPECTED_BYTE_LEN, length);
633 return null;
634 }
635
636 bb.skipBytes(SKIP_BYTES);
637 return U64.ofRaw(bb.readLong());
638 }
639
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700640 @Override
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700641 public void receivedRoleReply(Dpid dpid, RoleState requested,
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800642 RoleState response) {
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700643 // Do nothing here for now.
644 }
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700645
sangho89bf6fb2015-02-09 09:33:13 -0800646 private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies) {
alshabib64def642014-12-02 23:27:37 -0800647
alshabib54ce5892014-09-23 17:50:51 -0700648 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
alshabib54ce5892014-09-23 17:50:51 -0700649
alshabib64def642014-12-02 23:27:37 -0800650 List<FlowEntry> flowEntries = replies.getEntries().stream()
Jonathan Harte4e74f02016-03-03 12:57:40 -0800651 .map(entry -> new FlowEntryBuilder(did, entry, driverService).build())
alshabib64def642014-12-02 23:27:37 -0800652 .collect(Collectors.toList());
alshabib54ce5892014-09-23 17:50:51 -0700653
ssyoon9030fbcd92015-08-17 10:42:07 +0900654 if (adaptiveFlowSampling) {
655 NewAdaptiveFlowStatsCollector afsc = afsCollectors.get(dpid);
656
657 synchronized (afsc) {
658 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
Prince Pereira141ed812016-09-02 19:03:18 +0530659 log.debug("OpenFlowRuleProvider:pushFlowMetrics, flowMissingXid={}, " +
660 "OFFlowStatsReply Xid={}, for {}",
ssyoon9030fbcd92015-08-17 10:42:07 +0900661 afsc.getFlowMissingXid(), replies.getXid(), dpid);
662 }
663
664 // Check that OFFlowStatsReply Xid is same with the one of OFFlowStatsRequest?
665 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
666 if (afsc.getFlowMissingXid() == replies.getXid()) {
667 // call entire flow stats update with flowMissing synchronization.
668 // used existing pushFlowMetrics
669 providerService.pushFlowMetrics(did, flowEntries);
670 }
671 // reset flowMissingXid to NO_FLOW_MISSING_XID
672 afsc.setFlowMissingXid(NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID);
673
674 } else {
675 // call individual flow stats update
676 providerService.pushFlowMetricsWithoutFlowMissing(did, flowEntries);
677 }
678
679 // Update TypedFlowEntry to deviceFlowEntries in NewAdaptiveFlowStatsCollector
680 afsc.pushFlowMetrics(flowEntries);
681 }
682 } else {
683 // call existing entire flow stats update with flowMissing synchronization
684 providerService.pushFlowMetrics(did, flowEntries);
685 }
alshabib5c370ff2014-09-18 10:12:14 -0700686 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700687
688 private void pushTableStatistics(Dpid dpid, OFTableStatsReply replies) {
689
690 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
691 List<TableStatisticsEntry> tableStatsEntries = replies.getEntries().stream()
692 .map(entry -> buildTableStatistics(did, entry))
693 .filter(Objects::nonNull)
694 .collect(Collectors.toList());
695 providerService.pushTableStatistics(did, tableStatsEntries);
696 }
697
698 private TableStatisticsEntry buildTableStatistics(DeviceId deviceId,
699 OFTableStatsEntry ofEntry) {
700 TableStatisticsEntry entry = null;
701 if (ofEntry != null) {
702 entry = new DefaultTableStatisticsEntry(deviceId,
703 ofEntry.getTableId().getValue(),
704 ofEntry.getActiveCount(),
705 ofEntry.getLookupCount().getValue(),
706 ofEntry.getMatchedCount().getValue());
707 }
708
709 return entry;
710
711 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700712 }
alshabib1cc04f72014-09-16 16:09:58 -0700713
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800714 /**
jcc3d4e14a2015-04-21 11:32:05 +0800715 * The internal cache entry holding the original request as well as
716 * accumulating the any failures along the way.
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700717 * <p/>
jcc3d4e14a2015-04-21 11:32:05 +0800718 * If this entry is evicted from the cache then the entire operation is
719 * considered failed. Otherwise, only the failures reported by the device
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800720 * will be propagated up.
721 */
722 private class InternalCacheEntry {
alshabib902d41b2014-10-07 16:52:05 -0700723
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800724 private final FlowRuleBatchOperation operation;
725 private final Set<FlowRule> failures = Sets.newConcurrentHashSet();
alshabib193525b2014-10-08 18:58:03 -0700726
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800727 public InternalCacheEntry(FlowRuleBatchOperation operation) {
728 this.operation = operation;
alshabib902d41b2014-10-07 16:52:05 -0700729 }
730
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800731 /**
732 * Appends a failed rule to the set of failed items.
jcc3d4e14a2015-04-21 11:32:05 +0800733 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800734 * @param rule the failed rule
735 */
736 public void appendFailure(FlowRule rule) {
737 failures.add(rule);
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800738 }
739
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800740 /**
741 * Fails the entire batch and returns the failed operation.
jcc3d4e14a2015-04-21 11:32:05 +0800742 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800743 * @return the failed operation
744 */
745 public CompletedBatchOperation failedCompletion() {
746 Set<FlowRule> fails = operation.getOperations().stream()
747 .map(op -> op.target()).collect(Collectors.toSet());
jcc3d4e14a2015-04-21 11:32:05 +0800748 return new CompletedBatchOperation(false,
749 Collections
750 .unmodifiableSet(fails),
751 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700752 }
753
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800754 /**
755 * Returns the completed operation and whether the batch suceeded.
jcc3d4e14a2015-04-21 11:32:05 +0800756 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800757 * @return the completed operation
758 */
759 public CompletedBatchOperation completed() {
jcc3d4e14a2015-04-21 11:32:05 +0800760 return new CompletedBatchOperation(
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700761 failures.isEmpty(),
762 Collections
763 .unmodifiableSet(failures),
764 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700765 }
alshabib902d41b2014-10-07 16:52:05 -0700766 }
alshabiba68eb962014-09-24 20:34:13 -0700767
alshabib1cc04f72014-09-16 16:09:58 -0700768}