blob: 2bb75a30e6477ef0e21d8a37006427f82ed105c3 [file] [log] [blame]
/*
* Copyright 2019-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.p4runtime.ctl.codec;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;
import org.onosproject.net.pi.model.PiPipeconf;
import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
import org.onosproject.p4runtime.ctl.utils.PipeconfHelper;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Abstract implementation of a general codec that translates pipeconf-related
* objects into protobuf messages and vice versa.
*
* @param <P> object
* @param <M> protobuf message class
* @param <X> metadata class
*/
abstract class AbstractCodec<P, M extends Message, X> {
protected final Logger log = getLogger(this.getClass());
protected abstract M encode(P object, X metadata, PiPipeconf pipeconf,
P4InfoBrowser browser)
throws CodecException, P4InfoBrowser.NotFoundException;
protected abstract P decode(M message, X metadata, PiPipeconf pipeconf,
P4InfoBrowser browser)
throws CodecException, P4InfoBrowser.NotFoundException;
/**
* Returns a protobuf message that is equivalent to the given object for the
* given metadata and pipeconf.
*
* @param object object
* @param metadata metadata
* @param pipeconf pipeconf
* @return protobuf message
* @throws CodecException if the given object cannot be encoded (see
* exception message)
*/
public M encode(P object, X metadata, PiPipeconf pipeconf)
throws CodecException {
checkNotNull(object);
try {
return encode(object, metadata, pipeconf, browserOrFail(pipeconf));
} catch (P4InfoBrowser.NotFoundException e) {
throw new CodecException(e.getMessage());
}
}
/**
* Returns a object that is equivalent to the protobuf message for the given
* metadata and pipeconf.
*
* @param message protobuf message
* @param metadata metadata
* @param pipeconf pipeconf pipeconf
* @return object
* @throws CodecException if the given protobuf message cannot be decoded
* (see exception message)
*/
public P decode(M message, X metadata, PiPipeconf pipeconf)
throws CodecException {
checkNotNull(message);
try {
return decode(message, metadata, pipeconf, browserOrFail(pipeconf));
} catch (P4InfoBrowser.NotFoundException e) {
throw new CodecException(e.getMessage());
}
}
/**
* Same as {@link #encode(Object, Object, PiPipeconf)} but returns null in
* case of exceptions, while the error message is logged.
*
* @param object object
* @param metadata metadata
* @param pipeconf pipeconf
* @return protobuf message
*/
private M encodeOrNull(P object, X metadata, PiPipeconf pipeconf) {
checkNotNull(object);
try {
return encode(object, metadata, pipeconf);
} catch (CodecException e) {
log.error("Unable to encode {}: {} [{}]",
object.getClass().getSimpleName(),
e.getMessage(), object.toString());
return null;
}
}
/**
* Same as {@link #decode(Message, Object, PiPipeconf)} but returns null in
* case of exceptions, while the error message is logged.
*
* @param message protobuf message
* @param metadata metadata
* @param pipeconf pipeconf pipeconf
* @return object
*/
private P decodeOrNull(M message, X metadata, PiPipeconf pipeconf) {
checkNotNull(message);
try {
return decode(message, metadata, pipeconf);
} catch (CodecException e) {
log.error("Unable to decode {}: {} [{}]",
message.getClass().getSimpleName(),
e.getMessage(), TextFormat.shortDebugString(message));
return null;
}
}
/**
* Encodes the given list of objects, skipping those that cannot be encoded,
* in which case an error message is logged. For this reason, the returned
* list might have different size than the returned one.
*
* @param objects list of objects
* @param metadata metadata
* @param pipeconf pipeconf
* @return list of protobuf messages
*/
private List<M> encodeAllSkipException(
Collection<P> objects, X metadata, PiPipeconf pipeconf) {
checkNotNull(objects);
if (objects.isEmpty()) {
return ImmutableList.of();
}
return objects.stream()
.map(p -> encodeOrNull(p, metadata, pipeconf))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* Decodes the given list of protobuf messages, skipping those that cannot
* be decoded, on which case an error message is logged. For this reason,
* the returned list might have different size than the returned one.
*
* @param messages list of protobuf messages
* @param metadata metadata
* @param pipeconf pipeconf
* @return list of objects
*/
private List<P> decodeAllSkipException(
Collection<M> messages, X metadata, PiPipeconf pipeconf) {
checkNotNull(messages);
if (messages.isEmpty()) {
return ImmutableList.of();
}
return messages.stream()
.map(m -> decodeOrNull(m, metadata, pipeconf))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* Encodes the given collection of objects. Throws an exception if one or
* more of the given objects cannot be encoded. The returned list is
* guaranteed to have same size and order as the given one.
*
* @param objects list of objects
* @param metadata metadata
* @param pipeconf pipeconf
* @return list of protobuf messages
* @throws CodecException if one or more of the given objects cannot be
* encoded
*/
List<M> encodeAll(Collection<P> objects, X metadata, PiPipeconf pipeconf)
throws CodecException {
checkNotNull(objects);
if (objects.isEmpty()) {
return ImmutableList.of();
}
final List<M> messages = encodeAllSkipException(objects, metadata, pipeconf);
if (objects.size() != messages.size()) {
throw new CodecException(format(
"Unable to encode %d entities of %d given " +
"(see previous logs for details)",
objects.size() - messages.size(), objects.size()));
}
return messages;
}
/**
* Decodes the given collection of protobuf messages. Throws an exception if
* one or more of the given protobuf messages cannot be decoded. The
* returned list is guaranteed to have same size and order as the given
* one.
*
* @param messages list of protobuf messages
* @param metadata metadata
* @param pipeconf pipeconf
* @return list of objects
* @throws CodecException if one or more of the given protobuf messages
* cannot be decoded
*/
List<P> decodeAll(Collection<M> messages, X metadata, PiPipeconf pipeconf)
throws CodecException {
checkNotNull(messages);
if (messages.isEmpty()) {
return ImmutableList.of();
}
final List<P> objects = decodeAllSkipException(messages, metadata, pipeconf);
if (messages.size() != objects.size()) {
throw new CodecException(format(
"Unable to decode %d messages of %d given " +
"(see previous logs for details)",
messages.size() - objects.size(), messages.size()));
}
return objects;
}
/**
* Returns a P4Info browser for the given pipeconf or throws a
* CodecException if not possible.
*
* @param pipeconf pipeconf
* @return P4Info browser
* @throws CodecException if a P4Info browser cannot be obtained
*/
P4InfoBrowser browserOrFail(PiPipeconf pipeconf) throws CodecException {
checkNotNull(pipeconf);
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
if (browser == null) {
throw new CodecException(format(
"Unable to get P4InfoBrowser for pipeconf %s", pipeconf.id()));
}
return browser;
}
}