blob: 2fafb8c223f54c39959431c27accba039d2f1ed9 [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;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070029import org.osgi.service.component.annotations.Activate;
30import org.osgi.service.component.annotations.Component;
31import org.osgi.service.component.annotations.Deactivate;
32import org.osgi.service.component.annotations.Modified;
33import org.osgi.service.component.annotations.Reference;
34import org.osgi.service.component.annotations.ReferenceCardinality;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070035import org.onosproject.cfg.ComponentConfigService;
Brian O'Connorabafb502014-12-02 22:26:20 -080036import org.onosproject.core.ApplicationId;
37import org.onosproject.net.DeviceId;
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -030038import org.onosproject.net.driver.DefaultDriverData;
39import org.onosproject.net.driver.DefaultDriverHandler;
40import org.onosproject.net.driver.Driver;
41import org.onosproject.net.driver.DriverHandler;
Jonathan Hart3c259162015-10-21 21:31:19 -070042import org.onosproject.net.driver.DriverService;
Brian O'Connorabafb502014-12-02 22:26:20 -080043import org.onosproject.net.flow.CompletedBatchOperation;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070044import org.onosproject.net.flow.DefaultTableStatisticsEntry;
Brian O'Connorabafb502014-12-02 22:26:20 -080045import org.onosproject.net.flow.FlowEntry;
46import org.onosproject.net.flow.FlowRule;
Ray Milkey7bf273c2017-09-27 16:15:15 -070047import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
48import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
Brian O'Connorabafb502014-12-02 22:26:20 -080049import org.onosproject.net.flow.FlowRuleProvider;
50import org.onosproject.net.flow.FlowRuleProviderRegistry;
51import org.onosproject.net.flow.FlowRuleProviderService;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070052import org.onosproject.net.flow.TableStatisticsEntry;
hjtsao1a4333c2018-10-22 11:02:00 -070053import org.onosproject.net.flow.IndexTableId;
Brian O'Connorabafb502014-12-02 22:26:20 -080054import org.onosproject.net.provider.AbstractProvider;
55import org.onosproject.net.provider.ProviderId;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070056import org.onosproject.net.statistic.DefaultLoad;
Brian O'Connorabafb502014-12-02 22:26:20 -080057import org.onosproject.openflow.controller.Dpid;
58import org.onosproject.openflow.controller.OpenFlowController;
59import org.onosproject.openflow.controller.OpenFlowEventListener;
60import org.onosproject.openflow.controller.OpenFlowSwitch;
61import org.onosproject.openflow.controller.OpenFlowSwitchListener;
62import org.onosproject.openflow.controller.RoleState;
Thomas Vachuska95caba32016-04-04 10:42:05 -070063import org.onosproject.provider.of.flow.util.FlowEntryBuilder;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070064import org.osgi.service.component.ComponentContext;
Thomas Vachuska3358af22015-05-19 18:40:34 -070065import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
alshabib902d41b2014-10-07 16:52:05 -070066import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
Laszlo Pappedadbe22017-12-14 20:05:49 +000067import org.projectfloodlight.openflow.protocol.OFCapabilities;
alshabib902d41b2014-10-07 16:52:05 -070068import org.projectfloodlight.openflow.protocol.OFErrorMsg;
Cem Türker3baff672017-10-12 15:09:01 +030069import org.projectfloodlight.openflow.protocol.OFFlowLightweightStatsReply;
alshabib193525b2014-10-08 18:58:03 -070070import org.projectfloodlight.openflow.protocol.OFFlowMod;
alshabib8f1cf4a2014-09-17 14:44:48 -070071import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
alshabib5c370ff2014-09-18 10:12:14 -070072import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
alshabib8f1cf4a2014-09-17 14:44:48 -070073import org.projectfloodlight.openflow.protocol.OFMessage;
74import org.projectfloodlight.openflow.protocol.OFPortStatus;
alshabib5c370ff2014-09-18 10:12:14 -070075import org.projectfloodlight.openflow.protocol.OFStatsReply;
sangho89bf6fb2015-02-09 09:33:13 -080076import org.projectfloodlight.openflow.protocol.OFStatsType;
Jonathan Hart3c259162015-10-21 21:31:19 -070077import org.projectfloodlight.openflow.protocol.OFTableStatsEntry;
78import org.projectfloodlight.openflow.protocol.OFTableStatsReply;
Prince Pereira141ed812016-09-02 19:03:18 +053079import org.projectfloodlight.openflow.protocol.OFType;
80import org.projectfloodlight.openflow.protocol.OFVersion;
Prince Pereira788797e2016-08-10 11:24:14 +053081import org.projectfloodlight.openflow.protocol.errormsg.OFBadActionErrorMsg;
82import org.projectfloodlight.openflow.protocol.errormsg.OFBadInstructionErrorMsg;
83import org.projectfloodlight.openflow.protocol.errormsg.OFBadMatchErrorMsg;
Thomas Vachuska3358af22015-05-19 18:40:34 -070084import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
alshabib193525b2014-10-08 18:58:03 -070085import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
Prince Pereira141ed812016-09-02 19:03:18 +053086import org.projectfloodlight.openflow.types.U16;
87import org.projectfloodlight.openflow.types.U64;
alshabib1cc04f72014-09-16 16:09:58 -070088import org.slf4j.Logger;
89
Thomas Vachuska75aaa672015-04-29 12:24:43 -070090import java.util.Collections;
91import java.util.Dictionary;
92import java.util.List;
93import java.util.Map;
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -070094import java.util.Objects;
Thomas Vachuska75aaa672015-04-29 12:24:43 -070095import java.util.Optional;
96import java.util.Set;
97import java.util.Timer;
98import java.util.concurrent.TimeUnit;
99import java.util.stream.Collectors;
100
ssyoon9030fbcd92015-08-17 10:42:07 +0900101import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700102import static com.google.common.base.Strings.isNullOrEmpty;
103import static org.onlab.util.Tools.get;
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700104import static org.onosproject.provider.of.flow.impl.OsgiPropertyConstants.*;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700105import static org.slf4j.LoggerFactory.getLogger;
alshabibeec3a062014-09-17 18:01:26 -0700106
alshabib1cc04f72014-09-16 16:09:58 -0700107/**
jcc3d4e14a2015-04-21 11:32:05 +0800108 * Provider which uses an OpenFlow controller to detect network end-station
109 * hosts.
alshabib1cc04f72014-09-16 16:09:58 -0700110 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700111@Component(immediate = true,
112 property = {
113 POLL_FREQUENCY + ":Integer=" + POLL_FREQUENCY_DEFAULT,
114 ADAPTIVE_FLOW_SAMPLING + ":Boolean=" + ADAPTIVE_FLOW_SAMPLING_DEFAULT,
115 })
jcc3d4e14a2015-04-21 11:32:05 +0800116public class OpenFlowRuleProvider extends AbstractProvider
117 implements FlowRuleProvider {
alshabib1cc04f72014-09-16 16:09:58 -0700118
119 private final Logger log = getLogger(getClass());
120
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabib1cc04f72014-09-16 16:09:58 -0700122 protected FlowRuleProviderRegistry providerRegistry;
123
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabib1cc04f72014-09-16 16:09:58 -0700125 protected OpenFlowController controller;
126
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700128 protected ComponentConfigService cfgService;
129
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart3c259162015-10-21 21:31:19 -0700131 protected DriverService driverService;
132
Prince Pereira141ed812016-09-02 19:03:18 +0530133 private static final int MIN_EXPECTED_BYTE_LEN = 56;
134 private static final int SKIP_BYTES = 4;
Prince Pereira141ed812016-09-02 19:03:18 +0530135
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700136 /** Frequency (in seconds) for polling flow statistics. */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700137 private int flowPollFrequency = POLL_FREQUENCY_DEFAULT;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700138
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700139 /** Adaptive Flow Sampling is on or off. */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700140 private boolean adaptiveFlowSampling = ADAPTIVE_FLOW_SAMPLING_DEFAULT;
ssyoon9030fbcd92015-08-17 10:42:07 +0900141
alshabib1cc04f72014-09-16 16:09:58 -0700142 private FlowRuleProviderService providerService;
143
alshabibeec3a062014-09-17 18:01:26 -0700144 private final InternalFlowProvider listener = new InternalFlowProvider();
145
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800146 private Cache<Long, InternalCacheEntry> pendingBatches;
alshabib193525b2014-10-08 18:58:03 -0700147
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900148 private final Timer timer = new Timer("onos-openflow-collector");
149
Cem Türker3baff672017-10-12 15:09:01 +0300150
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900151 // Old simple collector set
Madan Jampani6b266102016-06-23 00:56:36 -0700152 private final Map<Dpid, FlowStatsCollector> simpleCollectors = Maps.newConcurrentMap();
ssyoon9030fbcd92015-08-17 10:42:07 +0900153
154 // NewAdaptiveFlowStatsCollector Set
Madan Jampani6b266102016-06-23 00:56:36 -0700155 private final Map<Dpid, NewAdaptiveFlowStatsCollector> afsCollectors = Maps.newConcurrentMap();
156 private final Map<Dpid, TableStatisticsCollector> tableStatsCollectors = Maps.newConcurrentMap();
alshabib3d643ec2014-10-22 18:33:00 -0700157
alshabib1cc04f72014-09-16 16:09:58 -0700158 /**
159 * Creates an OpenFlow host provider.
160 */
161 public OpenFlowRuleProvider() {
Brian O'Connorabafb502014-12-02 22:26:20 -0800162 super(new ProviderId("of", "org.onosproject.provider.openflow"));
alshabib1cc04f72014-09-16 16:09:58 -0700163 }
164
165 @Activate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700166 protected void activate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700167 cfgService.registerProperties(getClass());
alshabib1cc04f72014-09-16 16:09:58 -0700168 providerService = providerRegistry.register(this);
alshabibeec3a062014-09-17 18:01:26 -0700169 controller.addListener(listener);
170 controller.addEventListener(listener);
alshabib3d643ec2014-10-22 18:33:00 -0700171
Antonio Marsico1c5ae1f2015-12-15 15:31:56 +0100172 modified(context);
173
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700174 pendingBatches = createBatchCache();
ssyoon9030fbcd92015-08-17 10:42:07 +0900175
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700176 createCollectors();
alshabib3d643ec2014-10-22 18:33:00 -0700177
ssyoon9030fbcd92015-08-17 10:42:07 +0900178 log.info("Started with flowPollFrequency = {}, adaptiveFlowSampling = {}",
179 flowPollFrequency, adaptiveFlowSampling);
alshabib1cc04f72014-09-16 16:09:58 -0700180 }
181
182 @Deactivate
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700183 protected void deactivate(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700184 cfgService.unregisterProperties(getClass(), false);
185 stopCollectors();
alshabib1cc04f72014-09-16 16:09:58 -0700186 providerRegistry.unregister(this);
187 providerService = null;
188
189 log.info("Stopped");
190 }
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800191
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700192 @Modified
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700193 protected void modified(ComponentContext context) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700194 Dictionary<?, ?> properties = context.getProperties();
195 int newFlowPollFrequency;
196 try {
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700197 String s = get(properties, POLL_FREQUENCY);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700198 newFlowPollFrequency = isNullOrEmpty(s) ? flowPollFrequency : Integer.parseInt(s.trim());
199
200 } catch (NumberFormatException | ClassCastException e) {
201 newFlowPollFrequency = flowPollFrequency;
202 }
203
204 if (newFlowPollFrequency != flowPollFrequency) {
205 flowPollFrequency = newFlowPollFrequency;
206 adjustRate();
207 }
208
209 log.info("Settings: flowPollFrequency={}", flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900210
211 boolean newAdaptiveFlowSampling;
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700212 String s = get(properties, ADAPTIVE_FLOW_SAMPLING);
ssyoon9030fbcd92015-08-17 10:42:07 +0900213 newAdaptiveFlowSampling = isNullOrEmpty(s) ? adaptiveFlowSampling : Boolean.parseBoolean(s.trim());
214
215 if (newAdaptiveFlowSampling != adaptiveFlowSampling) {
216 // stop previous collector
217 stopCollectors();
218 adaptiveFlowSampling = newAdaptiveFlowSampling;
219 // create new collectors
220 createCollectors();
221 }
222
223 log.info("Settings: adaptiveFlowSampling={}", adaptiveFlowSampling);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700224 }
225
226 private Cache<Long, InternalCacheEntry> createBatchCache() {
227 return CacheBuilder.newBuilder()
228 .expireAfterWrite(10, TimeUnit.SECONDS)
229 .removalListener((RemovalNotification<Long, InternalCacheEntry> notification) -> {
230 if (notification.getCause() == RemovalCause.EXPIRED) {
231 providerService.batchOperationCompleted(notification.getKey(),
232 notification.getValue().failedCompletion());
233 }
234 }).build();
235 }
236
237 private void createCollectors() {
238 controller.getSwitches().forEach(this::createCollector);
239 }
240
241 private void createCollector(OpenFlowSwitch sw) {
Kavitha Alagesan6704df32016-08-18 15:15:31 +0530242 if (sw == null) {
243 return;
244 }
Laszlo Papp58174812018-01-16 13:29:47 +0000245 if (sw.features().getCapabilities().contains(OFCapabilities.FLOW_STATS)) {
246 if (adaptiveFlowSampling) {
247 // NewAdaptiveFlowStatsCollector Constructor
248 NewAdaptiveFlowStatsCollector fsc =
249 new NewAdaptiveFlowStatsCollector(driverService, sw, flowPollFrequency);
250 stopCollectorIfNeeded(afsCollectors.put(new Dpid(sw.getId()), fsc));
251 fsc.start();
252 } else {
253 FlowStatsCollector fsc = new FlowStatsCollector(timer, sw, flowPollFrequency);
254 stopCollectorIfNeeded(simpleCollectors.put(new Dpid(sw.getId()), fsc));
255 fsc.start();
256 }
ssyoon9030fbcd92015-08-17 10:42:07 +0900257 }
Laszlo Pappedadbe22017-12-14 20:05:49 +0000258 if (sw.features().getCapabilities().contains(OFCapabilities.TABLE_STATS)) {
259 TableStatisticsCollector tsc = new TableStatisticsCollector(timer, sw, flowPollFrequency);
260 stopCollectorIfNeeded(tableStatsCollectors.put(new Dpid(sw.getId()), tsc));
261 tsc.start();
262 }
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700263 }
264
265 private void stopCollectorIfNeeded(SwitchDataCollector collector) {
266 if (collector != null) {
267 collector.stop();
268 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700269 }
270
271 private void stopCollectors() {
ssyoon9030fbcd92015-08-17 10:42:07 +0900272 if (adaptiveFlowSampling) {
273 // NewAdaptiveFlowStatsCollector Destructor
274 afsCollectors.values().forEach(NewAdaptiveFlowStatsCollector::stop);
275 afsCollectors.clear();
276 } else {
277 simpleCollectors.values().forEach(FlowStatsCollector::stop);
278 simpleCollectors.clear();
279 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700280 tableStatsCollectors.values().forEach(TableStatisticsCollector::stop);
281 tableStatsCollectors.clear();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700282 }
283
284 private void adjustRate() {
285 DefaultLoad.setPollInterval(flowPollFrequency);
ssyoon9030fbcd92015-08-17 10:42:07 +0900286 if (adaptiveFlowSampling) {
287 // NewAdaptiveFlowStatsCollector calAndPollInterval
288 afsCollectors.values().forEach(fsc -> fsc.adjustCalAndPollInterval(flowPollFrequency));
289 } else {
290 simpleCollectors.values().forEach(fsc -> fsc.adjustPollInterval(flowPollFrequency));
291 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700292 tableStatsCollectors.values().forEach(tsc -> tsc.adjustPollInterval(flowPollFrequency));
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700293 }
294
alshabib1cc04f72014-09-16 16:09:58 -0700295 @Override
296 public void applyFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800297 for (FlowRule flowRule : flowRules) {
298 applyRule(flowRule);
alshabib35edb1a2014-09-16 17:44:44 -0700299 }
alshabib1cc04f72014-09-16 16:09:58 -0700300 }
301
alshabib35edb1a2014-09-16 17:44:44 -0700302 private void applyRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900303 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
304 OpenFlowSwitch sw = controller.getSwitch(dpid);
305
Ray Milkey0ae473d2016-04-04 10:56:47 -0700306 if (sw == null) {
307 return;
308 }
309
alshabibbdcbb102015-04-22 14:16:38 -0700310 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700311 Optional.empty(), Optional.of(driverService)).buildFlowAdd());
alshabib35edb1a2014-09-16 17:44:44 -0700312 }
313
alshabib1cc04f72014-09-16 16:09:58 -0700314 @Override
315 public void removeFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800316 for (FlowRule flowRule : flowRules) {
317 removeRule(flowRule);
alshabib219ebaa2014-09-22 15:41:24 -0700318 }
alshabib1cc04f72014-09-16 16:09:58 -0700319 }
320
alshabib219ebaa2014-09-22 15:41:24 -0700321 private void removeRule(FlowRule flowRule) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900322 Dpid dpid = Dpid.dpid(flowRule.deviceId().uri());
323 OpenFlowSwitch sw = controller.getSwitch(dpid);
324
Ray Milkey0ae473d2016-04-04 10:56:47 -0700325 if (sw == null) {
326 return;
327 }
328
alshabibbdcbb102015-04-22 14:16:38 -0700329 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
Jonathan Hart3c259162015-10-21 21:31:19 -0700330 Optional.empty(), Optional.of(driverService)).buildFlowDel());
alshabib219ebaa2014-09-22 15:41:24 -0700331 }
332
alshabiba68eb962014-09-24 20:34:13 -0700333 @Override
334 public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
335 // TODO: optimize using the ApplicationId
336 removeFlowRule(flowRules);
337 }
338
alshabib193525b2014-10-08 18:58:03 -0700339 @Override
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800340 public void executeBatch(FlowRuleBatchOperation batch) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900341 checkNotNull(batch);
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800342
ssyoon9030fbcd92015-08-17 10:42:07 +0900343 Dpid dpid = Dpid.dpid(batch.deviceId().uri());
344 OpenFlowSwitch sw = controller.getSwitch(dpid);
Madan Jampani84382b92016-06-22 08:26:49 -0700345
346 // If switch no longer exists, simply return.
347 if (sw == null) {
348 Set<FlowRule> failures = ImmutableSet.copyOf(Lists.transform(batch.getOperations(), e -> e.target()));
349 providerService.batchOperationCompleted(batch.id(),
350 new CompletedBatchOperation(false, failures, batch.deviceId()));
351 return;
352 }
353 pendingBatches.put(batch.id(), new InternalCacheEntry(batch));
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800354 OFFlowMod mod;
alshabib193525b2014-10-08 18:58:03 -0700355 for (FlowRuleBatchEntry fbe : batch.getOperations()) {
Thomas Vachuskad07c0922015-10-06 14:48:06 -0700356 FlowModBuilder builder =
Jonathan Hart3c259162015-10-21 21:31:19 -0700357 FlowModBuilder.builder(fbe.target(), sw.factory(),
358 Optional.of(batch.id()), Optional.of(driverService));
Sho SHIMIZUaba9d002015-01-29 14:51:04 -0800359 switch (fbe.operator()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700360 case ADD:
361 mod = builder.buildFlowAdd();
362 break;
363 case REMOVE:
364 mod = builder.buildFlowDel();
365 break;
366 case MODIFY:
367 mod = builder.buildFlowMod();
368 break;
369 default:
370 log.error("Unsupported batch operation {}; skipping flowmod {}",
ssyoon9030fbcd92015-08-17 10:42:07 +0900371 fbe.operator(), fbe);
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700372 continue;
jcc3d4e14a2015-04-21 11:32:05 +0800373 }
Saurav Das3ea46622015-04-22 14:01:34 -0700374 sw.sendMsg(mod);
alshabib193525b2014-10-08 18:58:03 -0700375 }
jcc3d4e14a2015-04-21 11:32:05 +0800376 OFBarrierRequest.Builder builder = sw.factory().buildBarrierRequest()
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800377 .setXid(batch.id());
378 sw.sendMsg(builder.build());
alshabib193525b2014-10-08 18:58:03 -0700379 }
380
alshabib8f1cf4a2014-09-17 14:44:48 -0700381 private class InternalFlowProvider
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800382 implements OpenFlowSwitchListener, OpenFlowEventListener {
alshabib8f1cf4a2014-09-17 14:44:48 -0700383
alshabib8f1cf4a2014-09-17 14:44:48 -0700384 @Override
385 public void switchAdded(Dpid dpid) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700386 createCollector(controller.getSwitch(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700387 }
388
389 @Override
390 public void switchRemoved(Dpid dpid) {
ssyoon9030fbcd92015-08-17 10:42:07 +0900391 if (adaptiveFlowSampling) {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700392 stopCollectorIfNeeded(afsCollectors.remove(dpid));
ssyoon9030fbcd92015-08-17 10:42:07 +0900393 } else {
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700394 stopCollectorIfNeeded(simpleCollectors.remove(dpid));
alshabibdfc7afb2014-10-21 20:13:27 -0700395 }
Thomas Vachuskaa394b952016-06-14 15:02:09 -0700396 stopCollectorIfNeeded(tableStatsCollectors.remove(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700397 }
398
399 @Override
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700400 public void switchChanged(Dpid dpid) {
401 }
402
403 @Override
alshabib8f1cf4a2014-09-17 14:44:48 -0700404 public void portChanged(Dpid dpid, OFPortStatus status) {
jcc3d4e14a2015-04-21 11:32:05 +0800405 // TODO: Decide whether to evict flows internal store.
alshabib8f1cf4a2014-09-17 14:44:48 -0700406 }
407
408 @Override
409 public void handleMessage(Dpid dpid, OFMessage msg) {
Ray Milkeyada9e2d2016-04-05 16:42:35 -0700410 if (providerService == null) {
411 // We are shutting down, nothing to be done
412 return;
413 }
Jonathan Harte4e74f02016-03-03 12:57:40 -0800414 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid));
alshabib8f1cf4a2014-09-17 14:44:48 -0700415 switch (msg.getType()) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700416 case FLOW_REMOVED:
417 OFFlowRemoved removed = (OFFlowRemoved) msg;
alshabib6b5cfec2014-09-18 17:42:18 -0700418
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300419 FlowEntry fr = new FlowEntryBuilder(deviceId, removed, getDriver(deviceId)).build();
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700420 providerService.flowRemoved(fr);
421 break;
422 case STATS_REPLY:
423 if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) {
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300424 pushFlowMetrics(dpid, (OFFlowStatsReply) msg, getDriver(deviceId));
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700425 } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.TABLE) {
426 pushTableStatistics(dpid, (OFTableStatsReply) msg);
Cem Türker3baff672017-10-12 15:09:01 +0300427 } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW_LIGHTWEIGHT) {
428 pushFlowLightWeightMetrics(dpid, (OFFlowLightweightStatsReply) msg);
sangho89bf6fb2015-02-09 09:33:13 -0800429 }
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700430 break;
431 case BARRIER_REPLY:
432 try {
Thomas Vachuska3358af22015-05-19 18:40:34 -0700433 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800434 if (entry != null) {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700435 providerService
436 .batchOperationCompleted(msg.getXid(),
437 entry.completed());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800438 } else {
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700439 log.warn("Received unknown Barrier Reply: {}",
440 msg.getXid());
441 }
442 } finally {
443 pendingBatches.invalidate(msg.getXid());
444 }
445 break;
446 case ERROR:
Thomas Vachuska3358af22015-05-19 18:40:34 -0700447 // TODO: This needs to get suppressed in a better way.
448 if (msg instanceof OFBadRequestErrorMsg &&
449 ((OFBadRequestErrorMsg) msg).getCode() == OFBadRequestCode.BAD_TYPE) {
450 log.debug("Received error message {} from {}", msg, dpid);
451 } else {
452 log.warn("Received error message {} from {}", msg, dpid);
453 }
Prince Pereira788797e2016-08-10 11:24:14 +0530454 handleErrorMsg(deviceId, msg);
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800455 break;
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700456 default:
457 log.debug("Unhandled message type: {}", msg.getType());
alshabib8f1cf4a2014-09-17 14:44:48 -0700458 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700459 }
460
Prince Pereira788797e2016-08-10 11:24:14 +0530461 private void handleErrorMsg(DeviceId deviceId, OFMessage msg) {
Prince Pereira141ed812016-09-02 19:03:18 +0530462 InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid());
Prince Pereira788797e2016-08-10 11:24:14 +0530463 OFErrorMsg error = (OFErrorMsg) msg;
464 OFMessage ofMessage = null;
465 switch (error.getErrType()) {
466 case BAD_ACTION:
467 OFBadActionErrorMsg baErrorMsg = (OFBadActionErrorMsg) error;
468 if (baErrorMsg.getData().getParsedMessage().isPresent()) {
469 ofMessage = baErrorMsg.getData().getParsedMessage().get();
470 }
471 break;
472 case BAD_INSTRUCTION:
473 OFBadInstructionErrorMsg biErrorMsg = (OFBadInstructionErrorMsg) error;
474 if (biErrorMsg.getData().getParsedMessage().isPresent()) {
475 ofMessage = biErrorMsg.getData().getParsedMessage().get();
476 }
477 break;
478 case BAD_MATCH:
479 OFBadMatchErrorMsg bmErrorMsg = (OFBadMatchErrorMsg) error;
480 if (bmErrorMsg.getData().getParsedMessage().isPresent()) {
481 ofMessage = bmErrorMsg.getData().getParsedMessage().get();
482 }
483 break;
484 case FLOW_MOD_FAILED:
485 OFFlowModFailedErrorMsg fmFailed = (OFFlowModFailedErrorMsg) error;
486 if (fmFailed.getData().getParsedMessage().isPresent()) {
487 ofMessage = fmFailed.getData().getParsedMessage().get();
488 }
489 break;
490 default:
491 // Do nothing.
492 return;
493 }
Prince Pereira141ed812016-09-02 19:03:18 +0530494
Prince Pereira788797e2016-08-10 11:24:14 +0530495 if (ofMessage != null) {
Prince Pereira141ed812016-09-02 19:03:18 +0530496
Prince Pereira788797e2016-08-10 11:24:14 +0530497 if (entry != null) {
498 OFFlowMod ofFlowMod = (OFFlowMod) ofMessage;
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300499 entry.appendFailure(new FlowEntryBuilder(deviceId, ofFlowMod, getDriver(deviceId)).build());
Prince Pereira788797e2016-08-10 11:24:14 +0530500 } else {
501 log.error("No matching batch for this error: {}", error);
502 }
Prince Pereira141ed812016-09-02 19:03:18 +0530503
Prince Pereira788797e2016-08-10 11:24:14 +0530504 } else {
Prince Pereira141ed812016-09-02 19:03:18 +0530505
506 U64 cookieId = readCookieIdFromOFErrorMsg(error, msg.getVersion());
507
508 if (cookieId != null) {
509 long flowId = cookieId.getValue();
510
511 if (entry != null) {
512 for (FlowRuleBatchEntry fbEntry : entry.operation.getOperations()) {
513 if (fbEntry.target().id().value() == flowId) {
514 entry.appendFailure(fbEntry.target());
515 break;
516 }
517 }
518 } else {
519 log.error("No matching batch for this error: {}", error);
520 }
521
522 } else {
523 log.error("Flow installation failed but switch " +
524 "didn't tell us which one.");
525 }
Prince Pereira788797e2016-08-10 11:24:14 +0530526 }
527 }
528
Prince Pereira141ed812016-09-02 19:03:18 +0530529 /**
530 * Reading cookieId from OFErrorMsg.
531 *
532 * Loxigen OpenFlow API failed in parsing error messages because of
533 * 64 byte data truncation based on OpenFlow specs. The method written
534 * is a workaround to extract the cookieId from the packet till the
535 * issue is resolved in Loxigen OpenFlow code.
536 * Ref: https://groups.google.com/a/onosproject.org/forum/#!topic
537 * /onos-dev/_KwlHZDllLE
538 *
539 * @param msg OF error message
540 * @param ofVersion Openflow version
541 * @return cookieId
542 */
543 private U64 readCookieIdFromOFErrorMsg(OFErrorMsg msg,
544 OFVersion ofVersion) {
545
546 if (ofVersion.wireVersion < OFVersion.OF_13.wireVersion) {
547 log.debug("Unhandled error msg with OF version {} " +
548 "which is less than {}",
549 ofVersion, OFVersion.OF_13);
550 return null;
551 }
552
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700553 ByteBuf bb = Unpooled.wrappedBuffer(msg.getData().getData());
Prince Pereira141ed812016-09-02 19:03:18 +0530554
555 if (bb.readableBytes() < MIN_EXPECTED_BYTE_LEN) {
556 log.debug("Wrong length: Expected to be >= {}, was: {}",
557 MIN_EXPECTED_BYTE_LEN, bb.readableBytes());
558 return null;
559 }
560
561 byte ofVer = bb.readByte();
562
563 if (ofVer != ofVersion.wireVersion) {
564 log.debug("Wrong version: Expected={}, got={}",
565 ofVersion.wireVersion, ofVer);
566 return null;
567 }
568
569 byte type = bb.readByte();
570
571 if (type != OFType.FLOW_MOD.ordinal()) {
572 log.debug("Wrong type: Expected={}, got={}",
573 OFType.FLOW_MOD.ordinal(), type);
574 return null;
575 }
576
577 int length = U16.f(bb.readShort());
578
579 if (length < MIN_EXPECTED_BYTE_LEN) {
580 log.debug("Wrong length: Expected to be >= {}, was: {}",
581 MIN_EXPECTED_BYTE_LEN, length);
582 return null;
583 }
584
585 bb.skipBytes(SKIP_BYTES);
586 return U64.ofRaw(bb.readLong());
587 }
588
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700589 @Override
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700590 public void receivedRoleReply(Dpid dpid, RoleState requested,
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800591 RoleState response) {
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700592 // Do nothing here for now.
593 }
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700594
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300595 private DriverHandler getDriver(DeviceId devId) {
596 Driver driver = driverService.getDriver(devId);
597 DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, devId));
598 return handler;
599 }
600
601 private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies, DriverHandler handler) {
alshabib64def642014-12-02 23:27:37 -0800602
alshabib54ce5892014-09-23 17:50:51 -0700603 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900604 NewAdaptiveFlowStatsCollector afsc = afsCollectors.get(dpid);
alshabib54ce5892014-09-23 17:50:51 -0700605
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900606 if (adaptiveFlowSampling && afsc != null) {
607 List<FlowEntry> flowEntries = replies.getEntries().stream()
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300608 .map(entry -> new FlowEntryBuilder(did, entry, handler).withSetAfsc(afsc).build())
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900609 .collect(Collectors.toList());
alshabib54ce5892014-09-23 17:50:51 -0700610
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900611 // Check that OFFlowStatsReply Xid is same with the one of OFFlowStatsRequest?
612 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
613 log.debug("OpenFlowRuleProvider:pushFlowMetrics, flowMissingXid={}, "
614 + "OFFlowStatsReply Xid={}, for {}",
615 afsc.getFlowMissingXid(), replies.getXid(), dpid);
616 if (afsc.getFlowMissingXid() == replies.getXid()) {
617 // call entire flow stats update with flowMissing synchronization.
618 // used existing pushFlowMetrics
619 providerService.pushFlowMetrics(did, flowEntries);
ssyoon9030fbcd92015-08-17 10:42:07 +0900620 }
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900621 // reset flowMissingXid to NO_FLOW_MISSING_XID
622 afsc.setFlowMissingXid(NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID);
623 } else {
624 // call individual flow stats update
625 providerService.pushFlowMetricsWithoutFlowMissing(did, flowEntries);
ssyoon9030fbcd92015-08-17 10:42:07 +0900626 }
627 } else {
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900628 List<FlowEntry> flowEntries = replies.getEntries().stream()
Rodrigo Duarte Sousae37d1292017-08-29 17:01:24 -0300629 .map(entry -> new FlowEntryBuilder(did, entry, handler).build())
Sangsik Yoonb1b823f2016-05-16 18:55:39 +0900630 .collect(Collectors.toList());
631
ssyoon9030fbcd92015-08-17 10:42:07 +0900632 // call existing entire flow stats update with flowMissing synchronization
633 providerService.pushFlowMetrics(did, flowEntries);
634 }
alshabib5c370ff2014-09-18 10:12:14 -0700635 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700636
637 private void pushTableStatistics(Dpid dpid, OFTableStatsReply replies) {
638
639 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
640 List<TableStatisticsEntry> tableStatsEntries = replies.getEntries().stream()
641 .map(entry -> buildTableStatistics(did, entry))
642 .filter(Objects::nonNull)
643 .collect(Collectors.toList());
644 providerService.pushTableStatistics(did, tableStatsEntries);
645 }
646
Cem Türker3baff672017-10-12 15:09:01 +0300647 private void pushFlowLightWeightMetrics(Dpid dpid, OFFlowLightweightStatsReply replies) {
648
649 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
650 NewAdaptiveFlowStatsCollector afsc = afsCollectors.get(dpid);
651 if (adaptiveFlowSampling && afsc != null) {
652 List<FlowEntry> flowEntries = replies.getEntries().stream()
653 .map(entry -> new FlowEntryBuilder(did, entry, driverService).withSetAfsc(afsc).build())
654 .collect(Collectors.toList());
655
656 // Check that OFFlowStatsReply Xid is same with the one of OFFlowStatsRequest?
657 if (afsc.getFlowMissingXid() != NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID) {
658 log.debug("OpenFlowRuleProvider:pushFlowMetrics, flowMissingXid={}, "
659 + "OFFlowStatsReply Xid={}, for {}",
660 afsc.getFlowMissingXid(), replies.getXid(), dpid);
661 if (afsc.getFlowMissingXid() == replies.getXid()) {
662 // call entire flow stats update with flowMissing synchronization.
663 // used existing pushFlowMetrics
664 providerService.pushFlowMetrics(did, flowEntries);
665 }
666 // reset flowMissingXid to NO_FLOW_MISSING_XID
667 afsc.setFlowMissingXid(NewAdaptiveFlowStatsCollector.NO_FLOW_MISSING_XID);
668 } else {
669 // call individual flow stats update
670 providerService.pushFlowMetricsWithoutFlowMissing(did, flowEntries);
671 }
672 } else {
673 List<FlowEntry> flowEntries = replies.getEntries().stream()
674 .map(entry -> new FlowEntryBuilder(did, entry, driverService).build())
675 .collect(Collectors.toList());
676 // call existing entire flow stats update with flowMissing synchronization
677 providerService.pushFlowMetrics(did, flowEntries);
678 }
679 }
680
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700681 private TableStatisticsEntry buildTableStatistics(DeviceId deviceId,
682 OFTableStatsEntry ofEntry) {
683 TableStatisticsEntry entry = null;
684 if (ofEntry != null) {
hjtsao1a4333c2018-10-22 11:02:00 -0700685 IndexTableId tid = IndexTableId.of(ofEntry.getTableId().getValue());
686
687 try {
688 entry = DefaultTableStatisticsEntry.builder()
689 .withDeviceId(deviceId)
690 .withTableId(tid)
691 .withActiveFlowEntries(ofEntry.getActiveCount())
692 .withPacketsLookedUpCount(ofEntry.getLookupCount().getValue())
693 .withPacketsMatchedCount(ofEntry.getMatchedCount().getValue())
694 .withMaxSize(ofEntry.getMaxEntries()).build();
695 } catch (UnsupportedOperationException e) {
696 // The exception "UnsupportedOperationException" is thrown by "getMaxEntries()".
697 entry = DefaultTableStatisticsEntry.builder()
698 .withDeviceId(deviceId)
699 .withTableId(tid)
700 .withActiveFlowEntries(ofEntry.getActiveCount())
701 .withPacketsLookedUpCount(ofEntry.getLookupCount().getValue())
702 .withPacketsMatchedCount(ofEntry.getMatchedCount().getValue()).build();
703 }
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700704 }
705
706 return entry;
707
708 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700709 }
alshabib1cc04f72014-09-16 16:09:58 -0700710
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800711 /**
jcc3d4e14a2015-04-21 11:32:05 +0800712 * The internal cache entry holding the original request as well as
713 * accumulating the any failures along the way.
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700714 * <p/>
jcc3d4e14a2015-04-21 11:32:05 +0800715 * If this entry is evicted from the cache then the entire operation is
716 * considered failed. Otherwise, only the failures reported by the device
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800717 * will be propagated up.
718 */
719 private class InternalCacheEntry {
alshabib902d41b2014-10-07 16:52:05 -0700720
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800721 private final FlowRuleBatchOperation operation;
722 private final Set<FlowRule> failures = Sets.newConcurrentHashSet();
alshabib193525b2014-10-08 18:58:03 -0700723
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800724 public InternalCacheEntry(FlowRuleBatchOperation operation) {
725 this.operation = operation;
alshabib902d41b2014-10-07 16:52:05 -0700726 }
727
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800728 /**
729 * Appends a failed rule to the set of failed items.
jcc3d4e14a2015-04-21 11:32:05 +0800730 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800731 * @param rule the failed rule
732 */
733 public void appendFailure(FlowRule rule) {
734 failures.add(rule);
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800735 }
736
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800737 /**
738 * Fails the entire batch and returns the failed operation.
jcc3d4e14a2015-04-21 11:32:05 +0800739 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800740 * @return the failed operation
741 */
742 public CompletedBatchOperation failedCompletion() {
743 Set<FlowRule> fails = operation.getOperations().stream()
744 .map(op -> op.target()).collect(Collectors.toSet());
jcc3d4e14a2015-04-21 11:32:05 +0800745 return new CompletedBatchOperation(false,
746 Collections
747 .unmodifiableSet(fails),
748 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700749 }
750
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800751 /**
752 * Returns the completed operation and whether the batch suceeded.
jcc3d4e14a2015-04-21 11:32:05 +0800753 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800754 * @return the completed operation
755 */
756 public CompletedBatchOperation completed() {
jcc3d4e14a2015-04-21 11:32:05 +0800757 return new CompletedBatchOperation(
Thomas Vachuska75aaa672015-04-29 12:24:43 -0700758 failures.isEmpty(),
759 Collections
760 .unmodifiableSet(failures),
761 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700762 }
alshabib902d41b2014-10-07 16:52:05 -0700763 }
alshabiba68eb962014-09-24 20:34:13 -0700764
hjtsao1a4333c2018-10-22 11:02:00 -0700765}