HIGUCHI Yuta
Committed by Yuta HIGUCHI

ONOS-3472 Fixing ConsistentMap key equality

- ConsistentMap's key equality is based on serialized byte[].
  2 Problems fixed by this patch:

 (1) By caching Key -> String representation,
     Cache will use Key's Object#equals for look up,
     which can possibly have different equality than byte[] equality,
     leading to wrong String to be used as a key in backend Database.

     Fixed by reversing the mapping.

 (2) Similar issues with keySet(), entrySet()
     Set based on reference equality needs to be used to avoid
     deduplication based on Object#equals

     Fixed by replacing Set implementation with MappingSet.

Change-Id: I1b727abd2614a9b72b5b1d02ecca2de26493adcc
...@@ -20,6 +20,7 @@ import com.google.common.cache.CacheBuilder; ...@@ -20,6 +20,7 @@ import com.google.common.cache.CacheBuilder;
20 import com.google.common.cache.CacheLoader; 20 import com.google.common.cache.CacheLoader;
21 import com.google.common.cache.LoadingCache; 21 import com.google.common.cache.LoadingCache;
22 import com.google.common.collect.Maps; 22 import com.google.common.collect.Maps;
23 +
23 import org.onlab.util.HexString; 24 import org.onlab.util.HexString;
24 import org.onlab.util.SharedExecutors; 25 import org.onlab.util.SharedExecutors;
25 import org.onlab.util.Tools; 26 import org.onlab.util.Tools;
...@@ -33,6 +34,7 @@ import org.onosproject.store.service.Versioned; ...@@ -33,6 +34,7 @@ import org.onosproject.store.service.Versioned;
33 import org.slf4j.Logger; 34 import org.slf4j.Logger;
34 35
35 import java.util.Collection; 36 import java.util.Collection;
37 +import java.util.Collections;
36 import java.util.Map; 38 import java.util.Map;
37 import java.util.Map.Entry; 39 import java.util.Map.Entry;
38 import java.util.Objects; 40 import java.util.Objects;
...@@ -92,18 +94,25 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -92,18 +94,25 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
92 private static final String ERROR_NULL_KEY = "Key cannot be null"; 94 private static final String ERROR_NULL_KEY = "Key cannot be null";
93 private static final String ERROR_NULL_VALUE = "Null values are not allowed"; 95 private static final String ERROR_NULL_VALUE = "Null values are not allowed";
94 96
95 - private final LoadingCache<K, String> keyCache = CacheBuilder.newBuilder() 97 + // String representation of serialized byte[] -> original key Object
98 + private final LoadingCache<String, K> keyCache = CacheBuilder.newBuilder()
96 .softValues() 99 .softValues()
97 - .build(new CacheLoader<K, String>() { 100 + .build(new CacheLoader<String, K>() {
98 101
99 @Override 102 @Override
100 - public String load(K key) { 103 + public K load(String key) {
101 - return HexString.toHexString(serializer.encode(key)); 104 + return serializer.decode(HexString.fromHexString(key));
102 } 105 }
103 }); 106 });
104 107
108 + protected String sK(K key) {
109 + String s = HexString.toHexString(serializer.encode(key));
110 + keyCache.put(s, key);
111 + return s;
112 + }
113 +
105 protected K dK(String key) { 114 protected K dK(String key) {
106 - return serializer.decode(HexString.fromHexString(key)); 115 + return keyCache.getUnchecked(key);
107 } 116 }
108 117
109 public DefaultAsyncConsistentMap(String name, 118 public DefaultAsyncConsistentMap(String name,
...@@ -207,7 +216,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -207,7 +216,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
207 public CompletableFuture<Boolean> containsKey(K key) { 216 public CompletableFuture<Boolean> containsKey(K key) {
208 checkNotNull(key, ERROR_NULL_KEY); 217 checkNotNull(key, ERROR_NULL_KEY);
209 final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_KEY); 218 final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_KEY);
210 - return database.mapContainsKey(name, keyCache.getUnchecked(key)) 219 + return database.mapContainsKey(name, sK(key))
211 .whenComplete((r, e) -> timer.stop(e)); 220 .whenComplete((r, e) -> timer.stop(e));
212 } 221 }
213 222
...@@ -223,7 +232,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -223,7 +232,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
223 public CompletableFuture<Versioned<V>> get(K key) { 232 public CompletableFuture<Versioned<V>> get(K key) {
224 checkNotNull(key, ERROR_NULL_KEY); 233 checkNotNull(key, ERROR_NULL_KEY);
225 final MeteringAgent.Context timer = monitor.startTimer(GET); 234 final MeteringAgent.Context timer = monitor.startTimer(GET);
226 - return database.mapGet(name, keyCache.getUnchecked(key)) 235 + return database.mapGet(name, sK(key))
227 .whenComplete((r, e) -> timer.stop(e)) 236 .whenComplete((r, e) -> timer.stop(e))
228 .thenApply(v -> v != null ? v.map(serializer::decode) : null); 237 .thenApply(v -> v != null ? v.map(serializer::decode) : null);
229 } 238 }
...@@ -328,10 +337,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -328,10 +337,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
328 public CompletableFuture<Set<K>> keySet() { 337 public CompletableFuture<Set<K>> keySet() {
329 final MeteringAgent.Context timer = monitor.startTimer(KEY_SET); 338 final MeteringAgent.Context timer = monitor.startTimer(KEY_SET);
330 return database.mapKeySet(name) 339 return database.mapKeySet(name)
331 - .thenApply(s -> s 340 + .thenApply(s -> newMappingKeySet(s))
332 - .stream()
333 - .map(this::dK)
334 - .collect(Collectors.toSet()))
335 .whenComplete((r, e) -> timer.stop(e)); 341 .whenComplete((r, e) -> timer.stop(e));
336 } 342 }
337 343
...@@ -351,10 +357,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -351,10 +357,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
351 final MeteringAgent.Context timer = monitor.startTimer(ENTRY_SET); 357 final MeteringAgent.Context timer = monitor.startTimer(ENTRY_SET);
352 return database.mapEntrySet(name) 358 return database.mapEntrySet(name)
353 .whenComplete((r, e) -> timer.stop(e)) 359 .whenComplete((r, e) -> timer.stop(e))
354 - .thenApply(s -> s 360 + .thenApply(s -> newMappingEntrySet(s));
355 - .stream()
356 - .map(this::mapRawEntry)
357 - .collect(Collectors.toSet()));
358 } 361 }
359 362
360 @Override 363 @Override
...@@ -413,17 +416,31 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V ...@@ -413,17 +416,31 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V
413 checkIfUnmodifiable(); 416 checkIfUnmodifiable();
414 } 417 }
415 418
419 + private Set<K> newMappingKeySet(Set<String> s) {
420 + return new MappingSet<>(s, Collections::unmodifiableSet,
421 + this::sK, this::dK);
422 + }
423 +
424 + private Set<Entry<K, Versioned<V>>> newMappingEntrySet(Set<Entry<String, Versioned<byte[]>>> s) {
425 + return new MappingSet<>(s, Collections::unmodifiableSet,
426 + this::reverseMapRawEntry, this::mapRawEntry);
427 + }
428 +
416 private Map.Entry<K, Versioned<V>> mapRawEntry(Map.Entry<String, Versioned<byte[]>> e) { 429 private Map.Entry<K, Versioned<V>> mapRawEntry(Map.Entry<String, Versioned<byte[]>> e) {
417 return Maps.immutableEntry(dK(e.getKey()), e.getValue().<V>map(serializer::decode)); 430 return Maps.immutableEntry(dK(e.getKey()), e.getValue().<V>map(serializer::decode));
418 } 431 }
419 432
433 + private Map.Entry<String, Versioned<byte[]>> reverseMapRawEntry(Map.Entry<K, Versioned<V>> e) {
434 + return Maps.immutableEntry(sK(e.getKey()), e.getValue().map(serializer::encode));
435 + }
436 +
420 private CompletableFuture<UpdateResult<K, V>> updateAndGet(K key, 437 private CompletableFuture<UpdateResult<K, V>> updateAndGet(K key,
421 Match<V> oldValueMatch, 438 Match<V> oldValueMatch,
422 Match<Long> oldVersionMatch, 439 Match<Long> oldVersionMatch,
423 V value) { 440 V value) {
424 beforeUpdate(key); 441 beforeUpdate(key);
425 return database.mapUpdate(name, 442 return database.mapUpdate(name,
426 - keyCache.getUnchecked(key), 443 + sK(key),
427 oldValueMatch.map(serializer::encode), 444 oldValueMatch.map(serializer::encode),
428 oldVersionMatch, 445 oldVersionMatch,
429 value == null ? null : serializer.encode(value)) 446 value == null ? null : serializer.encode(value))
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
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 +package org.onosproject.store.consistent.impl;
17 +
18 +import java.util.Arrays;
19 +import java.util.Collection;
20 +import java.util.Iterator;
21 +import java.util.Set;
22 +import java.util.function.Function;
23 +import java.util.stream.Collectors;
24 +
25 +import com.google.common.collect.Iterators;
26 +
27 +/**
28 + * Set view backed by Set with element type {@code <BACK>} but returns
29 + * element as {@code <OUT>} for convenience.
30 + *
31 + * @param <BACK> Backing {@link Set} element type.
32 + * MappingSet will follow this type's equality behavior.
33 + * @param <OUT> external facing element type.
34 + * MappingSet will ignores equality defined by this type.
35 + */
36 +class MappingSet<BACK, OUT> implements Set<OUT> {
37 +
38 + private final Set<BACK> backedSet;
39 + private final Function<OUT, BACK> toBack;
40 + private final Function<BACK, OUT> toOut;
41 +
42 + public MappingSet(Set<BACK> backedSet,
43 + Function<Set<BACK>, Set<BACK>> supplier,
44 + Function<OUT, BACK> toBack, Function<BACK, OUT> toOut) {
45 + this.backedSet = supplier.apply(backedSet);
46 + this.toBack = toBack;
47 + this.toOut = toOut;
48 + }
49 +
50 + @Override
51 + public int size() {
52 + return backedSet.size();
53 + }
54 +
55 + @Override
56 + public boolean isEmpty() {
57 + return backedSet.isEmpty();
58 + }
59 +
60 + @Override
61 + public boolean contains(Object o) {
62 + return backedSet.contains(toBack.apply((OUT) o));
63 + }
64 +
65 + @Override
66 + public Iterator<OUT> iterator() {
67 + return Iterators.transform(backedSet.iterator(), toOut::apply);
68 + }
69 +
70 + @Override
71 + public Object[] toArray() {
72 + return backedSet.stream()
73 + .map(toOut)
74 + .toArray();
75 + }
76 +
77 + @Override
78 + public <T> T[] toArray(T[] a) {
79 + return backedSet.stream()
80 + .map(toOut)
81 + .toArray(size -> {
82 + if (size < a.length) {
83 + return (T[]) new Object[size];
84 + } else {
85 + Arrays.fill(a, null);
86 + return a;
87 + }
88 + });
89 + }
90 +
91 + @Override
92 + public boolean add(OUT e) {
93 + return backedSet.add(toBack.apply(e));
94 + }
95 +
96 + @Override
97 + public boolean remove(Object o) {
98 + return backedSet.remove(toBack.apply((OUT) o));
99 + }
100 +
101 + @Override
102 + public boolean containsAll(Collection<?> c) {
103 + return c.stream()
104 + .map(e -> toBack.apply((OUT) e))
105 + .allMatch(backedSet::contains);
106 + }
107 +
108 + @Override
109 + public boolean addAll(Collection<? extends OUT> c) {
110 + return backedSet.addAll(c.stream().map(toBack).collect(Collectors.toList()));
111 + }
112 +
113 + @Override
114 + public boolean retainAll(Collection<?> c) {
115 + return backedSet.retainAll(c.stream()
116 + .map(x -> toBack.apply((OUT) x))
117 + .collect(Collectors.toList()));
118 + }
119 +
120 + @Override
121 + public boolean removeAll(Collection<?> c) {
122 + return backedSet.removeAll(c.stream()
123 + .map(x -> toBack.apply((OUT) x))
124 + .collect(Collectors.toList()));
125 + }
126 +
127 + @Override
128 + public void clear() {
129 + backedSet.clear();
130 + }
131 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
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 +package org.onosproject.store.consistent.impl;
17 +
18 +import static java.util.Collections.unmodifiableCollection;
19 +import static java.util.Collections.unmodifiableSet;
20 +import static org.hamcrest.Matchers.is;
21 +import static org.junit.Assert.*;
22 +
23 +import java.util.Collection;
24 +import java.util.Map;
25 +import java.util.Map.Entry;
26 +import java.util.Objects;
27 +import java.util.Set;
28 +import java.util.concurrent.CompletableFuture;
29 +import java.util.concurrent.ConcurrentHashMap;
30 +import java.util.function.Consumer;
31 +
32 +import org.junit.After;
33 +import org.junit.Before;
34 +import org.junit.Test;
35 +import org.onosproject.core.ApplicationId;
36 +import org.onosproject.core.DefaultApplicationId;
37 +import org.onosproject.store.service.Serializer;
38 +import org.onosproject.store.service.Transaction;
39 +import org.onosproject.store.service.Versioned;
40 +
41 +import com.google.common.base.MoreObjects;
42 +import com.google.common.collect.ImmutableMap;
43 +import com.google.common.collect.ImmutableSet;
44 +import net.kuujo.copycat.Task;
45 +import net.kuujo.copycat.cluster.Cluster;
46 +import net.kuujo.copycat.resource.ResourceState;
47 +
48 +/**
49 + *
50 + */
51 +public class DefaultAsyncConsistentMapTest {
52 +
53 + private static final ApplicationId APP_ID = new DefaultApplicationId(42, "what");
54 +
55 + private static final TestData KEY1A = new TestData("One", "a");
56 + private static final TestData KEY1B = new TestData("One", "b");
57 +
58 + private static final TestData VALUE2A = new TestData("Two", "a");
59 + private static final TestData VALUE2B = new TestData("Two", "b");
60 +
61 + @Before
62 + public void setUp() throws Exception {
63 + }
64 +
65 + @After
66 + public void tearDown() throws Exception {
67 + }
68 +
69 + @Test
70 + public void testKeySet() throws Exception {
71 + DefaultAsyncConsistentMap<TestData, TestData> map;
72 + String name = "map_name";
73 + Database database = new TestDatabase();
74 + Serializer serializer = Serializer.forTypes(TestData.class);
75 +
76 + map = new DefaultAsyncConsistentMap<>(name, APP_ID, database, serializer,
77 + false, false, false);
78 + map.put(KEY1A, VALUE2A);
79 + map.put(KEY1B, VALUE2A);
80 +
81 + Set<TestData> set = map.keySet().get();
82 + assertEquals("Should contain 2 keys",
83 + 2, set.size());
84 + assertThat(set.contains(KEY1A), is(true));
85 + assertThat(set.contains(KEY1B), is(true));
86 + assertThat(set.contains(new TestData("One", "a")), is(true));
87 + }
88 +
89 + @Test
90 + public void testEntrySet() throws Exception {
91 + DefaultAsyncConsistentMap<TestData, TestData> map;
92 + String name = "map_name";
93 + Database database = new TestDatabase();
94 + Serializer serializer = Serializer.forTypes(TestData.class);
95 +
96 + map = new DefaultAsyncConsistentMap<>(name, APP_ID, database, serializer,
97 + false, false, false);
98 + map.put(KEY1A, VALUE2A);
99 + map.put(KEY1B, VALUE2A);
100 +
101 + assertEquals("Should contain 2 entry",
102 + 2,
103 + map.entrySet().get().size());
104 + }
105 +
106 + /**
107 + * Object to be used as a test data.
108 + *
109 + * {@link Object#equals(Object)} use only part of it's fields.
110 + *
111 + * As a result there can be 2 instances which the
112 + * serialized bytes are not-equal but
113 + * {@link Object#equals(Object)}-wise they are equal.
114 + */
115 + public static class TestData {
116 +
117 + private final String theKey;
118 +
119 + @SuppressWarnings("unused")
120 + private final String notUsedForEquals;
121 +
122 + public TestData(String theKey, String notUsedForEquals) {
123 + this.theKey = theKey;
124 + this.notUsedForEquals = notUsedForEquals;
125 + }
126 +
127 + @Override
128 + public int hashCode() {
129 + return Objects.hashCode(theKey);
130 + }
131 +
132 + @Override
133 + public boolean equals(Object obj) {
134 + if (obj instanceof TestData) {
135 + TestData that = (TestData) obj;
136 + return Objects.equals(this.theKey, that.theKey);
137 + }
138 + return false;
139 + }
140 +
141 + @Override
142 + public String toString() {
143 + return MoreObjects.toStringHelper(this)
144 + .add("theKey", theKey)
145 + .add("notUsedForEquals", notUsedForEquals)
146 + .toString();
147 + }
148 + }
149 +
150 + /**
151 + * {@link Database} implementation for testing.
152 + *
153 + * There is only 1 backing Map, {@code mapName} will be ignored.
154 + */
155 + public class TestDatabase implements Database {
156 +
157 + Map<String, Versioned<byte[]>> map = new ConcurrentHashMap<>();
158 +
159 + @Override
160 + public CompletableFuture<Set<String>> maps() {
161 + return CompletableFuture.completedFuture(ImmutableSet.of());
162 + }
163 +
164 + @Override
165 + public CompletableFuture<Map<String, Long>> counters() {
166 + return CompletableFuture.completedFuture(ImmutableMap.of());
167 + }
168 +
169 + @Override
170 + public CompletableFuture<Integer> mapSize(String mapName) {
171 + return CompletableFuture.completedFuture(map.size());
172 + }
173 +
174 + @Override
175 + public CompletableFuture<Boolean> mapIsEmpty(String mapName) {
176 + return CompletableFuture.completedFuture(map.isEmpty());
177 + }
178 +
179 + @Override
180 + public CompletableFuture<Boolean> mapContainsKey(String mapName,
181 + String key) {
182 + return CompletableFuture.completedFuture(map.containsKey(key));
183 + }
184 +
185 + @Override
186 + public CompletableFuture<Boolean> mapContainsValue(String mapName,
187 + byte[] value) {
188 + return CompletableFuture.completedFuture(map.containsValue(value));
189 + }
190 +
191 + @Override
192 + public CompletableFuture<Versioned<byte[]>> mapGet(String mapName,
193 + String key) {
194 + return CompletableFuture.completedFuture(map.get(key));
195 + }
196 +
197 + @Override
198 + public synchronized CompletableFuture<Result<UpdateResult<String, byte[]>>> mapUpdate(String mapName,
199 + String key,
200 + Match<byte[]> valueMatch,
201 + Match<Long> versionMatch,
202 + byte[] value) {
203 +
204 + boolean updated = false;
205 + final Versioned<byte[]> oldValue;
206 + final Versioned<byte[]> newValue;
207 +
208 + Versioned<byte[]> old = map.getOrDefault(key, new Versioned<byte[]>(null, 0));
209 + if (valueMatch.matches(old.value()) && versionMatch.matches(old.version())) {
210 + updated = true;
211 + oldValue = old;
212 + newValue = new Versioned<>(value, old.version() + 1);
213 + map.put(key, newValue);
214 + } else {
215 + updated = false;
216 + oldValue = old;
217 + newValue = old;
218 + }
219 + return CompletableFuture.completedFuture(
220 + Result.ok(new UpdateResult<String, byte[]>(updated,
221 + mapName, key, oldValue, newValue)));
222 + }
223 +
224 + @Override
225 + public CompletableFuture<Result<Void>> mapClear(String mapName) {
226 + throw new UnsupportedOperationException();
227 + }
228 +
229 + @Override
230 + public CompletableFuture<Set<String>> mapKeySet(String mapName) {
231 + return CompletableFuture.completedFuture(unmodifiableSet(map.keySet()));
232 + }
233 +
234 + @Override
235 + public CompletableFuture<Collection<Versioned<byte[]>>> mapValues(String mapName) {
236 + return CompletableFuture.completedFuture(unmodifiableCollection(map.values()));
237 + }
238 +
239 + @Override
240 + public CompletableFuture<Set<Entry<String, Versioned<byte[]>>>> mapEntrySet(String mapName) {
241 + return CompletableFuture.completedFuture(unmodifiableSet(map.entrySet()));
242 + }
243 +
244 + @Override
245 + public CompletableFuture<Long> counterAddAndGet(String counterName,
246 + long delta) {
247 + throw new UnsupportedOperationException();
248 + }
249 +
250 + @Override
251 + public CompletableFuture<Long> counterGetAndAdd(String counterName,
252 + long delta) {
253 + throw new UnsupportedOperationException();
254 + }
255 +
256 + @Override
257 + public CompletableFuture<Void> counterSet(String counterName,
258 + long value) {
259 + throw new UnsupportedOperationException();
260 + }
261 +
262 + @Override
263 + public CompletableFuture<Boolean> counterCompareAndSet(String counterName,
264 + long expectedValue,
265 + long update) {
266 + throw new UnsupportedOperationException();
267 + }
268 +
269 + @Override
270 + public CompletableFuture<Long> counterGet(String counterName) {
271 + throw new UnsupportedOperationException();
272 + }
273 +
274 + @Override
275 + public CompletableFuture<Long> queueSize(String queueName) {
276 + throw new UnsupportedOperationException();
277 + }
278 +
279 + @Override
280 + public CompletableFuture<Void> queuePush(String queueName,
281 + byte[] entry) {
282 + throw new UnsupportedOperationException();
283 + }
284 +
285 + @Override
286 + public CompletableFuture<byte[]> queuePop(String queueName) {
287 + throw new UnsupportedOperationException();
288 + }
289 +
290 + @Override
291 + public CompletableFuture<byte[]> queuePeek(String queueName) {
292 + throw new UnsupportedOperationException();
293 + }
294 +
295 + @Override
296 + public CompletableFuture<CommitResponse> prepareAndCommit(Transaction transaction) {
297 + throw new UnsupportedOperationException();
298 + }
299 +
300 + @Override
301 + public CompletableFuture<Boolean> prepare(Transaction transaction) {
302 + throw new UnsupportedOperationException();
303 + }
304 +
305 + @Override
306 + public CompletableFuture<CommitResponse> commit(Transaction transaction) {
307 + throw new UnsupportedOperationException();
308 + }
309 +
310 + @Override
311 + public CompletableFuture<Boolean> rollback(Transaction transaction) {
312 + throw new UnsupportedOperationException();
313 + }
314 +
315 + @Override
316 + public String name() {
317 + return "name";
318 + }
319 +
320 + @Override
321 + public ResourceState state() {
322 + return ResourceState.HEALTHY;
323 + }
324 +
325 + @Override
326 + public Cluster cluster() {
327 + throw new UnsupportedOperationException();
328 + }
329 +
330 + @Override
331 + public Database addStartupTask(Task<CompletableFuture<Void>> task) {
332 + throw new UnsupportedOperationException();
333 + }
334 +
335 + @Override
336 + public Database addShutdownTask(Task<CompletableFuture<Void>> task) {
337 + throw new UnsupportedOperationException();
338 + }
339 +
340 + @Override
341 + public CompletableFuture<Database> open() {
342 + return CompletableFuture.completedFuture(this);
343 + }
344 +
345 + @Override
346 + public boolean isOpen() {
347 + return true;
348 + }
349 +
350 + @Override
351 + public CompletableFuture<Void> close() {
352 + return CompletableFuture.completedFuture(null);
353 + }
354 +
355 + @Override
356 + public boolean isClosed() {
357 + return false;
358 + }
359 +
360 + @Override
361 + public void registerConsumer(Consumer<StateMachineUpdate> consumer) {
362 + }
363 +
364 + @Override
365 + public void unregisterConsumer(Consumer<StateMachineUpdate> consumer) {
366 + }
367 + }
368 +
369 +}