blob: 2b140a8f4219b88cf86f8748d3d223f4bce9e4c6 [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) {
217 if (!info.sink().isPresent()) {
218 log.warn("No sink given after sink removed event: {}", info);
219 return;
220 }
221 ConnectPoint loc = info.sink().get();
222
223 NextObjective next = DefaultNextObjective.builder()
224 .fromApp(appId)
225 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
226 .withType(NextObjective.Type.BROADCAST)
227 .withId(groups.get(info.route().group()))
228 .removeFromExisting(new ObjectiveContext() {
229 @Override
230 public void onSuccess(Objective objective) {
231 //TODO: change to debug
232 log.info("Next Objective {} installed", objective.id());
233 }
234
235 @Override
236 public void onError(Objective objective, ObjectiveError error) {
237 //TODO: change to debug
238 log.info("Next Objective {} failed, because {}",
239 objective.id(),
240 error);
241 }
242 });
243
244 flowObjectiveService.next(loc.deviceId(), next);
245
246
247 }
248
alshabib3b1eadc2016-02-01 17:57:00 -0800249 private void provisionGroup(McastRouteInfo info) {
250 if (!info.sink().isPresent()) {
251 log.warn("No sink given after sink added event: {}", info);
252 return;
253 }
254 ConnectPoint loc = info.sink().get();
255
Jonathan Hart28271642016-02-10 16:13:54 -0800256 final AtomicBoolean sync = new AtomicBoolean(false);
alshabib3b1eadc2016-02-01 17:57:00 -0800257
258 Integer nextId = groups.computeIfAbsent(info.route().group(), (g) -> {
Jonathan Hart28271642016-02-10 16:13:54 -0800259 Integer id = allocateId();
alshabib3b1eadc2016-02-01 17:57:00 -0800260
alshabibfc1cb032016-02-17 15:37:56 -0800261 NextObjective next = DefaultNextObjective.builder()
262 .fromApp(appId)
263 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
264 .withType(NextObjective.Type.BROADCAST)
265 .withId(id)
266 .add(new ObjectiveContext() {
267 @Override
268 public void onSuccess(Objective objective) {
269 //TODO: change to debug
270 log.info("Next Objective {} installed", objective.id());
271 }
272
273 @Override
274 public void onError(Objective objective, ObjectiveError error) {
275 //TODO: change to debug
276 log.info("Next Objective {} failed, because {}",
277 objective.id(),
278 error);
279 }
280 });
281
282 flowObjectiveService.next(loc.deviceId(), next);
283
284 TrafficSelector.Builder mcast = DefaultTrafficSelector.builder()
alshabib3b1eadc2016-02-01 17:57:00 -0800285 .matchEthType(Ethernet.TYPE_IPV4)
alshabibfc1cb032016-02-17 15:37:56 -0800286 .matchIPDst(g.toIpPrefix());
287
288
289 if (vlanEnabled) {
290 mcast.matchVlanId(VlanId.vlanId((short) mcastVlan));
291 }
alshabib3b1eadc2016-02-01 17:57:00 -0800292
293
294 ForwardingObjective fwd = DefaultForwardingObjective.builder()
295 .fromApp(appId)
296 .nextStep(id)
297 .makePermanent()
298 .withFlag(ForwardingObjective.Flag.VERSATILE)
299 .withPriority(priority)
alshabibfc1cb032016-02-17 15:37:56 -0800300 .withSelector(mcast.build())
alshabib3b1eadc2016-02-01 17:57:00 -0800301 .add(new ObjectiveContext() {
302 @Override
303 public void onSuccess(Objective objective) {
304 //TODO: change to debug
305 log.info("Forwarding objective installed {}", objective);
306 }
307
308 @Override
309 public void onError(Objective objective, ObjectiveError error) {
310 //TODO: change to debug
311 log.info("Forwarding objective failed {}", objective);
312 }
313 });
314
315 flowObjectiveService.forward(loc.deviceId(), fwd);
316
Jonathan Hart28271642016-02-10 16:13:54 -0800317 sync.set(true);
318
alshabib3b1eadc2016-02-01 17:57:00 -0800319 return id;
320 });
321
alshabibfc1cb032016-02-17 15:37:56 -0800322 if (!sync.get()) {
323 NextObjective next = DefaultNextObjective.builder()
324 .fromApp(appId)
325 .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
326 .withType(NextObjective.Type.BROADCAST)
327 .withId(nextId)
328 .addToExisting(new ObjectiveContext() {
329 @Override
330 public void onSuccess(Objective objective) {
331 //TODO: change to debug
332 log.info("Next Objective {} installed", objective.id());
333 }
alshabib3b1eadc2016-02-01 17:57:00 -0800334
alshabibfc1cb032016-02-17 15:37:56 -0800335 @Override
336 public void onError(Objective objective, ObjectiveError error) {
337 //TODO: change to debug
338 log.info("Next Objective {} failed, because {}",
339 objective.id(),
340 error);
341 }
342 });
alshabib3b1eadc2016-02-01 17:57:00 -0800343
alshabibfc1cb032016-02-17 15:37:56 -0800344 flowObjectiveService.next(loc.deviceId(), next);
345 }
Jonathan Hart28271642016-02-10 16:13:54 -0800346
347 if (sync.get()) {
348 syncRoute(info);
349 }
alshabib3b1eadc2016-02-01 17:57:00 -0800350 }
351
Jonathan Hart28271642016-02-10 16:13:54 -0800352 private void syncRoute(McastRouteInfo info) {
353 if (syncHost == null) {
354 log.warn("No host configured for synchronization; route will be dropped");
355 return;
356 }
357
358 log.debug("Sending route to other ONOS: {}", info.route());
359
360 WebResource.Builder builder = getClientBuilder(fabricOnosUrl);
361
362 ObjectNode json = codecService.getCodec(McastRoute.class)
363 .encode(info.route(), new AbstractWebResource());
364 builder.post(json.toString());
alshabib3b1eadc2016-02-01 17:57:00 -0800365 }
Jonathan Hart28271642016-02-10 16:13:54 -0800366
367 private Integer allocateId() {
368 return channels.getAndIncrement();
369 }
370
371 private WebResource.Builder getClientBuilder(String uri) {
372 Client client = Client.create();
373 client.addFilter(new HTTPBasicAuthFilter(user, password));
374 WebResource resource = client.resource(uri);
375 return resource.accept(JSON_UTF_8.toString())
376 .type(JSON_UTF_8.toString());
377 }
378
alshabib3b1eadc2016-02-01 17:57:00 -0800379}