This is an automated email from the ASF dual-hosted git repository. ctubbsii pushed a commit to branch 3.1 in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/3.1 by this push: new e560e0e0c3 Add ZooKeeper Namespace Id-to-name mapping (#4996) e560e0e0c3 is described below commit e560e0e0c3d83bfea17e49fb7135ef9c7f99642c Author: John K <69256191+meatballspaghe...@users.noreply.github.com> AuthorDate: Fri Nov 15 01:01:58 2024 -0500 Add ZooKeeper Namespace Id-to-name mapping (#4996) Add mapping for ZooKeeper Namespace Ids to Names in a single ZK node - Implement Json-formatted mapping of namespace Ids to Names in ZooKeeper. Stored on the "/namespaces" node. Initially created in ZooKeeperInitializer. - Create NamespaceMapping.java to hold methods initializing, adding, retrieving, serializing, and deserializing namespace Id to Name map in ZooKeeper. - Build namespace id to name mapping of current ZooKeeper namespaces in Upgrader11to12. Delete all current "/name" nodes. - Add relevant test code to Upgrader11to12Test. - Sort the mapping in serialize() for testing reliability and readability - Remove now-unneeded Utils.checkNamespaceDoesNotExist() This is part of #4698, but only for the namespace mapping. It does not yet address table mappings. --------- Co-authored-by: Christopher Tubbs <ctubb...@apache.org> --- .../java/org/apache/accumulo/core/Constants.java | 1 - .../accumulo/core/clientImpl/ClientContext.java | 6 + .../accumulo/core/clientImpl/NamespaceMapping.java | 164 +++++++++++++++++++++ .../accumulo/core/clientImpl/Namespaces.java | 56 +------ .../core/clientImpl/NamespaceMappingTest.java | 72 +++++++++ .../accumulo/server/init/ZooKeeperInitializer.java | 7 +- .../accumulo/server/tables/TableManager.java | 34 +++-- .../apache/accumulo/manager/tableOps/Utils.java | 12 -- .../create/PopulateZookeeperWithNamespace.java | 19 ++- .../tableOps/namespace/rename/RenameNamespace.java | 23 +-- .../accumulo/manager/upgrade/Upgrader11to12.java | 29 +++- .../manager/upgrade/Upgrader11to12Test.java | 43 +++++- 12 files changed, 353 insertions(+), 113 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/Constants.java b/core/src/main/java/org/apache/accumulo/core/Constants.java index 411af753cf..ca9e0b62ff 100644 --- a/core/src/main/java/org/apache/accumulo/core/Constants.java +++ b/core/src/main/java/org/apache/accumulo/core/Constants.java @@ -48,7 +48,6 @@ public class Constants { public static final String ZTABLE_NAMESPACE = "/namespace"; public static final String ZNAMESPACES = "/namespaces"; - public static final String ZNAMESPACE_NAME = "/name"; public static final String ZMANAGERS = "/managers"; public static final String ZMANAGER_LOCK = ZMANAGERS + "/lock"; diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java index 899bad6c10..298a14329d 100644 --- a/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java +++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java @@ -142,6 +142,7 @@ public class ClientContext implements AccumuloClient { private final Supplier<SaslConnectionParams> saslSupplier; private final Supplier<SslConnectionParams> sslSupplier; private final Supplier<ScanServerSelector> scanServerSelectorSupplier; + private final NamespaceMapping namespaces; private TCredentials rpcCreds; private ThriftTransportPool thriftTransportPool; private ZookeeperLockChecker zkLockChecker; @@ -249,6 +250,7 @@ public class ClientContext implements AccumuloClient { clientThreadPools = ThreadPools.getClientThreadPools(ueh); } } + this.namespaces = new NamespaceMapping(this); } public Ample getAmple() { @@ -1114,4 +1116,8 @@ public class ClientContext implements AccumuloClient { return this.zkLockChecker; } + public NamespaceMapping getNamespaces() { + return namespaces; + } + } diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/NamespaceMapping.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/NamespaceMapping.java new file mode 100644 index 0000000000..7a6ce7ab6b --- /dev/null +++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/NamespaceMapping.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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.apache.accumulo.core.clientImpl; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptySortedMap; +import static java.util.Objects.requireNonNull; +import static org.apache.accumulo.core.util.LazySingletons.GSON; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.clientImpl.thrift.TableOperation; +import org.apache.accumulo.core.clientImpl.thrift.TableOperationExceptionType; +import org.apache.accumulo.core.data.NamespaceId; +import org.apache.accumulo.core.fate.zookeeper.ZooCache; +import org.apache.accumulo.core.fate.zookeeper.ZooReaderWriter; +import org.apache.zookeeper.KeeperException; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.reflect.TypeToken; + +public class NamespaceMapping { + // type token must represent a mutable type, so it can be altered in the mutateExisting methods + // without needing to make a copy + private static final Type MAP_TYPE = new TypeToken<TreeMap<String,String>>() {}.getType(); + private final ClientContext context; + private volatile SortedMap<NamespaceId,String> currentNamespaceMap = emptySortedMap(); + private volatile SortedMap<String,NamespaceId> currentNamespaceReverseMap = emptySortedMap(); + private volatile long lastMzxid; + + public NamespaceMapping(ClientContext context) { + this.context = context; + } + + public static void put(ZooReaderWriter zoo, String zPath, NamespaceId namespaceId, + String namespaceName) + throws InterruptedException, KeeperException, AcceptableThriftTableOperationException { + if (Namespace.DEFAULT.id().equals(namespaceId) || Namespace.ACCUMULO.id().equals(namespaceId)) { + throw new AssertionError( + "Putting built-in namespaces in map should not be possible after init"); + } + zoo.mutateExisting(zPath, data -> { + var namespaces = deserialize(data); + if (namespaces.containsKey(namespaceId.canonical())) { + throw new AcceptableThriftTableOperationException(null, namespaceId.canonical(), + TableOperation.CREATE, TableOperationExceptionType.NAMESPACE_EXISTS, + "Namespace Id already exists"); + } + namespaces.put(namespaceId.canonical(), namespaceName); + return serialize(namespaces); + }); + } + + public static void remove(ZooReaderWriter zoo, String zPath, NamespaceId namespaceId) + throws InterruptedException, KeeperException, AcceptableThriftTableOperationException { + if (Namespace.DEFAULT.id().equals(namespaceId) || Namespace.ACCUMULO.id().equals(namespaceId)) { + throw new AssertionError("Removing built-in namespaces in map should not be possible"); + } + zoo.mutateExisting(zPath, data -> { + var namespaces = deserialize(data); + if (!namespaces.containsKey(namespaceId.canonical())) { + throw new AcceptableThriftTableOperationException(null, namespaceId.canonical(), + TableOperation.DELETE, TableOperationExceptionType.NAMESPACE_NOTFOUND, + "Namespace already removed while processing"); + } + namespaces.remove(namespaceId.canonical()); + return serialize(namespaces); + }); + } + + public static void rename(ZooReaderWriter zoo, String zPath, NamespaceId namespaceId, + String oldName, String newName) + throws InterruptedException, KeeperException, AcceptableThriftTableOperationException { + if (Namespace.DEFAULT.id().equals(namespaceId) || Namespace.ACCUMULO.id().equals(namespaceId)) { + throw new AssertionError("Renaming built-in namespaces in map should not be possible"); + } + zoo.mutateExisting(zPath, current -> { + var currentNamespaceMap = deserialize(current); + final String currentName = currentNamespaceMap.get(namespaceId.canonical()); + if (currentName.equals(newName)) { + return null; // assume in this case the operation is running again, so we are done + } + if (!currentName.equals(oldName)) { + throw new AcceptableThriftTableOperationException(null, oldName, TableOperation.RENAME, + TableOperationExceptionType.NAMESPACE_NOTFOUND, "Name changed while processing"); + } + if (currentNamespaceMap.containsValue(newName)) { + throw new AcceptableThriftTableOperationException(null, newName, TableOperation.RENAME, + TableOperationExceptionType.NAMESPACE_EXISTS, "Namespace name already exists"); + } + currentNamespaceMap.put(namespaceId.canonical(), newName); + return serialize(currentNamespaceMap); + }); + } + + public static byte[] serialize(Map<String,String> map) { + return GSON.get().toJson(new TreeMap<>(map), MAP_TYPE).getBytes(UTF_8); + } + + public static Map<String,String> deserialize(byte[] data) { + requireNonNull(data, "/namespaces node should not be null"); + return GSON.get().fromJson(new String(data, UTF_8), MAP_TYPE); + } + + private synchronized void update() { + final ZooCache zc = context.getZooCache(); + final String zPath = context.getZooKeeperRoot() + Constants.ZNAMESPACES; + final ZooCache.ZcStat stat = new ZooCache.ZcStat(); + + byte[] data = zc.get(zPath, stat); + if (stat.getMzxid() > lastMzxid) { + if (data == null) { + throw new IllegalStateException("namespaces node should not be null"); + } else { + Map<String,String> idToName = deserialize(data); + if (!idToName.containsKey(Namespace.DEFAULT.id().canonical()) + || !idToName.containsKey(Namespace.ACCUMULO.id().canonical())) { + throw new IllegalStateException("Built-in namespace is not present in map"); + } + var converted = ImmutableSortedMap.<NamespaceId,String>naturalOrder(); + var convertedReverse = ImmutableSortedMap.<String,NamespaceId>naturalOrder(); + idToName.forEach((idString, name) -> { + var id = NamespaceId.of(idString); + converted.put(id, name); + convertedReverse.put(name, id); + }); + currentNamespaceMap = converted.build(); + currentNamespaceReverseMap = convertedReverse.build(); + } + lastMzxid = stat.getMzxid(); + } + } + + public SortedMap<NamespaceId,String> getIdToNameMap() { + update(); + return currentNamespaceMap; + } + + public SortedMap<String,NamespaceId> getNameToIdMap() { + update(); + return currentNamespaceReverseMap; + } + +} diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/Namespaces.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/Namespaces.java index 4368b31896..366375f82f 100644 --- a/core/src/main/java/org/apache/accumulo/core/clientImpl/Namespaces.java +++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/Namespaces.java @@ -18,21 +18,14 @@ */ package org.apache.accumulo.core.clientImpl; -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.BiConsumer; -import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.NamespaceNotFoundException; import org.apache.accumulo.core.data.NamespaceId; import org.apache.accumulo.core.data.TableId; -import org.apache.accumulo.core.fate.zookeeper.ZooCache; import org.apache.accumulo.core.util.tables.TableNameUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,9 +34,7 @@ public class Namespaces { private static final Logger log = LoggerFactory.getLogger(Namespaces.class); public static boolean exists(ClientContext context, NamespaceId namespaceId) { - ZooCache zc = context.getZooCache(); - List<String> namespaceIds = zc.getChildren(context.getZooKeeperRoot() + Constants.ZNAMESPACES); - return namespaceIds.contains(namespaceId.canonical()); + return context.getNamespaces().getIdToNameMap().containsKey(namespaceId); } public static List<TableId> getTableIds(ClientContext context, NamespaceId namespaceId) @@ -70,39 +61,18 @@ public class Namespaces { return names; } - /** - * Gets all the namespaces from ZK. The first arg (t) the BiConsumer accepts is the ID and the - * second (u) is the namespaceName. - */ - private static void getAllNamespaces(ClientContext context, - BiConsumer<String,String> biConsumer) { - final ZooCache zc = context.getZooCache(); - List<String> namespaceIds = zc.getChildren(context.getZooKeeperRoot() + Constants.ZNAMESPACES); - for (String id : namespaceIds) { - byte[] path = zc.get(context.getZooKeeperRoot() + Constants.ZNAMESPACES + "/" + id - + Constants.ZNAMESPACE_NAME); - if (path != null) { - biConsumer.accept(id, new String(path, UTF_8)); - } - } - } - /** * Return sorted map with key = ID, value = namespaceName */ public static SortedMap<NamespaceId,String> getIdToNameMap(ClientContext context) { - SortedMap<NamespaceId,String> idMap = new TreeMap<>(); - getAllNamespaces(context, (id, name) -> idMap.put(NamespaceId.of(id), name)); - return idMap; + return context.getNamespaces().getIdToNameMap(); } /** * Return sorted map with key = namespaceName, value = ID */ public static SortedMap<String,NamespaceId> getNameToIdMap(ClientContext context) { - SortedMap<String,NamespaceId> nameMap = new TreeMap<>(); - getAllNamespaces(context, (id, name) -> nameMap.put(name, NamespaceId.of(id))); - return nameMap; + return context.getNamespaces().getNameToIdMap(); } /** @@ -110,17 +80,12 @@ public class Namespaces { */ public static NamespaceId getNamespaceId(ClientContext context, String namespaceName) throws NamespaceNotFoundException { - final ArrayList<NamespaceId> singleId = new ArrayList<>(1); - getAllNamespaces(context, (id, name) -> { - if (name.equals(namespaceName)) { - singleId.add(NamespaceId.of(id)); - } - }); - if (singleId.isEmpty()) { + var id = context.getNamespaces().getNameToIdMap().get(namespaceName); + if (id == null) { throw new NamespaceNotFoundException(null, namespaceName, "getNamespaceId() failed to find namespace"); } - return singleId.get(0); + return id; } /** @@ -150,13 +115,8 @@ public class Namespaces { */ public static String getNamespaceName(ClientContext context, NamespaceId namespaceId) throws NamespaceNotFoundException { - String name; - ZooCache zc = context.getZooCache(); - byte[] path = zc.get(context.getZooKeeperRoot() + Constants.ZNAMESPACES + "/" - + namespaceId.canonical() + Constants.ZNAMESPACE_NAME); - if (path != null) { - name = new String(path, UTF_8); - } else { + String name = getIdToNameMap(context).get(namespaceId); + if (name == null) { throw new NamespaceNotFoundException(namespaceId.canonical(), null, "getNamespaceName() failed to find namespace"); } diff --git a/core/src/test/java/org/apache/accumulo/core/clientImpl/NamespaceMappingTest.java b/core/src/test/java/org/apache/accumulo/core/clientImpl/NamespaceMappingTest.java new file mode 100644 index 0000000000..833f17ca4a --- /dev/null +++ b/core/src/test/java/org/apache/accumulo/core/clientImpl/NamespaceMappingTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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.apache.accumulo.core.clientImpl; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.junit.jupiter.api.Test; + +import com.google.gson.JsonSyntaxException; + +class NamespaceMappingTest { + + @Test + void testSerialize() { + assertThrows(NullPointerException.class, () -> NamespaceMapping.serialize(null)); + assertEquals("{}", new String(NamespaceMapping.serialize(Map.of()), UTF_8)); + assertEquals("{\"a\":\"b\"}", new String(NamespaceMapping.serialize(Map.of("a", "b")), UTF_8)); + assertEquals("{\"a\":\"b\",\"c\":\"d\"}", + new String(NamespaceMapping.serialize(Map.of("a", "b", "c", "d")), UTF_8)); + } + + @Test + void testDeserialize() { + assertThrows(NullPointerException.class, () -> NamespaceMapping.deserialize(null)); + assertEquals(null, NamespaceMapping.deserialize(new byte[0])); + assertTrue(NamespaceMapping.deserialize("{}".getBytes(UTF_8)) instanceof TreeMap); + assertEquals(Map.of(), NamespaceMapping.deserialize("{}".getBytes(UTF_8))); + assertEquals(Map.of("a", "b"), NamespaceMapping.deserialize("{\"a\":\"b\"}".getBytes(UTF_8))); + assertEquals(Map.of("a", "b", "c", "d"), + NamespaceMapping.deserialize("{\"a\":\"b\",\"c\":\"d\"}".getBytes(UTF_8))); + + // check malformed json + assertThrows(JsonSyntaxException.class, + () -> NamespaceMapping.deserialize("-".getBytes(UTF_8))); + // check incorrect json type for string value + assertThrows(JsonSyntaxException.class, + () -> NamespaceMapping.deserialize("{\"a\":{}}".getBytes(UTF_8))); + // check valid json, but not a map + assertThrows(JsonSyntaxException.class, + () -> NamespaceMapping.deserialize("\"[\"a\"]\"".getBytes(UTF_8))); + + // strange edge case because empty json array can be converted into an empty map; we don't ever + // expect this to be found, but there's no easy way to check for this edge case that is worth + // the effort + assertTrue(NamespaceMapping.deserialize("[]".getBytes(UTF_8)) instanceof SortedMap); + assertEquals(Map.of(), NamespaceMapping.deserialize("[]".getBytes(UTF_8))); + } + +} diff --git a/server/base/src/main/java/org/apache/accumulo/server/init/ZooKeeperInitializer.java b/server/base/src/main/java/org/apache/accumulo/server/init/ZooKeeperInitializer.java index 50dfc76b23..1fc771dc09 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/init/ZooKeeperInitializer.java +++ b/server/base/src/main/java/org/apache/accumulo/server/init/ZooKeeperInitializer.java @@ -21,10 +21,12 @@ package org.apache.accumulo.server.init; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; +import java.util.Map; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.admin.TimeType; import org.apache.accumulo.core.clientImpl.Namespace; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; import org.apache.accumulo.core.data.InstanceId; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Value; @@ -110,7 +112,10 @@ public class ZooKeeperInitializer { String zkInstanceRoot = Constants.ZROOT + "/" + instanceId; zoo.putPersistentData(zkInstanceRoot + Constants.ZTABLES, Constants.ZTABLES_INITIAL_ID, ZooUtil.NodeExistsPolicy.FAIL); - zoo.putPersistentData(zkInstanceRoot + Constants.ZNAMESPACES, new byte[0], + zoo.putPersistentData(zkInstanceRoot + Constants.ZNAMESPACES, + NamespaceMapping + .serialize(Map.of(Namespace.DEFAULT.id().canonical(), Namespace.DEFAULT.name(), + Namespace.ACCUMULO.id().canonical(), Namespace.ACCUMULO.name())), ZooUtil.NodeExistsPolicy.FAIL); TableManager.prepareNewNamespaceState(context, Namespace.DEFAULT.id(), Namespace.DEFAULT.name(), diff --git a/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java b/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java index 380942b8aa..15ce8d5941 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java +++ b/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java @@ -29,6 +29,9 @@ import java.util.Set; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.NamespaceNotFoundException; +import org.apache.accumulo.core.clientImpl.AcceptableThriftTableOperationException; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; +import org.apache.accumulo.core.clientImpl.thrift.TableOperationExceptionType; import org.apache.accumulo.core.data.InstanceId; import org.apache.accumulo.core.data.NamespaceId; import org.apache.accumulo.core.data.TableId; @@ -67,14 +70,14 @@ public class TableManager { private final ZooReaderWriter zoo; private final ZooCache zooStateCache; - public static void prepareNewNamespaceState(ZooReaderWriter zoo, final PropStore propStore, - InstanceId instanceId, NamespaceId namespaceId, String namespace, - NodeExistsPolicy existsPolicy) throws KeeperException, InterruptedException { + public static void prepareNewNamespaceState(final ServerContext context, NamespaceId namespaceId, + String namespace, NodeExistsPolicy existsPolicy) + throws KeeperException, InterruptedException { + final PropStore propStore = context.getPropStore(); + final InstanceId instanceId = context.getInstanceID(); log.debug("Creating ZooKeeper entries for new namespace {} (ID: {})", namespace, namespaceId); - String zPath = Constants.ZROOT + "/" + instanceId + Constants.ZNAMESPACES + "/" + namespaceId; - - zoo.putPersistentData(zPath, new byte[0], existsPolicy); - zoo.putPersistentData(zPath + Constants.ZNAMESPACE_NAME, namespace.getBytes(UTF_8), + context.getZooReaderWriter().putPersistentData( + Constants.ZROOT + "/" + instanceId + Constants.ZNAMESPACES + "/" + namespaceId, new byte[0], existsPolicy); var propKey = NamespacePropKey.of(instanceId, namespaceId); if (!propStore.exists(propKey)) { @@ -82,13 +85,6 @@ public class TableManager { } } - public static void prepareNewNamespaceState(final ServerContext context, NamespaceId namespaceId, - String namespace, NodeExistsPolicy existsPolicy) - throws KeeperException, InterruptedException { - prepareNewNamespaceState(context.getZooReaderWriter(), context.getPropStore(), - context.getInstanceID(), namespaceId, namespace, existsPolicy); - } - public static void prepareNewTableState(ZooReaderWriter zoo, PropStore propStore, InstanceId instanceId, TableId tableId, NamespaceId namespaceId, String tableName, TableState state, NodeExistsPolicy existsPolicy) @@ -329,7 +325,15 @@ public class TableManager { } public void removeNamespace(NamespaceId namespaceId) - throws KeeperException, InterruptedException { + throws KeeperException, InterruptedException, AcceptableThriftTableOperationException { + try { + NamespaceMapping.remove(zoo, zkRoot + Constants.ZNAMESPACES, namespaceId); + } catch (AcceptableThriftTableOperationException e) { + // ignore not found, because that's what we're trying to do anyway + if (e.getType() != TableOperationExceptionType.NAMESPACE_NOTFOUND) { + throw e; + } + } zoo.recursiveDelete(zkRoot + Constants.ZNAMESPACES + "/" + namespaceId, NodeMissingPolicy.SKIP); } diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/Utils.java b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/Utils.java index be051943a3..8fc09eac2c 100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/Utils.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/Utils.java @@ -258,18 +258,6 @@ public class Utils { return Utils.getLock(env.getContext(), id, tid, LockType.READ); } - public static void checkNamespaceDoesNotExist(ServerContext context, String namespace, - NamespaceId namespaceId, TableOperation operation) - throws AcceptableThriftTableOperationException { - - NamespaceId n = Namespaces.lookupNamespaceId(context, namespace); - - if (n != null && !n.equals(namespaceId)) { - throw new AcceptableThriftTableOperationException(null, namespace, operation, - TableOperationExceptionType.NAMESPACE_EXISTS, null); - } - } - /** * Given a fully-qualified Path and a flag indicating if the file info is base64 encoded or not, * retrieve the data from a file on the file system. It is assumed that the file is textual and diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/create/PopulateZookeeperWithNamespace.java b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/create/PopulateZookeeperWithNamespace.java index 939b6f0916..b47223f151 100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/create/PopulateZookeeperWithNamespace.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/create/PopulateZookeeperWithNamespace.java @@ -18,6 +18,8 @@ */ package org.apache.accumulo.manager.tableOps.namespace.create; +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; import org.apache.accumulo.core.clientImpl.thrift.TableOperation; import org.apache.accumulo.core.fate.Repo; import org.apache.accumulo.core.fate.zookeeper.DistributedReadWriteLock.LockType; @@ -50,17 +52,17 @@ class PopulateZookeeperWithNamespace extends ManagerRepo { Utils.getTableNameLock().lock(); try { - Utils.checkNamespaceDoesNotExist(manager.getContext(), namespaceInfo.namespaceName, - namespaceInfo.namespaceId, TableOperation.CREATE); - - TableManager.prepareNewNamespaceState(manager.getContext(), namespaceInfo.namespaceId, + var context = manager.getContext(); + NamespaceMapping.put(context.getZooReaderWriter(), + Constants.ZROOT + "/" + context.getInstanceID() + Constants.ZNAMESPACES, + namespaceInfo.namespaceId, namespaceInfo.namespaceName); + TableManager.prepareNewNamespaceState(context, namespaceInfo.namespaceId, namespaceInfo.namespaceName, NodeExistsPolicy.OVERWRITE); - PropUtil.setProperties(manager.getContext(), - NamespacePropKey.of(manager.getContext(), namespaceInfo.namespaceId), + PropUtil.setProperties(context, NamespacePropKey.of(context, namespaceInfo.namespaceId), namespaceInfo.props); - manager.getContext().clearTableListCache(); + context.clearTableListCache(); return new FinishCreateNamespace(namespaceInfo); } finally { @@ -70,8 +72,9 @@ class PopulateZookeeperWithNamespace extends ManagerRepo { @Override public void undo(long tid, Manager manager) throws Exception { + var context = manager.getContext(); manager.getTableManager().removeNamespace(namespaceInfo.namespaceId); - manager.getContext().clearTableListCache(); + context.clearTableListCache(); Utils.unreserveNamespace(manager, namespaceInfo.namespaceId, tid, LockType.WRITE); } diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/rename/RenameNamespace.java b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/rename/RenameNamespace.java index 3fdddf0758..b1fcb9f30b 100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/rename/RenameNamespace.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/namespace/rename/RenameNamespace.java @@ -18,12 +18,9 @@ */ package org.apache.accumulo.manager.tableOps.namespace.rename; -import static java.nio.charset.StandardCharsets.UTF_8; - import org.apache.accumulo.core.Constants; -import org.apache.accumulo.core.clientImpl.AcceptableThriftTableOperationException; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; import org.apache.accumulo.core.clientImpl.thrift.TableOperation; -import org.apache.accumulo.core.clientImpl.thrift.TableOperationExceptionType; import org.apache.accumulo.core.data.NamespaceId; import org.apache.accumulo.core.fate.Repo; import org.apache.accumulo.core.fate.zookeeper.DistributedReadWriteLock.LockType; @@ -59,23 +56,9 @@ public class RenameNamespace extends ManagerRepo { Utils.getTableNameLock().lock(); try { - Utils.checkNamespaceDoesNotExist(manager.getContext(), newName, namespaceId, - TableOperation.RENAME); - - final String tap = manager.getZooKeeperRoot() + Constants.ZNAMESPACES + "/" + namespaceId - + Constants.ZNAMESPACE_NAME; + NamespaceMapping.rename(zoo, manager.getZooKeeperRoot() + Constants.ZNAMESPACES, namespaceId, + oldName, newName); - zoo.mutateExisting(tap, current -> { - final String currentName = new String(current, UTF_8); - if (currentName.equals(newName)) { - return null; // assume in this case the operation is running again, so we are done - } - if (!currentName.equals(oldName)) { - throw new AcceptableThriftTableOperationException(null, oldName, TableOperation.RENAME, - TableOperationExceptionType.NAMESPACE_NOTFOUND, "Name changed while processing"); - } - return newName.getBytes(UTF_8); - }); manager.getContext().clearTableListCache(); } finally { Utils.getTableNameLock().unlock(); diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/upgrade/Upgrader11to12.java b/server/manager/src/main/java/org/apache/accumulo/manager/upgrade/Upgrader11to12.java index a130d11445..60d80aaa44 100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/upgrade/Upgrader11to12.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/upgrade/Upgrader11to12.java @@ -26,17 +26,20 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.BatchDeleter; import org.apache.accumulo.core.client.BatchWriter; import org.apache.accumulo.core.client.IsolatedScanner; import org.apache.accumulo.core.client.MutationsRejectedException; import org.apache.accumulo.core.client.Scanner; import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Range; @@ -81,7 +84,10 @@ public class Upgrader11to12 implements Upgrader { static final Set<Text> UPGRADE_FAMILIES = Set.of(DataFileColumnFamily.NAME, CHOPPED, ExternalCompactionColumnFamily.NAME); - public static final String ZTRACERS = "/tracers"; + private static final String ZTRACERS = "/tracers"; + + @VisibleForTesting + static final String ZNAMESPACE_NAME = "/name"; @Override public void upgradeZookeeper(@NonNull ServerContext context) { @@ -117,6 +123,27 @@ public class Upgrader11to12 implements Upgrader { zrw.overwritePersistentData(rootBase, rtm.toJson().getBytes(UTF_8), stat.getVersion()); log.info("Root metadata in ZooKeeper after upgrade: {}", rtm.toJson()); } + + String zPath = Constants.ZROOT + "/" + context.getInstanceID() + Constants.ZNAMESPACES; + byte[] namespacesData = zrw.getData(zPath); + if (namespacesData.length != 0) { + throw new IllegalStateException( + "Unexpected data found under namespaces node: " + new String(namespacesData, UTF_8)); + } + List<String> namespaceIdList = zrw.getChildren(zPath); + Map<String,String> namespaceMap = new HashMap<>(); + for (String namespaceId : namespaceIdList) { + String namespaceNamePath = zPath + "/" + namespaceId + ZNAMESPACE_NAME; + namespaceMap.put(namespaceId, new String(zrw.getData(namespaceNamePath), UTF_8)); + } + byte[] mapping = NamespaceMapping.serialize(namespaceMap); + zrw.putPersistentData(zPath, mapping, ZooUtil.NodeExistsPolicy.OVERWRITE); + + for (String namespaceId : namespaceIdList) { + String namespaceNamePath = zPath + "/" + namespaceId + ZNAMESPACE_NAME; + zrw.delete(namespaceNamePath); + } + } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException( diff --git a/server/manager/src/test/java/org/apache/accumulo/manager/upgrade/Upgrader11to12Test.java b/server/manager/src/test/java/org/apache/accumulo/manager/upgrade/Upgrader11to12Test.java index d176a4c56c..bb21b4efec 100644 --- a/server/manager/src/test/java/org/apache/accumulo/manager/upgrade/Upgrader11to12Test.java +++ b/server/manager/src/test/java/org/apache/accumulo/manager/upgrade/Upgrader11to12Test.java @@ -20,8 +20,11 @@ package org.apache.accumulo.manager.upgrade; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.accumulo.manager.upgrade.Upgrader11to12.UPGRADE_FAMILIES; +import static org.apache.accumulo.manager.upgrade.Upgrader11to12.ZNAMESPACE_NAME; +import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; @@ -40,10 +43,13 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.BatchWriter; import org.apache.accumulo.core.client.MutationsRejectedException; +import org.apache.accumulo.core.clientImpl.NamespaceMapping; import org.apache.accumulo.core.data.ColumnUpdate; import org.apache.accumulo.core.data.InstanceId; import org.apache.accumulo.core.data.Key; @@ -346,14 +352,18 @@ public class Upgrader11to12Test { Upgrader11to12 upgrader = new Upgrader11to12(); ServerContext context = createMock(ServerContext.class); - ZooReaderWriter zrw = createMock(ZooReaderWriter.class); + ZooReaderWriter zrw = createStrictMock(ZooReaderWriter.class); expect(context.getInstanceID()).andReturn(iid).anyTimes(); expect(context.getZooReaderWriter()).andReturn(zrw).anyTimes(); + zrw.recursiveDelete(Constants.ZROOT + "/" + iid.canonical() + "/tracers", + ZooUtil.NodeMissingPolicy.SKIP); + expectLastCall().once(); + Capture<Stat> statCapture = newCapture(); - expect(zrw.getData(eq("/accumulo/" + iid.canonical() + "/root_tablet"), capture(statCapture))) - .andAnswer(() -> { + expect(zrw.getData(eq(Constants.ZROOT + "/" + iid.canonical() + "/root_tablet"), + capture(statCapture))).andAnswer(() -> { Stat stat = statCapture.getValue(); stat.setCtime(System.currentTimeMillis()); stat.setMtime(System.currentTimeMillis()); @@ -364,12 +374,31 @@ public class Upgrader11to12Test { }).once(); Capture<byte[]> byteCapture = newCapture(); - expect(zrw.overwritePersistentData(eq("/accumulo/" + iid.canonical() + "/root_tablet"), + expect(zrw.overwritePersistentData(eq(Constants.ZROOT + "/" + iid.canonical() + "/root_tablet"), capture(byteCapture), eq(123))).andReturn(true).once(); - zrw.recursiveDelete("/accumulo/" + iid.canonical() + "/tracers", - ZooUtil.NodeMissingPolicy.SKIP); - expectLastCall().once(); + expect(zrw.getData(eq(Constants.ZROOT + "/" + iid.canonical() + Constants.ZNAMESPACES))) + .andReturn(new byte[0]).once(); + Map<String,String> mockNamespaces = Map.of("ns1", "ns1name", "ns2", "ns2name"); + expect(zrw.getChildren(eq(Constants.ZROOT + "/" + iid.canonical() + Constants.ZNAMESPACES))) + .andReturn(List.copyOf(mockNamespaces.keySet())).once(); + for (String ns : mockNamespaces.keySet()) { + Supplier<String> pathMatcher = () -> eq(Constants.ZROOT + "/" + iid.canonical() + + Constants.ZNAMESPACES + "/" + ns + ZNAMESPACE_NAME); + expect(zrw.getData(pathMatcher.get())).andReturn(mockNamespaces.get(ns).getBytes(UTF_8)) + .once(); + } + byte[] mapping = NamespaceMapping.serialize(mockNamespaces); + expect( + zrw.putPersistentData(eq(Constants.ZROOT + "/" + iid.canonical() + Constants.ZNAMESPACES), + aryEq(mapping), eq(ZooUtil.NodeExistsPolicy.OVERWRITE))) + .andReturn(true).once(); + for (String ns : mockNamespaces.keySet()) { + Supplier<String> pathMatcher = () -> eq(Constants.ZROOT + "/" + iid.canonical() + + Constants.ZNAMESPACES + "/" + ns + ZNAMESPACE_NAME); + zrw.delete(pathMatcher.get()); + expectLastCall().once(); + } replay(context, zrw);