/** * Copyright 2016 Yahoo Inc. * * 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.yahoo.pulsar.broker.cache; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.yahoo.pulsar.broker.cache.ConfigurationCacheService.POLICIES_ROOT; import static com.yahoo.pulsar.broker.web.PulsarWebResource.joinPath; import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.bookkeeper.util.ZkUtils; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.yahoo.pulsar.broker.PulsarServerException; import com.yahoo.pulsar.broker.namespace.NamespaceEphemeralData; import com.yahoo.pulsar.common.policies.data.LocalPolicies; import com.yahoo.pulsar.common.policies.data.Policies; import com.yahoo.pulsar.common.util.ObjectMapperFactory; import com.yahoo.pulsar.zookeeper.ZooKeeperCache; import com.yahoo.pulsar.zookeeper.ZooKeeperChildrenCache; import com.yahoo.pulsar.zookeeper.ZooKeeperDataCache; public class LocalZooKeeperCacheService { private static final Logger LOG = LoggerFactory.getLogger(LocalZooKeeperCacheService.class); private static final String MANAGED_LEDGER_ROOT = "/managed-ledgers"; public static final String OWNER_INFO_ROOT = "/namespace"; public static final String LOCAL_POLICIES_ROOT = "/admin/local-policies"; private final ZooKeeperCache cache; private ZooKeeperDataCache<NamespaceEphemeralData> ownerInfoCache; private ZooKeeperChildrenCache managedLedgerListCache; private ResourceQuotaCache resourceQuotaCache; private ZooKeeperDataCache<LocalPolicies> policiesCache; private ConfigurationCacheService configurationCacheService; public LocalZooKeeperCacheService(ZooKeeperCache cache, ConfigurationCacheService configurationCacheService) throws PulsarServerException { this.cache = cache; this.configurationCacheService = configurationCacheService; initZK(); this.ownerInfoCache = new ZooKeeperDataCache<NamespaceEphemeralData>(cache) { @Override public NamespaceEphemeralData deserialize(String path, byte[] content) throws Exception { return ObjectMapperFactory.getThreadLocal().readValue(content, NamespaceEphemeralData.class); } }; this.policiesCache = new ZooKeeperDataCache<LocalPolicies>(cache) { @Override public LocalPolicies deserialize(String path, byte[] content) throws Exception { return ObjectMapperFactory.getThreadLocal().readValue(content, LocalPolicies.class); } @Override public CompletableFuture<Optional<LocalPolicies>> getAsync(String path) { CompletableFuture<Optional<LocalPolicies>> future = new CompletableFuture<>(); // First check in local-zk cache super.getAsync(path).thenAccept(localPolicies -> { if (localPolicies.isPresent()) { future.complete(localPolicies); } else { // create new policies node under Local ZK by coping it from Global ZK createPolicies(path, true).thenAccept(p -> { LOG.info("Successfully created local policies for {} -- {}", path, p); future.complete(p); }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); } }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); return future; } }; this.managedLedgerListCache = new ZooKeeperChildrenCache(cache, MANAGED_LEDGER_ROOT); this.resourceQuotaCache = new ResourceQuotaCache(cache); this.resourceQuotaCache.initZK(); } private void initZK() throws PulsarServerException { String[] paths = new String[] { MANAGED_LEDGER_ROOT, OWNER_INFO_ROOT, LOCAL_POLICIES_ROOT }; // initialize the zk client with values try { ZooKeeper zk = cache.getZooKeeper(); for (String path : paths) { if (cache.exists(path)) { continue; } try { ZkUtils.createFullPathOptimistic(zk, path, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } catch (KeeperException.NodeExistsException e) { // Ok } } } catch (Exception e) { LOG.error(e.getMessage(), e); throw new PulsarServerException(e); } } /** * Create LocalPolicies with bundle-data in LocalZookeeper by fetching it from GlobalZookeeper * * @param path * znode path * @param readFromGlobal * if true copy policies from global zk to local zk else create a new znode with empty {@link Policies} * @throws Exception */ @SuppressWarnings("deprecation") public CompletableFuture<Optional<LocalPolicies>> createPolicies(String path, boolean readFromGlobal) { checkNotNull(path, "path can't be null"); checkArgument(path.startsWith(LOCAL_POLICIES_ROOT), "Invalid path of local policies"); CompletableFuture<Optional<LocalPolicies>> future = new CompletableFuture<>(); if (LOG.isDebugEnabled()) { LOG.debug("Creating local namespace policies for {} - readFromGlobal: {}", path, readFromGlobal); } CompletableFuture<Optional<LocalPolicies>> readFromGlobalFuture = new CompletableFuture<>(); if (readFromGlobal) { String globalPath = joinPath(POLICIES_ROOT, path.substring(path.indexOf(LOCAL_POLICIES_ROOT) + LOCAL_POLICIES_ROOT.length() + 1)); checkNotNull(configurationCacheService); checkNotNull(configurationCacheService.policiesCache()); checkNotNull(configurationCacheService.policiesCache().getAsync(globalPath)); configurationCacheService.policiesCache().getAsync(globalPath).thenAccept(policies -> { if (policies.isPresent()) { // Copying global bundles information to local policies LocalPolicies localPolicies = new LocalPolicies(); localPolicies.bundles = policies.get().bundles; readFromGlobalFuture.complete(Optional.of(localPolicies)); } else { // Policies are not present in global zk if (LOG.isDebugEnabled()) { LOG.debug("Global policies not found at {}", globalPath); } readFromGlobalFuture.complete(Optional.empty()); } }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); } else { // Use default local policies readFromGlobalFuture.complete(Optional.of(new LocalPolicies())); } readFromGlobalFuture.thenAccept(localPolicies -> { if (!localPolicies.isPresent()) { future.complete(Optional.empty()); } // When we have the updated localPolicies, we can write them back in local ZK byte[] content; try { content = ObjectMapperFactory.getThreadLocal().writeValueAsBytes(localPolicies.get()); } catch (Throwable t) { // Failed to serialize to json future.completeExceptionally(t); return; } ZkUtils.asyncCreateFullPathOptimistic(cache.getZooKeeper(), path, content, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, (rc, path1, ctx, name) -> { if (rc == KeeperException.Code.OK.intValue() || rc == KeeperException.Code.NODEEXISTS.intValue()) { LOG.info("Successfully copyied bundles data to local zk at {}", path); future.complete(localPolicies); } else { LOG.error("Failed to create policies for {} in local zookeeper: {}", path, KeeperException.Code.get(rc)); future.completeExceptionally(new PulsarServerException(KeeperException.create(rc))); } }, null); }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); return future; } public ResourceQuotaCache getResourceQuotaCache() { return this.resourceQuotaCache; } public ZooKeeperDataCache<NamespaceEphemeralData> ownerInfoCache() { return this.ownerInfoCache; } public ZooKeeperDataCache<LocalPolicies> policiesCache() { return this.policiesCache; } public ZooKeeperChildrenCache managedLedgerListCache() { return this.managedLedgerListCache; } }