blob: cc65ccc58d129cdcd152eb639a839a7f224e36e4 [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;
Jonathan Hart552e31f2015-02-06 11:11:59 -080020import org.onlab.packet.IpAddress;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080021import org.onlab.packet.IpPrefix;
Jonathan Hart552e31f2015-02-06 11:11:59 -080022import org.onlab.packet.MacAddress;
23import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080024import org.onosproject.core.ApplicationId;
Jonathan Hart552e31f2015-02-06 11:11:59 -080025import org.onosproject.net.ConnectPoint;
26import org.onosproject.net.flow.DefaultTrafficSelector;
27import org.onosproject.net.flow.DefaultTrafficTreatment;
28import org.onosproject.net.flow.TrafficSelector;
29import org.onosproject.net.flow.TrafficTreatment;
Brian O'Connorabafb502014-12-02 22:26:20 -080030import org.onosproject.net.flow.criteria.Criteria.IPCriterion;
31import org.onosproject.net.flow.criteria.Criterion;
32import org.onosproject.net.intent.Intent;
Brian O'Connorabafb502014-12-02 22:26:20 -080033import org.onosproject.net.intent.IntentService;
34import org.onosproject.net.intent.IntentState;
35import org.onosproject.net.intent.MultiPointToSinglePointIntent;
36import org.onosproject.net.intent.PointToPointIntent;
Jonathan Hart2da1e602015-02-18 19:09:24 -080037import org.onosproject.routing.FibListener;
38import org.onosproject.routing.FibUpdate;
39import org.onosproject.routing.config.BgpPeer;
40import org.onosproject.routing.config.Interface;
41import org.onosproject.routing.config.RoutingConfigurationService;
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 Hart90a02c22015-02-13 11:52:07 -080081 private final RoutingConfigurationService configService;
Jonathan Hart552e31f2015-02-06 11:11:59 -080082
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080083 /**
84 * Class constructor.
85 *
86 * @param appId the Application ID
87 * @param intentService the intent service
Jonathan Hart552e31f2015-02-06 11:11:59 -080088 * @param configService the SDN-IP configuration service
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080089 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080090 IntentSynchronizer(ApplicationId appId, IntentService intentService,
Jonathan Hart90a02c22015-02-13 11:52:07 -080091 RoutingConfigurationService configService) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080092 this.appId = appId;
93 this.intentService = intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080094 peerIntents = new ConcurrentHashMap<>();
95 routeIntents = new ConcurrentHashMap<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080096
Jonathan Hart552e31f2015-02-06 11:11:59 -080097 this.configService = configService;
Jonathan Hart552e31f2015-02-06 11:11:59 -080098
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080099 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
100 new ThreadFactoryBuilder()
Pavlin Radoslavov8b752442014-11-18 14:34:37 -0800101 .setNameFormat("sdnip-intents-synchronizer-%d").build());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800102 }
103
104 /**
105 * Starts the synchronizer.
106 */
107 public void start() {
108 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
109 @Override
110 public void run() {
111 doIntentSynchronizationThread();
112 }
113 });
114 }
115
116 /**
117 * Stops the synchronizer.
118 */
119 public void stop() {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800120 synchronized (this) {
121 // Stop the thread(s)
122 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800123
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800124 //
125 // Withdraw all SDN-IP intents
126 //
127 if (!isElectedLeader) {
128 return; // Nothing to do: not the leader anymore
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800129 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800130
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800131 //
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800132 // NOTE: We don't withdraw the intents during shutdown, because
133 // it creates flux in the data plane during switchover.
134 //
135
136 /*
137 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800138 // Build a batch operation to withdraw all intents from this
139 // application.
140 //
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800141 log.debug("SDN-IP Intent Synchronizer shutdown: " +
142 "withdrawing all intents...");
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800143 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800144 for (Intent intent : intentService.getIntents()) {
145 // Skip the intents from other applications
146 if (!intent.appId().equals(appId)) {
147 continue;
148 }
149
150 // Skip the intents that are already withdrawn
151 IntentState intentState =
152 intentService.getIntentState(intent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800153 if ((intentState == null) ||
154 intentState.equals(IntentState.WITHDRAWING) ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800155 intentState.equals(IntentState.WITHDRAWN)) {
156 continue;
157 }
158
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800159 log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800160 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800161 builder.addWithdrawOperation(intent.id());
162 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800163 IntentOperations intentOperations = builder.build();
164 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800165 leaderChanged(false);
166
167 peerIntents.clear();
168 routeIntents.clear();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800169 log.debug("SDN-IP Intent Synchronizer shutdown completed");
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800170 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800171 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800172 }
173
Jonathan Hart51372182014-12-03 21:32:34 -0800174 /**
175 * Signals the synchronizer that the SDN-IP leadership has changed.
176 *
177 * @param isLeader true if this instance is now the leader, otherwise false
178 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800179 public void leaderChanged(boolean isLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800180 log.debug("SDN-IP Leader changed: {}", isLeader);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800181
182 if (!isLeader) {
183 this.isElectedLeader = false;
184 this.isActivatedLeader = false;
185 return; // Nothing to do
186 }
187 this.isActivatedLeader = false;
188 this.isElectedLeader = true;
189
190 //
191 // Tell the Intents Synchronizer thread to start the synchronization
192 //
193 intentsSynchronizerSemaphore.release();
194 }
195
196 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800197 * Gets the route intents.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800198 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800199 * @return the route intents
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800200 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800201 public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
202 List<MultiPointToSinglePointIntent> result = new LinkedList<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800203
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800204 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800205 routeIntents.entrySet()) {
206 result.add(entry.getValue());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800207 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800208 return result;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800209 }
210
211 /**
212 * Thread for Intent Synchronization.
213 */
214 private void doIntentSynchronizationThread() {
215 boolean interrupted = false;
216 try {
217 while (!interrupted) {
218 try {
219 intentsSynchronizerSemaphore.acquire();
220 //
221 // Drain all permits, because a single synchronization is
222 // sufficient.
223 //
224 intentsSynchronizerSemaphore.drainPermits();
225 } catch (InterruptedException e) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800226 interrupted = true;
227 break;
228 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800229 synchronizeIntents();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800230 }
231 } finally {
232 if (interrupted) {
233 Thread.currentThread().interrupt();
234 }
235 }
236 }
237
238 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800239 * Submits a collection of point-to-point intents.
240 *
241 * @param intents the intents to submit
242 */
243 void submitPeerIntents(Collection<PointToPointIntent> intents) {
244 synchronized (this) {
245 // Store the intents in memory
246 for (PointToPointIntent intent : intents) {
247 peerIntents.put(new IntentKey(intent), intent);
248 }
249
250 // Push the intents
251 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800252 log.debug("SDN-IP Submitting all Peer Intents...");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800253 for (Intent intent : intents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800254 log.trace("SDN-IP Submitting intents: {}", intent);
255 intentService.submit(intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800256 }
257 }
258 }
259 }
260
261 /**
Jonathan Hart552e31f2015-02-06 11:11:59 -0800262 * Generates a route intent for a prefix, the next hop IP address, and
263 * the next hop MAC address.
264 * <p/>
265 * This method will find the egress interface for the intent.
266 * Intent will match dst IP prefix and rewrite dst MAC address at all other
267 * border switches, then forward packets according to dst MAC address.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800268 *
Jonathan Hart552e31f2015-02-06 11:11:59 -0800269 * @param prefix IP prefix of the route to add
270 * @param nextHopIpAddress IP address of the next hop
271 * @param nextHopMacAddress MAC address of the next hop
272 * @return the generated intent, or null if no intent should be submitted
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800273 */
Jonathan Hart552e31f2015-02-06 11:11:59 -0800274 private MultiPointToSinglePointIntent generateRouteIntent(
275 IpPrefix prefix,
276 IpAddress nextHopIpAddress,
277 MacAddress nextHopMacAddress) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800278
Jonathan Hart552e31f2015-02-06 11:11:59 -0800279 // Find the attachment point (egress interface) of the next hop
280 Interface egressInterface;
281 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
282 // Route to a peer
283 log.debug("Route to peer {}", nextHopIpAddress);
284 BgpPeer peer =
285 configService.getBgpPeers().get(nextHopIpAddress);
286 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800287 configService.getInterface(peer.connectPoint());
Jonathan Hart552e31f2015-02-06 11:11:59 -0800288 } else {
289 // Route to non-peer
290 log.debug("Route to non-peer {}", nextHopIpAddress);
291 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800292 configService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800293 if (egressInterface == null) {
294 log.warn("No outgoing interface found for {}",
295 nextHopIpAddress);
296 return null;
297 }
298 }
299
300 //
301 // Generate the intent itself
302 //
303 Set<ConnectPoint> ingressPorts = new HashSet<>();
304 ConnectPoint egressPort = egressInterface.connectPoint();
305 log.debug("Generating intent for prefix {}, next hop mac {}",
306 prefix, nextHopMacAddress);
307
Jonathan Hart90a02c22015-02-13 11:52:07 -0800308 for (Interface intf : configService.getInterfaces()) {
Jonathan Hart552e31f2015-02-06 11:11:59 -0800309 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
310 ConnectPoint srcPort = intf.connectPoint();
311 ingressPorts.add(srcPort);
312 }
313 }
314
315 // Match the destination IP prefix at the first hop
316 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
Pavlin Radoslavov87dd9302015-03-10 13:53:24 -0700317 if (prefix.isIp4()) {
Jonathan Hart552e31f2015-02-06 11:11:59 -0800318 selector.matchEthType(Ethernet.TYPE_IPV4);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800319 selector.matchIPDst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800320 } else {
321 selector.matchEthType(Ethernet.TYPE_IPV6);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800322 selector.matchIPv6Dst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800323 }
Jonathan Hart552e31f2015-02-06 11:11:59 -0800324
325 // Rewrite the destination MAC address
326 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
327 .setEthDst(nextHopMacAddress);
328 if (!egressInterface.vlan().equals(VlanId.NONE)) {
329 treatment.setVlanId(egressInterface.vlan());
330 // If we set VLAN ID, we have to make sure a VLAN tag exists.
331 // TODO support no VLAN -> VLAN routing
332 selector.matchVlanId(VlanId.ANY);
333 }
334
335 return new MultiPointToSinglePointIntent(appId, selector.build(),
336 treatment.build(),
337 ingressPorts, egressPort);
338 }
339
340 @Override
341 public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800342 //
343 // NOTE: Semantically, we MUST withdraw existing intents before
344 // submitting new intents.
345 //
346 synchronized (this) {
347 MultiPointToSinglePointIntent intent;
348
349 log.debug("SDN-IP submitting intents = {} withdrawing = {}",
Jonathan Hart552e31f2015-02-06 11:11:59 -0800350 updates.size(), withdraws.size());
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800351
352 //
353 // Prepare the Intent batch operations for the intents to withdraw
354 //
Jonathan Hart552e31f2015-02-06 11:11:59 -0800355 for (FibUpdate withdraw : withdraws) {
356 checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
357 "FibUpdate with wrong type in withdraws list");
358
359 IpPrefix prefix = withdraw.entry().prefix();
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800360 intent = routeIntents.remove(prefix);
361 if (intent == null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800362 log.trace("SDN-IP No intent in routeIntents to delete " +
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800363 "for prefix: {}", prefix);
364 continue;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800365 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800366 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800367 log.trace("SDN-IP Withdrawing intent: {}", intent);
Brian O'Connor03406a42015-02-03 17:28:57 -0800368 intentService.withdraw(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800369 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800370 }
371
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800372 //
373 // Prepare the Intent batch operations for the intents to submit
374 //
Jonathan Hart552e31f2015-02-06 11:11:59 -0800375 for (FibUpdate update : updates) {
376 checkArgument(update.type() == FibUpdate.Type.UPDATE,
377 "FibUpdate with wrong type in updates list");
378
379 IpPrefix prefix = update.entry().prefix();
380 intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
381 update.entry().nextHopMac());
382
383 if (intent == null) {
384 // This preserves the old semantics - if an intent can't be
385 // generated, we don't do anything with that prefix. But
386 // perhaps we should withdraw the old intent anyway?
387 continue;
388 }
389
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800390 MultiPointToSinglePointIntent oldIntent =
391 routeIntents.put(prefix, intent);
392 if (isElectedLeader && isActivatedLeader) {
393 if (oldIntent != null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800394 log.trace("SDN-IP Withdrawing old intent: {}",
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800395 oldIntent);
Brian O'Connor03406a42015-02-03 17:28:57 -0800396 intentService.withdraw(oldIntent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800397 }
Jonathan Hartb3b8a0a2015-02-12 17:36:13 -0800398 log.trace("SDN-IP Submitting intent: {}", intent);
399 intentService.submit(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800400 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800401 }
402 }
403 }
404
405 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800406 * Synchronize the in-memory Intents with the Intents in the Intent
407 * framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800408 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800409 void synchronizeIntents() {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800410 synchronized (this) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800411
412 Map<IntentKey, Intent> localIntents = new HashMap<>();
413 Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
414 Collection<Intent> storeInMemoryIntents = new LinkedList<>();
415 Collection<Intent> addIntents = new LinkedList<>();
416 Collection<Intent> deleteIntents = new LinkedList<>();
417
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800418 if (!isElectedLeader) {
419 return; // Nothing to do: not the leader anymore
420 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800421 log.debug("SDN-IP synchronizing all intents...");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800422
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800423 // Prepare the local intents
424 for (Intent intent : routeIntents.values()) {
425 localIntents.put(new IntentKey(intent), intent);
426 }
427 for (Intent intent : peerIntents.values()) {
428 localIntents.put(new IntentKey(intent), intent);
429 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800430
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800431 // Fetch all intents for this application
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800432 for (Intent intent : intentService.getIntents()) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800433 if (!intent.appId().equals(appId)) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800434 continue;
435 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800436 fetchedIntents.put(new IntentKey(intent), intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800437 }
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800438 if (log.isDebugEnabled()) {
439 for (Intent intent: fetchedIntents.values()) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800440 log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800441 intent);
442 }
443 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800444
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800445 computeIntentsDelta(localIntents, fetchedIntents,
446 storeInMemoryIntents, addIntents,
447 deleteIntents);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800448
449 //
450 // Perform the actions:
451 // 1. Store in memory fetched intents that are same. Can be done
452 // even if we are not the leader anymore
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800453 // 2. Delete intents: check if the leader before the operation
454 // 3. Add intents: check if the leader before the operation
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800455 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800456 for (Intent intent : storeInMemoryIntents) {
457 // Store the intent in memory based on its type
458 if (intent instanceof MultiPointToSinglePointIntent) {
459 MultiPointToSinglePointIntent mp2pIntent =
460 (MultiPointToSinglePointIntent) intent;
461 // Find the IP prefix
462 Criterion c =
463 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800464 if (c == null) {
465 // Try IPv6
466 c =
467 mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
468 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800469 if (c != null && c instanceof IPCriterion) {
470 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800471 IpPrefix ipPrefix = ipCriterion.ip();
472 if (ipPrefix == null) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800473 continue;
474 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800475 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800476 "in-memory Route Intent for prefix {}",
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800477 ipPrefix);
478 routeIntents.put(ipPrefix, mp2pIntent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800479 } else {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800480 log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800481 mp2pIntent.id());
482 }
483 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800484 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800485 if (intent instanceof PointToPointIntent) {
486 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800487 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800488 "in-memory Peer Intent {}", p2pIntent);
489 peerIntents.put(new IntentKey(intent), p2pIntent);
490 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800491 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800492 }
493
494 // Withdraw Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800495 for (Intent intent : deleteIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800496 intentService.withdraw(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800497 log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800498 intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800499 }
500 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800501 log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800502 "not elected leader anymore");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800503 isActivatedLeader = false;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800504 return;
505 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800506
507 // Add Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800508 for (Intent intent : addIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800509 intentService.submit(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800510 log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800511 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800512 }
513 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800514 log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800515 "not elected leader anymore");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800516 isActivatedLeader = false;
517 return;
518 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800519
520 if (isElectedLeader) {
521 isActivatedLeader = true; // Allow push of Intents
522 } else {
523 isActivatedLeader = false;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800524 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800525 log.debug("SDN-IP intent synchronization completed");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800526 }
527 }
528
529 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800530 * Computes the delta in two sets of Intents: local in-memory Intents,
531 * and intents fetched from the Intent framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800532 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800533 * @param localIntents the local in-memory Intents
534 * @param fetchedIntents the Intents fetched from the Intent framework
535 * @param storeInMemoryIntents the Intents that should be stored in memory.
536 * Note: This Collection must be allocated by the caller, and it will
537 * be populated by this method.
538 * @param addIntents the Intents that should be added to the Intent
539 * framework. Note: This Collection must be allocated by the caller, and
540 * it will be populated by this method.
541 * @param deleteIntents the Intents that should be deleted from the Intent
542 * framework. Note: This Collection must be allocated by the caller, and
543 * it will be populated by this method.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800544 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800545 private void computeIntentsDelta(
546 final Map<IntentKey, Intent> localIntents,
547 final Map<IntentKey, Intent> fetchedIntents,
548 Collection<Intent> storeInMemoryIntents,
549 Collection<Intent> addIntents,
550 Collection<Intent> deleteIntents) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800551
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800552 //
553 // Compute the deltas between the LOCAL in-memory Intents and the
554 // FETCHED Intents:
555 // - If an Intent is in both the LOCAL and FETCHED sets:
556 // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
557 // the LOCAL Intent should be added/installed; otherwise the
558 // FETCHED intent should be stored in the local memory
559 // (i.e., override the LOCAL Intent) to preserve the original
560 // Intent ID.
561 // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
562 // Intent should be added/installed.
563 // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
564 // Intent should be deleted/withdrawn.
565 //
566 for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
567 IntentKey intentKey = entry.getKey();
568 Intent localIntent = entry.getValue();
569 Intent fetchedIntent = fetchedIntents.get(intentKey);
570
571 if (fetchedIntent == null) {
572 //
573 // No FETCHED Intent found: push the LOCAL Intent.
574 //
575 addIntents.add(localIntent);
576 continue;
577 }
578
579 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800580 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800581 if (state == null ||
582 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800583 state == IntentState.WITHDRAWN) {
584 // The intent has been withdrawn but according to our route
585 // table it should be installed. We'll reinstall it.
586 addIntents.add(localIntent);
587 continue;
588 }
589 storeInMemoryIntents.add(fetchedIntent);
590 }
591
592 for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
593 IntentKey intentKey = entry.getKey();
594 Intent fetchedIntent = entry.getValue();
595 Intent localIntent = localIntents.get(intentKey);
596
597 if (localIntent != null) {
598 continue;
599 }
600
601 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800602 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800603 if (state == null ||
604 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800605 state == IntentState.WITHDRAWN) {
606 // Nothing to do. The intent has been already withdrawn.
607 continue;
608 }
609 //
610 // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
611 //
612 deleteIntents.add(fetchedIntent);
613 }
614 }
615
616 /**
617 * Helper class that can be used to compute the key for an Intent by
618 * by excluding the Intent ID.
619 */
620 static final class IntentKey {
621 private final Intent intent;
622
623 /**
624 * Constructor.
625 *
626 * @param intent the intent to use
627 */
628 IntentKey(Intent intent) {
629 checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
630 (intent instanceof PointToPointIntent),
631 "Intent type not recognized", intent);
632 this.intent = intent;
633 }
634
635 /**
636 * Compares two Multi-Point to Single-Point Intents whether they
637 * represent same logical intention.
638 *
639 * @param intent1 the first Intent to compare
640 * @param intent2 the second Intent to compare
641 * @return true if both Intents represent same logical intention,
642 * otherwise false
643 */
644 static boolean equalIntents(MultiPointToSinglePointIntent intent1,
645 MultiPointToSinglePointIntent intent2) {
646 return Objects.equals(intent1.appId(), intent2.appId()) &&
647 Objects.equals(intent1.selector(), intent2.selector()) &&
648 Objects.equals(intent1.treatment(), intent2.treatment()) &&
649 Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
650 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
651 }
652
653 /**
654 * Compares two Point-to-Point Intents whether they represent
655 * same logical intention.
656 *
657 * @param intent1 the first Intent to compare
658 * @param intent2 the second Intent to compare
659 * @return true if both Intents represent same logical intention,
660 * otherwise false
661 */
662 static boolean equalIntents(PointToPointIntent intent1,
663 PointToPointIntent intent2) {
664 return Objects.equals(intent1.appId(), intent2.appId()) &&
665 Objects.equals(intent1.selector(), intent2.selector()) &&
666 Objects.equals(intent1.treatment(), intent2.treatment()) &&
667 Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
668 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
669 }
670
671 @Override
672 public int hashCode() {
673 if (intent instanceof PointToPointIntent) {
674 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
675 return Objects.hash(p2pIntent.appId(),
676 p2pIntent.resources(),
677 p2pIntent.selector(),
678 p2pIntent.treatment(),
679 p2pIntent.constraints(),
680 p2pIntent.ingressPoint(),
681 p2pIntent.egressPoint());
682 }
683 if (intent instanceof MultiPointToSinglePointIntent) {
684 MultiPointToSinglePointIntent m2pIntent =
685 (MultiPointToSinglePointIntent) intent;
686 return Objects.hash(m2pIntent.appId(),
687 m2pIntent.resources(),
688 m2pIntent.selector(),
689 m2pIntent.treatment(),
690 m2pIntent.constraints(),
691 m2pIntent.ingressPoints(),
692 m2pIntent.egressPoint());
693 }
694 checkArgument(false, "Intent type not recognized", intent);
695 return 0;
696 }
697
698 @Override
699 public boolean equals(Object obj) {
700 if (this == obj) {
701 return true;
702 }
703 if ((obj == null) || (!(obj instanceof IntentKey))) {
704 return false;
705 }
706 IntentKey other = (IntentKey) obj;
707
708 if (this.intent instanceof PointToPointIntent) {
709 if (!(other.intent instanceof PointToPointIntent)) {
710 return false;
711 }
712 return equalIntents((PointToPointIntent) this.intent,
713 (PointToPointIntent) other.intent);
714 }
715 if (this.intent instanceof MultiPointToSinglePointIntent) {
716 if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
717 return false;
718 }
719 return equalIntents(
720 (MultiPointToSinglePointIntent) this.intent,
721 (MultiPointToSinglePointIntent) other.intent);
722 }
723 checkArgument(false, "Intent type not recognized", intent);
724 return false;
725 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800726 }
727}