blob: 2621f311afc33fc59d8c9e14e148cc94c83c5ee0 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
jcc3d4e14a2015-04-21 11:32:05 +08002 * Copyright 2014 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
jcc3d4e14a2015-04-21 11:32:05 +080018import static org.slf4j.LoggerFactory.getLogger;
Jonathan Hart2ffcd102015-01-16 16:47:50 -080019
jcc3d4e14a2015-04-21 11:32:05 +080020import java.util.Collections;
21import java.util.List;
22import java.util.Map;
23import java.util.Optional;
24import java.util.Set;
25import java.util.concurrent.TimeUnit;
26import java.util.stream.Collectors;
27
alshabib1cc04f72014-09-16 16:09:58 -070028import org.apache.felix.scr.annotations.Activate;
29import org.apache.felix.scr.annotations.Component;
30import org.apache.felix.scr.annotations.Deactivate;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
Brian O'Connorabafb502014-12-02 22:26:20 -080033import org.onosproject.core.ApplicationId;
34import org.onosproject.net.DeviceId;
Brian O'Connorabafb502014-12-02 22:26:20 -080035import org.onosproject.net.flow.CompletedBatchOperation;
Brian O'Connorabafb502014-12-02 22:26:20 -080036import org.onosproject.net.flow.FlowEntry;
37import org.onosproject.net.flow.FlowRule;
38import org.onosproject.net.flow.FlowRuleBatchEntry;
Brian O'Connor72cb19a2015-01-16 16:14:41 -080039import org.onosproject.net.flow.FlowRuleBatchOperation;
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -070040import org.onosproject.net.flow.FlowRuleExtPayLoad;
Brian O'Connorabafb502014-12-02 22:26:20 -080041import org.onosproject.net.flow.FlowRuleProvider;
42import org.onosproject.net.flow.FlowRuleProviderRegistry;
43import org.onosproject.net.flow.FlowRuleProviderService;
44import org.onosproject.net.provider.AbstractProvider;
45import org.onosproject.net.provider.ProviderId;
Brian O'Connorabafb502014-12-02 22:26:20 -080046import org.onosproject.openflow.controller.Dpid;
47import org.onosproject.openflow.controller.OpenFlowController;
48import org.onosproject.openflow.controller.OpenFlowEventListener;
49import org.onosproject.openflow.controller.OpenFlowSwitch;
50import org.onosproject.openflow.controller.OpenFlowSwitchListener;
51import org.onosproject.openflow.controller.RoleState;
jcc3d4e14a2015-04-21 11:32:05 +080052import org.onosproject.openflow.controller.ThirdPartyMessage;
alshabib902d41b2014-10-07 16:52:05 -070053import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
54import org.projectfloodlight.openflow.protocol.OFErrorMsg;
Brian O'Connor72cb19a2015-01-16 16:14:41 -080055import org.projectfloodlight.openflow.protocol.OFErrorType;
alshabib193525b2014-10-08 18:58:03 -070056import org.projectfloodlight.openflow.protocol.OFFlowMod;
alshabib8f1cf4a2014-09-17 14:44:48 -070057import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
alshabib5c370ff2014-09-18 10:12:14 -070058import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
alshabib8f1cf4a2014-09-17 14:44:48 -070059import org.projectfloodlight.openflow.protocol.OFMessage;
60import org.projectfloodlight.openflow.protocol.OFPortStatus;
alshabib5c370ff2014-09-18 10:12:14 -070061import org.projectfloodlight.openflow.protocol.OFStatsReply;
sangho89bf6fb2015-02-09 09:33:13 -080062import org.projectfloodlight.openflow.protocol.OFStatsType;
alshabib193525b2014-10-08 18:58:03 -070063import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
alshabib1cc04f72014-09-16 16:09:58 -070064import org.slf4j.Logger;
65
jcc3d4e14a2015-04-21 11:32:05 +080066import com.google.common.cache.Cache;
67import com.google.common.cache.CacheBuilder;
68import com.google.common.cache.RemovalCause;
69import com.google.common.cache.RemovalNotification;
70import com.google.common.collect.Maps;
71import com.google.common.collect.Sets;
alshabibeec3a062014-09-17 18:01:26 -070072
alshabib1cc04f72014-09-16 16:09:58 -070073/**
jcc3d4e14a2015-04-21 11:32:05 +080074 * Provider which uses an OpenFlow controller to detect network end-station
75 * hosts.
alshabib1cc04f72014-09-16 16:09:58 -070076 */
77@Component(immediate = true)
jcc3d4e14a2015-04-21 11:32:05 +080078public class OpenFlowRuleProvider extends AbstractProvider
79 implements FlowRuleProvider {
alshabib1cc04f72014-09-16 16:09:58 -070080
81 private final Logger log = getLogger(getClass());
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected FlowRuleProviderRegistry providerRegistry;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected OpenFlowController controller;
88
alshabib1cc04f72014-09-16 16:09:58 -070089 private FlowRuleProviderService providerService;
90
alshabibeec3a062014-09-17 18:01:26 -070091 private final InternalFlowProvider listener = new InternalFlowProvider();
92
Brian O'Connor72cb19a2015-01-16 16:14:41 -080093 private Cache<Long, InternalCacheEntry> pendingBatches;
alshabib193525b2014-10-08 18:58:03 -070094
alshabib3d643ec2014-10-22 18:33:00 -070095 private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap();
96
alshabib1cc04f72014-09-16 16:09:58 -070097 /**
98 * Creates an OpenFlow host provider.
99 */
100 public OpenFlowRuleProvider() {
Brian O'Connorabafb502014-12-02 22:26:20 -0800101 super(new ProviderId("of", "org.onosproject.provider.openflow"));
alshabib1cc04f72014-09-16 16:09:58 -0700102 }
103
104 @Activate
105 public void activate() {
106 providerService = providerRegistry.register(this);
alshabibeec3a062014-09-17 18:01:26 -0700107 controller.addListener(listener);
108 controller.addEventListener(listener);
alshabib3d643ec2014-10-22 18:33:00 -0700109
jcc3d4e14a2015-04-21 11:32:05 +0800110 pendingBatches = CacheBuilder
111 .newBuilder()
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800112 .expireAfterWrite(10, TimeUnit.SECONDS)
113 .removalListener((RemovalNotification<Long, InternalCacheEntry> notification) -> {
jcc3d4e14a2015-04-21 11:32:05 +0800114 if (notification.getCause() == RemovalCause.EXPIRED) {
115 providerService
116 .batchOperationCompleted(notification
117 .getKey(),
118 notification
119 .getValue()
120 .failedCompletion());
121 }
122 }).build();
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800123
alshabib3d643ec2014-10-22 18:33:00 -0700124 for (OpenFlowSwitch sw : controller.getSwitches()) {
125 FlowStatsCollector fsc = new FlowStatsCollector(sw, POLL_INTERVAL);
126 fsc.start();
127 collectors.put(new Dpid(sw.getId()), fsc);
128 }
129
alshabib1cc04f72014-09-16 16:09:58 -0700130 log.info("Started");
131 }
132
133 @Deactivate
134 public void deactivate() {
135 providerRegistry.unregister(this);
136 providerService = null;
137
138 log.info("Stopped");
139 }
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800140
alshabib1cc04f72014-09-16 16:09:58 -0700141 @Override
142 public void applyFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800143 for (FlowRule flowRule : flowRules) {
144 applyRule(flowRule);
alshabib35edb1a2014-09-16 17:44:44 -0700145 }
alshabib1cc04f72014-09-16 16:09:58 -0700146 }
147
alshabib35edb1a2014-09-16 17:44:44 -0700148 private void applyRule(FlowRule flowRule) {
jcc3d4e14a2015-04-21 11:32:05 +0800149 OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId()
150 .uri()));
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700151 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
152 if (hasPayload(flowRuleExtPayLoad)) {
153 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800154 sw.sendMsg(msg);
155 return;
156 }
alshabibbdcbb102015-04-22 14:16:38 -0700157 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
jcc3d4e14a2015-04-21 11:32:05 +0800158 Optional.empty()).buildFlowAdd());
alshabib35edb1a2014-09-16 17:44:44 -0700159 }
160
alshabib1cc04f72014-09-16 16:09:58 -0700161 @Override
162 public void removeFlowRule(FlowRule... flowRules) {
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800163 for (FlowRule flowRule : flowRules) {
164 removeRule(flowRule);
alshabib219ebaa2014-09-22 15:41:24 -0700165 }
alshabib1cc04f72014-09-16 16:09:58 -0700166 }
167
alshabib219ebaa2014-09-22 15:41:24 -0700168 private void removeRule(FlowRule flowRule) {
jcc3d4e14a2015-04-21 11:32:05 +0800169 OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId()
170 .uri()));
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700171 FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad();
172 if (hasPayload(flowRuleExtPayLoad)) {
173 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800174 sw.sendMsg(msg);
175 return;
176 }
alshabibbdcbb102015-04-22 14:16:38 -0700177 sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(),
jcc3d4e14a2015-04-21 11:32:05 +0800178 Optional.empty()).buildFlowDel());
alshabib219ebaa2014-09-22 15:41:24 -0700179 }
180
alshabiba68eb962014-09-24 20:34:13 -0700181 @Override
182 public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
183 // TODO: optimize using the ApplicationId
184 removeFlowRule(flowRules);
185 }
186
alshabib193525b2014-10-08 18:58:03 -0700187 @Override
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800188 public void executeBatch(FlowRuleBatchOperation batch) {
189
190 pendingBatches.put(batch.id(), new InternalCacheEntry(batch));
191
jcc3d4e14a2015-04-21 11:32:05 +0800192 OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(batch.deviceId()
193 .uri()));
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800194 OFFlowMod mod;
alshabib193525b2014-10-08 18:58:03 -0700195 for (FlowRuleBatchEntry fbe : batch.getOperations()) {
jcc3d4e14a2015-04-21 11:32:05 +0800196 // flow is the third party privacy flow
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700197
198 FlowRuleExtPayLoad flowRuleExtPayLoad = fbe.target().payLoad();
199 if (hasPayload(flowRuleExtPayLoad)) {
200 OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad());
jcc3d4e14a2015-04-21 11:32:05 +0800201 sw.sendMsg(msg);
202 continue;
203 }
204 FlowModBuilder builder = FlowModBuilder.builder(fbe.target(), sw
205 .factory(), Optional.of(batch.id()));
Sho SHIMIZUaba9d002015-01-29 14:51:04 -0800206 switch (fbe.operator()) {
jcc3d4e14a2015-04-21 11:32:05 +0800207 case ADD:
208 mod = builder.buildFlowAdd();
209 break;
210 case REMOVE:
211 mod = builder.buildFlowDel();
212 break;
213 case MODIFY:
214 mod = builder.buildFlowMod();
215 break;
216 default:
217 log.error("Unsupported batch operation {}; skipping flowmod {}",
218 fbe.operator(), fbe);
219 continue;
220 }
Saurav Das3ea46622015-04-22 14:01:34 -0700221 sw.sendMsg(mod);
alshabib193525b2014-10-08 18:58:03 -0700222 }
jcc3d4e14a2015-04-21 11:32:05 +0800223 OFBarrierRequest.Builder builder = sw.factory().buildBarrierRequest()
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800224 .setXid(batch.id());
225 sw.sendMsg(builder.build());
alshabib193525b2014-10-08 18:58:03 -0700226 }
227
Thomas Vachuskaa6c0d042015-04-23 10:17:37 -0700228 private boolean hasPayload(FlowRuleExtPayLoad flowRuleExtPayLoad) {
229 return flowRuleExtPayLoad != null &&
230 flowRuleExtPayLoad.payLoad() != null &&
231 flowRuleExtPayLoad.payLoad().length > 0;
232 }
233
alshabib8f1cf4a2014-09-17 14:44:48 -0700234 private class InternalFlowProvider
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800235 implements OpenFlowSwitchListener, OpenFlowEventListener {
alshabib8f1cf4a2014-09-17 14:44:48 -0700236
alshabib8f1cf4a2014-09-17 14:44:48 -0700237 @Override
238 public void switchAdded(Dpid dpid) {
jcc3d4e14a2015-04-21 11:32:05 +0800239 FlowStatsCollector fsc = new FlowStatsCollector(
240 controller
241 .getSwitch(dpid),
242 POLL_INTERVAL);
alshabibeec3a062014-09-17 18:01:26 -0700243 fsc.start();
244 collectors.put(dpid, fsc);
alshabib8f1cf4a2014-09-17 14:44:48 -0700245 }
246
247 @Override
248 public void switchRemoved(Dpid dpid) {
alshabibdfc7afb2014-10-21 20:13:27 -0700249 FlowStatsCollector collector = collectors.remove(dpid);
250 if (collector != null) {
251 collector.stop();
252 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700253 }
254
255 @Override
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700256 public void switchChanged(Dpid dpid) {
257 }
258
259 @Override
alshabib8f1cf4a2014-09-17 14:44:48 -0700260 public void portChanged(Dpid dpid, OFPortStatus status) {
jcc3d4e14a2015-04-21 11:32:05 +0800261 // TODO: Decide whether to evict flows internal store.
alshabib8f1cf4a2014-09-17 14:44:48 -0700262 }
263
264 @Override
265 public void handleMessage(Dpid dpid, OFMessage msg) {
alshabibda1644e2015-03-13 14:01:35 -0700266 OpenFlowSwitch sw = controller.getSwitch(dpid);
alshabib8f1cf4a2014-09-17 14:44:48 -0700267 switch (msg.getType()) {
jcc3d4e14a2015-04-21 11:32:05 +0800268 case FLOW_REMOVED:
269 OFFlowRemoved removed = (OFFlowRemoved) msg;
alshabib6b5cfec2014-09-18 17:42:18 -0700270
jcc3d4e14a2015-04-21 11:32:05 +0800271 FlowEntry fr = new FlowEntryBuilder(dpid, removed).build();
272 providerService.flowRemoved(fr);
273 break;
274 case STATS_REPLY:
275 if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) {
276 pushFlowMetrics(dpid, (OFFlowStatsReply) msg);
277 }
278 break;
279 case BARRIER_REPLY:
280 try {
281 InternalCacheEntry entry = pendingBatches.getIfPresent(msg
282 .getXid());
283 if (entry != null) {
284 providerService
285 .batchOperationCompleted(msg.getXid(),
286 entry.completed());
287 } else {
288 log.warn("Received unknown Barrier Reply: {}",
289 msg.getXid());
sangho89bf6fb2015-02-09 09:33:13 -0800290 }
jcc3d4e14a2015-04-21 11:32:05 +0800291 } finally {
292 pendingBatches.invalidate(msg.getXid());
293 }
294 break;
295 case ERROR:
296 log.warn("received Error message {} from {}", msg, dpid);
297
298 OFErrorMsg error = (OFErrorMsg) msg;
299 if (error.getErrType() == OFErrorType.FLOW_MOD_FAILED) {
300 OFFlowModFailedErrorMsg fmFailed = (OFFlowModFailedErrorMsg) error;
301 if (fmFailed.getData().getParsedMessage().isPresent()) {
302 OFMessage m = fmFailed.getData().getParsedMessage()
303 .get();
304 OFFlowMod fm = (OFFlowMod) m;
305 InternalCacheEntry entry = pendingBatches
306 .getIfPresent(msg.getXid());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800307 if (entry != null) {
jcc3d4e14a2015-04-21 11:32:05 +0800308 entry.appendFailure(new FlowEntryBuilder(dpid, fm)
309 .build());
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800310 } else {
jcc3d4e14a2015-04-21 11:32:05 +0800311 log.error("No matching batch for this error: {}",
312 error);
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800313 }
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800314 } else {
jcc3d4e14a2015-04-21 11:32:05 +0800315 // FIXME: Potentially add flowtracking to avoid this
316 // message.
317 log.error("Flow installation failed but switch didn't"
318 + " tell us which one.");
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800319 }
jcc3d4e14a2015-04-21 11:32:05 +0800320 } else {
321 log.warn("Received error {}", error);
322 }
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800323
jcc3d4e14a2015-04-21 11:32:05 +0800324 default:
325 log.debug("Unhandled message type: {}", msg.getType());
alshabib8f1cf4a2014-09-17 14:44:48 -0700326 }
327
328 }
329
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700330 @Override
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700331 public void receivedRoleReply(Dpid dpid, RoleState requested,
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800332 RoleState response) {
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700333 // Do nothing here for now.
334 }
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700335
sangho89bf6fb2015-02-09 09:33:13 -0800336 private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies) {
alshabib64def642014-12-02 23:27:37 -0800337
alshabib54ce5892014-09-23 17:50:51 -0700338 DeviceId did = DeviceId.deviceId(Dpid.uri(dpid));
Saurav Dasfa2fa932015-03-03 11:29:48 -0800339 OpenFlowSwitch sw = controller.getSwitch(dpid);
alshabib54ce5892014-09-23 17:50:51 -0700340
alshabib64def642014-12-02 23:27:37 -0800341 List<FlowEntry> flowEntries = replies.getEntries().stream()
alshabibbdcbb102015-04-22 14:16:38 -0700342 .map(entry -> new FlowEntryBuilder(dpid, entry).build())
alshabib64def642014-12-02 23:27:37 -0800343 .collect(Collectors.toList());
alshabib54ce5892014-09-23 17:50:51 -0700344
alshabib64def642014-12-02 23:27:37 -0800345 providerService.pushFlowMetrics(did, flowEntries);
346
alshabib5c370ff2014-09-18 10:12:14 -0700347 }
348
alshabib8f1cf4a2014-09-17 14:44:48 -0700349 }
alshabib1cc04f72014-09-16 16:09:58 -0700350
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800351 /**
jcc3d4e14a2015-04-21 11:32:05 +0800352 * The internal cache entry holding the original request as well as
353 * accumulating the any failures along the way.
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800354 *
jcc3d4e14a2015-04-21 11:32:05 +0800355 * If this entry is evicted from the cache then the entire operation is
356 * considered failed. Otherwise, only the failures reported by the device
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800357 * will be propagated up.
358 */
359 private class InternalCacheEntry {
alshabib902d41b2014-10-07 16:52:05 -0700360
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800361 private final FlowRuleBatchOperation operation;
362 private final Set<FlowRule> failures = Sets.newConcurrentHashSet();
alshabib193525b2014-10-08 18:58:03 -0700363
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800364 public InternalCacheEntry(FlowRuleBatchOperation operation) {
365 this.operation = operation;
alshabib902d41b2014-10-07 16:52:05 -0700366 }
367
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800368 /**
369 * Appends a failed rule to the set of failed items.
jcc3d4e14a2015-04-21 11:32:05 +0800370 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800371 * @param rule the failed rule
372 */
373 public void appendFailure(FlowRule rule) {
374 failures.add(rule);
Thomas Vachuska9b2da212014-11-10 19:30:25 -0800375 }
376
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800377 /**
378 * Fails the entire batch and returns the failed operation.
jcc3d4e14a2015-04-21 11:32:05 +0800379 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800380 * @return the failed operation
381 */
382 public CompletedBatchOperation failedCompletion() {
383 Set<FlowRule> fails = operation.getOperations().stream()
384 .map(op -> op.target()).collect(Collectors.toSet());
jcc3d4e14a2015-04-21 11:32:05 +0800385 return new CompletedBatchOperation(false,
386 Collections
387 .unmodifiableSet(fails),
388 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700389 }
390
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800391 /**
392 * Returns the completed operation and whether the batch suceeded.
jcc3d4e14a2015-04-21 11:32:05 +0800393 *
Brian O'Connor72cb19a2015-01-16 16:14:41 -0800394 * @return the completed operation
395 */
396 public CompletedBatchOperation completed() {
jcc3d4e14a2015-04-21 11:32:05 +0800397 return new CompletedBatchOperation(
398 failures.isEmpty(),
399 Collections
400 .unmodifiableSet(failures),
401 operation.deviceId());
alshabib902d41b2014-10-07 16:52:05 -0700402 }
403
alshabib902d41b2014-10-07 16:52:05 -0700404 }
alshabiba68eb962014-09-24 20:34:13 -0700405
alshabib1cc04f72014-09-16 16:09:58 -0700406}