| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * 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.drivers.barefoot; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import org.apache.commons.io.IOUtils; |
| import org.onlab.util.SharedExecutors; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| import org.onosproject.net.pi.model.PiPipeconf; |
| import org.onosproject.net.pi.model.PiPipeconf.ExtensionType; |
| import org.onosproject.net.pi.model.PiPipelineProgrammable; |
| import org.onosproject.p4runtime.api.P4RuntimeClient; |
| import org.onosproject.p4runtime.api.P4RuntimeController; |
| import org.slf4j.Logger; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.Buffer; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.charset.StandardCharsets; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| |
| import static com.google.common.base.Strings.isNullOrEmpty; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Implementation of the PiPipelineProgrammable for BMv2. |
| */ |
| public class TofinoPipelineProgrammable extends AbstractHandlerBehaviour implements PiPipelineProgrammable { |
| |
| // FIXME: default pipeconf should depend on the system. Maybe pass it via netcfg? |
| private static final PiPipeconf DEFAULT_PIPECONF = TofinoDefaultPipeconfFactory.getMavericks(); |
| |
| private final Logger log = getLogger(getClass()); |
| |
| @Override |
| public CompletableFuture<Boolean> deployPipeconf(PiPipeconf pipeconf) { |
| |
| CompletableFuture<Boolean> result = new CompletableFuture<>(); |
| |
| SharedExecutors.getPoolThreadExecutor().submit(() -> result.complete(doDeployConfig(pipeconf))); |
| |
| return result; |
| } |
| |
| private boolean doDeployConfig(PiPipeconf pipeconf) { |
| |
| P4RuntimeController controller = handler().get(P4RuntimeController.class); |
| |
| DeviceId deviceId = handler().data().deviceId(); |
| |
| if (!controller.hasClient(deviceId)) { |
| log.warn("Unable to find client for {}, aborting pipeconf deploy", deviceId); |
| return false; |
| } |
| |
| P4RuntimeClient client = controller.getClient(deviceId); |
| |
| //creating the ByteBuffer with all the needed elements to set the pipeline on the device |
| ByteBuffer pipelineBuffer = createPipelineBuffer(pipeconf, |
| ImmutableList.of(ExtensionType.TOFINO_BIN, ExtensionType.TOFINO_CONTEXT_JSON)); |
| |
| try { |
| if (!client.setPipelineConfig(pipeconf, pipelineBuffer).get()) { |
| log.warn("Unable to deploy pipeconf {} to {}", pipeconf.id(), deviceId); |
| return false; |
| } |
| |
| // It would be more logical to have this performed at device handshake, but P4runtime would reject any |
| // command if a P4info has not been set first. |
| if (!client.initStreamChannel().get()) { |
| log.warn("Unable to init stream channel to {}.", deviceId); |
| return false; |
| } |
| |
| //FIXME this hack calls the shell command to set up the ports |
| SharedExecutors.getPoolThreadExecutor().submit(() -> runPortCommand(deviceId)); |
| |
| } catch (InterruptedException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| |
| return true; |
| } |
| |
| // FIXME This is a hack to enable port until we either implement Thrift or gNMI |
| private void runPortCommand(DeviceId deviceId) { |
| // TODO we should have a more intelligent way of building the command |
| final String command; |
| if ("device:tofino:11".equals(deviceId.toString())) { |
| command = "./pm.py 10.254.1.38 38-port-config.txt"; |
| } else if ("device:tofino:12".equals(deviceId.toString())) { |
| command = "./pm.py 10.254.1.37 37-port-config.txt"; |
| } else if ("device:tofino:21".equals(deviceId.toString())) { |
| command = "./pm.py 10.254.1.40 40-port-config.txt"; |
| } else if ("device:tofino:22".equals(deviceId.toString())) { |
| command = "./pm.py 10.254.1.39 39-port-config.txt"; |
| } else { |
| log.error("Device port config not found: {}", deviceId); |
| return; |
| } |
| try { |
| // Give the switch 2 seconds to get settled with the new config |
| Thread.sleep(3000); |
| |
| String rootDir = System.getenv("ONOS_ROOT"); |
| if (rootDir == null) { |
| log.error("ONOS_ROOT is not set"); |
| return; |
| } |
| File workingDirectory = new File(rootDir, "tools/test/tofino-port-auto-setup-tool"); |
| if (!workingDirectory.isDirectory()) { |
| log.error("{} does not exist", workingDirectory); |
| return; |
| } |
| Process process = Runtime.getRuntime().exec(command, null, workingDirectory); |
| int exit = process.waitFor(); |
| if (exit != 0) { |
| log.error("port command returned non-zero status {} for device {}", exit, deviceId); |
| return; |
| } |
| log.info("Successfully executed port enable on {}", deviceId); |
| } catch (Exception e) { |
| log.error("Failed to run port command on {}", deviceId, e); |
| } |
| } |
| |
| private ByteBuffer createPipelineBuffer(PiPipeconf pipeconf, List<ExtensionType> targetConfigExtTypes) { |
| if (targetConfigExtTypes == null || targetConfigExtTypes.isEmpty() || isNullOrEmpty(pipeconf.id().toString())) { |
| |
| log.warn("Not enough information to deploy the Pipeconf {} on the switch {}, {}", pipeconf.id(), |
| targetConfigExtTypes); |
| } else { |
| List<ByteBuffer> buffers = Lists.newLinkedList(); |
| //Name of the pipeconf. |
| //Appears to be an arbitrary name and unrelated to p4 program |
| String name = pipeconf.id().toString(); |
| buffers.add(ByteBuffer.allocate(4 + name.length()) |
| .order(ByteOrder.LITTLE_ENDIAN) |
| .putInt(name.length()) |
| .put(name.getBytes(StandardCharsets.UTF_8))); |
| |
| // Build buffers for all the extensions needed to deploy the pipeconf to Tofino |
| targetConfigExtTypes.forEach(targetConfigExtType -> { |
| if (!pipeconf.extension(targetConfigExtType).isPresent()) { |
| // FIXME this will break the expected data format; the resulting buffer will be invalid. |
| // FIXME Consider a stronger response here, like an exception. |
| log.warn("Missing extension {} in pipeconf {}", targetConfigExtType, pipeconf.id()); |
| } |
| InputStream targetConfig = pipeconf.extension(targetConfigExtType).get(); |
| try { |
| log.info("Setting extension {} in pipeconf {}", targetConfigExtType, pipeconf.id()); |
| byte[] bin = IOUtils.toByteArray(targetConfig); |
| //length and byte of every extension |
| buffers.add(ByteBuffer.allocate(4 + bin.length) |
| .order(ByteOrder.LITTLE_ENDIAN) |
| .putInt(bin.length) |
| .put(bin)); |
| } catch (IOException ex) { |
| // FIXME this will break the expected data format; the resulting buffer will be invalid. |
| // FIXME Consider a stronger response here, like an exception. |
| log.warn("Unable to load target-specific config for {}", ex.getMessage()); |
| } |
| }); |
| |
| // Merge the buffers |
| int len = buffers.stream().mapToInt(Buffer::limit).sum(); |
| ByteBuffer deviceData = ByteBuffer.allocate(len); |
| for (ByteBuffer b : buffers) { |
| deviceData.put((ByteBuffer) b.flip()); |
| } |
| deviceData.flip(); // prepare for reading |
| return deviceData.asReadOnlyBuffer(); |
| } |
| return null; |
| } |
| |
| @Override |
| public Optional<PiPipeconf> getDefaultPipeconf() { |
| return Optional.of(DEFAULT_PIPECONF); |
| } |
| } |