blob: c10df947a71d2472665aae25a2f0a88393419456 [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;
Brian O'Connorabafb502014-12-02 22:26:20 -080034import org.onosproject.net.intent.IntentService;
35import org.onosproject.net.intent.IntentState;
36import org.onosproject.net.intent.MultiPointToSinglePointIntent;
37import org.onosproject.net.intent.PointToPointIntent;
Jonathan Hart2da1e602015-02-18 19:09:24 -080038import org.onosproject.routing.FibListener;
39import org.onosproject.routing.FibUpdate;
40import org.onosproject.routing.config.BgpPeer;
41import org.onosproject.routing.config.Interface;
42import org.onosproject.routing.config.RoutingConfigurationService;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
Jonathan Hart552e31f2015-02-06 11:11:59 -080046import java.util.Collection;
47import java.util.HashMap;
48import java.util.HashSet;
49import java.util.LinkedList;
50import java.util.List;
51import java.util.Map;
52import java.util.Objects;
53import java.util.Set;
54import java.util.concurrent.ConcurrentHashMap;
55import java.util.concurrent.ExecutorService;
56import java.util.concurrent.Executors;
57import java.util.concurrent.Semaphore;
58
59import static com.google.common.base.Preconditions.checkArgument;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080060
Jonathan Hart51372182014-12-03 21:32:34 -080061/**
62 * Synchronizes intents between the in-memory intent store and the
63 * IntentService.
64 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080065public class IntentSynchronizer implements FibListener {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080066 private static final Logger log =
67 LoggerFactory.getLogger(IntentSynchronizer.class);
68
69 private final ApplicationId appId;
70 private final IntentService intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080071 private final Map<IntentKey, PointToPointIntent> peerIntents;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080072 private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080073
74 //
75 // State to deal with SDN-IP Leader election and pushing Intents
76 //
77 private final ExecutorService bgpIntentsSynchronizerExecutor;
78 private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
79 private volatile boolean isElectedLeader = false;
80 private volatile boolean isActivatedLeader = false;
81
Jonathan Hart90a02c22015-02-13 11:52:07 -080082 private final RoutingConfigurationService configService;
Jonathan Hart552e31f2015-02-06 11:11:59 -080083
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
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080090 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080091 IntentSynchronizer(ApplicationId appId, IntentService intentService,
Jonathan Hart90a02c22015-02-13 11:52:07 -080092 RoutingConfigurationService configService) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080093 this.appId = appId;
94 this.intentService = intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080095 peerIntents = new ConcurrentHashMap<>();
96 routeIntents = new ConcurrentHashMap<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080097
Jonathan Hart552e31f2015-02-06 11:11:59 -080098 this.configService = configService;
Jonathan Hart552e31f2015-02-06 11:11:59 -080099
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800100 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
101 new ThreadFactoryBuilder()
Pavlin Radoslavov8b752442014-11-18 14:34:37 -0800102 .setNameFormat("sdnip-intents-synchronizer-%d").build());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800103 }
104
105 /**
106 * Starts the synchronizer.
107 */
108 public void start() {
109 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
110 @Override
111 public void run() {
112 doIntentSynchronizationThread();
113 }
114 });
115 }
116
117 /**
118 * Stops the synchronizer.
119 */
120 public void stop() {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800121 synchronized (this) {
122 // Stop the thread(s)
123 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800124
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800125 //
126 // Withdraw all SDN-IP intents
127 //
128 if (!isElectedLeader) {
129 return; // Nothing to do: not the leader anymore
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800130 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800131
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800132 //
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800133 // NOTE: We don't withdraw the intents during shutdown, because
134 // it creates flux in the data plane during switchover.
135 //
136
137 /*
138 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800139 // Build a batch operation to withdraw all intents from this
140 // application.
141 //
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800142 log.debug("SDN-IP Intent Synchronizer shutdown: " +
143 "withdrawing all intents...");
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800144 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800145 for (Intent intent : intentService.getIntents()) {
146 // Skip the intents from other applications
147 if (!intent.appId().equals(appId)) {
148 continue;
149 }
150
151 // Skip the intents that are already withdrawn
152 IntentState intentState =
153 intentService.getIntentState(intent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800154 if ((intentState == null) ||
155 intentState.equals(IntentState.WITHDRAWING) ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800156 intentState.equals(IntentState.WITHDRAWN)) {
157 continue;
158 }
159
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800160 log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800161 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800162 builder.addWithdrawOperation(intent.id());
163 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800164 IntentOperations intentOperations = builder.build();
165 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800166 leaderChanged(false);
167
168 peerIntents.clear();
169 routeIntents.clear();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800170 log.debug("SDN-IP Intent Synchronizer shutdown completed");
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800171 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800172 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800173 }
174
Jonathan Hart51372182014-12-03 21:32:34 -0800175 /**
176 * Signals the synchronizer that the SDN-IP leadership has changed.
177 *
178 * @param isLeader true if this instance is now the leader, otherwise false
179 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800180 public void leaderChanged(boolean isLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800181 log.debug("SDN-IP Leader changed: {}", isLeader);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800182
183 if (!isLeader) {
184 this.isElectedLeader = false;
185 this.isActivatedLeader = false;
186 return; // Nothing to do
187 }
188 this.isActivatedLeader = false;
189 this.isElectedLeader = true;
190
191 //
192 // Tell the Intents Synchronizer thread to start the synchronization
193 //
194 intentsSynchronizerSemaphore.release();
195 }
196
197 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800198 * Gets the route intents.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800199 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800200 * @return the route intents
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800201 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800202 public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
203 List<MultiPointToSinglePointIntent> result = new LinkedList<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800204
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800205 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800206 routeIntents.entrySet()) {
207 result.add(entry.getValue());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800208 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800209 return result;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800210 }
211
212 /**
213 * Thread for Intent Synchronization.
214 */
215 private void doIntentSynchronizationThread() {
216 boolean interrupted = false;
217 try {
218 while (!interrupted) {
219 try {
220 intentsSynchronizerSemaphore.acquire();
221 //
222 // Drain all permits, because a single synchronization is
223 // sufficient.
224 //
225 intentsSynchronizerSemaphore.drainPermits();
226 } catch (InterruptedException e) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800227 interrupted = true;
228 break;
229 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800230 synchronizeIntents();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800231 }
232 } finally {
233 if (interrupted) {
234 Thread.currentThread().interrupt();
235 }
236 }
237 }
238
239 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800240 * Submits a collection of point-to-point intents.
241 *
242 * @param intents the intents to submit
243 */
244 void submitPeerIntents(Collection<PointToPointIntent> intents) {
245 synchronized (this) {
246 // Store the intents in memory
247 for (PointToPointIntent intent : intents) {
248 peerIntents.put(new IntentKey(intent), intent);
249 }
250
251 // Push the intents
252 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800253 log.debug("SDN-IP Submitting all Peer Intents...");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800254 for (Intent intent : intents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800255 log.trace("SDN-IP Submitting intents: {}", intent);
256 intentService.submit(intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800257 }
258 }
259 }
260 }
261
262 /**
Jonathan Hart552e31f2015-02-06 11:11:59 -0800263 * Generates a route intent for a prefix, the next hop IP address, and
264 * the next hop MAC address.
265 * <p/>
266 * This method will find the egress interface for the intent.
267 * Intent will match dst IP prefix and rewrite dst MAC address at all other
268 * border switches, then forward packets according to dst MAC address.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800269 *
Jonathan Hart552e31f2015-02-06 11:11:59 -0800270 * @param prefix IP prefix of the route to add
271 * @param nextHopIpAddress IP address of the next hop
272 * @param nextHopMacAddress MAC address of the next hop
273 * @return the generated intent, or null if no intent should be submitted
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800274 */
Jonathan Hart552e31f2015-02-06 11:11:59 -0800275 private MultiPointToSinglePointIntent generateRouteIntent(
276 IpPrefix prefix,
277 IpAddress nextHopIpAddress,
278 MacAddress nextHopMacAddress) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800279
Jonathan Hart552e31f2015-02-06 11:11:59 -0800280 // Find the attachment point (egress interface) of the next hop
281 Interface egressInterface;
282 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
283 // Route to a peer
284 log.debug("Route to peer {}", nextHopIpAddress);
285 BgpPeer peer =
286 configService.getBgpPeers().get(nextHopIpAddress);
287 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800288 configService.getInterface(peer.connectPoint());
Jonathan Hart552e31f2015-02-06 11:11:59 -0800289 } else {
290 // Route to non-peer
291 log.debug("Route to non-peer {}", nextHopIpAddress);
292 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800293 configService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800294 if (egressInterface == null) {
295 log.warn("No outgoing interface found for {}",
296 nextHopIpAddress);
297 return null;
298 }
299 }
300
301 //
302 // Generate the intent itself
303 //
304 Set<ConnectPoint> ingressPorts = new HashSet<>();
305 ConnectPoint egressPort = egressInterface.connectPoint();
306 log.debug("Generating intent for prefix {}, next hop mac {}",
307 prefix, nextHopMacAddress);
308
Jonathan Hart90a02c22015-02-13 11:52:07 -0800309 for (Interface intf : configService.getInterfaces()) {
Jonathan Hart552e31f2015-02-06 11:11:59 -0800310 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
311 ConnectPoint srcPort = intf.connectPoint();
312 ingressPorts.add(srcPort);
313 }
314 }
315
316 // Match the destination IP prefix at the first hop
317 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
318 if (prefix.version() == Ip4Address.VERSION) {
319 selector.matchEthType(Ethernet.TYPE_IPV4);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800320 selector.matchIPDst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800321 } else {
322 selector.matchEthType(Ethernet.TYPE_IPV6);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800323 selector.matchIPv6Dst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800324 }
Jonathan Hart552e31f2015-02-06 11:11:59 -0800325
326 // Rewrite the destination MAC address
327 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
328 .setEthDst(nextHopMacAddress);
329 if (!egressInterface.vlan().equals(VlanId.NONE)) {
330 treatment.setVlanId(egressInterface.vlan());
331 // If we set VLAN ID, we have to make sure a VLAN tag exists.
332 // TODO support no VLAN -> VLAN routing
333 selector.matchVlanId(VlanId.ANY);
334 }
335
336 return new MultiPointToSinglePointIntent(appId, selector.build(),
337 treatment.build(),
338 ingressPorts, egressPort);
339 }
340
341 @Override
342 public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800343 //
344 // NOTE: Semantically, we MUST withdraw existing intents before
345 // submitting new intents.
346 //
347 synchronized (this) {
348 MultiPointToSinglePointIntent intent;
349
350 log.debug("SDN-IP submitting intents = {} withdrawing = {}",
Jonathan Hart552e31f2015-02-06 11:11:59 -0800351 updates.size(), withdraws.size());
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800352
353 //
354 // Prepare the Intent batch operations for the intents to withdraw
355 //
Jonathan Hart552e31f2015-02-06 11:11:59 -0800356 for (FibUpdate withdraw : withdraws) {
357 checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
358 "FibUpdate with wrong type in withdraws list");
359
360 IpPrefix prefix = withdraw.entry().prefix();
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800361 intent = routeIntents.remove(prefix);
362 if (intent == null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800363 log.trace("SDN-IP No intent in routeIntents to delete " +
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800364 "for prefix: {}", prefix);
365 continue;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800366 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800367 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800368 log.trace("SDN-IP Withdrawing intent: {}", intent);
Brian O'Connor03406a42015-02-03 17:28:57 -0800369 intentService.withdraw(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800370 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800371 }
372
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800373 //
374 // Prepare the Intent batch operations for the intents to submit
375 //
Jonathan Hart552e31f2015-02-06 11:11:59 -0800376 for (FibUpdate update : updates) {
377 checkArgument(update.type() == FibUpdate.Type.UPDATE,
378 "FibUpdate with wrong type in updates list");
379
380 IpPrefix prefix = update.entry().prefix();
381 intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
382 update.entry().nextHopMac());
383
384 if (intent == null) {
385 // This preserves the old semantics - if an intent can't be
386 // generated, we don't do anything with that prefix. But
387 // perhaps we should withdraw the old intent anyway?
388 continue;
389 }
390
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800391 MultiPointToSinglePointIntent oldIntent =
392 routeIntents.put(prefix, intent);
393 if (isElectedLeader && isActivatedLeader) {
394 if (oldIntent != null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800395 log.trace("SDN-IP Withdrawing old intent: {}",
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800396 oldIntent);
Brian O'Connor03406a42015-02-03 17:28:57 -0800397 intentService.withdraw(oldIntent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800398 }
Jonathan Hartb3b8a0a2015-02-12 17:36:13 -0800399 log.trace("SDN-IP Submitting intent: {}", intent);
400 intentService.submit(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800401 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800402 }
403 }
404 }
405
406 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800407 * Synchronize the in-memory Intents with the Intents in the Intent
408 * framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800409 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800410 void synchronizeIntents() {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800411 synchronized (this) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800412
413 Map<IntentKey, Intent> localIntents = new HashMap<>();
414 Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
415 Collection<Intent> storeInMemoryIntents = new LinkedList<>();
416 Collection<Intent> addIntents = new LinkedList<>();
417 Collection<Intent> deleteIntents = new LinkedList<>();
418
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800419 if (!isElectedLeader) {
420 return; // Nothing to do: not the leader anymore
421 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800422 log.debug("SDN-IP synchronizing all intents...");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800423
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800424 // Prepare the local intents
425 for (Intent intent : routeIntents.values()) {
426 localIntents.put(new IntentKey(intent), intent);
427 }
428 for (Intent intent : peerIntents.values()) {
429 localIntents.put(new IntentKey(intent), intent);
430 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800431
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800432 // Fetch all intents for this application
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800433 for (Intent intent : intentService.getIntents()) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800434 if (!intent.appId().equals(appId)) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800435 continue;
436 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800437 fetchedIntents.put(new IntentKey(intent), intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800438 }
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800439 if (log.isDebugEnabled()) {
440 for (Intent intent: fetchedIntents.values()) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800441 log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800442 intent);
443 }
444 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800445
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800446 computeIntentsDelta(localIntents, fetchedIntents,
447 storeInMemoryIntents, addIntents,
448 deleteIntents);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800449
450 //
451 // Perform the actions:
452 // 1. Store in memory fetched intents that are same. Can be done
453 // even if we are not the leader anymore
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800454 // 2. Delete intents: check if the leader before the operation
455 // 3. Add intents: check if the leader before the operation
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800456 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800457 for (Intent intent : storeInMemoryIntents) {
458 // Store the intent in memory based on its type
459 if (intent instanceof MultiPointToSinglePointIntent) {
460 MultiPointToSinglePointIntent mp2pIntent =
461 (MultiPointToSinglePointIntent) intent;
462 // Find the IP prefix
463 Criterion c =
464 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800465 if (c == null) {
466 // Try IPv6
467 c =
468 mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
469 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800470 if (c != null && c instanceof IPCriterion) {
471 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800472 IpPrefix ipPrefix = ipCriterion.ip();
473 if (ipPrefix == null) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800474 continue;
475 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800476 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800477 "in-memory Route Intent for prefix {}",
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800478 ipPrefix);
479 routeIntents.put(ipPrefix, mp2pIntent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800480 } else {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800481 log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800482 mp2pIntent.id());
483 }
484 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800485 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800486 if (intent instanceof PointToPointIntent) {
487 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800488 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800489 "in-memory Peer Intent {}", p2pIntent);
490 peerIntents.put(new IntentKey(intent), p2pIntent);
491 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800492 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800493 }
494
495 // Withdraw Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800496 for (Intent intent : deleteIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800497 intentService.withdraw(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800498 log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800499 intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800500 }
501 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800502 log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800503 "not elected leader anymore");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800504 isActivatedLeader = false;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800505 return;
506 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800507
508 // Add Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800509 for (Intent intent : addIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800510 intentService.submit(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800511 log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800512 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800513 }
514 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800515 log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800516 "not elected leader anymore");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800517 isActivatedLeader = false;
518 return;
519 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800520
521 if (isElectedLeader) {
522 isActivatedLeader = true; // Allow push of Intents
523 } else {
524 isActivatedLeader = false;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800525 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800526 log.debug("SDN-IP intent synchronization completed");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800527 }
528 }
529
530 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800531 * Computes the delta in two sets of Intents: local in-memory Intents,
532 * and intents fetched from the Intent framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800533 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800534 * @param localIntents the local in-memory Intents
535 * @param fetchedIntents the Intents fetched from the Intent framework
536 * @param storeInMemoryIntents the Intents that should be stored in memory.
537 * Note: This Collection must be allocated by the caller, and it will
538 * be populated by this method.
539 * @param addIntents the Intents that should be added to the Intent
540 * framework. Note: This Collection must be allocated by the caller, and
541 * it will be populated by this method.
542 * @param deleteIntents the Intents that should be deleted from the Intent
543 * framework. Note: This Collection must be allocated by the caller, and
544 * it will be populated by this method.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800545 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800546 private void computeIntentsDelta(
547 final Map<IntentKey, Intent> localIntents,
548 final Map<IntentKey, Intent> fetchedIntents,
549 Collection<Intent> storeInMemoryIntents,
550 Collection<Intent> addIntents,
551 Collection<Intent> deleteIntents) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800552
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800553 //
554 // Compute the deltas between the LOCAL in-memory Intents and the
555 // FETCHED Intents:
556 // - If an Intent is in both the LOCAL and FETCHED sets:
557 // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
558 // the LOCAL Intent should be added/installed; otherwise the
559 // FETCHED intent should be stored in the local memory
560 // (i.e., override the LOCAL Intent) to preserve the original
561 // Intent ID.
562 // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
563 // Intent should be added/installed.
564 // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
565 // Intent should be deleted/withdrawn.
566 //
567 for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
568 IntentKey intentKey = entry.getKey();
569 Intent localIntent = entry.getValue();
570 Intent fetchedIntent = fetchedIntents.get(intentKey);
571
572 if (fetchedIntent == null) {
573 //
574 // No FETCHED Intent found: push the LOCAL Intent.
575 //
576 addIntents.add(localIntent);
577 continue;
578 }
579
580 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800581 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800582 if (state == null ||
583 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800584 state == IntentState.WITHDRAWN) {
585 // The intent has been withdrawn but according to our route
586 // table it should be installed. We'll reinstall it.
587 addIntents.add(localIntent);
588 continue;
589 }
590 storeInMemoryIntents.add(fetchedIntent);
591 }
592
593 for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
594 IntentKey intentKey = entry.getKey();
595 Intent fetchedIntent = entry.getValue();
596 Intent localIntent = localIntents.get(intentKey);
597
598 if (localIntent != null) {
599 continue;
600 }
601
602 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800603 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800604 if (state == null ||
605 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800606 state == IntentState.WITHDRAWN) {
607 // Nothing to do. The intent has been already withdrawn.
608 continue;
609 }
610 //
611 // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
612 //
613 deleteIntents.add(fetchedIntent);
614 }
615 }
616
617 /**
618 * Helper class that can be used to compute the key for an Intent by
619 * by excluding the Intent ID.
620 */
621 static final class IntentKey {
622 private final Intent intent;
623
624 /**
625 * Constructor.
626 *
627 * @param intent the intent to use
628 */
629 IntentKey(Intent intent) {
630 checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
631 (intent instanceof PointToPointIntent),
632 "Intent type not recognized", intent);
633 this.intent = intent;
634 }
635
636 /**
637 * Compares two Multi-Point to Single-Point Intents whether they
638 * represent same logical intention.
639 *
640 * @param intent1 the first Intent to compare
641 * @param intent2 the second Intent to compare
642 * @return true if both Intents represent same logical intention,
643 * otherwise false
644 */
645 static boolean equalIntents(MultiPointToSinglePointIntent intent1,
646 MultiPointToSinglePointIntent intent2) {
647 return Objects.equals(intent1.appId(), intent2.appId()) &&
648 Objects.equals(intent1.selector(), intent2.selector()) &&
649 Objects.equals(intent1.treatment(), intent2.treatment()) &&
650 Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
651 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
652 }
653
654 /**
655 * Compares two Point-to-Point Intents whether they represent
656 * same logical intention.
657 *
658 * @param intent1 the first Intent to compare
659 * @param intent2 the second Intent to compare
660 * @return true if both Intents represent same logical intention,
661 * otherwise false
662 */
663 static boolean equalIntents(PointToPointIntent intent1,
664 PointToPointIntent intent2) {
665 return Objects.equals(intent1.appId(), intent2.appId()) &&
666 Objects.equals(intent1.selector(), intent2.selector()) &&
667 Objects.equals(intent1.treatment(), intent2.treatment()) &&
668 Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
669 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
670 }
671
672 @Override
673 public int hashCode() {
674 if (intent instanceof PointToPointIntent) {
675 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
676 return Objects.hash(p2pIntent.appId(),
677 p2pIntent.resources(),
678 p2pIntent.selector(),
679 p2pIntent.treatment(),
680 p2pIntent.constraints(),
681 p2pIntent.ingressPoint(),
682 p2pIntent.egressPoint());
683 }
684 if (intent instanceof MultiPointToSinglePointIntent) {
685 MultiPointToSinglePointIntent m2pIntent =
686 (MultiPointToSinglePointIntent) intent;
687 return Objects.hash(m2pIntent.appId(),
688 m2pIntent.resources(),
689 m2pIntent.selector(),
690 m2pIntent.treatment(),
691 m2pIntent.constraints(),
692 m2pIntent.ingressPoints(),
693 m2pIntent.egressPoint());
694 }
695 checkArgument(false, "Intent type not recognized", intent);
696 return 0;
697 }
698
699 @Override
700 public boolean equals(Object obj) {
701 if (this == obj) {
702 return true;
703 }
704 if ((obj == null) || (!(obj instanceof IntentKey))) {
705 return false;
706 }
707 IntentKey other = (IntentKey) obj;
708
709 if (this.intent instanceof PointToPointIntent) {
710 if (!(other.intent instanceof PointToPointIntent)) {
711 return false;
712 }
713 return equalIntents((PointToPointIntent) this.intent,
714 (PointToPointIntent) other.intent);
715 }
716 if (this.intent instanceof MultiPointToSinglePointIntent) {
717 if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
718 return false;
719 }
720 return equalIntents(
721 (MultiPointToSinglePointIntent) this.intent,
722 (MultiPointToSinglePointIntent) other.intent);
723 }
724 checkArgument(false, "Intent type not recognized", intent);
725 return false;
726 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800727 }
728}