| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // Licensed 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 |
| // |
| // http://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. |
| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed 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 |
| // |
| // http://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 com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper; |
| |
| import static org.apache.zookeeper.CreateMode.PERSISTENT; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| import org.apache.curator.framework.CuratorFramework; |
| import org.apache.curator.framework.api.PathAndBytesable; |
| import org.apache.curator.framework.api.transaction.CuratorOp; |
| import org.apache.curator.framework.api.transaction.CuratorTransactionResult; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| |
| public class ZkRefInfoDAO { |
| public static final String OBJECT_ID_PATH = "objectId"; |
| |
| public static String pathFor(ZkRefInfo info) { |
| return "/" + info.projectName() + "/" + info.refName(); |
| } |
| |
| public static String pathFor(String projectName, Ref ref) { |
| return pathFor(projectName, ref.getName()); |
| } |
| |
| public static String pathFor(String projectName, String refName) { |
| return "/" + projectName + "/" + refName; |
| } |
| |
| private final CuratorFramework client; |
| |
| public ZkRefInfoDAO(CuratorFramework client) { |
| this.client = client; |
| } |
| |
| public Optional<ZkRefInfo> read(String projectName, String refName) throws Exception { |
| final String rootPath = pathFor(projectName, refName); |
| |
| if (!exists(rootPath)) { |
| return Optional.empty(); |
| } |
| |
| final ObjectId objectId = readObjectIdAt(rootPath + "/" + OBJECT_ID_PATH); |
| |
| return Optional.of(new ZkRefInfo(projectName, refName, objectId)); |
| } |
| |
| public void update(ZkRefInfo info) throws Exception { |
| writeInTransaction(info, () -> client.transactionOp().setData()); |
| } |
| |
| public void create(ZkRefInfo info) throws Exception { |
| client.createContainers(pathFor(info)); |
| writeInTransaction(info, () -> client.transactionOp().create().withMode(PERSISTENT)); |
| } |
| |
| private void writeInTransaction( |
| ZkRefInfo info, Supplier<PathAndBytesable<CuratorOp>> writeOpBuilderSupplier) |
| throws Exception { |
| String commonPath = pathFor(info); |
| final List<CuratorTransactionResult> curatorTransactionResults = |
| client |
| .transaction() |
| .forOperations( |
| writeOpBuilderSupplier |
| .get() |
| .forPath(commonPath + "/" + OBJECT_ID_PATH, writeObjectId(info.objectId()))); |
| |
| for (CuratorTransactionResult result : curatorTransactionResults) { |
| if (result.getError() != 0) |
| throw new IOException( |
| String.format( |
| "Error with code %d trying to write path %s ", |
| result.getError(), result.getForPath())); |
| } |
| } |
| |
| private boolean exists(String path) throws Exception { |
| return client.checkExists().forPath(path) != null; |
| } |
| |
| private ObjectId readObjectIdAt(String path) throws Exception { |
| Optional<ObjectId> objectId = parseAt(path, ObjectId::fromRaw); |
| if (!objectId.isPresent()) { |
| throw new CorruptedZkStorageException( |
| String.format("Not able to read objectId from zookeeper path %s", path)); |
| } |
| return objectId.get(); |
| } |
| |
| private byte[] writeObjectId(ObjectId value) throws IOException { |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| final DataOutputStream stream = new DataOutputStream(out); |
| value.copyRawTo(stream); |
| return out.toByteArray(); |
| } |
| |
| private <T> Optional<T> parseAt(String path, Function<byte[], T> parser) throws Exception { |
| if (client.checkExists().forPath(path) == null) return Optional.empty(); |
| return Optional.ofNullable(client.getData().forPath(path)).map(parser); |
| } |
| |
| static class CorruptedZkStorageException extends Exception { |
| private static final long serialVersionUID = 1L; |
| |
| public CorruptedZkStorageException(String message) { |
| super(message); |
| } |
| } |
| } |