blob: 60b0dbc3a15af95afb988a16cd8c253ff611999a [file] [log] [blame]
/*
* 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.p4runtime;
import org.onosproject.cluster.ClusterService;
import org.onosproject.net.MastershipRole;
import org.onosproject.net.device.DeviceAgentListener;
import org.onosproject.net.device.DeviceHandshaker;
import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.p4runtime.api.P4RuntimeClient;
import org.onosproject.p4runtime.api.P4RuntimeClientKey;
import org.onosproject.p4runtime.api.P4RuntimeController;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
/**
* Implementation of DeviceHandshaker for P4Runtime.
*/
public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour implements DeviceHandshaker {
// This is needed to compute an election ID based on mastership term and
// preference. At the time of writing the practical maximum cluster size is
// 9. Since election IDs are 128bit numbers, we should'nt be too worried of
// being conservative when setting a static max size here. Making the
// cluster size dynamic would likely cause conflicts when generating
// election IDs (e.g. two nodes seeing different cluster sizes).
private static final int MAX_CLUSTER_SIZE = 20;
@Override
public CompletableFuture<Boolean> connect() {
return CompletableFuture
.supplyAsync(this::createClient);
}
private boolean createClient() {
final P4RuntimeClientKey clientKey = clientKey();
if (clientKey == null) {
return false;
}
if (!handler().get(P4RuntimeController.class).createClient(clientKey)) {
log.debug("Unable to create client for {}", data().deviceId());
return false;
}
return true;
}
@Override
public boolean isConnected() {
// This is based on the client key obtained from the current netcfg. If
// a client already exists for this device, but the netcfg with the
// server endpoints has changed, this will return false.
return getClientByKey() != null;
}
@Override
public void disconnect() {
// This removes clients associated with this device ID, even if the
// netcfg has changed and so the client key for this device.
handler().get(P4RuntimeController.class).removeClient(data().deviceId());
}
@Override
public boolean isReachable() {
final P4RuntimeClient client = getClientByKey();
return client != null && client.isServerReachable();
}
@Override
public CompletableFuture<Boolean> probeReachability() {
final P4RuntimeClient client = getClientByKey();
if (client == null) {
return completedFuture(false);
}
return client.probeService();
}
@Override
public boolean isAvailable() {
// To be available, we require a session open (for packet in/out) and a
// pipeline config set.
final P4RuntimeClient client = getClientByKey();
if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
return false;
}
// Since we cannot probe the device, we rely on what's known by the
// pipeconf watchdog service.
return PiPipeconfWatchdogService.PipelineStatus.READY.equals(
handler().get(PiPipeconfWatchdogService.class)
.getStatus(data().deviceId()));
}
@Override
public CompletableFuture<Boolean> probeAvailability() {
// To be available, we require a session open (for packet in/out) and a
// pipeline config set.
final P4RuntimeClient client = getClientByKey();
if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
return completedFuture(false);
}
return client.isAnyPipelineConfigSet();
}
@Override
public void roleChanged(MastershipRole newRole) {
if (newRole.equals(MastershipRole.NONE)) {
final P4RuntimeClient client = getClientByKey();
if (client != null) {
log.info("Notified role NONE, closing session...");
client.closeSession();
}
} else {
throw new UnsupportedOperationException(
"Use preference-based way for setting MASTER or STANDBY roles");
}
}
@Override
public void roleChanged(int preference, long term) {
if (setupBehaviour()) {
final int clusterSize = handler().get(ClusterService.class)
.getNodes().size();
if (clusterSize > MAX_CLUSTER_SIZE) {
throw new IllegalStateException(
"Cluster too big! Maz size supported is " + MAX_CLUSTER_SIZE);
}
BigInteger electionId = BigInteger.valueOf(term)
.multiply(BigInteger.valueOf(MAX_CLUSTER_SIZE))
.subtract(BigInteger.valueOf(preference));
client.setMastership(preference == 0, electionId);
}
}
@Override
public MastershipRole getRole() {
final P4RuntimeClient client = getClientByKey();
if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
return MastershipRole.NONE;
}
return client.isMaster() ? MastershipRole.MASTER : MastershipRole.STANDBY;
}
@Override
public void addDeviceAgentListener(ProviderId providerId, DeviceAgentListener listener) {
// Don't use controller/deviceId class variables as they might be uninitialized.
handler().get(P4RuntimeController.class)
.addDeviceAgentListener(data().deviceId(), providerId, listener);
}
@Override
public void removeDeviceAgentListener(ProviderId providerId) {
// Don't use controller/deviceId class variable as they might be uninitialized.
handler().get(P4RuntimeController.class)
.removeDeviceAgentListener(data().deviceId(), providerId);
}
}