blob: bf7f1413a7ae98e22578a13dd8d3287acd3373c1 [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 */
16package org.onlab.onos.sdnip;
17
18import java.util.Collection;
19import java.util.HashMap;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23import java.util.concurrent.ConcurrentHashMap;
24import java.util.concurrent.ExecutorService;
25import java.util.concurrent.Executors;
26import java.util.concurrent.Semaphore;
27
28import org.apache.commons.lang3.tuple.Pair;
29import org.onlab.onos.core.ApplicationId;
30import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
31import org.onlab.onos.net.flow.criteria.Criterion;
32import org.onlab.onos.net.intent.Intent;
33import org.onlab.onos.net.intent.IntentService;
34import org.onlab.onos.net.intent.IntentState;
35import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
36import org.onlab.packet.Ip4Prefix;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39
40import com.google.common.base.Objects;
41import com.google.common.util.concurrent.ThreadFactoryBuilder;
42
43public class IntentSynchronizer {
44 private static final Logger log =
45 LoggerFactory.getLogger(IntentSynchronizer.class);
46
47 private final ApplicationId appId;
48 private final IntentService intentService;
49 private final Map<Ip4Prefix, MultiPointToSinglePointIntent> pushedRouteIntents;
50
51 //
52 // State to deal with SDN-IP Leader election and pushing Intents
53 //
54 private final ExecutorService bgpIntentsSynchronizerExecutor;
55 private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
56 private volatile boolean isElectedLeader = false;
57 private volatile boolean isActivatedLeader = false;
58
59 /**
60 * Class constructor.
61 *
62 * @param appId the Application ID
63 * @param intentService the intent service
64 */
65 IntentSynchronizer(ApplicationId appId, IntentService intentService) {
66 this.appId = appId;
67 this.intentService = intentService;
68 pushedRouteIntents = new ConcurrentHashMap<>();
69
70 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
71 new ThreadFactoryBuilder()
72 .setNameFormat("bgp-intents-synchronizer-%d").build());
73 }
74
75 /**
76 * Starts the synchronizer.
77 */
78 public void start() {
79 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
80 @Override
81 public void run() {
82 doIntentSynchronizationThread();
83 }
84 });
85 }
86
87 /**
88 * Stops the synchronizer.
89 */
90 public void stop() {
91 // Stop the thread(s)
92 bgpIntentsSynchronizerExecutor.shutdownNow();
93
94 //
95 // Withdraw all SDN-IP intents
96 //
97 if (!isElectedLeader) {
98 return; // Nothing to do: not the leader anymore
99 }
100 log.debug("Withdrawing all SDN-IP Route Intents...");
101 for (Intent intent : intentService.getIntents()) {
102 if (!(intent instanceof MultiPointToSinglePointIntent)
103 || !intent.appId().equals(appId)) {
104 continue;
105 }
106 intentService.withdraw(intent);
107 }
108
109 pushedRouteIntents.clear();
110 }
111
112 //@Override TODO hook this up to something
113 public void leaderChanged(boolean isLeader) {
114 log.debug("Leader changed: {}", isLeader);
115
116 if (!isLeader) {
117 this.isElectedLeader = false;
118 this.isActivatedLeader = false;
119 return; // Nothing to do
120 }
121 this.isActivatedLeader = false;
122 this.isElectedLeader = true;
123
124 //
125 // Tell the Intents Synchronizer thread to start the synchronization
126 //
127 intentsSynchronizerSemaphore.release();
128 }
129
130 /**
131 * Gets the pushed route intents.
132 *
133 * @return the pushed route intents
134 */
135 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
136 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
137
138 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
139 pushedRouteIntents.entrySet()) {
140 pushedIntents.add(entry.getValue());
141 }
142 return pushedIntents;
143 }
144
145 /**
146 * Thread for Intent Synchronization.
147 */
148 private void doIntentSynchronizationThread() {
149 boolean interrupted = false;
150 try {
151 while (!interrupted) {
152 try {
153 intentsSynchronizerSemaphore.acquire();
154 //
155 // Drain all permits, because a single synchronization is
156 // sufficient.
157 //
158 intentsSynchronizerSemaphore.drainPermits();
159 } catch (InterruptedException e) {
160 log.debug("Interrupted while waiting to become " +
161 "Intent Synchronization leader");
162 interrupted = true;
163 break;
164 }
165 syncIntents();
166 }
167 } finally {
168 if (interrupted) {
169 Thread.currentThread().interrupt();
170 }
171 }
172 }
173
174 /**
175 * Submits a multi-point-to-single-point intent.
176 *
177 * @param prefix the IPv4 matching prefix for the intent to submit
178 * @param intent the intent to submit
179 */
180 void submitRouteIntent(Ip4Prefix prefix,
181 MultiPointToSinglePointIntent intent) {
182 synchronized (this) {
183 if (isElectedLeader && isActivatedLeader) {
184 log.debug("Intent installation: adding Intent for prefix: {}",
185 prefix);
186 intentService.submit(intent);
187 }
188
189 // Maintain the Intent
190 pushedRouteIntents.put(prefix, intent);
191 }
192 }
193
194 /**
195 * Withdraws a multi-point-to-single-point intent.
196 *
197 * @param prefix the IPv4 matching prefix for the intent to withdraw.
198 */
199 void withdrawRouteIntent(Ip4Prefix prefix) {
200 synchronized (this) {
201 MultiPointToSinglePointIntent intent =
202 pushedRouteIntents.remove(prefix);
203
204 if (intent == null) {
205 log.debug("There is no intent in pushedRouteIntents to delete " +
206 "for prefix: {}", prefix);
207 return;
208 }
209
210 if (isElectedLeader && isActivatedLeader) {
211 log.debug("Intent installation: deleting Intent for prefix: {}",
212 prefix);
213 intentService.withdraw(intent);
214 }
215 }
216 }
217
218 /**
219 * Performs Intents Synchronization between the internally stored Route
220 * Intents and the installed Route Intents.
221 */
222 private void syncIntents() {
223 synchronized (this) {
224 if (!isElectedLeader) {
225 return; // Nothing to do: not the leader anymore
226 }
227 log.debug("Syncing SDN-IP Route Intents...");
228
229 Map<Ip4Prefix, MultiPointToSinglePointIntent> fetchedIntents =
230 new HashMap<>();
231
232 //
233 // Fetch all intents, and classify the Multi-Point-to-Point Intents
234 // based on the matching prefix.
235 //
236 for (Intent intent : intentService.getIntents()) {
237
238 if (!(intent instanceof MultiPointToSinglePointIntent)
239 || !intent.appId().equals(appId)) {
240 continue;
241 }
242 MultiPointToSinglePointIntent mp2pIntent =
243 (MultiPointToSinglePointIntent) intent;
244
245 Criterion c =
246 mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
247 if (c != null && c instanceof IPCriterion) {
248 IPCriterion ipCriterion = (IPCriterion) c;
249 Ip4Prefix ip4Prefix = ipCriterion.ip().getIp4Prefix();
250 if (ip4Prefix == null) {
251 // TODO: For now we support only IPv4
252 continue;
253 }
254 fetchedIntents.put(ip4Prefix, mp2pIntent);
255 } else {
256 log.warn("No IPV4_DST criterion found for intent {}",
257 mp2pIntent.id());
258 }
259
260 }
261
262 //
263 // Compare for each prefix the local IN-MEMORY Intents with the
264 // FETCHED Intents:
265 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
266 // the FETCHED Intent in the local memory (i.e., override the
267 // IN-MEMORY Intent) to preserve the original Intent ID
268 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
269 // delete the FETCHED Intent, and push/install the IN-MEMORY
270 // Intent.
271 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
272 // Intent for same prefix, then push/install the IN-MEMORY
273 // Intent.
274 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
275 // Intent for same prefix, then delete/withdraw the FETCHED
276 // Intent.
277 //
278 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
279 storeInMemoryIntents = new LinkedList<>();
280 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
281 addIntents = new LinkedList<>();
282 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
283 deleteIntents = new LinkedList<>();
284 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
285 pushedRouteIntents.entrySet()) {
286 Ip4Prefix prefix = entry.getKey();
287 MultiPointToSinglePointIntent inMemoryIntent =
288 entry.getValue();
289 MultiPointToSinglePointIntent fetchedIntent =
290 fetchedIntents.get(prefix);
291
292 if (fetchedIntent == null) {
293 //
294 // No FETCHED Intent for same prefix: push the IN-MEMORY
295 // Intent.
296 //
297 addIntents.add(Pair.of(prefix, inMemoryIntent));
298 continue;
299 }
300
301 IntentState state = intentService.getIntentState(fetchedIntent.id());
302 if (state == IntentState.WITHDRAWING ||
303 state == IntentState.WITHDRAWN) {
304 // The intent has been withdrawn but according to our route
305 // table it should be installed. We'll reinstall it.
306 addIntents.add(Pair.of(prefix, inMemoryIntent));
307 }
308
309 //
310 // If IN-MEMORY Intent is same as the FETCHED Intent,
311 // store the FETCHED Intent in the local memory.
312 //
313 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
314 fetchedIntent)) {
315 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
316 } else {
317 //
318 // The IN-MEMORY Intent is not same as the FETCHED Intent,
319 // hence delete the FETCHED Intent, and install the
320 // IN-MEMORY Intent.
321 //
322 deleteIntents.add(Pair.of(prefix, fetchedIntent));
323 addIntents.add(Pair.of(prefix, inMemoryIntent));
324 }
325 fetchedIntents.remove(prefix);
326 }
327
328 //
329 // Any remaining FETCHED Intents have to be deleted/withdrawn
330 //
331 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
332 fetchedIntents.entrySet()) {
333 Ip4Prefix prefix = entry.getKey();
334 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
335 deleteIntents.add(Pair.of(prefix, fetchedIntent));
336 }
337
338 //
339 // Perform the actions:
340 // 1. Store in memory fetched intents that are same. Can be done
341 // even if we are not the leader anymore
342 // 2. Delete intents: check if the leader before each operation
343 // 3. Add intents: check if the leader before each operation
344 //
345 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
346 storeInMemoryIntents) {
347 Ip4Prefix prefix = pair.getLeft();
348 MultiPointToSinglePointIntent intent = pair.getRight();
349 log.debug("Intent synchronization: updating in-memory " +
350 "Intent for prefix: {}", prefix);
351 pushedRouteIntents.put(prefix, intent);
352 }
353 //
354 isActivatedLeader = true; // Allow push of Intents
355 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
356 deleteIntents) {
357 Ip4Prefix prefix = pair.getLeft();
358 MultiPointToSinglePointIntent intent = pair.getRight();
359 if (!isElectedLeader) {
360 isActivatedLeader = false;
361 return;
362 }
363 log.debug("Intent synchronization: deleting Intent for " +
364 "prefix: {}", prefix);
365 intentService.withdraw(intent);
366 }
367 //
368 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
369 addIntents) {
370 Ip4Prefix prefix = pair.getLeft();
371 MultiPointToSinglePointIntent intent = pair.getRight();
372 if (!isElectedLeader) {
373 isActivatedLeader = false;
374 return;
375 }
376 log.debug("Intent synchronization: adding Intent for " +
377 "prefix: {}", prefix);
378 intentService.submit(intent);
379 }
380 if (!isElectedLeader) {
381 isActivatedLeader = false;
382 }
383 log.debug("Syncing SDN-IP routes completed.");
384 }
385 }
386
387 /**
388 * Compares two Multi-point to Single Point Intents whether they represent
389 * same logical intention.
390 *
391 * @param intent1 the first Intent to compare
392 * @param intent2 the second Intent to compare
393 * @return true if both Intents represent same logical intention, otherwise
394 * false
395 */
396 private boolean compareMultiPointToSinglePointIntents(
397 MultiPointToSinglePointIntent intent1,
398 MultiPointToSinglePointIntent intent2) {
399
400 return Objects.equal(intent1.appId(), intent2.appId()) &&
401 Objects.equal(intent1.selector(), intent2.selector()) &&
402 Objects.equal(intent1.treatment(), intent2.treatment()) &&
403 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
404 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
405 }
406}