Java tutorial
/* * Copyright 2016-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.cordmcast; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Modified; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.onlab.packet.Ethernet; import org.onlab.packet.IpAddress; import org.onlab.packet.VlanId; import org.onosproject.cfg.ComponentConfigService; import org.onosproject.codec.CodecService; import org.onosproject.codec.JsonCodec; import org.onosproject.cordconfig.access.AccessAgentData; import org.onosproject.cordconfig.access.AccessDeviceData; import org.onosproject.cordconfig.access.CordConfigService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.net.ConnectPoint; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flowobjective.DefaultForwardingObjective; import org.onosproject.net.flowobjective.DefaultNextObjective; import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.flowobjective.ForwardingObjective; import org.onosproject.net.flowobjective.NextObjective; import org.onosproject.net.flowobjective.Objective; import org.onosproject.net.flowobjective.ObjectiveContext; import org.onosproject.net.flowobjective.ObjectiveError; import org.onosproject.net.mcast.McastEvent; import org.onosproject.net.mcast.McastListener; import org.onosproject.net.mcast.McastRoute; import org.onosproject.net.mcast.McastRouteInfo; import org.onosproject.net.mcast.MulticastRouteService; import org.onosproject.rest.AbstractWebResource; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.util.Dictionary; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.net.MediaType.JSON_UTF_8; import static org.onlab.util.Tools.get; import static org.slf4j.LoggerFactory.getLogger; /** * CORD multicast provisioning application. Operates by listening to * events on the multicast rib and provisioning groups to program multicast * flows on the dataplane. */ @Component(immediate = true) public class CordMcast { private final Logger log = getLogger(getClass()); private static final int DEFAULT_REST_TIMEOUT_MS = 1000; private static final int DEFAULT_PRIORITY = 500; private static final short DEFAULT_MCAST_VLAN = 4000; private static final String DEFAULT_SYNC_HOST = "10.90.0.8:8181"; private static final String DEFAULT_USER = "karaf"; private static final String DEFAULT_PASSWORD = "karaf"; private static final boolean DEFAULT_VLAN_ENABLED = true; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected MulticastRouteService mcastService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected FlowObjectiveService flowObjectiveService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CodecService codecService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ComponentConfigService componentConfigService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CordConfigService cordConfigService; protected McastListener listener = new InternalMulticastListener(); //TODO: move this to a ec map private Map<IpAddress, Integer> groups = Maps.newConcurrentMap(); private ApplicationId appId; @Property(name = "mcastVlan", intValue = DEFAULT_MCAST_VLAN, label = "VLAN for multicast traffic") private int mcastVlan = DEFAULT_MCAST_VLAN; @Property(name = "vlanEnabled", boolValue = DEFAULT_VLAN_ENABLED, label = "Use vlan for multicast traffic?") private boolean vlanEnabled = DEFAULT_VLAN_ENABLED; @Property(name = "priority", intValue = DEFAULT_PRIORITY, label = "Priority for multicast rules") private int priority = DEFAULT_PRIORITY; @Property(name = "syncHost", value = DEFAULT_SYNC_HOST, label = "host:port to synchronize routes to") private String syncHost = DEFAULT_SYNC_HOST; @Property(name = "username", value = DEFAULT_USER, label = "Username for REST password authentication") private String user = DEFAULT_USER; @Property(name = "password", value = DEFAULT_PASSWORD, label = "Password for REST authentication") private String password = DEFAULT_PASSWORD; private String fabricOnosUrl; @Activate public void activate(ComponentContext context) { componentConfigService.registerProperties(getClass()); modified(context); appId = coreService.registerApplication("org.onosproject.cordmcast"); clearRemoteRoutes(); mcastService.addListener(listener); mcastService.getRoutes().stream().map(r -> new ImmutablePair<>(r, mcastService.fetchSinks(r))) .filter(pair -> pair.getRight() != null && !pair.getRight().isEmpty()) .forEach(pair -> pair.getRight().forEach(sink -> provisionGroup(pair.getLeft(), sink))); log.info("Started"); } @Deactivate public void deactivate() { componentConfigService.unregisterProperties(getClass(), false); mcastService.removeListener(listener); log.info("Stopped"); } @Modified public void modified(ComponentContext context) { Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties(); try { String s = get(properties, "username"); user = isNullOrEmpty(s) ? DEFAULT_USER : s.trim(); s = get(properties, "password"); password = isNullOrEmpty(s) ? DEFAULT_PASSWORD : s.trim(); s = get(properties, "mcastVlan"); mcastVlan = isNullOrEmpty(s) ? DEFAULT_MCAST_VLAN : Short.parseShort(s.trim()); s = get(properties, "vlanEnabled"); vlanEnabled = isNullOrEmpty(s) ? DEFAULT_VLAN_ENABLED : Boolean.parseBoolean(s.trim()); s = get(properties, "priority"); priority = isNullOrEmpty(s) ? DEFAULT_PRIORITY : Integer.parseInt(s.trim()); s = get(properties, "syncHost"); syncHost = isNullOrEmpty(s) ? DEFAULT_SYNC_HOST : s.trim(); } catch (Exception e) { user = DEFAULT_USER; password = DEFAULT_PASSWORD; syncHost = DEFAULT_SYNC_HOST; mcastVlan = DEFAULT_MCAST_VLAN; vlanEnabled = false; priority = DEFAULT_PRIORITY; } fabricOnosUrl = createRemoteUrl(syncHost); } private static String createRemoteUrl(String remoteHost) { return "http://" + remoteHost + "/onos/v1/mcast"; } private class InternalMulticastListener implements McastListener { @Override public void event(McastEvent event) { McastRouteInfo info = event.subject(); switch (event.type()) { case ROUTE_ADDED: break; case ROUTE_REMOVED: break; case SOURCE_ADDED: break; case SINK_ADDED: if (!info.sink().isPresent()) { log.warn("No sink given after sink added event: {}", info); return; } provisionGroup(info.route(), info.sink().get()); break; case SINK_REMOVED: unprovisionGroup(event.subject()); break; default: log.warn("Unknown mcast event {}", event.type()); } } } private void unprovisionGroup(McastRouteInfo info) { if (info.sinks().isEmpty()) { removeRemoteRoute(info.route()); } if (!info.sink().isPresent()) { log.warn("No sink given after sink removed event: {}", info); return; } ConnectPoint loc = info.sink().get(); NextObjective next = DefaultNextObjective.builder().fromApp(appId) .addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build()) .withType(NextObjective.Type.BROADCAST).withId(groups.get(info.route().group())) .removeFromExisting(new ObjectiveContext() { @Override public void onSuccess(Objective objective) { //TODO: change to debug log.info("Next Objective {} installed", objective.id()); } @Override public void onError(Objective objective, ObjectiveError error) { //TODO: change to debug log.info("Next Objective {} failed, because {}", objective.id(), error); } }); flowObjectiveService.next(loc.deviceId(), next); } private void provisionGroup(McastRoute route, ConnectPoint sink) { checkNotNull(route, "Route cannot be null"); checkNotNull(sink, "Sink cannot be null"); Optional<AccessDeviceData> oltInfo = cordConfigService.getAccessDevice(sink.deviceId()); if (!oltInfo.isPresent()) { log.warn("Unknown OLT device : {}", sink.deviceId()); return; } final AtomicBoolean sync = new AtomicBoolean(false); Integer nextId = groups.computeIfAbsent(route.group(), (g) -> { Integer id = flowObjectiveService.allocateNextId(); NextObjective next = DefaultNextObjective.builder().fromApp(appId) .addTreatment(DefaultTrafficTreatment.builder().setOutput(sink.port()).build()) .withType(NextObjective.Type.BROADCAST).withId(id).add(new ObjectiveContext() { @Override public void onSuccess(Objective objective) { //TODO: change to debug log.info("Next Objective {} installed", objective.id()); } @Override public void onError(Objective objective, ObjectiveError error) { //TODO: change to debug log.info("Next Objective {} failed, because {}", objective.id(), error); } }); flowObjectiveService.next(sink.deviceId(), next); TrafficSelector.Builder mcast = DefaultTrafficSelector.builder().matchInPort(oltInfo.get().uplink()) .matchEthType(Ethernet.TYPE_IPV4).matchIPDst(g.toIpPrefix()); if (vlanEnabled) { mcast.matchVlanId(VlanId.vlanId((short) mcastVlan)); } ForwardingObjective fwd = DefaultForwardingObjective.builder().fromApp(appId).nextStep(id) .makePermanent().withFlag(ForwardingObjective.Flag.VERSATILE).withPriority(priority) .withSelector(mcast.build()).add(new ObjectiveContext() { @Override public void onSuccess(Objective objective) { //TODO: change to debug log.info("Forwarding objective installed {}", objective); } @Override public void onError(Objective objective, ObjectiveError error) { //TODO: change to debug log.info("Forwarding objective failed {}", objective); } }); flowObjectiveService.forward(sink.deviceId(), fwd); sync.set(true); return id; }); if (!sync.get()) { NextObjective next = DefaultNextObjective.builder().fromApp(appId) .addTreatment(DefaultTrafficTreatment.builder().setOutput(sink.port()).build()) .withType(NextObjective.Type.BROADCAST).withId(nextId).addToExisting(new ObjectiveContext() { @Override public void onSuccess(Objective objective) { //TODO: change to debug log.info("Next Objective {} installed", objective.id()); } @Override public void onError(Objective objective, ObjectiveError error) { //TODO: change to debug log.info("Next Objective {} failed, because {}", objective.id(), error); } }); flowObjectiveService.next(sink.deviceId(), next); } addRemoteRoute(route, sink); } private void addRemoteRoute(McastRoute route, ConnectPoint inPort) { checkNotNull(route); if (syncHost == null) { log.warn("No host configured for synchronization; route will be dropped"); return; } Optional<AccessAgentData> accessAgent = cordConfigService.getAccessAgent(inPort.deviceId()); if (!accessAgent.isPresent()) { log.warn("No accessAgent config found for in port {}", inPort); return; } if (!accessAgent.get().getOltConnectPoint(inPort).isPresent()) { log.warn("No OLT configured for in port {}", inPort); return; } ConnectPoint oltConnectPoint = accessAgent.get().getOltConnectPoint(inPort).get(); log.debug("Sending route {} to other ONOS {}", route, fabricOnosUrl); Invocation.Builder builder = getClientBuilder(fabricOnosUrl); ObjectNode json = codecService.getCodec(McastRoute.class).encode(route, new AbstractWebResource()); try { builder.post(Entity.json(json.toString())); builder = getClientBuilder(fabricOnosUrl + "/sinks/" + route.group() + "/" + route.source()); ObjectMapper mapper = new ObjectMapper(); ObjectNode obj = mapper.createObjectNode(); obj.putArray("sinks").add(oltConnectPoint.deviceId() + "/" + oltConnectPoint.port()); builder.post(Entity.json(obj.toString())); } catch (ProcessingException e) { log.warn("Unable to send route to remote controller: {}", e.getMessage()); } } private void removeRemoteRoute(McastRoute route) { if (syncHost == null) { log.warn("No host configured for synchronization; route will be dropped"); return; } log.debug("Removing route {} from other ONOS {}", route, fabricOnosUrl); Invocation.Builder builder = getClientBuilder(fabricOnosUrl) .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); ObjectNode json = codecService.getCodec(McastRoute.class).encode(route, new AbstractWebResource()); builder.method("DELETE", Entity.entity(json.asText(), MediaType.APPLICATION_OCTET_STREAM)); } private void clearRemoteRoutes() { if (syncHost == null) { log.warn("No host configured for synchronization"); return; } log.debug("Clearing remote multicast routes from {}", fabricOnosUrl); Invocation.Builder builder = getClientBuilder(fabricOnosUrl); List<McastRoute> mcastRoutes = Lists.newArrayList(); try { String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).get(String.class); JsonCodec<McastRoute> routeCodec = codecService.getCodec(McastRoute.class); ObjectMapper mapper = new ObjectMapper(); ObjectNode node = (ObjectNode) mapper.readTree(response); ArrayNode list = (ArrayNode) node.path("routes"); list.forEach(n -> mcastRoutes.add(routeCodec.decode((ObjectNode) n, new AbstractWebResource()))); } catch (IOException | ProcessingException e) { log.warn("Error clearing remote routes", e); } mcastRoutes.forEach(this::removeRemoteRoute); } private Invocation.Builder getClientBuilder(String uri) { ClientConfig config = new ClientConfig(); Client client = ClientBuilder.newClient(config); client.property(ClientProperties.CONNECT_TIMEOUT, DEFAULT_REST_TIMEOUT_MS); client.property(ClientProperties.READ_TIMEOUT, DEFAULT_REST_TIMEOUT_MS); client.register(HttpAuthenticationFeature.basic(user, password)); WebTarget wt = client.target(uri); return wt.request(JSON_UTF_8.toString()); } }