blob: 9f3c8729e42b5acfbf7e644f8fc6af1f23a1214b [file] [log] [blame]
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -08001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.sdnip;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080017
Jonathan Hart552e31f2015-02-06 11:11:59 -080018import com.google.common.util.concurrent.ThreadFactoryBuilder;
19import org.onlab.packet.Ethernet;
20import org.onlab.packet.Ip4Address;
21import org.onlab.packet.IpAddress;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080022import org.onlab.packet.IpPrefix;
Jonathan Hart552e31f2015-02-06 11:11:59 -080023import org.onlab.packet.MacAddress;
24import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080025import org.onosproject.core.ApplicationId;
Jonathan Hart552e31f2015-02-06 11:11:59 -080026import org.onosproject.net.ConnectPoint;
27import org.onosproject.net.flow.DefaultTrafficSelector;
28import org.onosproject.net.flow.DefaultTrafficTreatment;
29import org.onosproject.net.flow.TrafficSelector;
30import org.onosproject.net.flow.TrafficTreatment;
Brian O'Connorabafb502014-12-02 22:26:20 -080031import org.onosproject.net.flow.criteria.Criteria.IPCriterion;
32import org.onosproject.net.flow.criteria.Criterion;
33import org.onosproject.net.intent.Intent;
34import org.onosproject.net.intent.IntentOperations;
35import org.onosproject.net.intent.IntentService;
36import org.onosproject.net.intent.IntentState;
37import org.onosproject.net.intent.MultiPointToSinglePointIntent;
38import org.onosproject.net.intent.PointToPointIntent;
Jonathan Hart552e31f2015-02-06 11:11:59 -080039import org.onosproject.sdnip.config.BgpPeer;
40import org.onosproject.sdnip.config.Interface;
41import org.onosproject.sdnip.config.SdnIpConfigurationService;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080042import org.slf4j.Logger;
43import org.slf4j.LoggerFactory;
44
Jonathan Hart552e31f2015-02-06 11:11:59 -080045import java.util.Collection;
46import java.util.HashMap;
47import java.util.HashSet;
48import java.util.LinkedList;
49import java.util.List;
50import java.util.Map;
51import java.util.Objects;
52import java.util.Set;
53import java.util.concurrent.ConcurrentHashMap;
54import java.util.concurrent.ExecutorService;
55import java.util.concurrent.Executors;
56import java.util.concurrent.Semaphore;
57
58import static com.google.common.base.Preconditions.checkArgument;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080059
Jonathan Hart51372182014-12-03 21:32:34 -080060/**
61 * Synchronizes intents between the in-memory intent store and the
62 * IntentService.
63 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080064public class IntentSynchronizer implements FibListener {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080065 private static final Logger log =
66 LoggerFactory.getLogger(IntentSynchronizer.class);
67
68 private final ApplicationId appId;
69 private final IntentService intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080070 private final Map<IntentKey, PointToPointIntent> peerIntents;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080071 private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080072
73 //
74 // State to deal with SDN-IP Leader election and pushing Intents
75 //
76 private final ExecutorService bgpIntentsSynchronizerExecutor;
77 private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
78 private volatile boolean isElectedLeader = false;
79 private volatile boolean isActivatedLeader = false;
80
Jonathan Hart552e31f2015-02-06 11:11:59 -080081 private final SdnIpConfigurationService configService;
82 private final InterfaceService interfaceService;
83
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080084 /**
85 * Class constructor.
86 *
87 * @param appId the Application ID
88 * @param intentService the intent service
Jonathan Hart552e31f2015-02-06 11:11:59 -080089 * @param configService the SDN-IP configuration service
90 * @param interfaceService the interface service
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080091 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080092 IntentSynchronizer(ApplicationId appId, IntentService intentService,
93 SdnIpConfigurationService configService,
94 InterfaceService interfaceService) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080095 this.appId = appId;
96 this.intentService = intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080097 peerIntents = new ConcurrentHashMap<>();
98 routeIntents = new ConcurrentHashMap<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080099
Jonathan Hart552e31f2015-02-06 11:11:59 -0800100 this.configService = configService;
101 this.interfaceService = interfaceService;
102
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800103 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
104 new ThreadFactoryBuilder()
Pavlin Radoslavov8b752442014-11-18 14:34:37 -0800105 .setNameFormat("sdnip-intents-synchronizer-%d").build());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800106 }
107
108 /**
109 * Starts the synchronizer.
110 */
111 public void start() {
112 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
113 @Override
114 public void run() {
115 doIntentSynchronizationThread();
116 }
117 });
118 }
119
120 /**
121 * Stops the synchronizer.
122 */
123 public void stop() {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800124 synchronized (this) {
125 // Stop the thread(s)
126 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800127
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800128 //
129 // Withdraw all SDN-IP intents
130 //
131 if (!isElectedLeader) {
132 return; // Nothing to do: not the leader anymore
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800133 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800134
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800135 //
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800136 // NOTE: We don't withdraw the intents during shutdown, because
137 // it creates flux in the data plane during switchover.
138 //
139
140 /*
141 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800142 // Build a batch operation to withdraw all intents from this
143 // application.
144 //
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800145 log.debug("SDN-IP Intent Synchronizer shutdown: " +
146 "withdrawing all intents...");
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800147 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800148 for (Intent intent : intentService.getIntents()) {
149 // Skip the intents from other applications
150 if (!intent.appId().equals(appId)) {
151 continue;
152 }
153
154 // Skip the intents that are already withdrawn
155 IntentState intentState =
156 intentService.getIntentState(intent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800157 if ((intentState == null) ||
158 intentState.equals(IntentState.WITHDRAWING) ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800159 intentState.equals(IntentState.WITHDRAWN)) {
160 continue;
161 }
162
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800163 log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800164 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800165 builder.addWithdrawOperation(intent.id());
166 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800167 IntentOperations intentOperations = builder.build();
168 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800169 leaderChanged(false);
170
171 peerIntents.clear();
172 routeIntents.clear();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800173 log.debug("SDN-IP Intent Synchronizer shutdown completed");
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800174 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800175 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800176 }
177
Jonathan Hart51372182014-12-03 21:32:34 -0800178 /**
179 * Signals the synchronizer that the SDN-IP leadership has changed.
180 *
181 * @param isLeader true if this instance is now the leader, otherwise false
182 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800183 public void leaderChanged(boolean isLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800184 log.debug("SDN-IP Leader changed: {}", isLeader);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800185
186 if (!isLeader) {
187 this.isElectedLeader = false;
188 this.isActivatedLeader = false;
189 return; // Nothing to do
190 }
191 this.isActivatedLeader = false;
192 this.isElectedLeader = true;
193
194 //
195 // Tell the Intents Synchronizer thread to start the synchronization
196 //
197 intentsSynchronizerSemaphore.release();
198 }
199
200 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800201 * Gets the route intents.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800202 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800203 * @return the route intents
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800204 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800205 public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
206 List<MultiPointToSinglePointIntent> result = new LinkedList<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800207
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800208 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800209 routeIntents.entrySet()) {
210 result.add(entry.getValue());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800211 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800212 return result;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800213 }
214
215 /**
216 * Thread for Intent Synchronization.
217 */
218 private void doIntentSynchronizationThread() {
219 boolean interrupted = false;
220 try {
221 while (!interrupted) {
222 try {
223 intentsSynchronizerSemaphore.acquire();
224 //
225 // Drain all permits, because a single synchronization is
226 // sufficient.
227 //
228 intentsSynchronizerSemaphore.drainPermits();
229 } catch (InterruptedException e) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800230 interrupted = true;
231 break;
232 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800233 synchronizeIntents();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800234 }
235 } finally {
236 if (interrupted) {
237 Thread.currentThread().interrupt();
238 }
239 }
240 }
241
242 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800243 * Submits a collection of point-to-point intents.
244 *
245 * @param intents the intents to submit
246 */
247 void submitPeerIntents(Collection<PointToPointIntent> intents) {
248 synchronized (this) {
249 // Store the intents in memory
250 for (PointToPointIntent intent : intents) {
251 peerIntents.put(new IntentKey(intent), intent);
252 }
253
254 // Push the intents
255 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800256 log.debug("SDN-IP Submitting all Peer Intents...");
Brian O'Connor72a034c2014-11-26 18:24:23 -0800257 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800258 for (Intent intent : intents) {
Pavlin Radoslavovdde22ae2014-11-24 11:47:17 -0800259 builder.addSubmitOperation(intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800260 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800261 IntentOperations intentOperations = builder.build();
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800262 log.trace("SDN-IP Submitting intents: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800263 intentOperations.operations());
264 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800265 }
266 }
267 }
268
269 /**
Jonathan Hart552e31f2015-02-06 11:11:59 -0800270 * Generates a route intent for a prefix, the next hop IP address, and
271 * the next hop MAC address.
272 * <p/>
273 * This method will find the egress interface for the intent.
274 * Intent will match dst IP prefix and rewrite dst MAC address at all other
275 * border switches, then forward packets according to dst MAC address.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800276 *
Jonathan Hart552e31f2015-02-06 11:11:59 -0800277 * @param prefix IP prefix of the route to add
278 * @param nextHopIpAddress IP address of the next hop
279 * @param nextHopMacAddress MAC address of the next hop
280 * @return the generated intent, or null if no intent should be submitted
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800281 */
Jonathan Hart552e31f2015-02-06 11:11:59 -0800282 private MultiPointToSinglePointIntent generateRouteIntent(
283 IpPrefix prefix,
284 IpAddress nextHopIpAddress,
285 MacAddress nextHopMacAddress) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800286
Jonathan Hart552e31f2015-02-06 11:11:59 -0800287 // Find the attachment point (egress interface) of the next hop
288 Interface egressInterface;
289 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
290 // Route to a peer
291 log.debug("Route to peer {}", nextHopIpAddress);
292 BgpPeer peer =
293 configService.getBgpPeers().get(nextHopIpAddress);
294 egressInterface =
295 interfaceService.getInterface(peer.connectPoint());
296 } else {
297 // Route to non-peer
298 log.debug("Route to non-peer {}", nextHopIpAddress);
299 egressInterface =
300 interfaceService.getMatchingInterface(nextHopIpAddress);
301 if (egressInterface == null) {
302 log.warn("No outgoing interface found for {}",
303 nextHopIpAddress);
304 return null;
305 }
306 }
307
308 //
309 // Generate the intent itself
310 //
311 Set<ConnectPoint> ingressPorts = new HashSet<>();
312 ConnectPoint egressPort = egressInterface.connectPoint();
313 log.debug("Generating intent for prefix {}, next hop mac {}",
314 prefix, nextHopMacAddress);
315
316 for (Interface intf : interfaceService.getInterfaces()) {
317 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
318 ConnectPoint srcPort = intf.connectPoint();
319 ingressPorts.add(srcPort);
320 }
321 }
322
323 // Match the destination IP prefix at the first hop
324 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
325 if (prefix.version() == Ip4Address.VERSION) {
326 selector.matchEthType(Ethernet.TYPE_IPV4);
327 } else {
328 selector.matchEthType(Ethernet.TYPE_IPV6);
329 }
330 selector.matchIPDst(prefix);
331
332 // Rewrite the destination MAC address
333 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
334 .setEthDst(nextHopMacAddress);
335 if (!egressInterface.vlan().equals(VlanId.NONE)) {
336 treatment.setVlanId(egressInterface.vlan());
337 // If we set VLAN ID, we have to make sure a VLAN tag exists.
338 // TODO support no VLAN -> VLAN routing
339 selector.matchVlanId(VlanId.ANY);
340 }
341
342 return new MultiPointToSinglePointIntent(appId, selector.build(),
343 treatment.build(),
344 ingressPorts, egressPort);
345 }
346
347 @Override
348 public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800349 //
350 // NOTE: Semantically, we MUST withdraw existing intents before
351 // submitting new intents.
352 //
353 synchronized (this) {
354 MultiPointToSinglePointIntent intent;
355
356 log.debug("SDN-IP submitting intents = {} withdrawing = {}",
Jonathan Hart552e31f2015-02-06 11:11:59 -0800357 updates.size(), withdraws.size());
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800358
359 //
360 // Prepare the Intent batch operations for the intents to withdraw
361 //
362 IntentOperations.Builder withdrawBuilder =
363 IntentOperations.builder(appId);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800364 for (FibUpdate withdraw : withdraws) {
365 checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
366 "FibUpdate with wrong type in withdraws list");
367
368 IpPrefix prefix = withdraw.entry().prefix();
369
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800370 intent = routeIntents.remove(prefix);
371 if (intent == null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800372 log.trace("SDN-IP No intent in routeIntents to delete " +
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800373 "for prefix: {}", prefix);
374 continue;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800375 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800376 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800377 log.trace("SDN-IP Withdrawing intent: {}", intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800378 withdrawBuilder.addWithdrawOperation(intent.id());
379 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800380 }
381
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800382 //
383 // Prepare the Intent batch operations for the intents to submit
384 //
385 IntentOperations.Builder submitBuilder =
386 IntentOperations.builder(appId);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800387 for (FibUpdate update : updates) {
388 checkArgument(update.type() == FibUpdate.Type.UPDATE,
389 "FibUpdate with wrong type in updates list");
390
391 IpPrefix prefix = update.entry().prefix();
392 intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
393 update.entry().nextHopMac());
394
395 if (intent == null) {
396 // This preserves the old semantics - if an intent can't be
397 // generated, we don't do anything with that prefix. But
398 // perhaps we should withdraw the old intent anyway?
399 continue;
400 }
401
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800402 MultiPointToSinglePointIntent oldIntent =
403 routeIntents.put(prefix, intent);
404 if (isElectedLeader && isActivatedLeader) {
405 if (oldIntent != null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800406 log.trace("SDN-IP Withdrawing old intent: {}",
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800407 oldIntent);
408 withdrawBuilder.addWithdrawOperation(oldIntent.id());
409 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800410 log.trace("SDN-IP Submitting intent: {}", intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800411 submitBuilder.addSubmitOperation(intent);
412 }
413 }
414
415 //
416 // Submit the Intent operations
417 //
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800418 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800419 IntentOperations intentOperations = withdrawBuilder.build();
420 if (!intentOperations.operations().isEmpty()) {
421 log.debug("SDN-IP Withdrawing intents executed");
422 intentService.execute(intentOperations);
423 }
424 intentOperations = submitBuilder.build();
425 if (!intentOperations.operations().isEmpty()) {
426 log.debug("SDN-IP Submitting intents executed");
427 intentService.execute(intentOperations);
428 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800429 }
430 }
431 }
432
433 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800434 * Synchronize the in-memory Intents with the Intents in the Intent
435 * framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800436 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800437 void synchronizeIntents() {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800438 synchronized (this) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800439
440 Map<IntentKey, Intent> localIntents = new HashMap<>();
441 Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
442 Collection<Intent> storeInMemoryIntents = new LinkedList<>();
443 Collection<Intent> addIntents = new LinkedList<>();
444 Collection<Intent> deleteIntents = new LinkedList<>();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800445 IntentOperations intentOperations;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800446
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800447 if (!isElectedLeader) {
448 return; // Nothing to do: not the leader anymore
449 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800450 log.debug("SDN-IP synchronizing all intents...");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800451
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800452 // Prepare the local intents
453 for (Intent intent : routeIntents.values()) {
454 localIntents.put(new IntentKey(intent), intent);
455 }
456 for (Intent intent : peerIntents.values()) {
457 localIntents.put(new IntentKey(intent), intent);
458 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800459
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800460 // Fetch all intents for this application
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800461 for (Intent intent : intentService.getIntents()) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800462 if (!intent.appId().equals(appId)) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800463 continue;
464 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800465 fetchedIntents.put(new IntentKey(intent), intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800466 }
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800467 if (log.isDebugEnabled()) {
468 for (Intent intent: fetchedIntents.values()) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800469 log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800470 intent);
471 }
472 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800473
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800474 computeIntentsDelta(localIntents, fetchedIntents,
475 storeInMemoryIntents, addIntents,
476 deleteIntents);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800477
478 //
479 // Perform the actions:
480 // 1. Store in memory fetched intents that are same. Can be done
481 // even if we are not the leader anymore
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800482 // 2. Delete intents: check if the leader before the operation
483 // 3. Add intents: check if the leader before the operation
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800484 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800485 for (Intent intent : storeInMemoryIntents) {
486 // Store the intent in memory based on its type
487 if (intent instanceof MultiPointToSinglePointIntent) {
488 MultiPointToSinglePointIntent mp2pIntent =
489 (MultiPointToSinglePointIntent) intent;
490 // Find the IP prefix
491 Criterion c =
492 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800493 if (c == null) {
494 // Try IPv6
495 c =
496 mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
497 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800498 if (c != null && c instanceof IPCriterion) {
499 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800500 IpPrefix ipPrefix = ipCriterion.ip();
501 if (ipPrefix == null) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800502 continue;
503 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800504 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800505 "in-memory Route Intent for prefix {}",
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800506 ipPrefix);
507 routeIntents.put(ipPrefix, mp2pIntent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800508 } else {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800509 log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800510 mp2pIntent.id());
511 }
512 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800513 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800514 if (intent instanceof PointToPointIntent) {
515 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800516 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800517 "in-memory Peer Intent {}", p2pIntent);
518 peerIntents.put(new IntentKey(intent), p2pIntent);
519 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800520 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800521 }
522
523 // Withdraw Intents
Brian O'Connor72a034c2014-11-26 18:24:23 -0800524 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800525 for (Intent intent : deleteIntents) {
526 builder.addWithdrawOperation(intent.id());
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800527 log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800528 intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800529 }
530 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800531 log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800532 "not elected leader anymore");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800533 isActivatedLeader = false;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800534 return;
535 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800536 intentOperations = builder.build();
537 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800538
539 // Add Intents
Brian O'Connor72a034c2014-11-26 18:24:23 -0800540 builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800541 for (Intent intent : addIntents) {
542 builder.addSubmitOperation(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800543 log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800544 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800545 }
546 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800547 log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800548 "not elected leader anymore");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800549 isActivatedLeader = false;
550 return;
551 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800552 intentOperations = builder.build();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800553 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800554
555 if (isElectedLeader) {
556 isActivatedLeader = true; // Allow push of Intents
557 } else {
558 isActivatedLeader = false;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800559 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800560 log.debug("SDN-IP intent synchronization completed");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800561 }
562 }
563
564 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800565 * Computes the delta in two sets of Intents: local in-memory Intents,
566 * and intents fetched from the Intent framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800567 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800568 * @param localIntents the local in-memory Intents
569 * @param fetchedIntents the Intents fetched from the Intent framework
570 * @param storeInMemoryIntents the Intents that should be stored in memory.
571 * Note: This Collection must be allocated by the caller, and it will
572 * be populated by this method.
573 * @param addIntents the Intents that should be added to the Intent
574 * framework. Note: This Collection must be allocated by the caller, and
575 * it will be populated by this method.
576 * @param deleteIntents the Intents that should be deleted from the Intent
577 * framework. Note: This Collection must be allocated by the caller, and
578 * it will be populated by this method.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800579 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800580 private void computeIntentsDelta(
581 final Map<IntentKey, Intent> localIntents,
582 final Map<IntentKey, Intent> fetchedIntents,
583 Collection<Intent> storeInMemoryIntents,
584 Collection<Intent> addIntents,
585 Collection<Intent> deleteIntents) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800586
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800587 //
588 // Compute the deltas between the LOCAL in-memory Intents and the
589 // FETCHED Intents:
590 // - If an Intent is in both the LOCAL and FETCHED sets:
591 // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
592 // the LOCAL Intent should be added/installed; otherwise the
593 // FETCHED intent should be stored in the local memory
594 // (i.e., override the LOCAL Intent) to preserve the original
595 // Intent ID.
596 // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
597 // Intent should be added/installed.
598 // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
599 // Intent should be deleted/withdrawn.
600 //
601 for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
602 IntentKey intentKey = entry.getKey();
603 Intent localIntent = entry.getValue();
604 Intent fetchedIntent = fetchedIntents.get(intentKey);
605
606 if (fetchedIntent == null) {
607 //
608 // No FETCHED Intent found: push the LOCAL Intent.
609 //
610 addIntents.add(localIntent);
611 continue;
612 }
613
614 IntentState state =
615 intentService.getIntentState(fetchedIntent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800616 if (state == null ||
617 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800618 state == IntentState.WITHDRAWN) {
619 // The intent has been withdrawn but according to our route
620 // table it should be installed. We'll reinstall it.
621 addIntents.add(localIntent);
622 continue;
623 }
624 storeInMemoryIntents.add(fetchedIntent);
625 }
626
627 for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
628 IntentKey intentKey = entry.getKey();
629 Intent fetchedIntent = entry.getValue();
630 Intent localIntent = localIntents.get(intentKey);
631
632 if (localIntent != null) {
633 continue;
634 }
635
636 IntentState state =
637 intentService.getIntentState(fetchedIntent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800638 if (state == null ||
639 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800640 state == IntentState.WITHDRAWN) {
641 // Nothing to do. The intent has been already withdrawn.
642 continue;
643 }
644 //
645 // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
646 //
647 deleteIntents.add(fetchedIntent);
648 }
649 }
650
651 /**
652 * Helper class that can be used to compute the key for an Intent by
653 * by excluding the Intent ID.
654 */
655 static final class IntentKey {
656 private final Intent intent;
657
658 /**
659 * Constructor.
660 *
661 * @param intent the intent to use
662 */
663 IntentKey(Intent intent) {
664 checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
665 (intent instanceof PointToPointIntent),
666 "Intent type not recognized", intent);
667 this.intent = intent;
668 }
669
670 /**
671 * Compares two Multi-Point to Single-Point Intents whether they
672 * represent same logical intention.
673 *
674 * @param intent1 the first Intent to compare
675 * @param intent2 the second Intent to compare
676 * @return true if both Intents represent same logical intention,
677 * otherwise false
678 */
679 static boolean equalIntents(MultiPointToSinglePointIntent intent1,
680 MultiPointToSinglePointIntent intent2) {
681 return Objects.equals(intent1.appId(), intent2.appId()) &&
682 Objects.equals(intent1.selector(), intent2.selector()) &&
683 Objects.equals(intent1.treatment(), intent2.treatment()) &&
684 Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
685 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
686 }
687
688 /**
689 * Compares two Point-to-Point Intents whether they represent
690 * same logical intention.
691 *
692 * @param intent1 the first Intent to compare
693 * @param intent2 the second Intent to compare
694 * @return true if both Intents represent same logical intention,
695 * otherwise false
696 */
697 static boolean equalIntents(PointToPointIntent intent1,
698 PointToPointIntent intent2) {
699 return Objects.equals(intent1.appId(), intent2.appId()) &&
700 Objects.equals(intent1.selector(), intent2.selector()) &&
701 Objects.equals(intent1.treatment(), intent2.treatment()) &&
702 Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
703 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
704 }
705
706 @Override
707 public int hashCode() {
708 if (intent instanceof PointToPointIntent) {
709 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
710 return Objects.hash(p2pIntent.appId(),
711 p2pIntent.resources(),
712 p2pIntent.selector(),
713 p2pIntent.treatment(),
714 p2pIntent.constraints(),
715 p2pIntent.ingressPoint(),
716 p2pIntent.egressPoint());
717 }
718 if (intent instanceof MultiPointToSinglePointIntent) {
719 MultiPointToSinglePointIntent m2pIntent =
720 (MultiPointToSinglePointIntent) intent;
721 return Objects.hash(m2pIntent.appId(),
722 m2pIntent.resources(),
723 m2pIntent.selector(),
724 m2pIntent.treatment(),
725 m2pIntent.constraints(),
726 m2pIntent.ingressPoints(),
727 m2pIntent.egressPoint());
728 }
729 checkArgument(false, "Intent type not recognized", intent);
730 return 0;
731 }
732
733 @Override
734 public boolean equals(Object obj) {
735 if (this == obj) {
736 return true;
737 }
738 if ((obj == null) || (!(obj instanceof IntentKey))) {
739 return false;
740 }
741 IntentKey other = (IntentKey) obj;
742
743 if (this.intent instanceof PointToPointIntent) {
744 if (!(other.intent instanceof PointToPointIntent)) {
745 return false;
746 }
747 return equalIntents((PointToPointIntent) this.intent,
748 (PointToPointIntent) other.intent);
749 }
750 if (this.intent instanceof MultiPointToSinglePointIntent) {
751 if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
752 return false;
753 }
754 return equalIntents(
755 (MultiPointToSinglePointIntent) this.intent,
756 (MultiPointToSinglePointIntent) other.intent);
757 }
758 checkArgument(false, "Intent type not recognized", intent);
759 return false;
760 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800761 }
762}