blob: 86cd367332db7673a7fbd7c0c3d3d079981638d7 [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
141 public void activate() {
142 appId = coreService.registerApplication("org.onosproject.cordmcast");
Jonathan Hart28271642016-02-10 16:13:54 -0800143 componentConfigService.registerProperties(getClass());
alshabib3b1eadc2016-02-01 17:57:00 -0800144 mcastService.addListener(listener);
Jonathan Hart28271642016-02-10 16:13:54 -0800145
146 fabricOnosUrl = "http://" + syncHost + "/onos/v1/mcast";
147
alshabib3b1eadc2016-02-01 17:57:00 -0800148 //TODO: obtain all existing mcast routes
149 log.info("Started");
150 }
151
152 @Deactivate
153 public void deactivate() {
Jonathan Hart28271642016-02-10 16:13:54 -0800154 componentConfigService.unregisterProperties(getClass(), true);
alshabib3b1eadc2016-02-01 17:57:00 -0800155 mcastService.removeListener(listener);
156 log.info("Stopped");
157 }
158
Jonathan Hart28271642016-02-10 16:13:54 -0800159 @Modified
160 public void modified(ComponentContext context) {
alshabibfc1cb032016-02-17 15:37:56 -0800161 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
162
163
164 try {
165 String s = get(properties, "username");
166 user = isNullOrEmpty(s) ? DEFAULT_USER : s.trim();
167
168 s = get(properties, "password");
169 password = isNullOrEmpty(s) ? DEFAULT_PASSWORD : s.trim();
170
171 s = get(properties, "mcastVlan");
172 mcastVlan = isNullOrEmpty(s) ? DEFAULT_MCAST_VLAN : Short.parseShort(s.trim());
173
174 s = get(properties, "vlanEnabled");
175 vlanEnabled = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
176
177 s = get(properties, "priority");
178 priority = isNullOrEmpty(s) ? DEFAULT_PRIORITY : Integer.parseInt(s.trim());
179
180 s = get(properties, syncHost);
181 syncHost = isNullOrEmpty(s) ? DEFAULT_SYNC_HOST : s.trim();
182 } catch (Exception e) {
183 user = DEFAULT_USER;
184 password = DEFAULT_PASSWORD;
185 syncHost = DEFAULT_SYNC_HOST;
186 mcastVlan = DEFAULT_MCAST_VLAN;
187 vlanEnabled = false;
188 priority = DEFAULT_PRIORITY;
189 }
190
191
Jonathan Hart28271642016-02-10 16:13:54 -0800192 }
193
alshabib3b1eadc2016-02-01 17:57:00 -0800194 private class InternalMulticastListener implements McastListener {
195 @Override
196 public void event(McastEvent event) {
197 switch (event.type()) {
198 case ROUTE_ADDED:
199 break;
200 case ROUTE_REMOVED:
201 break;
202 case SOURCE_ADDED:
203 break;
204 case SINK_ADDED:
205 provisionGroup(event.subject());
206 break;
207 case SINK_REMOVED:
alshabibfc1cb032016-02-17 15:37:56 -0800208 unprovisionGroup(event.subject());
alshabib3b1eadc2016-02-01 17:57:00 -0800209 break;
210 default:
211 log.warn("Unknown mcast event {}", event.type());
212 }
213 }
214 }
215
alshabibfc1cb032016-02-17 15:37:56 -0800216 private void unprovisionGroup(McastRouteInfo info) {
Jonathan Hart718c0452016-02-18 15:56:22 -0800217 if (info.sinks().isEmpty()) {
218 removeSyncedRoute(info);
219 }
220
alshabibfc1cb032016-02-17 15:37:56 -0800221 if (!info.sink().isPresent()) {
222 log.warn("No sink given after sink removed event: {}", info);
223 return;
224 }
225 ConnectPoint loc = info.sink().get();
226
227 NextObjective next = DefaultNextObjective.builder()
228 .fromApp(appId)
229 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
230 .withType(NextObjective.Type.BROADCAST)
231 .withId(groups.get(info.route().group()))
232 .removeFromExisting(new ObjectiveContext() {
233 @Override
234 public void onSuccess(Objective objective) {
235 //TODO: change to debug
236 log.info("Next Objective {} installed", objective.id());
237 }
238
239 @Override
240 public void onError(Objective objective, ObjectiveError error) {
241 //TODO: change to debug
242 log.info("Next Objective {} failed, because {}",
243 objective.id(),
244 error);
245 }
246 });
247
248 flowObjectiveService.next(loc.deviceId(), next);
alshabibfc1cb032016-02-17 15:37:56 -0800249 }
250
alshabib3b1eadc2016-02-01 17:57:00 -0800251 private void provisionGroup(McastRouteInfo info) {
252 if (!info.sink().isPresent()) {
253 log.warn("No sink given after sink added event: {}", info);
254 return;
255 }
256 ConnectPoint loc = info.sink().get();
257
Jonathan Hart28271642016-02-10 16:13:54 -0800258 final AtomicBoolean sync = new AtomicBoolean(false);
alshabib3b1eadc2016-02-01 17:57:00 -0800259
260 Integer nextId = groups.computeIfAbsent(info.route().group(), (g) -> {
Jonathan Hart28271642016-02-10 16:13:54 -0800261 Integer id = allocateId();
alshabib3b1eadc2016-02-01 17:57:00 -0800262
alshabibfc1cb032016-02-17 15:37:56 -0800263 NextObjective next = DefaultNextObjective.builder()
264 .fromApp(appId)
265 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
266 .withType(NextObjective.Type.BROADCAST)
267 .withId(id)
268 .add(new ObjectiveContext() {
269 @Override
270 public void onSuccess(Objective objective) {
271 //TODO: change to debug
272 log.info("Next Objective {} installed", objective.id());
273 }
274
275 @Override
276 public void onError(Objective objective, ObjectiveError error) {
277 //TODO: change to debug
278 log.info("Next Objective {} failed, because {}",
279 objective.id(),
280 error);
281 }
282 });
283
284 flowObjectiveService.next(loc.deviceId(), next);
285
286 TrafficSelector.Builder mcast = DefaultTrafficSelector.builder()
alshabib3b1eadc2016-02-01 17:57:00 -0800287 .matchEthType(Ethernet.TYPE_IPV4)
alshabibfc1cb032016-02-17 15:37:56 -0800288 .matchIPDst(g.toIpPrefix());
289
290
291 if (vlanEnabled) {
292 mcast.matchVlanId(VlanId.vlanId((short) mcastVlan));
293 }
alshabib3b1eadc2016-02-01 17:57:00 -0800294
295
296 ForwardingObjective fwd = DefaultForwardingObjective.builder()
297 .fromApp(appId)
298 .nextStep(id)
299 .makePermanent()
300 .withFlag(ForwardingObjective.Flag.VERSATILE)
301 .withPriority(priority)
alshabibfc1cb032016-02-17 15:37:56 -0800302 .withSelector(mcast.build())
alshabib3b1eadc2016-02-01 17:57:00 -0800303 .add(new ObjectiveContext() {
304 @Override
305 public void onSuccess(Objective objective) {
306 //TODO: change to debug
307 log.info("Forwarding objective installed {}", objective);
308 }
309
310 @Override
311 public void onError(Objective objective, ObjectiveError error) {
312 //TODO: change to debug
313 log.info("Forwarding objective failed {}", objective);
314 }
315 });
316
317 flowObjectiveService.forward(loc.deviceId(), fwd);
318
Jonathan Hart28271642016-02-10 16:13:54 -0800319 sync.set(true);
320
alshabib3b1eadc2016-02-01 17:57:00 -0800321 return id;
322 });
323
alshabibfc1cb032016-02-17 15:37:56 -0800324 if (!sync.get()) {
325 NextObjective next = DefaultNextObjective.builder()
326 .fromApp(appId)
327 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
328 .withType(NextObjective.Type.BROADCAST)
329 .withId(nextId)
330 .addToExisting(new ObjectiveContext() {
331 @Override
332 public void onSuccess(Objective objective) {
333 //TODO: change to debug
334 log.info("Next Objective {} installed", objective.id());
335 }
alshabib3b1eadc2016-02-01 17:57:00 -0800336
alshabibfc1cb032016-02-17 15:37:56 -0800337 @Override
338 public void onError(Objective objective, ObjectiveError error) {
339 //TODO: change to debug
340 log.info("Next Objective {} failed, because {}",
341 objective.id(),
342 error);
343 }
344 });
alshabib3b1eadc2016-02-01 17:57:00 -0800345
alshabibfc1cb032016-02-17 15:37:56 -0800346 flowObjectiveService.next(loc.deviceId(), next);
347 }
Jonathan Hart28271642016-02-10 16:13:54 -0800348
349 if (sync.get()) {
350 syncRoute(info);
351 }
alshabib3b1eadc2016-02-01 17:57:00 -0800352 }
353
Jonathan Hart28271642016-02-10 16:13:54 -0800354 private void syncRoute(McastRouteInfo info) {
355 if (syncHost == null) {
356 log.warn("No host configured for synchronization; route will be dropped");
357 return;
358 }
359
360 log.debug("Sending route to other ONOS: {}", info.route());
361
362 WebResource.Builder builder = getClientBuilder(fabricOnosUrl);
363
364 ObjectNode json = codecService.getCodec(McastRoute.class)
365 .encode(info.route(), new AbstractWebResource());
366 builder.post(json.toString());
alshabib3b1eadc2016-02-01 17:57:00 -0800367 }
Jonathan Hart28271642016-02-10 16:13:54 -0800368
Jonathan Hart718c0452016-02-18 15:56:22 -0800369 private void removeSyncedRoute(McastRouteInfo info) {
370 if (syncHost == null) {
371 log.warn("No host configured for synchronization; route will be dropped");
372 return;
373 }
374
375 log.debug("Removing route from other ONOS: {}", info.route());
376
377 WebResource.Builder builder = getClientBuilder(fabricOnosUrl);
378
379 ObjectNode json = codecService.getCodec(McastRoute.class)
380 .encode(info.route(), new AbstractWebResource());
381 builder.delete(json.toString());
382 }
383
Jonathan Hart28271642016-02-10 16:13:54 -0800384 private Integer allocateId() {
385 return channels.getAndIncrement();
386 }
387
388 private WebResource.Builder getClientBuilder(String uri) {
389 Client client = Client.create();
390 client.addFilter(new HTTPBasicAuthFilter(user, password));
391 WebResource resource = client.resource(uri);
392 return resource.accept(JSON_UTF_8.toString())
393 .type(JSON_UTF_8.toString());
394 }
395
alshabib3b1eadc2016-02-01 17:57:00 -0800396}