/** * 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.namespace; import static com.google.common.base.Preconditions.checkNotNull; import static com.yahoo.pulsar.broker.PulsarService.webAddress; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import org.apache.bookkeeper.util.OrderedSafeExecutor; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.MockZooKeeper; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.hash.Hashing; import com.yahoo.pulsar.broker.PulsarService; import com.yahoo.pulsar.broker.ServiceConfiguration; import com.yahoo.pulsar.broker.cache.LocalZooKeeperCacheService; import com.yahoo.pulsar.broker.service.BrokerService; import com.yahoo.pulsar.common.naming.NamespaceBundle; import com.yahoo.pulsar.common.naming.NamespaceBundleFactory; import com.yahoo.pulsar.common.naming.NamespaceName; import com.yahoo.pulsar.zookeeper.LocalZooKeeperCache; import com.yahoo.pulsar.zookeeper.ZooKeeperCache; public class OwnershipCacheTest { private PulsarService pulsar; private ServiceConfiguration config; private String selfBrokerUrl; private ZooKeeperCache zkCache; private LocalZooKeeperCacheService localCache; private NamespaceBundleFactory bundleFactory; private NamespaceService nsService; private BrokerService brokerService; private OrderedSafeExecutor executor; private ScheduledExecutorService scheduledExecutor; @BeforeMethod public void setup() throws Exception { final int port = 8080; selfBrokerUrl = "tcp://localhost:" + port; pulsar = mock(PulsarService.class); config = mock(ServiceConfiguration.class); executor = new OrderedSafeExecutor(1, "test"); scheduledExecutor = Executors.newScheduledThreadPool(2); zkCache = new LocalZooKeeperCache(MockZooKeeper.newInstance(), executor, scheduledExecutor); localCache = new LocalZooKeeperCacheService(zkCache, null); bundleFactory = new NamespaceBundleFactory(pulsar, Hashing.crc32()); nsService = mock(NamespaceService.class); brokerService = mock(BrokerService.class); doReturn(CompletableFuture.completedFuture(1)).when(brokerService).unloadServiceUnit(anyObject()); doReturn(zkCache).when(pulsar).getLocalZkCache(); doReturn(localCache).when(pulsar).getLocalZkCacheService(); doReturn(config).when(pulsar).getConfiguration(); doReturn(nsService).when(pulsar).getNamespaceService(); doReturn(port).when(config).getBrokerServicePort(); doReturn(brokerService).when(pulsar).getBrokerService(); doReturn(webAddress(config)).when(pulsar).getWebServiceAddress(); doReturn(selfBrokerUrl).when(pulsar).getBrokerServiceUrl(); } @AfterMethod public void teardown() throws Exception { executor.shutdown(); scheduledExecutor.shutdown(); } @Test public void testConstructor() { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); assertNotNull(cache); assertNotNull(cache.getOwnedBundles()); } @Test public void testDisableOwnership() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceBundle testBundle = bundleFactory.getFullBundle(new NamespaceName("pulsar/test/ns-1")); assertFalse(cache.getOwnerAsync(testBundle).get().isPresent()); NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(testBundle).get(); assertTrue(!data1.isDisabled()); cache.disableOwnership(testBundle); // force the next read to get directly from ZK // localCache.ownerInfoCache().invalidate(ServiceUnitZkUtils.path(testNs)); data1 = cache.getOwnerAsync(testBundle).get().get(); assertTrue(data1.isDisabled()); } @Test public void testGetOrSetOwner() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceBundle testFullBundle = bundleFactory.getFullBundle(new NamespaceName("pulsar/test/ns-2")); // case 1: no one owns the namespace assertFalse(cache.getOwnerAsync(testFullBundle).get().isPresent()); NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(testFullBundle).get(); assertEquals(data1.getNativeUrl(), selfBrokerUrl); assertTrue(!data1.isDisabled()); // case 2: the local broker owned the namespace and disabled, getOrSetOwner() should not change it OwnedBundle nsObj = cache.getOwnedBundle(testFullBundle); // this would disable the ownership doReturn(cache).when(nsService).getOwnershipCache(); nsObj.handleUnloadRequest(pulsar); Thread.sleep(1000); // case 3: some other broker owned the namespace, getOrSetOwner() should return other broker's URL // The only chance that we lost an already existing ephemeral node is when the broker dies or unload has // succeeded in both cases, the ownerInfoCache will be updated (i.e. invalidated the entry) localCache.ownerInfoCache().invalidate(ServiceUnitZkUtils.path(testFullBundle)); ServiceUnitZkUtils.acquireNameSpace(zkCache.getZooKeeper(), ServiceUnitZkUtils.path(testFullBundle), new NamespaceEphemeralData("pulsar://otherhost:8881", "pulsar://otherhost:8884", "http://localhost:8080", "https://localhost:4443", false)); data1 = cache.tryAcquiringOwnership(testFullBundle).get(); assertEquals(data1.getNativeUrl(), "pulsar://otherhost:8881"); assertEquals(data1.getNativeUrlTls(), "pulsar://otherhost:8884"); assertTrue(!data1.isDisabled()); } @Test public void testGetOwner() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceBundle testBundle = bundleFactory.getFullBundle(new NamespaceName("pulsar/test/ns-3")); // case 1: no one owns the namespace assertFalse(cache.getOwnerAsync(testBundle).get().isPresent()); // case 2: someone owns the namespace ServiceUnitZkUtils.acquireNameSpace(zkCache.getZooKeeper(), ServiceUnitZkUtils.path(testBundle), new NamespaceEphemeralData("pulsar://otherhost:8881", "pulsar://otherhost:8884", "http://otherhost:8080", "https://otherhost:4443", false)); // try to acquire, which will load the read-only cache NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(testBundle).get(); assertEquals(data1.getNativeUrl(), "pulsar://otherhost:8881"); assertEquals(data1.getNativeUrlTls(), "pulsar://otherhost:8884"); assertTrue(!data1.isDisabled()); // Now do getOwner and compare w/ the returned values NamespaceEphemeralData readOnlyData = cache.getOwnerAsync(testBundle).get().get(); assertEquals(data1, readOnlyData); MockZooKeeper mockZk = (MockZooKeeper) zkCache.getZooKeeper(); mockZk.failNow(KeeperException.Code.NONODE); Optional<NamespaceEphemeralData> res = cache .getOwnerAsync(bundleFactory.getFullBundle(new NamespaceName("pulsar/test/ns-none"))).get(); assertFalse(res.isPresent()); } @Test public void testGetOwnedServiceUnit() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceName testNs = new NamespaceName("pulsar/test/ns-5"); NamespaceBundle testBundle = bundleFactory.getFullBundle(testNs); // case 1: no one owns the namespace assertFalse(cache.getOwnerAsync(testBundle).get().isPresent()); try { checkNotNull(cache.getOwnedBundle(testBundle)); fail("Should have failed"); } catch (NullPointerException npe) { // OK for not owned namespace } // case 2: someone else owns the namespace ServiceUnitZkUtils.acquireNameSpace(zkCache.getZooKeeper(), ServiceUnitZkUtils.path(testBundle), new NamespaceEphemeralData("pulsar://otherhost:8881", "pulsar://otherhost:8884", "http://otherhost:8080", "https://otherhost:4443", false)); try { checkNotNull(cache.getOwnedBundle(testBundle)); fail("Should have failed"); } catch (NullPointerException npe) { // OK for not owned namespace } // try to acquire, which will load the read-only cache NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(testBundle).get(); assertEquals(data1.getNativeUrl(), "pulsar://otherhost:8881"); assertEquals(data1.getNativeUrlTls(), "pulsar://otherhost:8884"); assertTrue(!data1.isDisabled()); try { checkNotNull(cache.getOwnedBundle(testBundle)); fail("Should have failed"); } catch (NullPointerException npe) { // OK for not owned namespace } // case 3: this broker owns the namespace // delete the ephemeral node by others zkCache.getZooKeeper().delete(ServiceUnitZkUtils.path(testBundle), -1); // force to read directly from ZK localCache.ownerInfoCache().invalidate(ServiceUnitZkUtils.path(testBundle)); data1 = cache.tryAcquiringOwnership(testBundle).get(); assertEquals(data1.getNativeUrl(), selfBrokerUrl); assertTrue(!data1.isDisabled()); assertNotNull(cache.getOwnedBundle(testBundle)); } @Test public void testGetOwnedServiceUnits() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceName testNs = new NamespaceName("pulsar/test/ns-6"); NamespaceBundle testBundle = bundleFactory.getFullBundle(testNs); // case 1: no one owns the namespace assertFalse(cache.getOwnerAsync(testBundle).get().isPresent()); assertTrue(cache.getOwnedBundles().isEmpty()); // case 2: someone else owns the namespace ServiceUnitZkUtils.acquireNameSpace(zkCache.getZooKeeper(), ServiceUnitZkUtils.path(testBundle), new NamespaceEphemeralData("pulsar://otherhost:8881", "pulsar://otherhost:8884", "http://otherhost:8080", "https://otherhost:4443", false)); assertTrue(cache.getOwnedBundles().isEmpty()); // try to acquire, which will load the read-only cache NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(testBundle).get(); assertEquals(data1.getNativeUrl(), "pulsar://otherhost:8881"); assertEquals(data1.getNativeUrlTls(), "pulsar://otherhost:8884"); assertTrue(!data1.isDisabled()); assertTrue(cache.getOwnedBundles().isEmpty()); // case 3: this broker owns the namespace // delete the ephemeral node by others zkCache.getZooKeeper().delete(ServiceUnitZkUtils.path(testBundle), -1); // force to read directly from ZK localCache.ownerInfoCache().invalidate(ServiceUnitZkUtils.path(testBundle)); data1 = cache.tryAcquiringOwnership(testBundle).get(); assertEquals(data1.getNativeUrl(), selfBrokerUrl); assertTrue(!data1.isDisabled()); assertTrue(cache.getOwnedBundles().size() == 1); } @Test public void testRemoveOwnership() throws Exception { OwnershipCache cache = new OwnershipCache(this.pulsar, bundleFactory); NamespaceName testNs = new NamespaceName("pulsar/test/ns-7"); NamespaceBundle bundle = bundleFactory.getFullBundle(testNs); // case 1: no one owns the namespace assertFalse(cache.getOwnerAsync(bundle).get().isPresent()); cache.removeOwnership(bundle).get(); assertTrue(cache.getOwnedBundles().isEmpty()); // case 2: this broker owns the namespace NamespaceEphemeralData data1 = cache.tryAcquiringOwnership(bundle).get(); assertEquals(data1.getNativeUrl(), selfBrokerUrl); assertTrue(!data1.isDisabled()); assertTrue(cache.getOwnedBundles().size() == 1); cache.removeOwnership(bundle); Thread.sleep(500); assertTrue(cache.getOwnedBundles().isEmpty()); Thread.sleep(500); try { zkCache.getZooKeeper().getData(ServiceUnitZkUtils.path(bundle), null, null); fail("Should have failed"); } catch (NoNodeException nne) { // OK } } }