blob: 698a2d6b36572fbc8b4a7a09d66616783fbed9ea [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 Hart41349e92015-02-09 14:14:02 -080039import org.onosproject.routingapi.FibListener;
40import org.onosproject.routingapi.FibUpdate;
Jonathan Hart552e31f2015-02-06 11:11:59 -080041import org.onosproject.sdnip.config.BgpPeer;
42import org.onosproject.sdnip.config.Interface;
43import org.onosproject.sdnip.config.SdnIpConfigurationService;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Jonathan Hart552e31f2015-02-06 11:11:59 -080047import java.util.Collection;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.LinkedList;
51import java.util.List;
52import java.util.Map;
53import java.util.Objects;
54import java.util.Set;
55import java.util.concurrent.ConcurrentHashMap;
56import java.util.concurrent.ExecutorService;
57import java.util.concurrent.Executors;
58import java.util.concurrent.Semaphore;
59
60import static com.google.common.base.Preconditions.checkArgument;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080061
Jonathan Hart51372182014-12-03 21:32:34 -080062/**
63 * Synchronizes intents between the in-memory intent store and the
64 * IntentService.
65 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080066public class IntentSynchronizer implements FibListener {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080067 private static final Logger log =
68 LoggerFactory.getLogger(IntentSynchronizer.class);
69
70 private final ApplicationId appId;
71 private final IntentService intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080072 private final Map<IntentKey, PointToPointIntent> peerIntents;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080073 private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080074
75 //
76 // State to deal with SDN-IP Leader election and pushing Intents
77 //
78 private final ExecutorService bgpIntentsSynchronizerExecutor;
79 private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
80 private volatile boolean isElectedLeader = false;
81 private volatile boolean isActivatedLeader = false;
82
Jonathan Hart552e31f2015-02-06 11:11:59 -080083 private final SdnIpConfigurationService configService;
84 private final InterfaceService interfaceService;
85
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080086 /**
87 * Class constructor.
88 *
89 * @param appId the Application ID
90 * @param intentService the intent service
Jonathan Hart552e31f2015-02-06 11:11:59 -080091 * @param configService the SDN-IP configuration service
92 * @param interfaceService the interface service
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080093 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080094 IntentSynchronizer(ApplicationId appId, IntentService intentService,
95 SdnIpConfigurationService configService,
96 InterfaceService interfaceService) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080097 this.appId = appId;
98 this.intentService = intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080099 peerIntents = new ConcurrentHashMap<>();
100 routeIntents = new ConcurrentHashMap<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800101
Jonathan Hart552e31f2015-02-06 11:11:59 -0800102 this.configService = configService;
103 this.interfaceService = interfaceService;
104
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800105 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
106 new ThreadFactoryBuilder()
Pavlin Radoslavov8b752442014-11-18 14:34:37 -0800107 .setNameFormat("sdnip-intents-synchronizer-%d").build());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800108 }
109
110 /**
111 * Starts the synchronizer.
112 */
113 public void start() {
114 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
115 @Override
116 public void run() {
117 doIntentSynchronizationThread();
118 }
119 });
120 }
121
122 /**
123 * Stops the synchronizer.
124 */
125 public void stop() {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800126 synchronized (this) {
127 // Stop the thread(s)
128 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800129
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800130 //
131 // Withdraw all SDN-IP intents
132 //
133 if (!isElectedLeader) {
134 return; // Nothing to do: not the leader anymore
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800135 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800136
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800137 //
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800138 // NOTE: We don't withdraw the intents during shutdown, because
139 // it creates flux in the data plane during switchover.
140 //
141
142 /*
143 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800144 // Build a batch operation to withdraw all intents from this
145 // application.
146 //
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800147 log.debug("SDN-IP Intent Synchronizer shutdown: " +
148 "withdrawing all intents...");
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800149 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800150 for (Intent intent : intentService.getIntents()) {
151 // Skip the intents from other applications
152 if (!intent.appId().equals(appId)) {
153 continue;
154 }
155
156 // Skip the intents that are already withdrawn
157 IntentState intentState =
158 intentService.getIntentState(intent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800159 if ((intentState == null) ||
160 intentState.equals(IntentState.WITHDRAWING) ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800161 intentState.equals(IntentState.WITHDRAWN)) {
162 continue;
163 }
164
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800165 log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800166 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800167 builder.addWithdrawOperation(intent.id());
168 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800169 IntentOperations intentOperations = builder.build();
170 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800171 leaderChanged(false);
172
173 peerIntents.clear();
174 routeIntents.clear();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800175 log.debug("SDN-IP Intent Synchronizer shutdown completed");
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800176 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800177 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800178 }
179
Jonathan Hart51372182014-12-03 21:32:34 -0800180 /**
181 * Signals the synchronizer that the SDN-IP leadership has changed.
182 *
183 * @param isLeader true if this instance is now the leader, otherwise false
184 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800185 public void leaderChanged(boolean isLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800186 log.debug("SDN-IP Leader changed: {}", isLeader);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800187
188 if (!isLeader) {
189 this.isElectedLeader = false;
190 this.isActivatedLeader = false;
191 return; // Nothing to do
192 }
193 this.isActivatedLeader = false;
194 this.isElectedLeader = true;
195
196 //
197 // Tell the Intents Synchronizer thread to start the synchronization
198 //
199 intentsSynchronizerSemaphore.release();
200 }
201
202 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800203 * Gets the route intents.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800204 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800205 * @return the route intents
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800206 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800207 public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
208 List<MultiPointToSinglePointIntent> result = new LinkedList<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800209
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800210 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800211 routeIntents.entrySet()) {
212 result.add(entry.getValue());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800213 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800214 return result;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800215 }
216
217 /**
218 * Thread for Intent Synchronization.
219 */
220 private void doIntentSynchronizationThread() {
221 boolean interrupted = false;
222 try {
223 while (!interrupted) {
224 try {
225 intentsSynchronizerSemaphore.acquire();
226 //
227 // Drain all permits, because a single synchronization is
228 // sufficient.
229 //
230 intentsSynchronizerSemaphore.drainPermits();
231 } catch (InterruptedException e) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800232 interrupted = true;
233 break;
234 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800235 synchronizeIntents();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800236 }
237 } finally {
238 if (interrupted) {
239 Thread.currentThread().interrupt();
240 }
241 }
242 }
243
244 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800245 * Submits a collection of point-to-point intents.
246 *
247 * @param intents the intents to submit
248 */
249 void submitPeerIntents(Collection<PointToPointIntent> intents) {
250 synchronized (this) {
251 // Store the intents in memory
252 for (PointToPointIntent intent : intents) {
253 peerIntents.put(new IntentKey(intent), intent);
254 }
255
256 // Push the intents
257 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800258 log.debug("SDN-IP Submitting all Peer Intents...");
Brian O'Connor72a034c2014-11-26 18:24:23 -0800259 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800260 for (Intent intent : intents) {
Pavlin Radoslavovdde22ae2014-11-24 11:47:17 -0800261 builder.addSubmitOperation(intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800262 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800263 IntentOperations intentOperations = builder.build();
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800264 log.trace("SDN-IP Submitting intents: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800265 intentOperations.operations());
266 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800267 }
268 }
269 }
270
271 /**
Jonathan Hart552e31f2015-02-06 11:11:59 -0800272 * Generates a route intent for a prefix, the next hop IP address, and
273 * the next hop MAC address.
274 * <p/>
275 * This method will find the egress interface for the intent.
276 * Intent will match dst IP prefix and rewrite dst MAC address at all other
277 * border switches, then forward packets according to dst MAC address.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800278 *
Jonathan Hart552e31f2015-02-06 11:11:59 -0800279 * @param prefix IP prefix of the route to add
280 * @param nextHopIpAddress IP address of the next hop
281 * @param nextHopMacAddress MAC address of the next hop
282 * @return the generated intent, or null if no intent should be submitted
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800283 */
Jonathan Hart552e31f2015-02-06 11:11:59 -0800284 private MultiPointToSinglePointIntent generateRouteIntent(
285 IpPrefix prefix,
286 IpAddress nextHopIpAddress,
287 MacAddress nextHopMacAddress) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800288
Jonathan Hart552e31f2015-02-06 11:11:59 -0800289 // Find the attachment point (egress interface) of the next hop
290 Interface egressInterface;
291 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
292 // Route to a peer
293 log.debug("Route to peer {}", nextHopIpAddress);
294 BgpPeer peer =
295 configService.getBgpPeers().get(nextHopIpAddress);
296 egressInterface =
297 interfaceService.getInterface(peer.connectPoint());
298 } else {
299 // Route to non-peer
300 log.debug("Route to non-peer {}", nextHopIpAddress);
301 egressInterface =
302 interfaceService.getMatchingInterface(nextHopIpAddress);
303 if (egressInterface == null) {
304 log.warn("No outgoing interface found for {}",
305 nextHopIpAddress);
306 return null;
307 }
308 }
309
310 //
311 // Generate the intent itself
312 //
313 Set<ConnectPoint> ingressPorts = new HashSet<>();
314 ConnectPoint egressPort = egressInterface.connectPoint();
315 log.debug("Generating intent for prefix {}, next hop mac {}",
316 prefix, nextHopMacAddress);
317
318 for (Interface intf : interfaceService.getInterfaces()) {
319 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
320 ConnectPoint srcPort = intf.connectPoint();
321 ingressPorts.add(srcPort);
322 }
323 }
324
325 // Match the destination IP prefix at the first hop
326 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
327 if (prefix.version() == Ip4Address.VERSION) {
328 selector.matchEthType(Ethernet.TYPE_IPV4);
329 } else {
330 selector.matchEthType(Ethernet.TYPE_IPV6);
331 }
332 selector.matchIPDst(prefix);
333
334 // Rewrite the destination MAC address
335 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
336 .setEthDst(nextHopMacAddress);
337 if (!egressInterface.vlan().equals(VlanId.NONE)) {
338 treatment.setVlanId(egressInterface.vlan());
339 // If we set VLAN ID, we have to make sure a VLAN tag exists.
340 // TODO support no VLAN -> VLAN routing
341 selector.matchVlanId(VlanId.ANY);
342 }
343
344 return new MultiPointToSinglePointIntent(appId, selector.build(),
345 treatment.build(),
346 ingressPorts, egressPort);
347 }
348
349 @Override
350 public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800351 //
352 // NOTE: Semantically, we MUST withdraw existing intents before
353 // submitting new intents.
354 //
355 synchronized (this) {
356 MultiPointToSinglePointIntent intent;
357
358 log.debug("SDN-IP submitting intents = {} withdrawing = {}",
Jonathan Hart552e31f2015-02-06 11:11:59 -0800359 updates.size(), withdraws.size());
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800360
361 //
362 // Prepare the Intent batch operations for the intents to withdraw
363 //
364 IntentOperations.Builder withdrawBuilder =
365 IntentOperations.builder(appId);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800366 for (FibUpdate withdraw : withdraws) {
367 checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
368 "FibUpdate with wrong type in withdraws list");
369
370 IpPrefix prefix = withdraw.entry().prefix();
371
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800372 intent = routeIntents.remove(prefix);
373 if (intent == null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800374 log.trace("SDN-IP No intent in routeIntents to delete " +
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800375 "for prefix: {}", prefix);
376 continue;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800377 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800378 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800379 log.trace("SDN-IP Withdrawing intent: {}", intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800380 withdrawBuilder.addWithdrawOperation(intent.id());
381 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800382 }
383
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800384 //
385 // Prepare the Intent batch operations for the intents to submit
386 //
387 IntentOperations.Builder submitBuilder =
388 IntentOperations.builder(appId);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800389 for (FibUpdate update : updates) {
390 checkArgument(update.type() == FibUpdate.Type.UPDATE,
391 "FibUpdate with wrong type in updates list");
392
393 IpPrefix prefix = update.entry().prefix();
394 intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
395 update.entry().nextHopMac());
396
397 if (intent == null) {
398 // This preserves the old semantics - if an intent can't be
399 // generated, we don't do anything with that prefix. But
400 // perhaps we should withdraw the old intent anyway?
401 continue;
402 }
403
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800404 MultiPointToSinglePointIntent oldIntent =
405 routeIntents.put(prefix, intent);
406 if (isElectedLeader && isActivatedLeader) {
407 if (oldIntent != null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800408 log.trace("SDN-IP Withdrawing old intent: {}",
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800409 oldIntent);
410 withdrawBuilder.addWithdrawOperation(oldIntent.id());
411 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800412 log.trace("SDN-IP Submitting intent: {}", intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800413 submitBuilder.addSubmitOperation(intent);
414 }
415 }
416
417 //
418 // Submit the Intent operations
419 //
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800420 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800421 IntentOperations intentOperations = withdrawBuilder.build();
422 if (!intentOperations.operations().isEmpty()) {
423 log.debug("SDN-IP Withdrawing intents executed");
424 intentService.execute(intentOperations);
425 }
426 intentOperations = submitBuilder.build();
427 if (!intentOperations.operations().isEmpty()) {
428 log.debug("SDN-IP Submitting intents executed");
429 intentService.execute(intentOperations);
430 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800431 }
432 }
433 }
434
435 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800436 * Synchronize the in-memory Intents with the Intents in the Intent
437 * framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800438 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800439 void synchronizeIntents() {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800440 synchronized (this) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800441
442 Map<IntentKey, Intent> localIntents = new HashMap<>();
443 Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
444 Collection<Intent> storeInMemoryIntents = new LinkedList<>();
445 Collection<Intent> addIntents = new LinkedList<>();
446 Collection<Intent> deleteIntents = new LinkedList<>();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800447 IntentOperations intentOperations;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800448
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800449 if (!isElectedLeader) {
450 return; // Nothing to do: not the leader anymore
451 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800452 log.debug("SDN-IP synchronizing all intents...");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800453
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800454 // Prepare the local intents
455 for (Intent intent : routeIntents.values()) {
456 localIntents.put(new IntentKey(intent), intent);
457 }
458 for (Intent intent : peerIntents.values()) {
459 localIntents.put(new IntentKey(intent), intent);
460 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800461
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800462 // Fetch all intents for this application
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800463 for (Intent intent : intentService.getIntents()) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800464 if (!intent.appId().equals(appId)) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800465 continue;
466 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800467 fetchedIntents.put(new IntentKey(intent), intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800468 }
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800469 if (log.isDebugEnabled()) {
470 for (Intent intent: fetchedIntents.values()) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800471 log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800472 intent);
473 }
474 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800475
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800476 computeIntentsDelta(localIntents, fetchedIntents,
477 storeInMemoryIntents, addIntents,
478 deleteIntents);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800479
480 //
481 // Perform the actions:
482 // 1. Store in memory fetched intents that are same. Can be done
483 // even if we are not the leader anymore
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800484 // 2. Delete intents: check if the leader before the operation
485 // 3. Add intents: check if the leader before the operation
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800486 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800487 for (Intent intent : storeInMemoryIntents) {
488 // Store the intent in memory based on its type
489 if (intent instanceof MultiPointToSinglePointIntent) {
490 MultiPointToSinglePointIntent mp2pIntent =
491 (MultiPointToSinglePointIntent) intent;
492 // Find the IP prefix
493 Criterion c =
494 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800495 if (c == null) {
496 // Try IPv6
497 c =
498 mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
499 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800500 if (c != null && c instanceof IPCriterion) {
501 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800502 IpPrefix ipPrefix = ipCriterion.ip();
503 if (ipPrefix == null) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800504 continue;
505 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800506 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800507 "in-memory Route Intent for prefix {}",
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800508 ipPrefix);
509 routeIntents.put(ipPrefix, mp2pIntent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800510 } else {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800511 log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800512 mp2pIntent.id());
513 }
514 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800515 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800516 if (intent instanceof PointToPointIntent) {
517 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800518 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800519 "in-memory Peer Intent {}", p2pIntent);
520 peerIntents.put(new IntentKey(intent), p2pIntent);
521 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800522 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800523 }
524
525 // Withdraw Intents
Brian O'Connor72a034c2014-11-26 18:24:23 -0800526 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800527 for (Intent intent : deleteIntents) {
528 builder.addWithdrawOperation(intent.id());
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800529 log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800530 intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800531 }
532 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800533 log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800534 "not elected leader anymore");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800535 isActivatedLeader = false;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800536 return;
537 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800538 intentOperations = builder.build();
539 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800540
541 // Add Intents
Brian O'Connor72a034c2014-11-26 18:24:23 -0800542 builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800543 for (Intent intent : addIntents) {
544 builder.addSubmitOperation(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800545 log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800546 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800547 }
548 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800549 log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800550 "not elected leader anymore");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800551 isActivatedLeader = false;
552 return;
553 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800554 intentOperations = builder.build();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800555 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800556
557 if (isElectedLeader) {
558 isActivatedLeader = true; // Allow push of Intents
559 } else {
560 isActivatedLeader = false;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800561 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800562 log.debug("SDN-IP intent synchronization completed");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800563 }
564 }
565
566 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800567 * Computes the delta in two sets of Intents: local in-memory Intents,
568 * and intents fetched from the Intent framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800569 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800570 * @param localIntents the local in-memory Intents
571 * @param fetchedIntents the Intents fetched from the Intent framework
572 * @param storeInMemoryIntents the Intents that should be stored in memory.
573 * Note: This Collection must be allocated by the caller, and it will
574 * be populated by this method.
575 * @param addIntents the Intents that should be added to the Intent
576 * framework. Note: This Collection must be allocated by the caller, and
577 * it will be populated by this method.
578 * @param deleteIntents the Intents that should be deleted from the Intent
579 * framework. Note: This Collection must be allocated by the caller, and
580 * it will be populated by this method.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800581 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800582 private void computeIntentsDelta(
583 final Map<IntentKey, Intent> localIntents,
584 final Map<IntentKey, Intent> fetchedIntents,
585 Collection<Intent> storeInMemoryIntents,
586 Collection<Intent> addIntents,
587 Collection<Intent> deleteIntents) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800588
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800589 //
590 // Compute the deltas between the LOCAL in-memory Intents and the
591 // FETCHED Intents:
592 // - If an Intent is in both the LOCAL and FETCHED sets:
593 // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
594 // the LOCAL Intent should be added/installed; otherwise the
595 // FETCHED intent should be stored in the local memory
596 // (i.e., override the LOCAL Intent) to preserve the original
597 // Intent ID.
598 // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
599 // Intent should be added/installed.
600 // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
601 // Intent should be deleted/withdrawn.
602 //
603 for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
604 IntentKey intentKey = entry.getKey();
605 Intent localIntent = entry.getValue();
606 Intent fetchedIntent = fetchedIntents.get(intentKey);
607
608 if (fetchedIntent == null) {
609 //
610 // No FETCHED Intent found: push the LOCAL Intent.
611 //
612 addIntents.add(localIntent);
613 continue;
614 }
615
616 IntentState state =
617 intentService.getIntentState(fetchedIntent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800618 if (state == null ||
619 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800620 state == IntentState.WITHDRAWN) {
621 // The intent has been withdrawn but according to our route
622 // table it should be installed. We'll reinstall it.
623 addIntents.add(localIntent);
624 continue;
625 }
626 storeInMemoryIntents.add(fetchedIntent);
627 }
628
629 for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
630 IntentKey intentKey = entry.getKey();
631 Intent fetchedIntent = entry.getValue();
632 Intent localIntent = localIntents.get(intentKey);
633
634 if (localIntent != null) {
635 continue;
636 }
637
638 IntentState state =
639 intentService.getIntentState(fetchedIntent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800640 if (state == null ||
641 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800642 state == IntentState.WITHDRAWN) {
643 // Nothing to do. The intent has been already withdrawn.
644 continue;
645 }
646 //
647 // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
648 //
649 deleteIntents.add(fetchedIntent);
650 }
651 }
652
653 /**
654 * Helper class that can be used to compute the key for an Intent by
655 * by excluding the Intent ID.
656 */
657 static final class IntentKey {
658 private final Intent intent;
659
660 /**
661 * Constructor.
662 *
663 * @param intent the intent to use
664 */
665 IntentKey(Intent intent) {
666 checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
667 (intent instanceof PointToPointIntent),
668 "Intent type not recognized", intent);
669 this.intent = intent;
670 }
671
672 /**
673 * Compares two Multi-Point to Single-Point Intents whether they
674 * represent same logical intention.
675 *
676 * @param intent1 the first Intent to compare
677 * @param intent2 the second Intent to compare
678 * @return true if both Intents represent same logical intention,
679 * otherwise false
680 */
681 static boolean equalIntents(MultiPointToSinglePointIntent intent1,
682 MultiPointToSinglePointIntent intent2) {
683 return Objects.equals(intent1.appId(), intent2.appId()) &&
684 Objects.equals(intent1.selector(), intent2.selector()) &&
685 Objects.equals(intent1.treatment(), intent2.treatment()) &&
686 Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
687 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
688 }
689
690 /**
691 * Compares two Point-to-Point Intents whether they represent
692 * same logical intention.
693 *
694 * @param intent1 the first Intent to compare
695 * @param intent2 the second Intent to compare
696 * @return true if both Intents represent same logical intention,
697 * otherwise false
698 */
699 static boolean equalIntents(PointToPointIntent intent1,
700 PointToPointIntent intent2) {
701 return Objects.equals(intent1.appId(), intent2.appId()) &&
702 Objects.equals(intent1.selector(), intent2.selector()) &&
703 Objects.equals(intent1.treatment(), intent2.treatment()) &&
704 Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
705 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
706 }
707
708 @Override
709 public int hashCode() {
710 if (intent instanceof PointToPointIntent) {
711 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
712 return Objects.hash(p2pIntent.appId(),
713 p2pIntent.resources(),
714 p2pIntent.selector(),
715 p2pIntent.treatment(),
716 p2pIntent.constraints(),
717 p2pIntent.ingressPoint(),
718 p2pIntent.egressPoint());
719 }
720 if (intent instanceof MultiPointToSinglePointIntent) {
721 MultiPointToSinglePointIntent m2pIntent =
722 (MultiPointToSinglePointIntent) intent;
723 return Objects.hash(m2pIntent.appId(),
724 m2pIntent.resources(),
725 m2pIntent.selector(),
726 m2pIntent.treatment(),
727 m2pIntent.constraints(),
728 m2pIntent.ingressPoints(),
729 m2pIntent.egressPoint());
730 }
731 checkArgument(false, "Intent type not recognized", intent);
732 return 0;
733 }
734
735 @Override
736 public boolean equals(Object obj) {
737 if (this == obj) {
738 return true;
739 }
740 if ((obj == null) || (!(obj instanceof IntentKey))) {
741 return false;
742 }
743 IntentKey other = (IntentKey) obj;
744
745 if (this.intent instanceof PointToPointIntent) {
746 if (!(other.intent instanceof PointToPointIntent)) {
747 return false;
748 }
749 return equalIntents((PointToPointIntent) this.intent,
750 (PointToPointIntent) other.intent);
751 }
752 if (this.intent instanceof MultiPointToSinglePointIntent) {
753 if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
754 return false;
755 }
756 return equalIntents(
757 (MultiPointToSinglePointIntent) this.intent,
758 (MultiPointToSinglePointIntent) other.intent);
759 }
760 checkArgument(false, "Intent type not recognized", intent);
761 return false;
762 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800763 }
764}