これは、なにをしたくて書いたもの?
InfinispanのHot Rod Clientには、Cacheの作成、削除などを行う管理APIがあります。
今までも何回か使っていたのですが、よく忘れるので単体でメモしておくことにしました。
RemoteCacheManagerAdminインターフェース
RemoteCacheManagerAdmin
というインターフェースを使うことで、Infinispan ServerにおけるCacheの作成、削除などが
できます。
RemoteCacheManagerAdmin (Infinispan JavaDoc 12.1.10.Final API)
クラスタ構成のInfinispan Serverであっても、各Nodeに一気にCacheを作れたりするので便利です。
各Nodeそれぞれに対して、XMLファイルを修正しなくてすみますからね。
もっともCLIで操作した場合も同じように各NodeにCacheの定義を反映してくれるのですが、APIで書けた方が
便利な時もあるかな、と。
ちなみに、CLIであっても管理APIであっても、作成したCacheの情報はクラスタに新しいNodeが参加した場合、
そのCacheの情報もコピーされます。この点でも便利です。
ドキュメントとしては、このあたりに記載があります。
Deploying and Configuring Infinispan Servers / Creating Remote Caches with Hot Rod Clients
Hot Rod Java Clients / Creating Remote Caches with Hot Rod Clients
書かれている内容は同じなのですが。
RemoteCacheManagerAdmin
ではなく、RemoteCacheConfigurationBuilder#configuration
でCacheの定義を行う方法も
紹介されているのですが、こちらはパスします…。
RemoteCacheConfigurationBuilder (Infinispan JavaDoc 12.1.10.Final API)
環境
今回の環境は、こちらです。
$ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-81-generic", arch: "amd64", family: "unix"
Infinispan Serverは12.1.7.Finalを使い、172.17.0.2〜4の3 Node用意します。
準備
各Nodeには、admin
グループに属するユーザーと、application
グループに属するユーザーをそれぞれ作成しておきます。
$ bin/cli.sh user create -g admin -p password admin-user $ bin/cli.sh user create -g application -p password app-user
各グループは、ClusterRoleMapper
を意識しています。
Deploying and Configuring Infinispan Servers / User Roles and Permissions
Cacheの定義などは行いません。
Maven依存関係などはこちら。
<dependencies> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-client-hotrod</artifactId> <version>12.1.7.Final</version> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-core</artifactId> <version>12.1.7.Final</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.20.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build>
今回のお題には、最低限infinispan-client-hotrod
が必要です。infinispan-core
はなくてもRemoteCacheManagerAdmin
を
使うことはできるのですが、あった方がCacheの定義をするには便利かなと思います。理由は後述します。
動作確認はテストコードで行うので、テスト用のライブラリも追加しておきます。
テストコードの雛形
テストコードの雛形は、こちら。
src/test/java/org/littlewings/remote/admin/CreateCacheTest.java
package org.littlewings.remote.admin; import java.util.stream.IntStream; import java.util.stream.Stream; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.RemoteCacheManagerAdmin; import org.infinispan.client.hotrod.exceptions.HotRodClientException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class CreateCacheTest { private static String createUri(String userName, String password) { return String.format("hotrod://%s:%s@172.17.0.2:11222,172.17.0.3:11222,172.17.0.4:11222", userName, password); } // ここに、テストコードを書く }
createUri
というメソッドは、接続するユーザー名とパスワードを使って、Infinispan Serverへ接続するためのURIを
作成するメソッドです。こちらを接続情報とします。
RemoteCacheManagerAdminを使ってCacheを作成してみる
では、RemoteCacheManagerAdmin
を使ってCacheを作成してみます。
使用例はこちら。
@Test public void createCacheByAdminUser() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration cacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); RemoteCache<String, String> cache = admin.createCache("distCache", cacheConfiguration); IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, "value" + i)); assertThat(cache.size()).isEqualTo(100); admin.removeCache("distCache"); } }
RemoteCacheManagerAdmin
は、RemoteCacheManager#administration
で取得できます。
なお、接続にはadmin
グループに属しているユーザーを使用しています。
try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration();
Cacheの作成はRemoteCacheManagerAdmin#createCache
で行えばよいのですが
RemoteCache<String, String> cache = admin.createCache("distCache", cacheConfiguration);
この時に作成するCacheの定義情報が必要です。
Cacheの定義情報はString
とXMLStringConfiguration
から作成でき、依存関係もinfinispan-client-hotrod
だけで済みますが
Cacheの定義を文字列で用意することになり、まあ面倒です。この作り方は、最後に載せたいと思います。
もうひとつは、Embedded CacheでのConfiguration
を使う方法です。こちらを使うとAPIでCache定義を組み立てられるの
ですが、infinispan-core
が必要になります。依存関係にinfinispan-core
を追加したのはこれが理由ですね。
こんな感じでCacheの定義情報を作成します。infinispan-client-hotrod
の範囲外であることがわかるようにFQCNで
クラス名を書いています。
org.infinispan.configuration.cache.Configuration cacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build();
Cacheの種類としては、Distributed Cacheとしました。
これでクラスタに参加する各NodeにCacheが作成され、すぐに使うことができます。
IntStream.rangeClosed(1, 100).forEach(i -> cache.put("key" + i, "value" + i)); assertThat(cache.size()).isEqualTo(100);
RemoteCacheManagerAdmin
を使うと、Cacheの削除も可能です。
admin.removeCache("distCache");
なお、Cacheの作成にはADMIN
権限が必要です。このため、application
グループに属したユーザーでは
RemoteCacheManagerAdmin#createCache
の呼び出しに失敗します。
@Test public void createCacheByApplicationUser() { String uri = createUri("app-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration cacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); assertThatThrownBy(() -> admin.createCache("distCache", cacheConfiguration)) .hasMessageContaining("org.infinispan.commons.CacheException: java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [app-user, RolePrincipal{name='application'},") .hasMessageContaining("' lacks 'ADMIN' permission") .isInstanceOf(HotRodClientException.class); } }
ClusterRoleMapper
でADMIN
権限が使えるのは、ALL
が割り当てられているadmin
グループしかありません。
RemoteCacheManagerAdmin#getOrCreateCacheを使う
RemoteCacheManagerAdmin#createCache
でCacheの作成が可能なことはわかりましたが、作成対象のCacheがすでに
存在している場合は例外がスローされます。
@Test public void recreateCache() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration cacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); assertThat(admin.createCache("distCache", cacheConfiguration)) .isNotNull() .isInstanceOf(RemoteCache.class); assertThatThrownBy(() -> admin.createCache("distCache", cacheConfiguration)) .hasMessage("org.infinispan.commons.CacheConfigurationException: ISPN000507: Cache distCache already exists") .isInstanceOf(HotRodClientException.class); admin.removeCache("distCache"); } }
これを避ける場合は、RemoteCacheManagerAdmin#getOrCreateCache
を使えばOKです。
@Test public void createOrGetCache() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration cacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); assertThat(admin.createCache("distCache", cacheConfiguration)) .isNotNull() .isInstanceOf(RemoteCache.class); assertThat(admin.getOrCreateCache("distCache", cacheConfiguration)) .isNotNull() .isInstanceOf(RemoteCache.class); admin.removeCache("distCache"); assertThat(admin.getOrCreateCache("distCache", cacheConfiguration)) .isNotNull() .isInstanceOf(RemoteCache.class); admin.removeCache("distCache"); } }
対象のCacheがなければ作成し、すでに存在していればそれを使うという動作になります。
最初からこれを使ったらいいのでは、という話もありますね。
複数のCacheを作成してみる
ここまでDistributed Cacheばかり作っていましたが、さらにReplicated Cacheも作ってみましょう。
@Test public void createVariousCache() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration distCacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); org.infinispan.configuration.cache.Configuration replCacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.REPL_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); RemoteCache<String, String> distCache = admin.getOrCreateCache("distCache", distCacheConfiguration); RemoteCache<String, String> replCache = admin.getOrCreateCache("replCache", replCacheConfiguration); IntStream.rangeClosed(1, 100).forEach(i -> distCache.put("key" + i, "value" + i)); assertThat(distCache.size()).isEqualTo(100); IntStream.rangeClosed(1, 100).forEach(i -> replCache.put("key" + i, "value" + i)); assertThat(replCache.size()).isEqualTo(100); admin.removeCache("distCache"); admin.removeCache("replCache"); } }
まあ、ちょっとしたオマケ的な感じですが…。
で、ここまでCacheを作成してきましたが、Infinispan Server側にどのように存在するかは特に書いてきませんでした。
Cacheを作成しても、infinispan.xml
そのものは変わりません。
server/conf/infinispan.xml
<cache-container name="default" statistics="true"> <transport cluster="${infinispan.cluster.name:cluster}" stack="${infinispan.cluster.stack:tcp}" node-name="${infinispan.node.name:}"/> <security> <authorization/> </security> </cache-container>
動的に作成したCacheは、server/data/caches.xml
ファイルに記載されます。
server/data/caches.xml
<?xml version="1.0"?> <infinispan xmlns="urn:infinispan:config:12.1"> <cache-container> <replicated-cache mode="SYNC" remote-timeout="17500" name="replCache" statistics="true"> <encoding> <key media-type="application/x-protostream"/> <value media-type="application/x-protostream"/> </encoding> <locking concurrency-level="1000" acquire-timeout="15000" striping="false"/> <state-transfer timeout="60000"/> </replicated-cache> <distributed-cache mode="SYNC" remote-timeout="17500" name="distCache" statistics="true"> <encoding> <key media-type="application/x-protostream"/> <value media-type="application/x-protostream"/> </encoding> <locking concurrency-level="1000" acquire-timeout="15000" striping="false"/> <state-transfer timeout="60000"/> </distributed-cache> </cache-container> </infinispan>
ConfigurationBuilder
でCacheの定義を行った時はほぼ最低限の設定しか行っていなかったのに、いろいろと
デフォルト値が埋め込まれていますね。
また、最初にも書きましたが、クラスタに新しいNodeが参加した場合は、このファイルも新しいNodeに展開されます。
文字列でCache定義を行う
最後に、Cacheの定義を文字列で作成します。ドキュメントに記載されていたのは、この方法ですね。
@Test public void createCacheByString() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); String cacheConfigurationAsString = " <distributed-cache name=\"distCache\">\n" + " <encoding>\n" + " <key media-type=\"application/x-protostream\"/>\n" + " <value media-type=\"application/x-protostream\"/>\n" + " </encoding>\n" + " </distributed-cache>"; org.infinispan.commons.configuration.XMLStringConfiguration cacheConfiguration = new org.infinispan.commons.configuration.XMLStringConfiguration(cacheConfigurationAsString); RemoteCache<String, String> distCache = admin.getOrCreateCache("distCache", cacheConfiguration); IntStream.rangeClosed(1, 100).forEach(i -> distCache.put("key" + i, "value" + i)); assertThat(distCache.size()).isEqualTo(100); admin.removeCache("distCache"); assertThatThrownBy(() -> admin.getOrCreateCache("distributedCache", cacheConfiguration)) .hasMessageContaining("org.infinispan.commons.CacheConfigurationException: ISPN005031: The supplied configuration for cache 'distributedCache' is missing a named configuration for it:") .isInstanceOf(HotRodClientException.class); } }
ポイントはこちらで、Cache定義の断片をXMLStringConfiguration
として作成します。
String cacheConfigurationAsString = " <distributed-cache name=\"distCache\">\n" + " <encoding>\n" + " <key media-type=\"application/x-protostream\"/>\n" + " <value media-type=\"application/x-protostream\"/>\n" + " </encoding>\n" + " </distributed-cache>"; org.infinispan.commons.configuration.XMLStringConfiguration cacheConfiguration = new org.infinispan.commons.configuration.XMLStringConfiguration(cacheConfigurationAsString);
これをRemoteCacheManagerAdmin#createCache
やRemoteCacheManagerAdmin#getOrCreateCache
に渡せば
OKです。
RemoteCache<String, String> distCache = admin.getOrCreateCache("distCache", cacheConfiguration);
この方法であればinfinispan-client-hotrod
の依存関係に含まれる、infinispan-commons
があれば依存関係としては
十分です。つまり、Hot Rod Clientのみで実行することができます。
ちなみに、XML定義のCache名と作成するCacheの名前が異なる場合は、例外がスローされるので注意しましょう。。
assertThatThrownBy(() -> admin.getOrCreateCache("distributedCache", cacheConfiguration)) .hasMessageContaining("org.infinispan.commons.CacheConfigurationException: ISPN005031: The supplied configuration for cache 'distributedCache' is missing a named configuration for it:") .isInstanceOf(HotRodClientException.class);
これでひととおり確認できた感じですね。
こちらのチュートリアルを見ていると、Configuration Templateも定義できそうですが、今回はパスします。
Embedded CacheのConfigurationは、どう使われているの?
オマケ的にですが。
こちらがRemoteCacheManagerAdmin
インターフェースの実装です。
Embedded CacheのConfiguration
を渡した時にはどうなるのかな?と思ったのですが、結局文字列に変換されているので
最終的にはXML定義をInfinispan Serverに送ってCacheを作成するみたいですね。
まとめ
InfinispanのHot Rod ClientのRemoteCacheManagerAdmin
を使って、Infinispan Server上にCacheを動的に作成して
みました。
時々使っているのですが、本当によく忘れるのでいい機会かな、と。
今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-create-cache