BUG-5280: centralize ShardSnapshot operations
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / persisted / ShardDataTreeSnapshot.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.cluster.datastore.persisted;
9
10 import com.google.common.annotations.Beta;
11 import java.io.ByteArrayInputStream;
12 import java.io.DataInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.util.Optional;
16 import javax.annotation.Nonnull;
17 import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils;
18 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * Abstract base class for snapshots of the ShardDataTree.
24  *
25  * @author Robert Varga
26  */
27 @Beta
28 public abstract class ShardDataTreeSnapshot {
29     private static final Logger LOG = LoggerFactory.getLogger(ShardDataTreeSnapshot.class);
30
31     ShardDataTreeSnapshot() {
32         // Hidden to prevent subclassing from outside of this package
33     }
34
35     public static ShardDataTreeSnapshot deserialize(final byte[] bytes) throws IOException {
36         /**
37          * Unfortunately versions prior to Boron did not include any way to evolve the snapshot format and contained
38          * only the raw data stored in the datastore. Furthermore utilities involved do not check if the array is
39          * completely consumed, which has a nasty side-effect when coupled with the fact that PayloadVersion writes
40          * a short value.
41          *
42          * Since our versions fit into a single byte, we end up writing the 0 as the first byte, which would be
43          * interpreted as 'not present' by the old snapshot format, which uses writeBoolean/readBoolean. A further
44          * complication is that readBoolean interprets any non-zero value as true, hence we cannot use a wild value
45          * to cause it to fail.
46          */
47         if (isLegacyStream(bytes)) {
48             return deserializeLegacy(bytes);
49         }
50
51         try {
52             try (final InputStream is = new ByteArrayInputStream(bytes)) {
53                 try (final DataInputStream dis = new DataInputStream(is)) {
54                     final ShardDataTreeSnapshot ret = AbstractVersionedShardDataTreeSnapshot.deserialize(dis);
55
56                     // Make sure we consume all bytes, otherwise something went very wrong
57                     final int bytesLeft = dis.available();
58                     if (bytesLeft != 0) {
59                         throw new IOException("Deserialization left " + bytesLeft + " in the buffer");
60                     }
61
62
63                     return ret;
64                 }
65             }
66         } catch (IOException e) {
67             LOG.debug("Failed to deserialize versioned stream, attempting pre-Lithium ProtoBuf", e);
68             return deserializeLegacy(bytes);
69         }
70     }
71
72     /**
73      * Get the root data node contained in this snapshot.
74      *
75      * @return An optional root node.
76      */
77     public abstract Optional<NormalizedNode<?, ?>> getRootNode();
78
79     /**
80      * Serialize this snapshot into a byte array for persistence.
81      *
82      * @return Serialized snapshot
83      * @throws IOException when a serialization problem occurs
84      */
85     public abstract @Nonnull byte[] serialize() throws IOException;
86
87     private static boolean isLegacyStream(final byte[] bytes) {
88         if (bytes.length < 2) {
89             // Versioned streams have at least two bytes
90             return true;
91         }
92
93         /*
94          * The stream could potentially be a versioned stream. Here we rely on the signature marker available
95          * in org.opendaylight.controller.cluster.datastore.node.utils.stream.TokenTypes.
96          *
97          * For an old stream to be this long, the first byte has to be non-zero and the second byte has to be 0xAB.
98          *
99          * For a versioned stream, that translates to at least version 427 -- giving us at least 421 further versions
100          * before this check breaks.
101          */
102         return bytes[0] != 0 && bytes[1] == (byte)0xAB;
103     }
104
105     @Deprecated
106     private static ShardDataTreeSnapshot deserializeLegacy(final byte[] bytes) {
107         return new PreBoronShardDataTreeSnapshot(SerializationUtils.deserializeNormalizedNode(bytes));
108     }
109 }
110