blob: 9e4cf24d1db0d2f3ba664b6195bc3e8e8acea94b [file] [log] [blame]
Andrea Campanellabf1301d2017-08-07 18:33:52 +02001/*
2 * Copyright 2017-present Open Networking Foundation
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 */
16
17package org.onosproject.drivers.barefoot;
18
19import com.google.common.collect.ImmutableList;
Carmelo Cascone0deee482017-12-19 18:57:10 +000020import com.google.common.collect.ImmutableMap;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020021import com.google.common.collect.Lists;
22import org.apache.commons.io.IOUtils;
Yi Tseng59ef1702017-10-05 23:33:22 -070023import org.apache.thrift.TException;
24import org.apache.thrift.protocol.TBinaryProtocol;
25import org.apache.thrift.protocol.TMultiplexedProtocol;
26import org.apache.thrift.protocol.TProtocol;
27import org.apache.thrift.transport.TSocket;
28import org.apache.thrift.transport.TTransport;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020029import org.onlab.util.SharedExecutors;
Carmelo Cascone0deee482017-12-19 18:57:10 +000030import org.onosproject.drivers.barefoot.pal.InvalidPalOperation;
Yi Tseng59ef1702017-10-05 23:33:22 -070031import org.onosproject.drivers.barefoot.pal.pal;
32import org.onosproject.drivers.barefoot.pal.pal_fec_type_t;
33import org.onosproject.drivers.barefoot.pal.pal_port_speed_t;
34import org.onosproject.incubator.net.config.basics.PortDescriptionsConfig;
Carmelo Cascone0deee482017-12-19 18:57:10 +000035import org.onosproject.net.AnnotationKeys;
36import org.onosproject.net.Device;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020037import org.onosproject.net.DeviceId;
Carmelo Cascone0deee482017-12-19 18:57:10 +000038import org.onosproject.net.Port;
Yi Tseng59ef1702017-10-05 23:33:22 -070039import org.onosproject.net.config.NetworkConfigService;
Carmelo Cascone0deee482017-12-19 18:57:10 +000040import org.onosproject.net.config.basics.BasicDeviceConfig;
41import org.onosproject.net.device.DeviceDescription;
42import org.onosproject.net.device.DeviceDescriptionDiscovery;
43import org.onosproject.net.device.DeviceService;
Yi Tseng59ef1702017-10-05 23:33:22 -070044import org.onosproject.net.device.PortDescription;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020045import org.onosproject.net.driver.AbstractHandlerBehaviour;
Carmelo Cascone0deee482017-12-19 18:57:10 +000046import org.onosproject.net.driver.Driver;
47import org.onosproject.net.driver.DriverService;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020048import org.onosproject.net.pi.model.PiPipeconf;
49import org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
50import org.onosproject.net.pi.model.PiPipelineProgrammable;
51import org.onosproject.p4runtime.api.P4RuntimeClient;
52import org.onosproject.p4runtime.api.P4RuntimeController;
Yi Tseng59ef1702017-10-05 23:33:22 -070053import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020054import org.slf4j.Logger;
55
Carmelo Cascone0deee482017-12-19 18:57:10 +000056import java.io.File;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020057import java.io.IOException;
58import java.io.InputStream;
59import java.nio.Buffer;
60import java.nio.ByteBuffer;
61import java.nio.ByteOrder;
62import java.nio.charset.StandardCharsets;
Carmelo Cascone0deee482017-12-19 18:57:10 +000063import java.util.ArrayList;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020064import java.util.List;
Yi Tseng59ef1702017-10-05 23:33:22 -070065import java.util.Map;
Andrea Campanellabf1301d2017-08-07 18:33:52 +020066import java.util.Optional;
67import java.util.concurrent.CompletableFuture;
68import java.util.concurrent.ExecutionException;
69
70import static com.google.common.base.Strings.isNullOrEmpty;
71import static org.slf4j.LoggerFactory.getLogger;
72
73/**
74 * Implementation of the PiPipelineProgrammable for BMv2.
75 */
76public class TofinoPipelineProgrammable extends AbstractHandlerBehaviour implements PiPipelineProgrammable {
77
Carmelo Cascone5db39682017-09-07 16:36:42 +020078 // FIXME: default pipeconf should depend on the system. Maybe pass it via netcfg?
79 private static final PiPipeconf DEFAULT_PIPECONF = TofinoDefaultPipeconfFactory.getMavericks();
Yi Tseng59ef1702017-10-05 23:33:22 -070080 private static final String P4RUNTIME = "p4runtime";
81 private static final String PROTOCOL_KEY_IP = "ip";
82 private static final String DEVICE_ID = "deviceId";
83 private static final int PAL_MGMT_PORT = 9090;
84 private static final String PAL_THRIFT_SERVICE = "pal";
Andrea Campanellabf1301d2017-08-07 18:33:52 +020085
86 private final Logger log = getLogger(getClass());
87
88 @Override
89 public CompletableFuture<Boolean> deployPipeconf(PiPipeconf pipeconf) {
90
91 CompletableFuture<Boolean> result = new CompletableFuture<>();
92
93 SharedExecutors.getPoolThreadExecutor().submit(() -> result.complete(doDeployConfig(pipeconf)));
94
95 return result;
96 }
97
98 private boolean doDeployConfig(PiPipeconf pipeconf) {
99
100 P4RuntimeController controller = handler().get(P4RuntimeController.class);
101
102 DeviceId deviceId = handler().data().deviceId();
103
104 if (!controller.hasClient(deviceId)) {
105 log.warn("Unable to find client for {}, aborting pipeconf deploy", deviceId);
106 return false;
Andrea Campanellabf1301d2017-08-07 18:33:52 +0200107 }
108
109 P4RuntimeClient client = controller.getClient(deviceId);
110
111 //creating the ByteBuffer with all the needed elements to set the pipeline on the device
112 ByteBuffer pipelineBuffer = createPipelineBuffer(pipeconf,
113 ImmutableList.of(ExtensionType.TOFINO_BIN, ExtensionType.TOFINO_CONTEXT_JSON));
114
115 try {
116 if (!client.setPipelineConfig(pipeconf, pipelineBuffer).get()) {
117 log.warn("Unable to deploy pipeconf {} to {}", pipeconf.id(), deviceId);
118 return false;
119 }
120
121 // It would be more logical to have this performed at device handshake, but P4runtime would reject any
122 // command if a P4info has not been set first.
123 if (!client.initStreamChannel().get()) {
124 log.warn("Unable to init stream channel to {}.", deviceId);
125 return false;
126 }
127
Yi Tseng59ef1702017-10-05 23:33:22 -0700128 SharedExecutors.getPoolThreadExecutor().submit(() -> setupPorts(deviceId));
Brian O'Connor98fcd802017-09-13 23:40:45 -0700129
Andrea Campanellabf1301d2017-08-07 18:33:52 +0200130 } catch (InterruptedException | ExecutionException e) {
131 throw new RuntimeException(e);
132 }
133
134 return true;
135 }
136
Yi Tseng59ef1702017-10-05 23:33:22 -0700137 // FIXME Currently use thrift, may use gNMI to manage it in the future.
138 private void setupPorts(DeviceId deviceId) {
139 NetworkConfigService netcfgService = handler().get(NetworkConfigService.class);
140 GeneralProviderDeviceConfig providerConfig =
141 netcfgService.getConfig(deviceId, GeneralProviderDeviceConfig.class);
142 PortDescriptionsConfig portConfig =
143 netcfgService.getConfig(deviceId, PortDescriptionsConfig.class);
Brian O'Connor98fcd802017-09-13 23:40:45 -0700144
Yi Tseng59ef1702017-10-05 23:33:22 -0700145 Map<String, String> values =
146 providerConfig.protocolsInfo().get(P4RUNTIME).configValues();
147 String ip = values.get(PROTOCOL_KEY_IP);
148 byte tofinoDeviceId = Byte.parseByte(values.get(DEVICE_ID));
149
150 List<PortDescription> ports = portConfig.portDescriptions();
151 ports.forEach(port -> {
152 pal_port_speed_t speed = getPortSpeed(port.portSpeed());
153 addAndEnablePort(ip, tofinoDeviceId, (int) port.portNumber().toLong(), speed);
154 });
155 }
156
157 private pal_port_speed_t getPortSpeed(long speed) {
158 // Mbps -> BF_SPEED (1G~100G)
159 speed = speed / 1000;
160
161 if (speed >= 100) {
162 return pal_port_speed_t.BF_SPEED_100G;
163 }
164 if (speed >= 50) {
165 return pal_port_speed_t.BF_SPEED_50G;
166 }
167 if (speed >= 40) {
168 return pal_port_speed_t.BF_SPEED_40G;
169 }
170 if (speed >= 25) {
171 return pal_port_speed_t.BF_SPEED_25G;
172 }
173 if (speed >= 10) {
174 return pal_port_speed_t.BF_SPEED_10G;
175 }
176
177 return pal_port_speed_t.BF_SPEED_1G;
178 }
179
180 private void addAndEnablePort(String ip, byte deviceId, int dp, pal_port_speed_t speed) {
181 TTransport transport = null;
182 try {
183 transport = new TSocket(ip, PAL_MGMT_PORT);
184 transport.open();
185 TProtocol protocol = new TBinaryProtocol(transport);
186 TMultiplexedProtocol mProtocol = new TMultiplexedProtocol(protocol,
187 PAL_THRIFT_SERVICE);
188 pal.Client client = new pal.Client(mProtocol);
189
190 client.pal_port_add(deviceId, dp, speed, pal_fec_type_t.BF_FEC_TYP_NONE);
191 client.pal_port_enable(deviceId, dp);
192 transport.close();
193 } catch (TException x) {
194 log.error("Error adding port {} to device {} ({})", dp, deviceId, ip, x);
195 } finally {
196 if (transport != null) {
197 transport.close();
Brian O'Connor98fcd802017-09-13 23:40:45 -0700198 }
Brian O'Connor98fcd802017-09-13 23:40:45 -0700199 }
200 }
201
Andrea Campanellabf1301d2017-08-07 18:33:52 +0200202 private ByteBuffer createPipelineBuffer(PiPipeconf pipeconf, List<ExtensionType> targetConfigExtTypes) {
203 if (targetConfigExtTypes == null || targetConfigExtTypes.isEmpty() || isNullOrEmpty(pipeconf.id().toString())) {
204
205 log.warn("Not enough information to deploy the Pipeconf {} on the switch {}, {}", pipeconf.id(),
206 targetConfigExtTypes);
207 } else {
208 List<ByteBuffer> buffers = Lists.newLinkedList();
209 //Name of the pipeconf.
210 //Appears to be an arbitrary name and unrelated to p4 program
211 String name = pipeconf.id().toString();
212 buffers.add(ByteBuffer.allocate(4 + name.length())
213 .order(ByteOrder.LITTLE_ENDIAN)
214 .putInt(name.length())
215 .put(name.getBytes(StandardCharsets.UTF_8)));
216
217 // Build buffers for all the extensions needed to deploy the pipeconf to Tofino
218 targetConfigExtTypes.forEach(targetConfigExtType -> {
219 if (!pipeconf.extension(targetConfigExtType).isPresent()) {
220 // FIXME this will break the expected data format; the resulting buffer will be invalid.
221 // FIXME Consider a stronger response here, like an exception.
222 log.warn("Missing extension {} in pipeconf {}", targetConfigExtType, pipeconf.id());
223 }
224 InputStream targetConfig = pipeconf.extension(targetConfigExtType).get();
225 try {
226 log.info("Setting extension {} in pipeconf {}", targetConfigExtType, pipeconf.id());
227 byte[] bin = IOUtils.toByteArray(targetConfig);
228 //length and byte of every extension
229 buffers.add(ByteBuffer.allocate(4 + bin.length)
230 .order(ByteOrder.LITTLE_ENDIAN)
231 .putInt(bin.length)
232 .put(bin));
233 } catch (IOException ex) {
234 // FIXME this will break the expected data format; the resulting buffer will be invalid.
235 // FIXME Consider a stronger response here, like an exception.
236 log.warn("Unable to load target-specific config for {}", ex.getMessage());
237 }
238 });
239
240 // Merge the buffers
241 int len = buffers.stream().mapToInt(Buffer::limit).sum();
242 ByteBuffer deviceData = ByteBuffer.allocate(len);
243 for (ByteBuffer b : buffers) {
244 deviceData.put((ByteBuffer) b.flip());
245 }
246 deviceData.flip(); // prepare for reading
247 return deviceData.asReadOnlyBuffer();
248 }
249 return null;
250 }
251
252 @Override
253 public Optional<PiPipeconf> getDefaultPipeconf() {
254 return Optional.of(DEFAULT_PIPECONF);
255 }
256}