blob: cb078437351807fefe79cd9142513c010845df90 [file] [log] [blame]
Michele Santuarice256492017-06-23 14:44:01 +02001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016 Open Networking Foundation
Michele Santuarice256492017-06-23 14:44:01 +02003 *
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 */
16
17package org.onosproject.drivers.juniper;
18
19import com.google.common.annotations.Beta;
20import com.google.common.base.Strings;
21import org.apache.commons.lang.StringUtils;
22import org.onlab.packet.Ip4Address;
23import org.onosproject.net.AnnotationKeys;
24import org.onosproject.net.ConnectPoint;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.Link;
27import org.onosproject.net.Port;
28import org.onosproject.net.PortNumber;
29import org.onosproject.net.device.DeviceService;
30import org.onosproject.net.driver.AbstractHandlerBehaviour;
31import org.onosproject.net.flow.FlowEntry;
32import org.onosproject.net.flow.FlowRule;
33import org.onosproject.net.flow.FlowRuleProgrammable;
34import org.onosproject.net.flow.FlowRuleService;
35import org.onosproject.net.flow.criteria.Criterion;
36import org.onosproject.net.flow.criteria.IPCriterion;
37import org.onosproject.net.flow.instructions.Instruction;
38import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
39import org.onosproject.net.link.LinkService;
40import org.onosproject.netconf.DatastoreId;
41import org.onosproject.netconf.NetconfController;
42import org.onosproject.netconf.NetconfException;
43import org.onosproject.netconf.NetconfSession;
44
Michele Santuarice256492017-06-23 14:44:01 +020045import java.util.Collection;
46import java.util.Collections;
47import java.util.HashSet;
48import java.util.Map;
49import java.util.Optional;
50import java.util.Set;
51import java.util.stream.Collectors;
52
53import static com.google.common.base.Preconditions.checkNotNull;
54import static org.onosproject.drivers.juniper.JuniperUtils.OperationType;
55import static org.onosproject.drivers.juniper.JuniperUtils.OperationType.ADD;
56import static org.onosproject.drivers.juniper.JuniperUtils.OperationType.REMOVE;
57import static org.onosproject.drivers.juniper.JuniperUtils.commitBuilder;
58import static org.onosproject.drivers.juniper.JuniperUtils.rollbackBuilder;
59import static org.onosproject.drivers.juniper.JuniperUtils.routeAddBuilder;
60import static org.onosproject.drivers.juniper.JuniperUtils.routeDeleteBuilder;
61import static org.onosproject.drivers.utilities.XmlConfigParser.loadXmlString;
62import static org.onosproject.net.flow.FlowEntry.FlowEntryState.PENDING_REMOVE;
63import static org.onosproject.net.flow.FlowEntry.FlowEntryState.REMOVED;
64import static org.slf4j.LoggerFactory.getLogger;
65
66/**
67 * Conversion of FlowRules into static routes and retrieve of installed
68 * static routes as FlowRules.
69 * The selector of the FlowRule must contains the IPv4 address
70 * {@link org.onosproject.net.flow.TrafficSelector.Builder#matchIPDst(org.onlab.packet.IpPrefix)}
71 * of the host to connect and the treatment must include an
72 * output port {@link org.onosproject.net.flow.TrafficTreatment.Builder#setOutput(PortNumber)}
73 * All other instructions in the selector and treatment are ignored.
74 * <p>
75 * This implementation requires an IP adjacency
76 * (e.g., IP link discovered by {@link LinkDiscoveryJuniperImpl}) between the routers
77 * to find the next hop IP address.
78 */
79@Beta
80public class FlowRuleJuniperImpl extends AbstractHandlerBehaviour
81 implements FlowRuleProgrammable {
82
83 private static final String OK = "<ok/>";
84 public static final String IP_STRING = "ip";
85 private final org.slf4j.Logger log = getLogger(getClass());
86
87 @Override
88 public Collection<FlowEntry> getFlowEntries() {
89
90 DeviceId devId = checkNotNull(this.data().deviceId());
91 NetconfController controller = checkNotNull(handler().get(NetconfController.class));
92 NetconfSession session = controller.getDevicesMap().get(devId).getSession();
93 if (session == null) {
94 log.warn("Device {} is not registered in netconf", devId);
95 return Collections.EMPTY_LIST;
96 }
97
98 //Installed static routes
99 String reply;
100 try {
101 reply = session.get(routingTableBuilder());
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700102 } catch (NetconfException e) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800103 throw new IllegalStateException(new NetconfException("Failed to retrieve configuration.",
Michele Santuarice256492017-06-23 14:44:01 +0200104 e));
105 }
106 Collection<StaticRoute> devicesStaticRoutes =
107 JuniperUtils.parseRoutingTable(loadXmlString(reply));
108
109 //Expected FlowEntries installed
110 FlowRuleService flowRuleService = this.handler().get(FlowRuleService.class);
111 Iterable<FlowEntry> flowEntries = flowRuleService.getFlowEntries(devId);
112
113 Collection<FlowEntry> installedRules = new HashSet<>();
114 flowEntries.forEach(flowEntry -> {
115 Optional<IPCriterion> ipCriterion = getIpCriterion(flowEntry);
116 if (!ipCriterion.isPresent()) {
117 return;
118 }
119
120 Optional<OutputInstruction> output = getOutput(flowEntry);
121 if (!output.isPresent()) {
122 return;
123 }
124 //convert FlowRule into static route
125 getStaticRoute(devId, ipCriterion.get(), output.get(), flowEntry.priority()).ifPresent(staticRoute -> {
126 //Two type of FlowRules:
127 //1. FlowRules to forward to a remote subnet: they are translated into static route
128 // configuration. So a removal request will be processed.
129 //2. FlowRules to forward on a subnet directly attached to the router (Generally speaking called local):
130 // those routes do not require any configuration because the router is already able to forward on
131 // directly attached subnet. In this case, when the driver receive the request to remove,
132 // it will report as removed.
133
134 if (staticRoute.isLocalRoute()) {
135 //if the FlowRule is in PENDING_REMOVE or REMOVED, it is not reported.
136 if (flowEntry.state() == PENDING_REMOVE || flowEntry.state() == REMOVED) {
137 devicesStaticRoutes.remove(staticRoute);
138 } else {
139 //FlowRule is reported installed
140 installedRules.add(flowEntry);
141 devicesStaticRoutes.remove(staticRoute);
142 }
143
144 } else {
145 //if the route is found in the device, the FlowRule is reported installed.
146 if (devicesStaticRoutes.contains(staticRoute)) {
147 installedRules.add(flowEntry);
148 devicesStaticRoutes.remove(staticRoute);
149 }
150 }
151 });
152 });
153
154 if (!devicesStaticRoutes.isEmpty()) {
155 log.info("Found static routes on device {} not installed by ONOS: {}",
156 devId, devicesStaticRoutes);
157// FIXME: enable configuration to purge already installed flows.
158// It cannot be allowed by default because it may remove needed management routes
159// log.warn("Removing from device {} the FlowEntries not expected {}", deviceId, devicesStaticRoutes);
160// devicesStaticRoutes.forEach(staticRoute -> editRoute(session, REMOVE, staticRoute));
161 }
162 return installedRules;
163 }
164
165 @Override
166 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
167 return manageRules(rules, ADD);
168 }
169
170 @Override
171 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
172 return manageRules(rules, REMOVE);
173 }
174
175 private Collection<FlowRule> manageRules(Collection<FlowRule> rules, OperationType type) {
176
177 DeviceId deviceId = this.data().deviceId();
178
179 log.debug("{} flow entries to NETCONF device {}", type, deviceId);
180 NetconfController controller = checkNotNull(handler()
181 .get(NetconfController.class));
182 NetconfSession session = controller.getDevicesMap().get(deviceId)
183 .getSession();
184 Collection<FlowRule> managedRules = new HashSet<>();
185
186 for (FlowRule flowRule : rules) {
187
188 Optional<IPCriterion> ipCriterion = getIpCriterion(flowRule);
189 if (!ipCriterion.isPresent()) {
190 log.error("Currently not supported: IPv4 destination match must be used");
191 continue;
192 }
193
194 Optional<OutputInstruction> output = getOutput(flowRule);
195 if (!output.isPresent()) {
196 log.error("Currently not supported: the output action is needed");
197 continue;
198 }
199
200 getStaticRoute(deviceId, ipCriterion.get(), output.get(), flowRule.priority()).ifPresent(
201 staticRoute -> {
202 //If the static route is not local, the driver tries to install
203 if (!staticRoute.isLocalRoute()) {
204 //Only if the installation is successful, the driver report the
205 // FlowRule as installed.
206 if (editRoute(session, type, staticRoute)) {
207 managedRules.add(flowRule);
208 }
209 //If static route are local, they are not installed
210 // because are not required. Directly connected routes
211 //are automatically forwarded.
212 } else {
213 managedRules.add(flowRule);
214 }
215 }
216 );
217 }
218 return rules;
219 }
220
221 private boolean editRoute(NetconfSession session, OperationType type,
222 StaticRoute staticRoute) {
223 try {
224 boolean reply = false;
225 if (type == ADD) {
226 reply = session
227 .editConfig(DatastoreId.CANDIDATE, "merge",
228 routeAddBuilder(staticRoute));
229 } else if (type == REMOVE) {
230 reply = session
231 .editConfig(DatastoreId.CANDIDATE, "none", routeDeleteBuilder(staticRoute));
232 }
233 if (reply && commit()) {
234 return true;
235 } else {
236 if (!rollback()) {
237 log.error("Something went wrong in the configuration and impossible to rollback");
238 } else {
239 log.error("Something went wrong in the configuration: a static route has not been {} {}",
240 type == ADD ? "added" : "removed", staticRoute);
241 }
242 }
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700243 } catch (NetconfException e) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800244 throw new IllegalStateException(new NetconfException("Failed to retrieve configuration.",
Michele Santuarice256492017-06-23 14:44:01 +0200245 e));
246 }
247 return false;
248 }
249
250 /**
251 * Helper method to convert FlowRule into an abstraction of static route
252 * {@link StaticRoute}.
253 *
254 * @param devId the device id
255 * @param criteria the IP destination criteria
256 * @param output the output instruction
257 * @return optional of Static Route
258 */
259 private Optional<StaticRoute> getStaticRoute(DeviceId devId,
260 IPCriterion criteria,
261 OutputInstruction output,
262 int priority) {
263
264 DeviceService deviceService = this.handler().get(DeviceService.class);
265 Collection<Port> ports = deviceService.getPorts(devId);
266 Optional<Port> port = ports.stream().filter(x -> x.number().equals(output.port())).findAny();
267 if (!port.isPresent()) {
268 log.error("The port {} does not exist in the device",
269 output.port());
270 return Optional.empty();
271 }
272
273 //Find if the route refers to a local interface.
274 Optional<Port> local = deviceService.getPorts(devId).stream().filter(this::isIp)
275 .filter(p -> criteria.ip().getIp4Prefix().contains(
276 Ip4Address.valueOf(p.annotations().value(IP_STRING)))).findAny();
277
278 if (local.isPresent()) {
279 return Optional.of(new StaticRoute(criteria.ip().getIp4Prefix(),
280 criteria.ip().getIp4Prefix().address(), true, priority));
281 }
282
283 Optional<Ip4Address> nextHop = findIpDst(devId, port.get());
284 if (nextHop.isPresent()) {
285 return Optional.of(
286 new StaticRoute(criteria.ip().getIp4Prefix(), nextHop.get(), false, priority));
287 } else {
288 log.error("The destination interface has not an IP {}", port.get());
289 return Optional.empty();
290 }
291
292 }
293
294 /**
295 * Helper method to get the IP destination criterion given a flow rule.
296 *
297 * @param flowRule the flow rule
298 * @return optional of IP destination criterion
299 */
300 private Optional<IPCriterion> getIpCriterion(FlowRule flowRule) {
301
302 Criterion ip = flowRule.selector().getCriterion(Criterion.Type.IPV4_DST);
303 return Optional.ofNullable((IPCriterion) ip);
304 }
305
306 /**
307 * Helper method to get the output instruction given a flow rule.
308 *
309 * @param flowRule the flow rule
310 * @return the output instruction
311 */
312 private Optional<OutputInstruction> getOutput(FlowRule flowRule) {
313 Optional<OutputInstruction> output = flowRule
314 .treatment().allInstructions().stream()
315 .filter(instruction -> instruction
316 .type() == Instruction.Type.OUTPUT)
317 .map(x -> (OutputInstruction) x).findFirst();
318 return output;
319 }
320
321 private String routingTableBuilder() {
322 StringBuilder rpc = new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
323 rpc.append("<get-route-information/>");
324 rpc.append("</rpc>");
325 return rpc.toString();
326 }
327
328 private boolean commit() {
329 NetconfController controller = checkNotNull(handler()
330 .get(NetconfController.class));
331 NetconfSession session = controller.getDevicesMap()
332 .get(handler().data().deviceId()).getSession();
333
334 String replay;
335 try {
336 replay = session.get(commitBuilder());
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700337 } catch (NetconfException e) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800338 throw new IllegalStateException(new NetconfException("Failed to retrieve configuration.",
Michele Santuarice256492017-06-23 14:44:01 +0200339 e));
340 }
341
342 if (replay != null && replay.indexOf(OK) >= 0) {
343 return true;
344 }
345 return false;
346 }
347
348 private boolean rollback() {
349 NetconfController controller = checkNotNull(handler()
350 .get(NetconfController.class));
351 NetconfSession session = controller.getDevicesMap()
352 .get(handler().data().deviceId()).getSession();
353
354 String replay;
355 try {
356 replay = session.get(rollbackBuilder(0));
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700357 } catch (NetconfException e) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800358 throw new IllegalStateException(new NetconfException("Failed to retrieve configuration.",
Michele Santuarice256492017-06-23 14:44:01 +0200359 e));
360 }
361
362 if (replay != null && replay.indexOf(OK) >= 0) {
363 return true;
364 }
365 return false;
366 }
367
368 /**
369 * Helper method to find the next hop IP address.
370 * The logic is to check if the destination ports have an IP address
371 * by checking the logical interface (e.g., for port physical ge-2/0/1,
372 * a logical interface may be ge-2/0/1.0
373 *
374 * @param deviceId the device id of the flow rule to be installed
375 * @param port output port of the flow rule treatment
376 * @return optional IPv4 address of a next hop
377 */
378 private Optional<Ip4Address> findIpDst(DeviceId deviceId, Port port) {
379 LinkService linkService = this.handler().get(LinkService.class);
380 Set<Link> links = linkService.getEgressLinks(new ConnectPoint(deviceId, port.number()));
381 DeviceService deviceService = this.handler().get(DeviceService.class);
382 //Using only links with adjacency discovered by the LLDP protocol (see LinkDiscoveryJuniperImpl)
383 Map<DeviceId, Port> dstPorts = links.stream().filter(l ->
384 IP_STRING.toUpperCase().equals(l.annotations().value("layer")))
385 .collect(Collectors.toMap(
386 l -> l.dst().deviceId(),
387 l -> deviceService.getPort(l.dst().deviceId(), l.dst().port())));
388 for (Map.Entry<DeviceId, Port> entry : dstPorts.entrySet()) {
389 String portName = entry.getValue().annotations().value(AnnotationKeys.PORT_NAME);
390
391 Optional<Port> childPort = deviceService.getPorts(entry.getKey()).stream()
392 .filter(p -> Strings.nullToEmpty(
393 p.annotations().value(AnnotationKeys.PORT_NAME)).contains(portName.trim()))
394 .filter(p -> isIp(p))
395 .findAny();
396 if (childPort.isPresent()) {
397 return Optional.ofNullable(Ip4Address.valueOf(childPort.get().annotations().value("ip")));
398 }
399 }
400
401 return Optional.empty();
402 }
403
404 /**
405 * Helper method to find if an interface has an IP address.
406 * It will check the annotations of the port.
407 *
408 * @param port the port
409 * @return true if the IP address is present. Otherwise false.
410 */
411 private boolean isIp(Port port) {
412 String ip4 = port.annotations().value(IP_STRING);
413 if (StringUtils.isEmpty(ip4)) {
414 return false;
415 }
416 try {
417
418 Ip4Address.valueOf(port.annotations().value(IP_STRING));
419 } catch (IllegalArgumentException e) {
420 return false;
421 }
422 return true;
423 }
424}