| /* |
| * 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.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.thrift.TException; |
| import org.apache.thrift.protocol.TBinaryProtocol; |
| import org.apache.thrift.protocol.TMultiplexedProtocol; |
| import org.apache.thrift.protocol.TProtocol; |
| import org.apache.thrift.transport.TSocket; |
| import org.apache.thrift.transport.TTransport; |
| import org.onlab.util.SharedExecutors; |
| import org.onosproject.drivers.barefoot.pal.InvalidPalOperation; |
| import org.onosproject.drivers.barefoot.pal.pal; |
| import org.onosproject.drivers.barefoot.pal.pal_fec_type_t; |
| import org.onosproject.drivers.barefoot.pal.pal_port_speed_t; |
| import org.onosproject.incubator.net.config.basics.PortDescriptionsConfig; |
| import org.onosproject.net.AnnotationKeys; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.Port; |
| import org.onosproject.net.config.NetworkConfigService; |
| import org.onosproject.net.config.basics.BasicDeviceConfig; |
| import org.onosproject.net.device.DeviceDescription; |
| import org.onosproject.net.device.DeviceDescriptionDiscovery; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.device.PortDescription; |
| import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| import org.onosproject.net.driver.Driver; |
| import org.onosproject.net.driver.DriverService; |
| 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.onosproject.provider.general.device.api.GeneralProviderDeviceConfig; |
| 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.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| 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 static final String P4RUNTIME = "p4runtime"; |
| private static final String PROTOCOL_KEY_IP = "ip"; |
| private static final String DEVICE_ID = "deviceId"; |
| private static final int PAL_MGMT_PORT = 9090; |
| private static final String PAL_THRIFT_SERVICE = "pal"; |
| |
| 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; |
| } |
| |
| SharedExecutors.getPoolThreadExecutor().submit(() -> setupPorts(deviceId)); |
| |
| } catch (InterruptedException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| |
| return true; |
| } |
| |
| // FIXME Currently use thrift, may use gNMI to manage it in the future. |
| private void setupPorts(DeviceId deviceId) { |
| NetworkConfigService netcfgService = handler().get(NetworkConfigService.class); |
| GeneralProviderDeviceConfig providerConfig = |
| netcfgService.getConfig(deviceId, GeneralProviderDeviceConfig.class); |
| PortDescriptionsConfig portConfig = |
| netcfgService.getConfig(deviceId, PortDescriptionsConfig.class); |
| |
| Map<String, String> values = |
| providerConfig.protocolsInfo().get(P4RUNTIME).configValues(); |
| String ip = values.get(PROTOCOL_KEY_IP); |
| byte tofinoDeviceId = Byte.parseByte(values.get(DEVICE_ID)); |
| |
| List<PortDescription> ports = portConfig.portDescriptions(); |
| ports.forEach(port -> { |
| pal_port_speed_t speed = getPortSpeed(port.portSpeed()); |
| addAndEnablePort(ip, tofinoDeviceId, (int) port.portNumber().toLong(), speed); |
| }); |
| } |
| |
| private pal_port_speed_t getPortSpeed(long speed) { |
| // Mbps -> BF_SPEED (1G~100G) |
| speed = speed / 1000; |
| |
| if (speed >= 100) { |
| return pal_port_speed_t.BF_SPEED_100G; |
| } |
| if (speed >= 50) { |
| return pal_port_speed_t.BF_SPEED_50G; |
| } |
| if (speed >= 40) { |
| return pal_port_speed_t.BF_SPEED_40G; |
| } |
| if (speed >= 25) { |
| return pal_port_speed_t.BF_SPEED_25G; |
| } |
| if (speed >= 10) { |
| return pal_port_speed_t.BF_SPEED_10G; |
| } |
| |
| return pal_port_speed_t.BF_SPEED_1G; |
| } |
| |
| private void addAndEnablePort(String ip, byte deviceId, int dp, pal_port_speed_t speed) { |
| TTransport transport = null; |
| try { |
| transport = new TSocket(ip, PAL_MGMT_PORT); |
| transport.open(); |
| TProtocol protocol = new TBinaryProtocol(transport); |
| TMultiplexedProtocol mProtocol = new TMultiplexedProtocol(protocol, |
| PAL_THRIFT_SERVICE); |
| pal.Client client = new pal.Client(mProtocol); |
| |
| client.pal_port_add(deviceId, dp, speed, pal_fec_type_t.BF_FEC_TYP_NONE); |
| client.pal_port_enable(deviceId, dp); |
| transport.close(); |
| } catch (TException x) { |
| log.error("Error adding port {} to device {} ({})", dp, deviceId, ip, x); |
| } finally { |
| if (transport != null) { |
| transport.close(); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |