blob: 8fd533a9c6eb54a6d349238a650d3f923aac363e [file] [log] [blame]
alshabib3b1eadc2016-02-01 17:57:00 -08001/*
2 * Copyright 2015-2016 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.onosproject.cordmcast;
17
Jonathan Hart28271642016-02-10 16:13:54 -080018import com.fasterxml.jackson.databind.node.ObjectNode;
alshabib3b1eadc2016-02-01 17:57:00 -080019import com.google.common.collect.Maps;
Jonathan Hart28271642016-02-10 16:13:54 -080020import com.sun.jersey.api.client.Client;
21import com.sun.jersey.api.client.WebResource;
22import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
alshabib3b1eadc2016-02-01 17:57:00 -080023import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
Jonathan Hart28271642016-02-10 16:13:54 -080026import org.apache.felix.scr.annotations.Modified;
27import org.apache.felix.scr.annotations.Property;
alshabib3b1eadc2016-02-01 17:57:00 -080028import org.apache.felix.scr.annotations.Reference;
29import org.apache.felix.scr.annotations.ReferenceCardinality;
30import org.onlab.packet.Ethernet;
alshabib3b1eadc2016-02-01 17:57:00 -080031import org.onlab.packet.IpAddress;
32import org.onlab.packet.VlanId;
Jonathan Hart28271642016-02-10 16:13:54 -080033import org.onosproject.cfg.ComponentConfigService;
34import org.onosproject.codec.CodecService;
alshabib3b1eadc2016-02-01 17:57:00 -080035import org.onosproject.core.ApplicationId;
36import org.onosproject.core.CoreService;
37import 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.flowobjective.DefaultForwardingObjective;
42import org.onosproject.net.flowobjective.DefaultNextObjective;
43import org.onosproject.net.flowobjective.FlowObjectiveService;
44import org.onosproject.net.flowobjective.ForwardingObjective;
45import org.onosproject.net.flowobjective.NextObjective;
46import org.onosproject.net.flowobjective.Objective;
47import org.onosproject.net.flowobjective.ObjectiveContext;
48import org.onosproject.net.flowobjective.ObjectiveError;
49import org.onosproject.net.group.GroupService;
50import org.onosproject.net.mcast.McastEvent;
51import org.onosproject.net.mcast.McastListener;
Jonathan Hart28271642016-02-10 16:13:54 -080052import org.onosproject.net.mcast.McastRoute;
alshabib3b1eadc2016-02-01 17:57:00 -080053import org.onosproject.net.mcast.McastRouteInfo;
54import org.onosproject.net.mcast.MulticastRouteService;
Jonathan Hart28271642016-02-10 16:13:54 -080055import org.onosproject.rest.AbstractWebResource;
56import org.osgi.service.component.ComponentContext;
alshabib3b1eadc2016-02-01 17:57:00 -080057import org.slf4j.Logger;
58
Jonathan Hart28271642016-02-10 16:13:54 -080059import java.util.Dictionary;
alshabib3b1eadc2016-02-01 17:57:00 -080060import java.util.Map;
alshabibfc1cb032016-02-17 15:37:56 -080061import java.util.Properties;
Jonathan Hart28271642016-02-10 16:13:54 -080062import java.util.concurrent.atomic.AtomicBoolean;
alshabib3b1eadc2016-02-01 17:57:00 -080063import java.util.concurrent.atomic.AtomicInteger;
64
alshabibfc1cb032016-02-17 15:37:56 -080065import static com.google.common.base.Strings.isNullOrEmpty;
Jonathan Hart28271642016-02-10 16:13:54 -080066import static com.google.common.net.MediaType.JSON_UTF_8;
alshabibfc1cb032016-02-17 15:37:56 -080067import static org.onlab.util.Tools.get;
alshabib3b1eadc2016-02-01 17:57:00 -080068import static org.slf4j.LoggerFactory.getLogger;
69
70/**
71 * CORD multicast provisoning application. Operates by listening to
Jonathan Hart28271642016-02-10 16:13:54 -080072 * events on the multicast rib and provisioning groups to program multicast
alshabib3b1eadc2016-02-01 17:57:00 -080073 * flows on the dataplane.
74 */
75@Component(immediate = true)
76public class CordMcast {
77
78 private static final int DEFAULT_PRIORITY = 1000;
79 private static final short DEFAULT_MCAST_VLAN = 4000;
alshabibfc1cb032016-02-17 15:37:56 -080080 private static final String DEFAULT_SYNC_HOST = "10.90.0.8:8181";
81 private static final String DEFAULT_USER = "karaf";
82 private static final String DEFAULT_PASSWORD = "karaf";
83
alshabib3b1eadc2016-02-01 17:57:00 -080084 private final Logger log = getLogger(getClass());
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected MulticastRouteService mcastService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected GroupService groupService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected FlowObjectiveService flowObjectiveService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected CoreService coreService;
97
Jonathan Hart28271642016-02-10 16:13:54 -080098 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected CodecService codecService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected ComponentConfigService componentConfigService;
103
alshabib3b1eadc2016-02-01 17:57:00 -0800104 protected McastListener listener = new InternalMulticastListener();
105
alshabib3b1eadc2016-02-01 17:57:00 -0800106 //TODO: move this to a ec map
107 private Map<IpAddress, Integer> groups = Maps.newConcurrentMap();
108
109 //TODO: move this to distributed atomic long
110 private AtomicInteger channels = new AtomicInteger(0);
111
112 private ApplicationId appId;
113
alshabibfc1cb032016-02-17 15:37:56 -0800114 @Property(name = "mcastVlan", intValue = DEFAULT_MCAST_VLAN,
115 label = "VLAN for multicast traffic")
116 private int mcastVlan = DEFAULT_MCAST_VLAN;
alshabib3b1eadc2016-02-01 17:57:00 -0800117
alshabibfc1cb032016-02-17 15:37:56 -0800118 @Property(name = "vlanEnabled", boolValue = false,
119 label = "Use vlan for multicast traffic")
120 private boolean vlanEnabled = false;
121
122 @Property(name = "priority", intValue = DEFAULT_PRIORITY,
123 label = "Priority for multicast rules")
alshabib3b1eadc2016-02-01 17:57:00 -0800124 private int priority = DEFAULT_PRIORITY;
125
alshabibfc1cb032016-02-17 15:37:56 -0800126 @Property(name = "syncHost", value = DEFAULT_SYNC_HOST,
Jonathan Hart28271642016-02-10 16:13:54 -0800127 label = "host:port to synchronize routes to")
alshabibfc1cb032016-02-17 15:37:56 -0800128 private String syncHost = DEFAULT_SYNC_HOST;
Jonathan Hart28271642016-02-10 16:13:54 -0800129
130 @Property(name = "username", value = DEFAULT_USER,
131 label = "Username for REST password authentication")
132 private String user = DEFAULT_USER;
133
134 @Property(name = "password", value = DEFAULT_PASSWORD,
135 label = "Password for REST authentication")
136 private String password = DEFAULT_PASSWORD;
137
138 private String fabricOnosUrl;
139
alshabib3b1eadc2016-02-01 17:57:00 -0800140 @Activate
Jonathan Hart435ffc42016-02-19 10:32:05 -0800141 public void activate(ComponentContext context) {
142 modified(context);
143
alshabib3b1eadc2016-02-01 17:57:00 -0800144 appId = coreService.registerApplication("org.onosproject.cordmcast");
Jonathan Hart28271642016-02-10 16:13:54 -0800145 componentConfigService.registerProperties(getClass());
alshabib3b1eadc2016-02-01 17:57:00 -0800146 mcastService.addListener(listener);
Jonathan Hart28271642016-02-10 16:13:54 -0800147
148 fabricOnosUrl = "http://" + syncHost + "/onos/v1/mcast";
149
alshabib3b1eadc2016-02-01 17:57:00 -0800150 //TODO: obtain all existing mcast routes
151 log.info("Started");
152 }
153
154 @Deactivate
155 public void deactivate() {
Jonathan Hart28271642016-02-10 16:13:54 -0800156 componentConfigService.unregisterProperties(getClass(), true);
alshabib3b1eadc2016-02-01 17:57:00 -0800157 mcastService.removeListener(listener);
158 log.info("Stopped");
159 }
160
Jonathan Hart28271642016-02-10 16:13:54 -0800161 @Modified
162 public void modified(ComponentContext context) {
alshabibfc1cb032016-02-17 15:37:56 -0800163 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
164
165
166 try {
167 String s = get(properties, "username");
168 user = isNullOrEmpty(s) ? DEFAULT_USER : s.trim();
169
170 s = get(properties, "password");
171 password = isNullOrEmpty(s) ? DEFAULT_PASSWORD : s.trim();
172
173 s = get(properties, "mcastVlan");
174 mcastVlan = isNullOrEmpty(s) ? DEFAULT_MCAST_VLAN : Short.parseShort(s.trim());
175
176 s = get(properties, "vlanEnabled");
177 vlanEnabled = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
178
179 s = get(properties, "priority");
180 priority = isNullOrEmpty(s) ? DEFAULT_PRIORITY : Integer.parseInt(s.trim());
181
182 s = get(properties, syncHost);
183 syncHost = isNullOrEmpty(s) ? DEFAULT_SYNC_HOST : s.trim();
184 } catch (Exception e) {
185 user = DEFAULT_USER;
186 password = DEFAULT_PASSWORD;
187 syncHost = DEFAULT_SYNC_HOST;
188 mcastVlan = DEFAULT_MCAST_VLAN;
189 vlanEnabled = false;
190 priority = DEFAULT_PRIORITY;
191 }
192
193
Jonathan Hart28271642016-02-10 16:13:54 -0800194 }
195
alshabib3b1eadc2016-02-01 17:57:00 -0800196 private class InternalMulticastListener implements McastListener {
197 @Override
198 public void event(McastEvent event) {
199 switch (event.type()) {
200 case ROUTE_ADDED:
201 break;
202 case ROUTE_REMOVED:
203 break;
204 case SOURCE_ADDED:
205 break;
206 case SINK_ADDED:
207 provisionGroup(event.subject());
208 break;
209 case SINK_REMOVED:
alshabibfc1cb032016-02-17 15:37:56 -0800210 unprovisionGroup(event.subject());
alshabib3b1eadc2016-02-01 17:57:00 -0800211 break;
212 default:
213 log.warn("Unknown mcast event {}", event.type());
214 }
215 }
216 }
217
alshabibfc1cb032016-02-17 15:37:56 -0800218 private void unprovisionGroup(McastRouteInfo info) {
Jonathan Hart718c0452016-02-18 15:56:22 -0800219 if (info.sinks().isEmpty()) {
220 removeSyncedRoute(info);
221 }
222
alshabibfc1cb032016-02-17 15:37:56 -0800223 if (!info.sink().isPresent()) {
224 log.warn("No sink given after sink removed event: {}", info);
225 return;
226 }
227 ConnectPoint loc = info.sink().get();
228
229 NextObjective next = DefaultNextObjective.builder()
230 .fromApp(appId)
231 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
232 .withType(NextObjective.Type.BROADCAST)
233 .withId(groups.get(info.route().group()))
234 .removeFromExisting(new ObjectiveContext() {
235 @Override
236 public void onSuccess(Objective objective) {
237 //TODO: change to debug
238 log.info("Next Objective {} installed", objective.id());
239 }
240
241 @Override
242 public void onError(Objective objective, ObjectiveError error) {
243 //TODO: change to debug
244 log.info("Next Objective {} failed, because {}",
245 objective.id(),
246 error);
247 }
248 });
249
250 flowObjectiveService.next(loc.deviceId(), next);
alshabibfc1cb032016-02-17 15:37:56 -0800251 }
252
alshabib3b1eadc2016-02-01 17:57:00 -0800253 private void provisionGroup(McastRouteInfo info) {
254 if (!info.sink().isPresent()) {
255 log.warn("No sink given after sink added event: {}", info);
256 return;
257 }
258 ConnectPoint loc = info.sink().get();
259
Jonathan Hart28271642016-02-10 16:13:54 -0800260 final AtomicBoolean sync = new AtomicBoolean(false);
alshabib3b1eadc2016-02-01 17:57:00 -0800261
262 Integer nextId = groups.computeIfAbsent(info.route().group(), (g) -> {
Jonathan Hart28271642016-02-10 16:13:54 -0800263 Integer id = allocateId();
alshabib3b1eadc2016-02-01 17:57:00 -0800264
alshabibfc1cb032016-02-17 15:37:56 -0800265 NextObjective next = DefaultNextObjective.builder()
266 .fromApp(appId)
267 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
268 .withType(NextObjective.Type.BROADCAST)
269 .withId(id)
270 .add(new ObjectiveContext() {
271 @Override
272 public void onSuccess(Objective objective) {
273 //TODO: change to debug
274 log.info("Next Objective {} installed", objective.id());
275 }
276
277 @Override
278 public void onError(Objective objective, ObjectiveError error) {
279 //TODO: change to debug
280 log.info("Next Objective {} failed, because {}",
281 objective.id(),
282 error);
283 }
284 });
285
286 flowObjectiveService.next(loc.deviceId(), next);
287
288 TrafficSelector.Builder mcast = DefaultTrafficSelector.builder()
alshabib3b1eadc2016-02-01 17:57:00 -0800289 .matchEthType(Ethernet.TYPE_IPV4)
alshabibfc1cb032016-02-17 15:37:56 -0800290 .matchIPDst(g.toIpPrefix());
291
292
293 if (vlanEnabled) {
294 mcast.matchVlanId(VlanId.vlanId((short) mcastVlan));
295 }
alshabib3b1eadc2016-02-01 17:57:00 -0800296
297
298 ForwardingObjective fwd = DefaultForwardingObjective.builder()
299 .fromApp(appId)
300 .nextStep(id)
301 .makePermanent()
302 .withFlag(ForwardingObjective.Flag.VERSATILE)
303 .withPriority(priority)
alshabibfc1cb032016-02-17 15:37:56 -0800304 .withSelector(mcast.build())
alshabib3b1eadc2016-02-01 17:57:00 -0800305 .add(new ObjectiveContext() {
306 @Override
307 public void onSuccess(Objective objective) {
308 //TODO: change to debug
309 log.info("Forwarding objective installed {}", objective);
310 }
311
312 @Override
313 public void onError(Objective objective, ObjectiveError error) {
314 //TODO: change to debug
315 log.info("Forwarding objective failed {}", objective);
316 }
317 });
318
319 flowObjectiveService.forward(loc.deviceId(), fwd);
320
Jonathan Hart28271642016-02-10 16:13:54 -0800321 sync.set(true);
322
alshabib3b1eadc2016-02-01 17:57:00 -0800323 return id;
324 });
325
alshabibfc1cb032016-02-17 15:37:56 -0800326 if (!sync.get()) {
327 NextObjective next = DefaultNextObjective.builder()
328 .fromApp(appId)
329 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
330 .withType(NextObjective.Type.BROADCAST)
331 .withId(nextId)
332 .addToExisting(new ObjectiveContext() {
333 @Override
334 public void onSuccess(Objective objective) {
335 //TODO: change to debug
336 log.info("Next Objective {} installed", objective.id());
337 }
alshabib3b1eadc2016-02-01 17:57:00 -0800338
alshabibfc1cb032016-02-17 15:37:56 -0800339 @Override
340 public void onError(Objective objective, ObjectiveError error) {
341 //TODO: change to debug
342 log.info("Next Objective {} failed, because {}",
343 objective.id(),
344 error);
345 }
346 });
alshabib3b1eadc2016-02-01 17:57:00 -0800347
alshabibfc1cb032016-02-17 15:37:56 -0800348 flowObjectiveService.next(loc.deviceId(), next);
349 }
Jonathan Hart28271642016-02-10 16:13:54 -0800350
351 if (sync.get()) {
352 syncRoute(info);
353 }
alshabib3b1eadc2016-02-01 17:57:00 -0800354 }
355
Jonathan Hart28271642016-02-10 16:13:54 -0800356 private void syncRoute(McastRouteInfo info) {
357 if (syncHost == null) {
358 log.warn("No host configured for synchronization; route will be dropped");
359 return;
360 }
361
362 log.debug("Sending route to other ONOS: {}", info.route());
363
364 WebResource.Builder builder = getClientBuilder(fabricOnosUrl);
365
366 ObjectNode json = codecService.getCodec(McastRoute.class)
367 .encode(info.route(), new AbstractWebResource());
368 builder.post(json.toString());
alshabib3b1eadc2016-02-01 17:57:00 -0800369 }
Jonathan Hart28271642016-02-10 16:13:54 -0800370
Jonathan Hart718c0452016-02-18 15:56:22 -0800371 private void removeSyncedRoute(McastRouteInfo info) {
372 if (syncHost == null) {
373 log.warn("No host configured for synchronization; route will be dropped");
374 return;
375 }
376
377 log.debug("Removing route from other ONOS: {}", info.route());
378
379 WebResource.Builder builder = getClientBuilder(fabricOnosUrl);
380
381 ObjectNode json = codecService.getCodec(McastRoute.class)
382 .encode(info.route(), new AbstractWebResource());
383 builder.delete(json.toString());
384 }
385
Jonathan Hart28271642016-02-10 16:13:54 -0800386 private Integer allocateId() {
387 return channels.getAndIncrement();
388 }
389
390 private WebResource.Builder getClientBuilder(String uri) {
391 Client client = Client.create();
392 client.addFilter(new HTTPBasicAuthFilter(user, password));
393 WebResource resource = client.resource(uri);
394 return resource.accept(JSON_UTF_8.toString())
395 .type(JSON_UTF_8.toString());
396 }
397
alshabib3b1eadc2016-02-01 17:57:00 -0800398}