blob: 2bb75a30e6477ef0e21d8a37006427f82ed105c3 [file] [log] [blame]
Carmelo Cascone4c289b72019-01-22 15:30:45 -08001/*
2 * Copyright 2019-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.p4runtime.ctl.codec;
18
19import com.google.common.collect.ImmutableList;
20import com.google.protobuf.Message;
21import com.google.protobuf.TextFormat;
22import org.onosproject.net.pi.model.PiPipeconf;
23import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
24import org.onosproject.p4runtime.ctl.utils.PipeconfHelper;
25import org.slf4j.Logger;
26
27import java.util.Collection;
28import java.util.List;
29import java.util.Objects;
30import java.util.stream.Collectors;
31
32import static com.google.common.base.Preconditions.checkNotNull;
33import static java.lang.String.format;
34import static org.slf4j.LoggerFactory.getLogger;
35
36/**
37 * Abstract implementation of a general codec that translates pipeconf-related
38 * objects into protobuf messages and vice versa.
39 *
40 * @param <P> object
41 * @param <M> protobuf message class
42 * @param <X> metadata class
43 */
44abstract class AbstractCodec<P, M extends Message, X> {
45
46 protected final Logger log = getLogger(this.getClass());
47
48 protected abstract M encode(P object, X metadata, PiPipeconf pipeconf,
49 P4InfoBrowser browser)
50 throws CodecException, P4InfoBrowser.NotFoundException;
51
52 protected abstract P decode(M message, X metadata, PiPipeconf pipeconf,
53 P4InfoBrowser browser)
54 throws CodecException, P4InfoBrowser.NotFoundException;
55
56 /**
57 * Returns a protobuf message that is equivalent to the given object for the
58 * given metadata and pipeconf.
59 *
60 * @param object object
61 * @param metadata metadata
62 * @param pipeconf pipeconf
63 * @return protobuf message
64 * @throws CodecException if the given object cannot be encoded (see
65 * exception message)
66 */
67 public M encode(P object, X metadata, PiPipeconf pipeconf)
68 throws CodecException {
69 checkNotNull(object);
70 try {
71 return encode(object, metadata, pipeconf, browserOrFail(pipeconf));
72 } catch (P4InfoBrowser.NotFoundException e) {
73 throw new CodecException(e.getMessage());
74 }
75 }
76
77 /**
78 * Returns a object that is equivalent to the protobuf message for the given
79 * metadata and pipeconf.
80 *
81 * @param message protobuf message
82 * @param metadata metadata
83 * @param pipeconf pipeconf pipeconf
84 * @return object
85 * @throws CodecException if the given protobuf message cannot be decoded
86 * (see exception message)
87 */
88 public P decode(M message, X metadata, PiPipeconf pipeconf)
89 throws CodecException {
90 checkNotNull(message);
91 try {
92 return decode(message, metadata, pipeconf, browserOrFail(pipeconf));
93 } catch (P4InfoBrowser.NotFoundException e) {
94 throw new CodecException(e.getMessage());
95 }
96 }
97
98 /**
99 * Same as {@link #encode(Object, Object, PiPipeconf)} but returns null in
100 * case of exceptions, while the error message is logged.
101 *
102 * @param object object
103 * @param metadata metadata
104 * @param pipeconf pipeconf
105 * @return protobuf message
106 */
107 private M encodeOrNull(P object, X metadata, PiPipeconf pipeconf) {
108 checkNotNull(object);
109 try {
110 return encode(object, metadata, pipeconf);
111 } catch (CodecException e) {
112 log.error("Unable to encode {}: {} [{}]",
113 object.getClass().getSimpleName(),
114 e.getMessage(), object.toString());
115 return null;
116 }
117 }
118
119 /**
120 * Same as {@link #decode(Message, Object, PiPipeconf)} but returns null in
121 * case of exceptions, while the error message is logged.
122 *
123 * @param message protobuf message
124 * @param metadata metadata
125 * @param pipeconf pipeconf pipeconf
126 * @return object
127 */
128 private P decodeOrNull(M message, X metadata, PiPipeconf pipeconf) {
129 checkNotNull(message);
130 try {
131 return decode(message, metadata, pipeconf);
132 } catch (CodecException e) {
133 log.error("Unable to decode {}: {} [{}]",
134 message.getClass().getSimpleName(),
135 e.getMessage(), TextFormat.shortDebugString(message));
136 return null;
137 }
138 }
139
140 /**
141 * Encodes the given list of objects, skipping those that cannot be encoded,
142 * in which case an error message is logged. For this reason, the returned
143 * list might have different size than the returned one.
144 *
145 * @param objects list of objects
146 * @param metadata metadata
147 * @param pipeconf pipeconf
148 * @return list of protobuf messages
149 */
150 private List<M> encodeAllSkipException(
151 Collection<P> objects, X metadata, PiPipeconf pipeconf) {
152 checkNotNull(objects);
153 if (objects.isEmpty()) {
154 return ImmutableList.of();
155 }
156 return objects.stream()
157 .map(p -> encodeOrNull(p, metadata, pipeconf))
158 .filter(Objects::nonNull)
159 .collect(Collectors.toList());
160 }
161
162 /**
163 * Decodes the given list of protobuf messages, skipping those that cannot
164 * be decoded, on which case an error message is logged. For this reason,
165 * the returned list might have different size than the returned one.
166 *
167 * @param messages list of protobuf messages
168 * @param metadata metadata
169 * @param pipeconf pipeconf
170 * @return list of objects
171 */
172 private List<P> decodeAllSkipException(
173 Collection<M> messages, X metadata, PiPipeconf pipeconf) {
174 checkNotNull(messages);
175 if (messages.isEmpty()) {
176 return ImmutableList.of();
177 }
178 return messages.stream()
179 .map(m -> decodeOrNull(m, metadata, pipeconf))
180 .filter(Objects::nonNull)
181 .collect(Collectors.toList());
182 }
183
184 /**
185 * Encodes the given collection of objects. Throws an exception if one or
186 * more of the given objects cannot be encoded. The returned list is
187 * guaranteed to have same size and order as the given one.
188 *
189 * @param objects list of objects
190 * @param metadata metadata
191 * @param pipeconf pipeconf
192 * @return list of protobuf messages
193 * @throws CodecException if one or more of the given objects cannot be
194 * encoded
195 */
196 List<M> encodeAll(Collection<P> objects, X metadata, PiPipeconf pipeconf)
197 throws CodecException {
198 checkNotNull(objects);
199 if (objects.isEmpty()) {
200 return ImmutableList.of();
201 }
202 final List<M> messages = encodeAllSkipException(objects, metadata, pipeconf);
203 if (objects.size() != messages.size()) {
204 throw new CodecException(format(
205 "Unable to encode %d entities of %d given " +
206 "(see previous logs for details)",
207 objects.size() - messages.size(), objects.size()));
208 }
209 return messages;
210 }
211
212 /**
213 * Decodes the given collection of protobuf messages. Throws an exception if
214 * one or more of the given protobuf messages cannot be decoded. The
215 * returned list is guaranteed to have same size and order as the given
216 * one.
217 *
218 * @param messages list of protobuf messages
219 * @param metadata metadata
220 * @param pipeconf pipeconf
221 * @return list of objects
222 * @throws CodecException if one or more of the given protobuf messages
223 * cannot be decoded
224 */
225 List<P> decodeAll(Collection<M> messages, X metadata, PiPipeconf pipeconf)
226 throws CodecException {
227 checkNotNull(messages);
228 if (messages.isEmpty()) {
229 return ImmutableList.of();
230 }
231 final List<P> objects = decodeAllSkipException(messages, metadata, pipeconf);
232 if (messages.size() != objects.size()) {
233 throw new CodecException(format(
234 "Unable to decode %d messages of %d given " +
235 "(see previous logs for details)",
236 messages.size() - objects.size(), messages.size()));
237 }
238 return objects;
239 }
240
241 /**
242 * Returns a P4Info browser for the given pipeconf or throws a
243 * CodecException if not possible.
244 *
245 * @param pipeconf pipeconf
246 * @return P4Info browser
247 * @throws CodecException if a P4Info browser cannot be obtained
248 */
249 P4InfoBrowser browserOrFail(PiPipeconf pipeconf) throws CodecException {
250 checkNotNull(pipeconf);
251 final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
252 if (browser == null) {
253 throw new CodecException(format(
254 "Unable to get P4InfoBrowser for pipeconf %s", pipeconf.id()));
255 }
256 return browser;
257 }
258}