blob: d1e9de2e764675c6566ffa0f5e186cb5f13514e7 [file] [log] [blame]
Yi Tseng0b809722017-11-03 10:23:26 -07001/*
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
Carmelo Cascone356ab8b2019-09-25 01:02:53 -070017package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
Yi Tseng0b809722017-11-03 10:23:26 -070018
19import com.google.common.collect.ImmutableList;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020020import org.onlab.packet.Ethernet;
Yi Tseng0b809722017-11-03 10:23:26 -070021import org.onlab.util.KryoNamespace;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080022import org.onlab.util.SharedExecutors;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020023import org.onosproject.core.ApplicationId;
24import org.onosproject.core.CoreService;
Yi Tseng0b809722017-11-03 10:23:26 -070025import org.onosproject.net.DeviceId;
26import org.onosproject.net.PortNumber;
27import org.onosproject.net.behaviour.NextGroup;
28import org.onosproject.net.behaviour.Pipeliner;
29import org.onosproject.net.behaviour.PipelinerContext;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020030import org.onosproject.net.flow.DefaultFlowRule;
31import org.onosproject.net.flow.DefaultTrafficSelector;
32import org.onosproject.net.flow.DefaultTrafficTreatment;
Yi Tseng0b809722017-11-03 10:23:26 -070033import org.onosproject.net.flow.FlowRule;
34import org.onosproject.net.flow.FlowRuleOperations;
Yi Tseng0b809722017-11-03 10:23:26 -070035import org.onosproject.net.flow.FlowRuleService;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020036import org.onosproject.net.flow.TrafficSelector;
37import org.onosproject.net.flow.TrafficTreatment;
38import org.onosproject.net.flow.criteria.Criteria;
39import org.onosproject.net.flow.criteria.PiCriterion;
Yi Tseng0b809722017-11-03 10:23:26 -070040import org.onosproject.net.flowobjective.FilteringObjective;
41import org.onosproject.net.flowobjective.FlowObjectiveStore;
42import org.onosproject.net.flowobjective.ForwardingObjective;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080043import org.onosproject.net.flowobjective.IdNextTreatment;
Yi Tseng0b809722017-11-03 10:23:26 -070044import org.onosproject.net.flowobjective.NextObjective;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080045import org.onosproject.net.flowobjective.NextTreatment;
Yi Tseng0b809722017-11-03 10:23:26 -070046import org.onosproject.net.flowobjective.Objective;
47import org.onosproject.net.flowobjective.ObjectiveError;
48import org.onosproject.net.group.GroupDescription;
Yi Tseng0b809722017-11-03 10:23:26 -070049import org.onosproject.net.group.GroupService;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020050import org.onosproject.net.pi.runtime.PiAction;
51import org.onosproject.net.pi.runtime.PiActionParam;
52import org.onosproject.pipelines.fabric.FabricConstants;
53import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
Carmelo Cascone356ab8b2019-09-25 01:02:53 -070054import org.onosproject.pipelines.fabric.impl.behaviour.AbstractFabricHandlerBehavior;
55import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
Yi Tseng0b809722017-11-03 10:23:26 -070056import org.onosproject.store.serializers.KryoNamespaces;
57import org.slf4j.Logger;
58
59import java.util.Collection;
60import java.util.List;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080061import java.util.Objects;
Yi Tsengfe13f3e2018-08-19 03:09:54 +080062import java.util.concurrent.CompletableFuture;
Yi Tsengfe13f3e2018-08-19 03:09:54 +080063import java.util.concurrent.ExecutorService;
Yi Tseng1b154bd2017-11-20 17:48:19 -080064import java.util.stream.Collectors;
Yi Tseng0b809722017-11-03 10:23:26 -070065
Carmelo Casconeb5324e72018-11-25 02:26:32 -080066import static java.lang.String.format;
pierventre4e02fb52020-08-13 16:35:15 +020067import static org.onosproject.net.flowobjective.NextObjective.Type.SIMPLE;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020068import static org.onosproject.pipelines.fabric.impl.behaviour.FabricInterpreter.ONE;
69import static org.onosproject.pipelines.fabric.impl.behaviour.FabricInterpreter.ZERO;
Carmelo Cascone356ab8b2019-09-25 01:02:53 -070070import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020071import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.FilteringObjectiveTranslator.FWD_IPV4_ROUTING;
Yi Tseng0b809722017-11-03 10:23:26 -070072import static org.slf4j.LoggerFactory.getLogger;
73
74/**
Carmelo Casconeb5324e72018-11-25 02:26:32 -080075 * Pipeliner implementation for fabric pipeline which uses ObjectiveTranslator
76 * implementations to translate flow objectives for the different blocks,
77 * filtering, forwarding and next.
Yi Tseng0b809722017-11-03 10:23:26 -070078 */
Carmelo Casconeb5324e72018-11-25 02:26:32 -080079public class FabricPipeliner extends AbstractFabricHandlerBehavior
80 implements Pipeliner {
81
Yi Tseng0b809722017-11-03 10:23:26 -070082 private static final Logger log = getLogger(FabricPipeliner.class);
Carmelo Cascone2388cc12021-05-26 19:30:30 +020083 private static final int DEFAULT_FLOW_PRIORITY = 100;
84 public static final int DEFAULT_VLAN = 4094;
Yi Tseng0b809722017-11-03 10:23:26 -070085
86 protected static final KryoNamespace KRYO = new KryoNamespace.Builder()
87 .register(KryoNamespaces.API)
88 .register(FabricNextGroup.class)
89 .build("FabricPipeliner");
90
Yi Tseng0b809722017-11-03 10:23:26 -070091 protected DeviceId deviceId;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020092 protected ApplicationId appId;
Yi Tseng0b809722017-11-03 10:23:26 -070093 protected FlowRuleService flowRuleService;
94 protected GroupService groupService;
95 protected FlowObjectiveStore flowObjectiveStore;
Carmelo Cascone2388cc12021-05-26 19:30:30 +020096 protected CoreService coreService;
Yi Tseng0b809722017-11-03 10:23:26 -070097
Carmelo Casconeb5324e72018-11-25 02:26:32 -080098 private FilteringObjectiveTranslator filteringTranslator;
99 private ForwardingObjectiveTranslator forwardingTranslator;
100 private NextObjectiveTranslator nextTranslator;
Charles Chan91ea9722018-08-30 15:56:32 -0700101
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800102 private final ExecutorService callbackExecutor = SharedExecutors.getPoolThreadExecutor();
Yi Tseng0b809722017-11-03 10:23:26 -0700103
Daniele Morof51d0c12019-07-30 10:43:10 -0700104 /**
105 * Creates a new instance of this behavior with the given capabilities.
106 *
107 * @param capabilities capabilities
108 */
109 public FabricPipeliner(FabricCapabilities capabilities) {
110 super(capabilities);
111 }
112
113 /**
114 * Create a new instance of this behaviour. Used by the abstract projectable
115 * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
116 */
117 public FabricPipeliner() {
118 super();
119 }
120
Yi Tseng0b809722017-11-03 10:23:26 -0700121 @Override
122 public void init(DeviceId deviceId, PipelinerContext context) {
123 this.deviceId = deviceId;
124 this.flowRuleService = context.directory().get(FlowRuleService.class);
125 this.groupService = context.directory().get(GroupService.class);
126 this.flowObjectiveStore = context.directory().get(FlowObjectiveStore.class);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800127 this.filteringTranslator = new FilteringObjectiveTranslator(deviceId, capabilities);
128 this.forwardingTranslator = new ForwardingObjectiveTranslator(deviceId, capabilities);
129 this.nextTranslator = new NextObjectiveTranslator(deviceId, capabilities);
Carmelo Cascone2388cc12021-05-26 19:30:30 +0200130 this.coreService = context.directory().get(CoreService.class);
131 this.appId = coreService.getAppId(FabricPipeconfLoader.PIPELINE_APP_NAME);
Carmelo Cascone2a308ff2021-06-01 18:31:57 -0700132
133 initializePipeline();
Carmelo Cascone2388cc12021-05-26 19:30:30 +0200134 }
135
136 protected void initializePipeline() {
137 // Set up rules for packet-out forwarding. We support only IPv4 routing.
138 final int cpuPort = capabilities.cpuPort().get();
139 flowRuleService.applyFlowRules(
140 ingressVlanRule(cpuPort, false, DEFAULT_VLAN),
141 fwdClassifierRule(cpuPort, null, Ethernet.TYPE_IPV4, FWD_IPV4_ROUTING,
142 DEFAULT_FLOW_PRIORITY));
Yi Tseng0b809722017-11-03 10:23:26 -0700143 }
144
145 @Override
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800146 public void filter(FilteringObjective obj) {
147 final ObjectiveTranslation result = filteringTranslator.translate(obj);
148 handleResult(obj, result);
Yi Tseng0b809722017-11-03 10:23:26 -0700149 }
150
151 @Override
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800152 public void forward(ForwardingObjective obj) {
153 final ObjectiveTranslation result = forwardingTranslator.translate(obj);
154 handleResult(obj, result);
Yi Tseng0b809722017-11-03 10:23:26 -0700155 }
156
157 @Override
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800158 public void next(NextObjective obj) {
159 if (obj.op() == Objective.Operation.VERIFY) {
Yi Tseng1b154bd2017-11-20 17:48:19 -0800160 // TODO: support VERIFY operation
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800161 log.debug("VERIFY operation not yet supported for NextObjective, will return success");
162 success(obj);
Yi Tseng1b154bd2017-11-20 17:48:19 -0800163 return;
164 }
165
pierventre4e02fb52020-08-13 16:35:15 +0200166 if (obj.op() == Objective.Operation.MODIFY && obj.type() != SIMPLE) {
167 log.warn("MODIFY operation not yet supported for NextObjective {}, will return failure :(",
168 obj.type());
169 if (log.isTraceEnabled()) {
170 log.trace("Objective {}", obj);
171 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800172 fail(obj, ObjectiveError.UNSUPPORTED);
Charles Chan91ea9722018-08-30 15:56:32 -0700173 return;
174 }
175
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800176 final ObjectiveTranslation result = nextTranslator.translate(obj);
177 handleResult(obj, result);
Yi Tseng0b809722017-11-03 10:23:26 -0700178 }
179
180 @Override
181 public List<String> getNextMappings(NextGroup nextGroup) {
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800182 final FabricNextGroup fabricNextGroup = KRYO.deserialize(nextGroup.data());
183 return fabricNextGroup.nextMappings().stream()
184 .map(m -> format("%s -> %s", fabricNextGroup.type(), m))
Yi Tseng1b154bd2017-11-20 17:48:19 -0800185 .collect(Collectors.toList());
Yi Tseng0b809722017-11-03 10:23:26 -0700186 }
187
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800188 private void handleResult(Objective obj, ObjectiveTranslation result) {
189 if (result.error().isPresent()) {
190 fail(obj, result.error().get());
191 return;
192 }
193 processGroups(obj, result.groups());
194 processFlows(obj, result.flowRules());
195 if (obj instanceof NextObjective) {
196 handleNextGroup((NextObjective) obj);
197 }
198 success(obj);
Yi Tseng0b809722017-11-03 10:23:26 -0700199 }
200
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800201 private void handleNextGroup(NextObjective obj) {
202 switch (obj.op()) {
203 case REMOVE:
204 removeNextGroup(obj);
205 break;
206 case ADD:
207 case ADD_TO_EXISTING:
208 case REMOVE_FROM_EXISTING:
209 case MODIFY:
210 putNextGroup(obj);
211 break;
212 case VERIFY:
213 break;
214 default:
215 log.error("Unknown NextObjective operation '{}'", obj.op());
216 }
217 }
218
219 private void processFlows(Objective objective, Collection<FlowRule> flowRules) {
Yi Tseng0b809722017-11-03 10:23:26 -0700220 if (flowRules.isEmpty()) {
Yi Tsengf78e1742018-04-08 19:57:17 +0800221 return;
Yi Tseng0b809722017-11-03 10:23:26 -0700222 }
pierventre4e02fb52020-08-13 16:35:15 +0200223
224 if (log.isTraceEnabled()) {
225 log.trace("Objective {} -> Flows {}", objective, flowRules);
226 }
227
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800228 final FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
229 switch (objective.op()) {
230 case ADD:
231 case ADD_TO_EXISTING:
pierventre4e02fb52020-08-13 16:35:15 +0200232 case MODIFY:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800233 flowRules.forEach(ops::add);
234 break;
235 case REMOVE:
236 case REMOVE_FROM_EXISTING:
237 flowRules.forEach(ops::remove);
238 break;
239 default:
pierventre4e02fb52020-08-13 16:35:15 +0200240 log.warn("Unsupported Objective operation {}", objective.op());
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800241 return;
wu914ed232018-10-23 11:19:53 +0800242 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800243 flowRuleService.apply(ops.build());
Yi Tseng0b809722017-11-03 10:23:26 -0700244 }
245
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800246 private void processGroups(Objective objective, Collection<GroupDescription> groups) {
Yi Tseng0b809722017-11-03 10:23:26 -0700247 if (groups.isEmpty()) {
Yi Tsengf78e1742018-04-08 19:57:17 +0800248 return;
Yi Tseng0b809722017-11-03 10:23:26 -0700249 }
pierventre4e02fb52020-08-13 16:35:15 +0200250
251 if (log.isTraceEnabled()) {
252 log.trace("Objective {} -> Groups {}", objective, groups);
253 }
254
Yi Tseng0b809722017-11-03 10:23:26 -0700255 switch (objective.op()) {
256 case ADD:
257 groups.forEach(groupService::addGroup);
258 break;
259 case REMOVE:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800260 groups.forEach(group -> groupService.removeGroup(
261 deviceId, group.appCookie(), objective.appId()));
Yi Tseng0b809722017-11-03 10:23:26 -0700262 break;
Yi Tseng1b154bd2017-11-20 17:48:19 -0800263 case ADD_TO_EXISTING:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800264 groups.forEach(group -> groupService.addBucketsToGroup(
265 deviceId, group.appCookie(), group.buckets(),
266 group.appCookie(), group.appId())
Charles Chan91ea9722018-08-30 15:56:32 -0700267 );
Yi Tseng1b154bd2017-11-20 17:48:19 -0800268 break;
269 case REMOVE_FROM_EXISTING:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800270 groups.forEach(group -> groupService.removeBucketsFromGroup(
271 deviceId, group.appCookie(), group.buckets(),
272 group.appCookie(), group.appId())
Charles Chan91ea9722018-08-30 15:56:32 -0700273 );
Yi Tseng1b154bd2017-11-20 17:48:19 -0800274 break;
pierventre4e02fb52020-08-13 16:35:15 +0200275 case MODIFY:
276 // Modify is only supported for simple next objective
277 // Replace group bucket directly
278 groups.forEach(group -> groupService.setBucketsForGroup(
279 deviceId, group.appCookie(), group.buckets(),
280 group.appCookie(), group.appId())
281 );
282 break;
Yi Tseng0b809722017-11-03 10:23:26 -0700283 default:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800284 log.warn("Unsupported Objective operation {}", objective.op());
Yi Tseng0b809722017-11-03 10:23:26 -0700285 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800286 }
Yi Tseng0b809722017-11-03 10:23:26 -0700287
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800288 private void fail(Objective objective, ObjectiveError error) {
289 CompletableFuture.runAsync(
290 () -> objective.context().ifPresent(
291 ctx -> ctx.onError(objective, error)), callbackExecutor);
Yi Tsengfe13f3e2018-08-19 03:09:54 +0800292
Yi Tseng0b809722017-11-03 10:23:26 -0700293 }
294
Charles Chan91ea9722018-08-30 15:56:32 -0700295
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800296 private void success(Objective objective) {
297 CompletableFuture.runAsync(
298 () -> objective.context().ifPresent(
299 ctx -> ctx.onSuccess(objective)), callbackExecutor);
Yi Tseng0b809722017-11-03 10:23:26 -0700300 }
301
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800302 private void removeNextGroup(NextObjective obj) {
303 final NextGroup removed = flowObjectiveStore.removeNextGroup(obj.id());
304 if (removed == null) {
Carmelo Cascone2388cc12021-05-26 19:30:30 +0200305 log.debug("NextGroup {} was not found in FlowObjectiveStore", obj);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800306 }
Charles Chan91ea9722018-08-30 15:56:32 -0700307 }
308
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800309 private void putNextGroup(NextObjective obj) {
310 final List<String> nextMappings = obj.nextTreatments().stream()
311 .map(this::nextTreatmentToMappingString)
312 .filter(Objects::nonNull)
313 .collect(Collectors.toList());
314 final FabricNextGroup nextGroup = new FabricNextGroup(obj.type(), nextMappings);
315 flowObjectiveStore.putNextGroup(obj.id(), nextGroup);
316 }
317
318 private String nextTreatmentToMappingString(NextTreatment n) {
319 switch (n.type()) {
320 case TREATMENT:
321 final PortNumber p = outputPort(n);
322 return p == null ? "UNKNOWN"
323 : format("OUTPUT:%s", p.toString());
324 case ID:
325 final IdNextTreatment id = (IdNextTreatment) n;
326 return format("NEXT_ID:%d", id.nextId());
Yi Tseng0b809722017-11-03 10:23:26 -0700327 default:
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800328 log.warn("Unknown NextTreatment type '{}'", n.type());
329 return "???";
Yi Tseng0b809722017-11-03 10:23:26 -0700330 }
Yi Tseng0b809722017-11-03 10:23:26 -0700331 }
332
Carmelo Cascone2388cc12021-05-26 19:30:30 +0200333 public FlowRule ingressVlanRule(long port, boolean vlanValid, int vlanId) {
334 final TrafficSelector selector = DefaultTrafficSelector.builder()
335 .add(Criteria.matchInPort(PortNumber.portNumber(port)))
336 .add(PiCriterion.builder()
337 .matchExact(FabricConstants.HDR_VLAN_IS_VALID, vlanValid ? ONE : ZERO)
338 .build())
339 .build();
340 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
341 .piTableAction(PiAction.builder()
342 .withId(vlanValid ? FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT
343 : FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
344 .withParameter(new PiActionParam(FabricConstants.VLAN_ID, vlanId))
345 .build())
346 .build();
347 return DefaultFlowRule.builder()
348 .withSelector(selector)
349 .withTreatment(treatment)
350 .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
351 .makePermanent()
352 .withPriority(DEFAULT_FLOW_PRIORITY)
353 .forDevice(deviceId)
354 .fromApp(appId)
355 .build();
356 }
357
358 public FlowRule fwdClassifierRule(int port, Short ethType, short ipEthType, byte fwdType, int priority) {
359 final TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
360 .matchInPort(PortNumber.portNumber(port))
361 .matchPi(PiCriterion.builder()
362 .matchExact(FabricConstants.HDR_IP_ETH_TYPE, ipEthType)
363 .build());
364 if (ethType != null) {
365 selectorBuilder.matchEthType(ethType);
366 }
367 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
368 .piTableAction(PiAction.builder()
369 .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
370 .withParameter(new PiActionParam(FabricConstants.FWD_TYPE, fwdType))
371 .build())
372 .build();
373 return DefaultFlowRule.builder()
374 .withSelector(selectorBuilder.build())
375 .withTreatment(treatment)
376 .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
377 .makePermanent()
378 .withPriority(priority)
379 .forDevice(deviceId)
380 .fromApp(appId)
381 .build();
382 }
383
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800384 /**
385 * NextGroup implementation.
386 */
387 private static class FabricNextGroup implements NextGroup {
Yi Tseng0b809722017-11-03 10:23:26 -0700388
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800389 private final NextObjective.Type type;
390 private final List<String> nextMappings;
391
392 FabricNextGroup(NextObjective.Type type, List<String> nextMappings) {
Yi Tseng0b809722017-11-03 10:23:26 -0700393 this.type = type;
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800394 this.nextMappings = ImmutableList.copyOf(nextMappings);
Yi Tseng0b809722017-11-03 10:23:26 -0700395 }
396
Charles Chan91ea9722018-08-30 15:56:32 -0700397 NextObjective.Type type() {
Yi Tseng0b809722017-11-03 10:23:26 -0700398 return type;
399 }
400
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800401 Collection<String> nextMappings() {
402 return nextMappings;
Yi Tseng0b809722017-11-03 10:23:26 -0700403 }
404
405 @Override
406 public byte[] data() {
407 return KRYO.serialize(this);
408 }
409 }
Yi Tseng0b809722017-11-03 10:23:26 -0700410}