blob: f08e513e053c86c6969c40c9d603161c29290246 [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
Ray Milkeyebc5d222015-03-18 15:45:36 -070018import java.util.Collection;
19import java.util.HashMap;
20import java.util.HashSet;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Map;
24import java.util.Objects;
25import java.util.Set;
26import java.util.concurrent.ConcurrentHashMap;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29import java.util.concurrent.Semaphore;
30
Jonathan Hart552e31f2015-02-06 11:11:59 -080031import org.onlab.packet.Ethernet;
Jonathan Hart552e31f2015-02-06 11:11:59 -080032import org.onlab.packet.IpAddress;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080033import org.onlab.packet.IpPrefix;
Jonathan Hart552e31f2015-02-06 11:11:59 -080034import org.onlab.packet.MacAddress;
35import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080036import org.onosproject.core.ApplicationId;
Jonathan Hart552e31f2015-02-06 11:11:59 -080037import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.flow.DefaultTrafficTreatment;
40import org.onosproject.net.flow.TrafficSelector;
41import org.onosproject.net.flow.TrafficTreatment;
Brian O'Connorabafb502014-12-02 22:26:20 -080042import org.onosproject.net.flow.criteria.Criteria.IPCriterion;
43import org.onosproject.net.flow.criteria.Criterion;
44import org.onosproject.net.intent.Intent;
Brian O'Connorabafb502014-12-02 22:26:20 -080045import org.onosproject.net.intent.IntentService;
46import org.onosproject.net.intent.IntentState;
Pavlin Radoslavov2aa1f322015-03-11 17:59:44 -070047import org.onosproject.net.intent.Key;
Brian O'Connorabafb502014-12-02 22:26:20 -080048import org.onosproject.net.intent.MultiPointToSinglePointIntent;
49import org.onosproject.net.intent.PointToPointIntent;
Jonathan Hart2da1e602015-02-18 19:09:24 -080050import org.onosproject.routing.FibListener;
51import org.onosproject.routing.FibUpdate;
52import org.onosproject.routing.config.BgpPeer;
53import org.onosproject.routing.config.Interface;
54import org.onosproject.routing.config.RoutingConfigurationService;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080055import org.slf4j.Logger;
56import org.slf4j.LoggerFactory;
57
Ray Milkeyebc5d222015-03-18 15:45:36 -070058import com.google.common.util.concurrent.ThreadFactoryBuilder;
Jonathan Hart552e31f2015-02-06 11:11:59 -080059
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 Radoslavov2aa1f322015-03-11 17:59:44 -070067 private static final int PRIORITY_OFFSET = 100;
68 private static final int PRIORITY_MULTIPLIER = 5;
69
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080070 private static final Logger log =
71 LoggerFactory.getLogger(IntentSynchronizer.class);
72
73 private final ApplicationId appId;
74 private final IntentService intentService;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -080075 private final Map<IntentKey, PointToPointIntent> peerIntents;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080076 private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080077
78 //
79 // State to deal with SDN-IP Leader election and pushing Intents
80 //
81 private final ExecutorService bgpIntentsSynchronizerExecutor;
82 private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
83 private volatile boolean isElectedLeader = false;
84 private volatile boolean isActivatedLeader = false;
85
Jonathan Hart90a02c22015-02-13 11:52:07 -080086 private final RoutingConfigurationService configService;
Jonathan Hart552e31f2015-02-06 11:11:59 -080087
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080088 /**
89 * Class constructor.
90 *
91 * @param appId the Application ID
92 * @param intentService the intent service
Jonathan Hart552e31f2015-02-06 11:11:59 -080093 * @param configService the SDN-IP configuration service
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080094 */
Jonathan Hart552e31f2015-02-06 11:11:59 -080095 IntentSynchronizer(ApplicationId appId, IntentService intentService,
Jonathan Hart90a02c22015-02-13 11:52:07 -080096 RoutingConfigurationService configService) {
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;
Jonathan Hart552e31f2015-02-06 11:11:59 -0800103
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800104 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
105 new ThreadFactoryBuilder()
Pavlin Radoslavov8b752442014-11-18 14:34:37 -0800106 .setNameFormat("sdnip-intents-synchronizer-%d").build());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800107 }
108
109 /**
110 * Starts the synchronizer.
111 */
112 public void start() {
113 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
114 @Override
115 public void run() {
116 doIntentSynchronizationThread();
117 }
118 });
119 }
120
121 /**
122 * Stops the synchronizer.
123 */
124 public void stop() {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800125 synchronized (this) {
126 // Stop the thread(s)
127 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800128
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800129 //
130 // Withdraw all SDN-IP intents
131 //
132 if (!isElectedLeader) {
133 return; // Nothing to do: not the leader anymore
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800134 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800135
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800136 //
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800137 // NOTE: We don't withdraw the intents during shutdown, because
138 // it creates flux in the data plane during switchover.
139 //
140
141 /*
142 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800143 // Build a batch operation to withdraw all intents from this
144 // application.
145 //
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800146 log.debug("SDN-IP Intent Synchronizer shutdown: " +
147 "withdrawing all intents...");
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800148 IntentOperations.Builder builder = IntentOperations.builder(appId);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800149 for (Intent intent : intentService.getIntents()) {
150 // Skip the intents from other applications
151 if (!intent.appId().equals(appId)) {
152 continue;
153 }
154
155 // Skip the intents that are already withdrawn
156 IntentState intentState =
157 intentService.getIntentState(intent.id());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800158 if ((intentState == null) ||
159 intentState.equals(IntentState.WITHDRAWING) ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800160 intentState.equals(IntentState.WITHDRAWN)) {
161 continue;
162 }
163
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800164 log.trace("SDN-IP Intent Synchronizer withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800165 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800166 builder.addWithdrawOperation(intent.id());
167 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800168 IntentOperations intentOperations = builder.build();
169 intentService.execute(intentOperations);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800170 leaderChanged(false);
171
172 peerIntents.clear();
173 routeIntents.clear();
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800174 log.debug("SDN-IP Intent Synchronizer shutdown completed");
Pavlin Radoslavov20be3e62014-11-25 18:52:08 -0800175 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800176 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800177 }
178
Jonathan Hart51372182014-12-03 21:32:34 -0800179 /**
180 * Signals the synchronizer that the SDN-IP leadership has changed.
181 *
182 * @param isLeader true if this instance is now the leader, otherwise false
183 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800184 public void leaderChanged(boolean isLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800185 log.debug("SDN-IP Leader changed: {}", isLeader);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800186
187 if (!isLeader) {
188 this.isElectedLeader = false;
189 this.isActivatedLeader = false;
190 return; // Nothing to do
191 }
192 this.isActivatedLeader = false;
193 this.isElectedLeader = true;
194
195 //
196 // Tell the Intents Synchronizer thread to start the synchronization
197 //
198 intentsSynchronizerSemaphore.release();
199 }
200
201 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800202 * Gets the route intents.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800203 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800204 * @return the route intents
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800205 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800206 public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
207 List<MultiPointToSinglePointIntent> result = new LinkedList<>();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800208
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800209 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800210 routeIntents.entrySet()) {
211 result.add(entry.getValue());
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800212 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800213 return result;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800214 }
215
216 /**
217 * Thread for Intent Synchronization.
218 */
219 private void doIntentSynchronizationThread() {
220 boolean interrupted = false;
221 try {
222 while (!interrupted) {
223 try {
224 intentsSynchronizerSemaphore.acquire();
225 //
226 // Drain all permits, because a single synchronization is
227 // sufficient.
228 //
229 intentsSynchronizerSemaphore.drainPermits();
230 } catch (InterruptedException e) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800231 interrupted = true;
232 break;
233 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800234 synchronizeIntents();
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800235 }
236 } finally {
237 if (interrupted) {
238 Thread.currentThread().interrupt();
239 }
240 }
241 }
242
243 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800244 * Submits a collection of point-to-point intents.
245 *
246 * @param intents the intents to submit
247 */
248 void submitPeerIntents(Collection<PointToPointIntent> intents) {
249 synchronized (this) {
250 // Store the intents in memory
251 for (PointToPointIntent intent : intents) {
252 peerIntents.put(new IntentKey(intent), intent);
253 }
254
255 // Push the intents
256 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800257 log.debug("SDN-IP Submitting all Peer Intents...");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800258 for (Intent intent : intents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800259 log.trace("SDN-IP Submitting intents: {}", intent);
260 intentService.submit(intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800261 }
262 }
263 }
264 }
265
266 /**
Jonathan Hart552e31f2015-02-06 11:11:59 -0800267 * Generates a route intent for a prefix, the next hop IP address, and
268 * the next hop MAC address.
269 * <p/>
270 * This method will find the egress interface for the intent.
271 * Intent will match dst IP prefix and rewrite dst MAC address at all other
272 * border switches, then forward packets according to dst MAC address.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800273 *
Jonathan Hart552e31f2015-02-06 11:11:59 -0800274 * @param prefix IP prefix of the route to add
275 * @param nextHopIpAddress IP address of the next hop
276 * @param nextHopMacAddress MAC address of the next hop
277 * @return the generated intent, or null if no intent should be submitted
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800278 */
Jonathan Hart552e31f2015-02-06 11:11:59 -0800279 private MultiPointToSinglePointIntent generateRouteIntent(
280 IpPrefix prefix,
281 IpAddress nextHopIpAddress,
282 MacAddress nextHopMacAddress) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800283
Jonathan Hart552e31f2015-02-06 11:11:59 -0800284 // Find the attachment point (egress interface) of the next hop
285 Interface egressInterface;
286 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
287 // Route to a peer
288 log.debug("Route to peer {}", nextHopIpAddress);
289 BgpPeer peer =
290 configService.getBgpPeers().get(nextHopIpAddress);
291 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800292 configService.getInterface(peer.connectPoint());
Jonathan Hart552e31f2015-02-06 11:11:59 -0800293 } else {
294 // Route to non-peer
295 log.debug("Route to non-peer {}", nextHopIpAddress);
296 egressInterface =
Jonathan Hart90a02c22015-02-13 11:52:07 -0800297 configService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800298 if (egressInterface == null) {
299 log.warn("No outgoing interface found for {}",
300 nextHopIpAddress);
301 return null;
302 }
303 }
304
305 //
306 // Generate the intent itself
307 //
308 Set<ConnectPoint> ingressPorts = new HashSet<>();
309 ConnectPoint egressPort = egressInterface.connectPoint();
310 log.debug("Generating intent for prefix {}, next hop mac {}",
311 prefix, nextHopMacAddress);
312
Jonathan Hart90a02c22015-02-13 11:52:07 -0800313 for (Interface intf : configService.getInterfaces()) {
Jonathan Hart552e31f2015-02-06 11:11:59 -0800314 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
315 ConnectPoint srcPort = intf.connectPoint();
316 ingressPorts.add(srcPort);
317 }
318 }
319
320 // Match the destination IP prefix at the first hop
321 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
Pavlin Radoslavov87dd9302015-03-10 13:53:24 -0700322 if (prefix.isIp4()) {
Jonathan Hart552e31f2015-02-06 11:11:59 -0800323 selector.matchEthType(Ethernet.TYPE_IPV4);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800324 selector.matchIPDst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800325 } else {
326 selector.matchEthType(Ethernet.TYPE_IPV6);
Pavlin Radoslavova8537092015-02-23 10:15:20 -0800327 selector.matchIPv6Dst(prefix);
Jonathan Hart552e31f2015-02-06 11:11:59 -0800328 }
Jonathan Hart552e31f2015-02-06 11:11:59 -0800329
330 // Rewrite the destination MAC address
331 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
332 .setEthDst(nextHopMacAddress);
333 if (!egressInterface.vlan().equals(VlanId.NONE)) {
334 treatment.setVlanId(egressInterface.vlan());
335 // If we set VLAN ID, we have to make sure a VLAN tag exists.
336 // TODO support no VLAN -> VLAN routing
337 selector.matchVlanId(VlanId.ANY);
338 }
339
Pavlin Radoslavov2aa1f322015-03-11 17:59:44 -0700340 int priority =
341 prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
342 Key key = Key.of(prefix.toString(), appId);
Ray Milkeyebc5d222015-03-18 15:45:36 -0700343 return MultiPointToSinglePointIntent.builder()
344 .appId(appId)
345 .key(key)
346 .selector(selector.build())
347 .treatment(treatment.build())
348 .ingressPoints(ingressPorts)
349 .egressPoint(egressPort)
350 .priority(priority)
351 .build();
Jonathan Hart552e31f2015-02-06 11:11:59 -0800352 }
353
354 @Override
355 public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800356 //
357 // NOTE: Semantically, we MUST withdraw existing intents before
358 // submitting new intents.
359 //
360 synchronized (this) {
361 MultiPointToSinglePointIntent intent;
362
363 log.debug("SDN-IP submitting intents = {} withdrawing = {}",
Jonathan Hart552e31f2015-02-06 11:11:59 -0800364 updates.size(), withdraws.size());
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800365
366 //
367 // Prepare the Intent batch operations for the intents to withdraw
368 //
Jonathan Hart552e31f2015-02-06 11:11:59 -0800369 for (FibUpdate withdraw : withdraws) {
370 checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
371 "FibUpdate with wrong type in withdraws list");
372
373 IpPrefix prefix = withdraw.entry().prefix();
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800374 intent = routeIntents.remove(prefix);
375 if (intent == null) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800376 log.trace("SDN-IP No intent in routeIntents to delete " +
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800377 "for prefix: {}", prefix);
378 continue;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800379 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800380 if (isElectedLeader && isActivatedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800381 log.trace("SDN-IP Withdrawing intent: {}", intent);
Brian O'Connor03406a42015-02-03 17:28:57 -0800382 intentService.withdraw(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800383 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800384 }
385
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800386 //
387 // Prepare the Intent batch operations for the intents to submit
388 //
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);
Brian O'Connor03406a42015-02-03 17:28:57 -0800410 intentService.withdraw(oldIntent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800411 }
Jonathan Hartb3b8a0a2015-02-12 17:36:13 -0800412 log.trace("SDN-IP Submitting intent: {}", intent);
413 intentService.submit(intent);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800414 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800415 }
416 }
417 }
418
419 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800420 * Synchronize the in-memory Intents with the Intents in the Intent
421 * framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800422 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800423 void synchronizeIntents() {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800424 synchronized (this) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800425
426 Map<IntentKey, Intent> localIntents = new HashMap<>();
427 Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
428 Collection<Intent> storeInMemoryIntents = new LinkedList<>();
429 Collection<Intent> addIntents = new LinkedList<>();
430 Collection<Intent> deleteIntents = new LinkedList<>();
431
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800432 if (!isElectedLeader) {
433 return; // Nothing to do: not the leader anymore
434 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800435 log.debug("SDN-IP synchronizing all intents...");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800436
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800437 // Prepare the local intents
438 for (Intent intent : routeIntents.values()) {
439 localIntents.put(new IntentKey(intent), intent);
440 }
441 for (Intent intent : peerIntents.values()) {
442 localIntents.put(new IntentKey(intent), intent);
443 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800444
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800445 // Fetch all intents for this application
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800446 for (Intent intent : intentService.getIntents()) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800447 if (!intent.appId().equals(appId)) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800448 continue;
449 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800450 fetchedIntents.put(new IntentKey(intent), intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800451 }
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800452 if (log.isDebugEnabled()) {
453 for (Intent intent: fetchedIntents.values()) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800454 log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800455 intent);
456 }
457 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800458
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800459 computeIntentsDelta(localIntents, fetchedIntents,
460 storeInMemoryIntents, addIntents,
461 deleteIntents);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800462
463 //
464 // Perform the actions:
465 // 1. Store in memory fetched intents that are same. Can be done
466 // even if we are not the leader anymore
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800467 // 2. Delete intents: check if the leader before the operation
468 // 3. Add intents: check if the leader before the operation
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800469 //
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800470 for (Intent intent : storeInMemoryIntents) {
471 // Store the intent in memory based on its type
472 if (intent instanceof MultiPointToSinglePointIntent) {
473 MultiPointToSinglePointIntent mp2pIntent =
474 (MultiPointToSinglePointIntent) intent;
475 // Find the IP prefix
476 Criterion c =
477 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800478 if (c == null) {
479 // Try IPv6
480 c =
481 mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
482 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800483 if (c != null && c instanceof IPCriterion) {
484 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800485 IpPrefix ipPrefix = ipCriterion.ip();
486 if (ipPrefix == null) {
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800487 continue;
488 }
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800489 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800490 "in-memory Route Intent for prefix {}",
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800491 ipPrefix);
492 routeIntents.put(ipPrefix, mp2pIntent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800493 } else {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800494 log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800495 mp2pIntent.id());
496 }
497 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800498 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800499 if (intent instanceof PointToPointIntent) {
500 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800501 log.trace("SDN-IP Intent Synchronizer: updating " +
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800502 "in-memory Peer Intent {}", p2pIntent);
503 peerIntents.put(new IntentKey(intent), p2pIntent);
504 continue;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800505 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800506 }
507
508 // Withdraw Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800509 for (Intent intent : deleteIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800510 intentService.withdraw(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800511 log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800512 intent);
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800513 }
514 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800515 log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800516 "not elected leader anymore");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800517 isActivatedLeader = false;
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800518 return;
519 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800520
521 // Add Intents
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800522 for (Intent intent : addIntents) {
Brian O'Connor03406a42015-02-03 17:28:57 -0800523 intentService.submit(intent);
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800524 log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800525 intent);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800526 }
527 if (!isElectedLeader) {
Pavlin Radoslavov8049bb82014-12-02 13:58:35 -0800528 log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
Pavlin Radoslavovcaf63372014-11-26 11:59:11 -0800529 "not elected leader anymore");
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800530 isActivatedLeader = false;
531 return;
532 }
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800533
534 if (isElectedLeader) {
535 isActivatedLeader = true; // Allow push of Intents
536 } else {
537 isActivatedLeader = false;
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800538 }
Pavlin Radoslavov93ae8322014-11-24 20:54:36 -0800539 log.debug("SDN-IP intent synchronization completed");
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800540 }
541 }
542
543 /**
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800544 * Computes the delta in two sets of Intents: local in-memory Intents,
545 * and intents fetched from the Intent framework.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800546 *
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800547 * @param localIntents the local in-memory Intents
548 * @param fetchedIntents the Intents fetched from the Intent framework
549 * @param storeInMemoryIntents the Intents that should be stored in memory.
550 * Note: This Collection must be allocated by the caller, and it will
551 * be populated by this method.
552 * @param addIntents the Intents that should be added to the Intent
553 * framework. Note: This Collection must be allocated by the caller, and
554 * it will be populated by this method.
555 * @param deleteIntents the Intents that should be deleted from the Intent
556 * framework. Note: This Collection must be allocated by the caller, and
557 * it will be populated by this method.
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800558 */
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800559 private void computeIntentsDelta(
560 final Map<IntentKey, Intent> localIntents,
561 final Map<IntentKey, Intent> fetchedIntents,
562 Collection<Intent> storeInMemoryIntents,
563 Collection<Intent> addIntents,
564 Collection<Intent> deleteIntents) {
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800565
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800566 //
567 // Compute the deltas between the LOCAL in-memory Intents and the
568 // FETCHED Intents:
569 // - If an Intent is in both the LOCAL and FETCHED sets:
570 // If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
571 // the LOCAL Intent should be added/installed; otherwise the
572 // FETCHED intent should be stored in the local memory
573 // (i.e., override the LOCAL Intent) to preserve the original
574 // Intent ID.
575 // - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
576 // Intent should be added/installed.
577 // - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
578 // Intent should be deleted/withdrawn.
579 //
580 for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
581 IntentKey intentKey = entry.getKey();
582 Intent localIntent = entry.getValue();
583 Intent fetchedIntent = fetchedIntents.get(intentKey);
584
585 if (fetchedIntent == null) {
586 //
587 // No FETCHED Intent found: push the LOCAL Intent.
588 //
589 addIntents.add(localIntent);
590 continue;
591 }
592
593 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800594 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800595 if (state == null ||
596 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800597 state == IntentState.WITHDRAWN) {
598 // The intent has been withdrawn but according to our route
599 // table it should be installed. We'll reinstall it.
600 addIntents.add(localIntent);
601 continue;
602 }
603 storeInMemoryIntents.add(fetchedIntent);
604 }
605
606 for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
607 IntentKey intentKey = entry.getKey();
608 Intent fetchedIntent = entry.getValue();
609 Intent localIntent = localIntents.get(intentKey);
610
611 if (localIntent != null) {
612 continue;
613 }
614
615 IntentState state =
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800616 intentService.getIntentState(fetchedIntent.key());
Pavlin Radoslavovdeb8a102014-11-26 13:31:36 -0800617 if (state == null ||
618 state == IntentState.WITHDRAWING ||
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800619 state == IntentState.WITHDRAWN) {
620 // Nothing to do. The intent has been already withdrawn.
621 continue;
622 }
623 //
624 // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
625 //
626 deleteIntents.add(fetchedIntent);
627 }
628 }
629
630 /**
631 * Helper class that can be used to compute the key for an Intent by
632 * by excluding the Intent ID.
633 */
634 static final class IntentKey {
635 private final Intent intent;
636
637 /**
638 * Constructor.
639 *
640 * @param intent the intent to use
641 */
642 IntentKey(Intent intent) {
643 checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
644 (intent instanceof PointToPointIntent),
645 "Intent type not recognized", intent);
646 this.intent = intent;
647 }
648
649 /**
650 * Compares two Multi-Point to Single-Point Intents whether they
651 * represent same logical intention.
652 *
653 * @param intent1 the first Intent to compare
654 * @param intent2 the second Intent to compare
655 * @return true if both Intents represent same logical intention,
656 * otherwise false
657 */
658 static boolean equalIntents(MultiPointToSinglePointIntent intent1,
659 MultiPointToSinglePointIntent intent2) {
660 return Objects.equals(intent1.appId(), intent2.appId()) &&
661 Objects.equals(intent1.selector(), intent2.selector()) &&
662 Objects.equals(intent1.treatment(), intent2.treatment()) &&
663 Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
664 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
665 }
666
667 /**
668 * Compares two Point-to-Point Intents whether they represent
669 * same logical intention.
670 *
671 * @param intent1 the first Intent to compare
672 * @param intent2 the second Intent to compare
673 * @return true if both Intents represent same logical intention,
674 * otherwise false
675 */
676 static boolean equalIntents(PointToPointIntent intent1,
677 PointToPointIntent intent2) {
678 return Objects.equals(intent1.appId(), intent2.appId()) &&
679 Objects.equals(intent1.selector(), intent2.selector()) &&
680 Objects.equals(intent1.treatment(), intent2.treatment()) &&
681 Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
682 Objects.equals(intent1.egressPoint(), intent2.egressPoint());
683 }
684
685 @Override
686 public int hashCode() {
687 if (intent instanceof PointToPointIntent) {
688 PointToPointIntent p2pIntent = (PointToPointIntent) intent;
689 return Objects.hash(p2pIntent.appId(),
690 p2pIntent.resources(),
691 p2pIntent.selector(),
692 p2pIntent.treatment(),
693 p2pIntent.constraints(),
694 p2pIntent.ingressPoint(),
695 p2pIntent.egressPoint());
696 }
697 if (intent instanceof MultiPointToSinglePointIntent) {
698 MultiPointToSinglePointIntent m2pIntent =
699 (MultiPointToSinglePointIntent) intent;
700 return Objects.hash(m2pIntent.appId(),
701 m2pIntent.resources(),
702 m2pIntent.selector(),
703 m2pIntent.treatment(),
704 m2pIntent.constraints(),
705 m2pIntent.ingressPoints(),
706 m2pIntent.egressPoint());
707 }
708 checkArgument(false, "Intent type not recognized", intent);
709 return 0;
710 }
711
712 @Override
713 public boolean equals(Object obj) {
714 if (this == obj) {
715 return true;
716 }
717 if ((obj == null) || (!(obj instanceof IntentKey))) {
718 return false;
719 }
720 IntentKey other = (IntentKey) obj;
721
722 if (this.intent instanceof PointToPointIntent) {
723 if (!(other.intent instanceof PointToPointIntent)) {
724 return false;
725 }
726 return equalIntents((PointToPointIntent) this.intent,
727 (PointToPointIntent) other.intent);
728 }
729 if (this.intent instanceof MultiPointToSinglePointIntent) {
730 if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
731 return false;
732 }
733 return equalIntents(
734 (MultiPointToSinglePointIntent) this.intent,
735 (MultiPointToSinglePointIntent) other.intent);
736 }
737 checkArgument(false, "Intent type not recognized", intent);
738 return false;
739 }
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800740 }
741}