├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ ├── pr-builder.yml
│ ├── IMDG-snapshot.yml
│ └── publish-snapshot.yml
├── src
├── test
│ ├── resources
│ │ ├── keystore.jks
│ │ └── ca.crt
│ └── java
│ │ └── com
│ │ └── hazelcast
│ │ └── kubernetes
│ │ ├── KubernetesPropertiesTest.java
│ │ ├── HazelcastKubernetesDiscoveryStrategyFactoryTest.java
│ │ ├── RestClientTest.java
│ │ ├── RetryUtilsTest.java
│ │ ├── KubernetesApiEndpointResolverTest.java
│ │ ├── DnsEndpointResolverTest.java
│ │ ├── KubernetesConfigTest.java
│ │ └── KubernetesClientTest.java
└── main
│ ├── resources
│ └── META-INF
│ │ └── services
│ │ └── com.hazelcast.spi.discovery.DiscoveryStrategyFactory
│ └── java
│ └── com
│ └── hazelcast
│ └── kubernetes
│ ├── package-info.java
│ ├── KubernetesClientException.java
│ ├── RestClientException.java
│ ├── RetryUtils.java
│ ├── HazelcastKubernetesDiscoveryStrategyFactory.java
│ ├── KubernetesApiEndpointResolver.java
│ ├── DnsEndpointResolver.java
│ ├── HazelcastKubernetesDiscoveryStrategy.java
│ ├── KubernetesProperties.java
│ ├── RestClient.java
│ ├── KubernetesConfig.java
│ └── KubernetesClient.java
├── .gitignore
├── README.md
├── rbac.yaml
├── checkstyle
├── ClassHeader.txt
├── suppressions.xml
└── checkstyle.xml
├── findbugs
└── findbugs-exclude.xml
├── LICENSE
└── pom.xml
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @cloud-native
2 |
--------------------------------------------------------------------------------
/src/test/resources/keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hazelcast/hazelcast-kubernetes/HEAD/src/test/resources/keystore.jks
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | build/
3 | .gradle/
4 | .project
5 | .classpath
6 | .settings/
7 | .idea/
8 | .patch
9 | .diff
10 | *.iml
11 | *.ipr
12 | *.iws
13 | .DS_Store
14 | atlassian-ide-plugin.xml
15 | .checkstyle
16 | .fbExcludeFilterFile
17 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | time: "02:00"
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 | time: "02:00"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### **DEPRECATED:** `hazelcast-kubernetes` plugin has been merged with [`hazelcast`](https://github.com/hazelcast/hazelcast)!
2 | Since version `5.0` `hazelcast` includes `hazelcast-kubernetes` and does not require additional dependency. For details about running Hazelcast on Kubernetes consider the [documentation](https://docs.hazelcast.com/hazelcast/latest/kubernetes/deploying-in-kubernetes).
3 |
4 |
--------------------------------------------------------------------------------
/rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: hazelcast-cluster-role
5 | rules:
6 | - apiGroups:
7 | - ""
8 | resources:
9 | - endpoints
10 | - pods
11 | - nodes
12 | - services
13 | verbs:
14 | - get
15 | - list
16 |
17 | ---
18 |
19 | apiVersion: rbac.authorization.k8s.io/v1
20 | kind: ClusterRoleBinding
21 | metadata:
22 | name: hazelcast-cluster-role-binding
23 | roleRef:
24 | apiGroup: rbac.authorization.k8s.io
25 | kind: ClusterRole
26 | name: hazelcast-cluster-role
27 | subjects:
28 | - kind: ServiceAccount
29 | name: default
30 | namespace: default
31 |
--------------------------------------------------------------------------------
/checkstyle/ClassHeader.txt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
--------------------------------------------------------------------------------
/findbugs/findbugs-exclude.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/com.hazelcast.spi.discovery.DiscoveryStrategyFactory:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | com.hazelcast.kubernetes.HazelcastKubernetesDiscoveryStrategyFactory
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Provides interfaces/classes for Hazelcast Kubernetes Discovery Plugin
19 | */
20 | package com.hazelcast.kubernetes;
21 |
--------------------------------------------------------------------------------
/.github/workflows/pr-builder.yml:
--------------------------------------------------------------------------------
1 | name: Pull-Request
2 | on:
3 | pull_request:
4 | branches:
5 | - master
6 | - 1.5.x
7 | paths-ignore:
8 | - '**.md'
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | java: [ '8' ]
16 | architecture: [ 'x64' ]
17 | name: Build with JDK ${{ matrix.java }} on ${{ matrix.architecture }}
18 | steps:
19 | - uses: actions/checkout@v2.3.4
20 | - name: Setup JDK
21 | uses: actions/setup-java@v1
22 | with:
23 | java-version: ${{ matrix.java }}
24 | architecture: ${{ matrix.architecture }}
25 |
26 | - uses: actions/cache@v2.1.6
27 | with:
28 | path: ~/.m2/repository
29 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
30 | restore-keys: ${{ runner.os }}-maven-
31 |
32 | - name: Build with Maven
33 | run: mvn -B verify
34 |
--------------------------------------------------------------------------------
/checkstyle/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/IMDG-snapshot.yml:
--------------------------------------------------------------------------------
1 | name: IMDG-Snapshot
2 | on:
3 | schedule:
4 | - cron: '0 3 * * *'
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | java: [ '8' ]
12 | architecture: [ 'x64' ]
13 | hazelcast: [ '5.0-SNAPSHOT' ]
14 | name: Build against IMDG ${{ matrix.hazelcast }} with JDK ${{ matrix.java }} on ${{ matrix.architecture }}
15 | steps:
16 | - uses: actions/checkout@v2.3.4
17 | - name: Setup JDK
18 | uses: actions/setup-java@v1
19 | with:
20 | java-version: ${{ matrix.java }}
21 | architecture: ${{ matrix.architecture }}
22 |
23 | - uses: actions/cache@v2.1.6
24 | with:
25 | path: ~/.m2/repository
26 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
27 | restore-keys: ${{ runner.os }}-maven-
28 |
29 | - name: Build with Maven
30 | run: mvn -Dhazelcast.version=${{ matrix.hazelcast }} -U verify
31 |
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/KubernetesPropertiesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.test.HazelcastTestSupport;
20 | import org.junit.Test;
21 |
22 | public class KubernetesPropertiesTest
23 | extends HazelcastTestSupport {
24 |
25 | @Test
26 | public void privateConstructorTest() {
27 | assertUtilityConstructor(KubernetesProperties.class);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/KubernetesClientException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.core.HazelcastException;
20 |
21 | /**
22 | * Exception to indicate any issues with {@link KubernetesClient}.
23 | */
24 | class KubernetesClientException
25 | extends HazelcastException {
26 | KubernetesClientException(String message) {
27 | super(message);
28 | }
29 |
30 | KubernetesClientException(String message, Throwable cause) {
31 | super(message, cause);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/RestClientException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | /**
20 | * Exception to indicate any issues while executing a REST call.
21 | */
22 | class RestClientException
23 | extends RuntimeException {
24 | private int httpErrorCode;
25 |
26 | RestClientException(String message, int httpErrorCode) {
27 | super(String.format("%s. HTTP Error Code: %s", message, httpErrorCode));
28 | this.httpErrorCode = httpErrorCode;
29 | }
30 |
31 | RestClientException(String message, Throwable cause) {
32 | super(message, cause);
33 | }
34 |
35 | int getHttpErrorCode() {
36 | return httpErrorCode;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/publish-snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Publish-Snapshot
2 | on:
3 | push:
4 | branches: master
5 | paths-ignore:
6 | - '**.md'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | java: [ '8' ]
14 | architecture: [ 'x64' ]
15 | name: Build with JDK ${{ matrix.java }} on ${{ matrix.architecture }}
16 | steps:
17 | - uses: actions/checkout@v2.3.4
18 | - name: Setup JDK
19 | uses: actions/setup-java@v1
20 | with:
21 | java-version: ${{ matrix.java }}
22 | architecture: ${{ matrix.architecture }}
23 |
24 | - uses: actions/cache@v2.1.6
25 | with:
26 | path: ~/.m2/repository
27 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
28 | restore-keys: ${{ runner.os }}-maven-
29 |
30 | - name: Build with Maven
31 | run: mvn -B verify
32 | publish:
33 | needs: build
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v2
37 | - name: Set up Maven Central Repository
38 | uses: actions/setup-java@v1
39 | with:
40 | java-version: 1.8
41 | server-id: snapshot-repository
42 | server-username: MAVEN_CENTRAL_USERNAME
43 | server-password: MAVEN_CENTRAL_PASSWORD
44 | - name: Publish Snapshot JARs
45 | run: mvn -B deploy -Prelease-snapshot -DskipTests
46 | env:
47 | MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
48 | MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
49 |
--------------------------------------------------------------------------------
/src/test/resources/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDMjCCAhqgAwIBAgIIbACAU0ZsknAwDQYJKoZIhvcNAQELBQAwNzESMBAGA1UE
3 | CxMJb3BlbnNoaWZ0MSEwHwYDVQQDExhrdWJlLWFwaXNlcnZlci1sYi1zaWduZXIw
4 | HhcNMjAwMTIyMjE0MzE4WhcNMzAwMTE5MjE0MzE4WjA3MRIwEAYDVQQLEwlvcGVu
5 | c2hpZnQxITAfBgNVBAMTGGt1YmUtYXBpc2VydmVyLWxiLXNpZ25lcjCCASIwDQYJ
6 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBANX8oMkxb4skmWGLXQxTbjYO50EZJ0uD
7 | AgCQs/LDCWmNSbUdjUsCSLqQCadSEiH7f4VqtYyCfQMfcf3vtaebLL/qZ4RSQy67
8 | xw6bYyN7Qh/ryNSPoczr0GXkrho4CYehRfnLaehUj6bGRe0Rhz5gisRMQ3jftKRj
9 | rY0FP18R4kbSyMVDZWtc82WGTUxXc2gyklTWzPUVLihHAJw/ip39TmmewvYqCYhh
10 | GGAZDbKVfVzZ9145/9K0EbVBRDn6lAWfh/44yrWy9cZAUidXFxLTo2YWoNzb5QJn
11 | YPt/EXhaANS3E5SPgQrlZsbxXKMfACTPWc2Y9Wb4ES89NNP1bl4DC4sCAwEAAaNC
12 | MEAwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJWe
13 | blS+7Fg7apqyu3sMraGZ34OoMA0GCSqGSIb3DQEBCwUAA4IBAQCu3BbKy3H85zEY
14 | hJTmCsRd4ZUyFjQd2D4Z0bZjdprbrW83P7UoHSA3giebvABrIzdrSANTfRLYTXCT
15 | LuxBwcY9IabxggL9Ki8p80NoKC4sWwLO3qFLGNFUTCOVU7gl7RHaHLVpHUoGFRh1
16 | kFPWnGnH1RniuY0vte8dYbUgKCzHdOW70ZdfxADwVwj1G31/KO93yO3BbX7d8IYa
17 | mDvHco/FQWxDK1MigAOCRBpPAEdj+wz1DfnCx9ZqVy5AV1XChoEkK1ZROzHIH+mc
18 | u5HFZn0ce0++aTZfnYVja7hGaFVf5rGi5i2NnACYbtT7rCXq9y/CsMb78HRly/En
19 | adDce2Y3
20 | -----END CERTIFICATE-----
21 |
22 | -----BEGIN CERTIFICATE-----
23 | MIICeDCCAeGgAwIBAgIECFbWTjANBgkqhkiG9w0BAQsFADBuMRAwDgYDVQQGEwdV
24 | bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
25 | VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv
26 | c3QwIBcNMjAwMTI4MTU1MDQ4WhgPMjEyMDAxMDQxNTUwNDhaMG4xEDAOBgNVBAYT
27 | B1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24xEDAO
28 | BgNVBAoTB1Vua25vd24xEDAOBgNVBAsTB1Vua25vd24xEjAQBgNVBAMTCWxvY2Fs
29 | aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqUga3JPPycHEsHS7LCRi
30 | zZ9nruarwTzKSGoLUtJEH5SgmFucK+cJs0SvXWVTJ4BRlsyOjHYxnJN1gBSbzLt9
31 | MkaoxzYfqPoQYuePOjFiBsmbbLBEk4yZhUB/ik4yLzu/aGUwuyJQayMNo3Th1WQ4
32 | 3qxzSdWAASPgBenOugiWTFECAwEAAaMhMB8wHQYDVR0OBBYEFARNaKaAcaGEuezm
33 | meCv76mqmPVdMA0GCSqGSIb3DQEBCwUAA4GBAGznOs7mw+Swnzy3jtmLms1ajRlb
34 | qhCQtd3d/I5NJYpq7z8n/5fkNx1orLcQ1cLQpoHQ4TIKjlrOlJnDp0m3i3fFyLv3
35 | /zguGfAg9Fh//uY2fqd1H0f6tCd4631rTqH1aN15OFfyRGIItxp2xO+NEl2yMyc8
36 | CyJtWtjG31l3ayBB
37 | -----END CERTIFICATE-----
38 |
39 | -----BEGIN CERTIFICATE-----
40 | MIIDQDCCAiigAwIBAgIIaDKbna6B3DMwDQYJKoZIhvcNAQELBQAwPjESMBAGA1UE
41 | CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt
42 | c2lnbmVyMB4XDTIwMDEyMjIxNDMxOFoXDTMwMDExOTIxNDMxOFowPjESMBAGA1UE
43 | CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt
44 | c2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApAxgtdFW7XX3
45 | KPeWAIFK7+mT2YMRxZo/WEMVEU56duWQJQ5vSOr2nxvMvuvmTRbMDMouDk2xllsk
46 | AkB8osibpH4onK44RNXROLylnsEvANv7UhN1vAIfB5G6eoR/Lh6HMaeL/7jMUyxY
47 | bKjQgUqgtzaNunNfgrpgB6TN8Kl6PD0kN+/SXZF1+VBvoptei0utBa1PGbHmGSid
48 | 2dy96CUKnlJBISLwW4kPdI6j9bl8+wbf50YB4f5cyiaQY7AwhBwZX1QsZNy74mXw
49 | wNdre8YZF7ZwVpJ1Is7khFduEL5ya6PaU7/wNo5oejwkQ+4hDSptVl5/n3gJDhgg
50 | 3JMxufArrwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB
51 | /zAdBgNVHQ4EFgQUmGd2zDD68DdK2qOvVaOdL7jRGacwDQYJKoZIhvcNAQELBQAD
52 | ggEBAAejSalM9im0Q4BIXw1JBwVCXvkL9z1USE+wB2rf3oS/GScNnpP2eMTpSoF0
53 | U1ZYoHn/ARKKQX8SLmBmgUjasi7ZTZPpESh+hyMvAVyp7F8a1y0DiL4UIAksds4h
54 | 8jiW98paKuhQR3wAX54Q9n48LvusrRQVdEWyXsH30S1FkMawuzZgh5G0DvpaNgpP
55 | b0syMDonXJ2yOkAGCr3Cm7zfhbfX09hsgoWh8ynMahtE4vXEDE7k+S7Brukn7uac
56 | G/mUQBQYIjzRKgzPN54H0tCfTj+vDRYw9JuKEYPjOfK1+udMVx3zO/TjlnjOpH73
57 | phFZaYFEgK7MESMl9oqpHYe/GJQ=
58 | -----END CERTIFICATE-----
59 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/RetryUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.core.HazelcastException;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.logging.Logger;
22 |
23 | import java.util.List;
24 | import java.util.concurrent.Callable;
25 |
26 | /**
27 | * Static utility class to retry operations related to connecting to Kubernetes master.
28 | */
29 | final class RetryUtils {
30 | static final long INITIAL_BACKOFF_MS = 1500L;
31 | static final long MAX_BACKOFF_MS = 5 * 60 * 1000L;
32 | static final double BACKOFF_MULTIPLIER = 1.5;
33 |
34 | private static final ILogger LOGGER = Logger.getLogger(RetryUtils.class);
35 |
36 | private static final long MS_IN_SECOND = 1000L;
37 |
38 | private RetryUtils() {
39 | }
40 |
41 | /**
42 | * Calls {@code callable.call()} until it does not throw an exception (but no more than {@code retries} times).
43 | *
44 | * Note that {@code callable} should be an idempotent operation which is a call to the Kubernetes master.
45 | *
46 | * If {@code callable} throws an unchecked exception, it is wrapped into {@link HazelcastException}.
47 | */
48 | public static T retry(Callable callable, int retries, List nonRetryableKeywords) {
49 | int retryCount = 0;
50 | while (true) {
51 | try {
52 | return callable.call();
53 | } catch (Exception e) {
54 | retryCount++;
55 | if (retryCount > retries || containsAnyOf(e, nonRetryableKeywords)) {
56 | throw unchecked(e);
57 | }
58 | long waitIntervalMs = backoffIntervalForRetry(retryCount);
59 | LOGGER.warning(
60 | String.format("Couldn't discover Hazelcast members using Kubernetes API, [%s] retrying in %s seconds...",
61 | retryCount, waitIntervalMs / MS_IN_SECOND));
62 | sleep(waitIntervalMs);
63 | }
64 | }
65 | }
66 |
67 | private static RuntimeException unchecked(Exception e) {
68 | if (e instanceof RuntimeException) {
69 | return (RuntimeException) e;
70 | }
71 | return new HazelcastException(e);
72 | }
73 |
74 | private static boolean containsAnyOf(Exception e, List nonRetryableKeywords) {
75 | Throwable currentException = e;
76 | while (currentException != null) {
77 | String exceptionMessage = currentException.getMessage();
78 | for (String keyword : nonRetryableKeywords) {
79 | if (exceptionMessage != null && exceptionMessage.contains(keyword)) {
80 | return true;
81 | }
82 | }
83 | currentException = currentException.getCause();
84 | }
85 | return false;
86 | }
87 |
88 | private static long backoffIntervalForRetry(int retryCount) {
89 | long result = INITIAL_BACKOFF_MS;
90 | for (int i = 1; i < retryCount; i++) {
91 | result *= BACKOFF_MULTIPLIER;
92 | if (result > MAX_BACKOFF_MS) {
93 | return MAX_BACKOFF_MS;
94 | }
95 | }
96 | return result;
97 | }
98 |
99 | private static void sleep(long millis) {
100 | try {
101 | Thread.sleep(millis);
102 | } catch (InterruptedException e) {
103 | Thread.currentThread().interrupt();
104 | throw new HazelcastException(e);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/HazelcastKubernetesDiscoveryStrategyFactoryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.config.properties.PropertyDefinition;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.logging.NoLogFactory;
22 | import com.hazelcast.spi.discovery.DiscoveryNode;
23 | import com.hazelcast.spi.discovery.DiscoveryStrategy;
24 | import com.hazelcast.spi.discovery.DiscoveryStrategyFactory.DiscoveryStrategyLevel;
25 | import org.junit.Before;
26 | import org.junit.Test;
27 | import org.junit.runner.RunWith;
28 | import org.mockito.Mock;
29 | import org.mockito.Mockito;
30 | import org.powermock.api.mockito.PowerMockito;
31 | import org.powermock.core.classloader.annotations.PrepareForTest;
32 | import org.powermock.modules.junit4.PowerMockRunner;
33 |
34 | import java.io.File;
35 | import java.util.Collection;
36 | import java.util.HashMap;
37 |
38 | import static org.junit.Assert.assertEquals;
39 | import static org.junit.Assert.assertTrue;
40 | import static org.powermock.api.mockito.PowerMockito.mock;
41 |
42 | @RunWith(PowerMockRunner.class)
43 | @PrepareForTest({KubernetesApiEndpointResolver.class, HazelcastKubernetesDiscoveryStrategyFactory.class})
44 | public class HazelcastKubernetesDiscoveryStrategyFactoryTest {
45 |
46 | private static final ILogger LOGGER = new NoLogFactory().getLogger("no");
47 | private static final String API_TOKEN = "token";
48 | private static final String CA_CERTIFICATE = "ca-certificate";
49 |
50 | @Mock
51 | DiscoveryNode discoveryNode;
52 |
53 | @Mock
54 | private KubernetesClient client;
55 |
56 | @Before
57 | public void setup()
58 | throws Exception {
59 | PowerMockito.whenNew(KubernetesClient.class).withAnyArguments().thenReturn(client);
60 | }
61 |
62 | @Test
63 | public void checkDiscoveryStrategyType() {
64 | HazelcastKubernetesDiscoveryStrategyFactory factory = new HazelcastKubernetesDiscoveryStrategyFactory();
65 | Class extends DiscoveryStrategy> strategyType = factory.getDiscoveryStrategyType();
66 | assertEquals(HazelcastKubernetesDiscoveryStrategy.class.getCanonicalName(), strategyType.getCanonicalName());
67 | }
68 |
69 | @Test
70 | public void checkProperties() {
71 | HazelcastKubernetesDiscoveryStrategyFactory factory = new HazelcastKubernetesDiscoveryStrategyFactory();
72 | Collection propertyDefinitions = factory.getConfigurationProperties();
73 | assertTrue(propertyDefinitions.contains(KubernetesProperties.SERVICE_DNS));
74 | assertTrue(propertyDefinitions.contains(KubernetesProperties.SERVICE_PORT));
75 | }
76 |
77 | @Test
78 | public void createDiscoveryStrategy() {
79 | HashMap properties = new HashMap();
80 | properties.put(KubernetesProperties.KUBERNETES_API_TOKEN.key(), API_TOKEN);
81 | properties.put(KubernetesProperties.KUBERNETES_CA_CERTIFICATE.key(), CA_CERTIFICATE);
82 | properties.put(String.valueOf(KubernetesProperties.SERVICE_PORT), 333);
83 | properties.put(KubernetesProperties.NAMESPACE.key(), "default");
84 | HazelcastKubernetesDiscoveryStrategyFactory factory = new HazelcastKubernetesDiscoveryStrategyFactory();
85 | DiscoveryStrategy strategy = factory.newDiscoveryStrategy(discoveryNode, LOGGER, properties);
86 | assertTrue(strategy instanceof HazelcastKubernetesDiscoveryStrategy);
87 | strategy.start();
88 | strategy.destroy();
89 | }
90 |
91 | @Test
92 | public void autoDetection() throws Exception {
93 | // given
94 | File mockFile = mock(File.class);
95 | Mockito.doReturn(true).when(mockFile).exists();
96 | PowerMockito.whenNew(File.class).withArguments("/var/run/secrets/kubernetes.io/serviceaccount/token")
97 | .thenReturn(mockFile);
98 | HazelcastKubernetesDiscoveryStrategyFactory factory = new HazelcastKubernetesDiscoveryStrategyFactory();
99 |
100 | // when & then
101 | assertTrue(factory.tokenFileExists());
102 | assertEquals(DiscoveryStrategyLevel.PLATFORM, factory.discoveryStrategyLevel());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/HazelcastKubernetesDiscoveryStrategyFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.config.properties.PropertyDefinition;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.logging.Logger;
22 | import com.hazelcast.spi.discovery.DiscoveryNode;
23 | import com.hazelcast.spi.discovery.DiscoveryStrategy;
24 | import com.hazelcast.spi.discovery.DiscoveryStrategyFactory;
25 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26 |
27 | import java.io.File;
28 | import java.net.InetAddress;
29 | import java.net.UnknownHostException;
30 | import java.util.Arrays;
31 | import java.util.Collection;
32 | import java.util.Collections;
33 | import java.util.Map;
34 |
35 | /**
36 | * Just the factory to create the Kubernetes Discovery Strategy
37 | */
38 | public class HazelcastKubernetesDiscoveryStrategyFactory
39 | implements DiscoveryStrategyFactory {
40 | private static final Collection PROPERTY_DEFINITIONS;
41 |
42 | static {
43 | PROPERTY_DEFINITIONS = Collections.unmodifiableCollection(Arrays.asList(
44 | KubernetesProperties.SERVICE_DNS,
45 | KubernetesProperties.SERVICE_DNS_TIMEOUT,
46 | KubernetesProperties.SERVICE_NAME,
47 | KubernetesProperties.SERVICE_LABEL_NAME,
48 | KubernetesProperties.SERVICE_LABEL_VALUE,
49 | KubernetesProperties.NAMESPACE,
50 | KubernetesProperties.POD_LABEL_NAME,
51 | KubernetesProperties.POD_LABEL_VALUE,
52 | KubernetesProperties.RESOLVE_NOT_READY_ADDRESSES,
53 | KubernetesProperties.USE_NODE_NAME_AS_EXTERNAL_ADDRESS,
54 | KubernetesProperties.KUBERNETES_API_RETIRES,
55 | KubernetesProperties.KUBERNETES_MASTER_URL,
56 | KubernetesProperties.KUBERNETES_API_TOKEN,
57 | KubernetesProperties.KUBERNETES_CA_CERTIFICATE,
58 | KubernetesProperties.SERVICE_PORT));
59 | }
60 |
61 | public Class extends DiscoveryStrategy> getDiscoveryStrategyType() {
62 | return HazelcastKubernetesDiscoveryStrategy.class;
63 | }
64 |
65 | public DiscoveryStrategy newDiscoveryStrategy(DiscoveryNode discoveryNode, ILogger logger,
66 | Map properties) {
67 |
68 | return new HazelcastKubernetesDiscoveryStrategy(logger, properties);
69 | }
70 |
71 | public Collection getConfigurationProperties() {
72 | return PROPERTY_DEFINITIONS;
73 | }
74 |
75 | /**
76 | * In all Kubernetes environments the file "/var/run/secrets/kubernetes.io/serviceaccount/token" is injected into the
77 | * container. That is why we can use it to verify if this code is run in the Kubernetes environment.
78 | *
79 | * Note that if the Kubernetes environment is not configured correctly, this file my not exist. However, in such case,
80 | * this plugin won't work anyway, so it makes perfect sense to return {@code false}.
81 | *
82 | * @return true if running in the Kubernetes environment
83 | */
84 | @Override
85 | public boolean isAutoDetectionApplicable() {
86 | return tokenFileExists() && defaultKubernetesMasterReachable();
87 | }
88 |
89 | @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
90 | boolean tokenFileExists() {
91 | return new File("/var/run/secrets/kubernetes.io/serviceaccount/token").exists();
92 | }
93 |
94 | private boolean defaultKubernetesMasterReachable() {
95 | try {
96 | InetAddress.getByName("kubernetes.default.svc");
97 | return true;
98 | } catch (UnknownHostException e) {
99 | ILogger logger = Logger.getLogger(HazelcastKubernetesDiscoveryStrategyFactory.class);
100 | logger.warning("Hazelcast running on Kubernetes, but \"kubernetes.default.svc\" is not reachable. "
101 | + "Check your Kubernetes DNS configuration.");
102 | logger.finest(e);
103 | return false;
104 | }
105 | }
106 |
107 | @Override
108 | public DiscoveryStrategyLevel discoveryStrategyLevel() {
109 | return DiscoveryStrategyLevel.PLATFORM;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/RestClientTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.github.tomakehurst.wiremock.junit.WireMockRule;
20 | import org.junit.Before;
21 | import org.junit.Rule;
22 | import org.junit.Test;
23 |
24 | import javax.net.ssl.HostnameVerifier;
25 | import javax.net.ssl.HttpsURLConnection;
26 | import javax.net.ssl.SSLSession;
27 | import java.io.File;
28 |
29 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
30 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
31 | import static com.hazelcast.kubernetes.KubernetesConfig.readFileContents;
32 | import static org.junit.Assert.assertEquals;
33 |
34 | public class RestClientTest {
35 | private static final String API_ENDPOINT = "/some/endpoint";
36 | private static final String BODY_REQUEST = "some body request";
37 | private static final String BODY_RESPONSE = "some body response";
38 |
39 | @Rule
40 | public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
41 | .dynamicHttpsPort()
42 | .keystorePath(pathTo("keystore.jks"))
43 | );
44 |
45 | private String address;
46 |
47 | @Before
48 | public void setUp() {
49 | // disable hostname HTTPS verification for testing
50 | HttpsURLConnection.setDefaultHostnameVerifier(
51 | new HostnameVerifier() {
52 | public boolean verify(String hostname, SSLSession sslSession) {
53 | return true;
54 | }
55 | });
56 | address = String.format("https://localhost:%s", wireMockRule.httpsPort());
57 | }
58 |
59 | @Test
60 | public void getSuccess() {
61 | // given
62 | stubFor(get(urlEqualTo(API_ENDPOINT))
63 | .willReturn(aResponse().withStatus(200).withBody(BODY_RESPONSE)));
64 |
65 | // when
66 | String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
67 | .withCaCertificates(readFile("ca.crt"))
68 | .get();
69 |
70 | // then
71 | assertEquals(BODY_RESPONSE, result);
72 | }
73 |
74 | @Test
75 | public void getWithHeaderSuccess() {
76 | // given
77 | String headerKey = "Metadata-Flavor";
78 | String headerValue = "Google";
79 | stubFor(get(urlEqualTo(API_ENDPOINT))
80 | .withHeader(headerKey, equalTo(headerValue))
81 | .willReturn(aResponse().withStatus(200).withBody(BODY_RESPONSE)));
82 |
83 | // when
84 | String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
85 | .withHeader(headerKey, headerValue)
86 | .withCaCertificates(readFile("ca.crt"))
87 | .get();
88 |
89 | // then
90 | assertEquals(BODY_RESPONSE, result);
91 | }
92 |
93 | @Test(expected = RestClientException.class)
94 | public void getFailure() {
95 | // given
96 | stubFor(get(urlEqualTo(API_ENDPOINT))
97 | .willReturn(aResponse().withStatus(500).withBody("Internal error")));
98 |
99 | // when
100 | RestClient.create(String.format("%s%s", address, API_ENDPOINT))
101 | .withCaCertificates(readFile("ca.crt"))
102 | .get();
103 |
104 | // then
105 | // throw exception
106 | }
107 |
108 | @Test
109 | public void postSuccess() {
110 | // given
111 | stubFor(post(urlEqualTo(API_ENDPOINT))
112 | .withRequestBody(equalTo(BODY_REQUEST))
113 | .willReturn(aResponse().withStatus(200).withBody(BODY_RESPONSE)));
114 |
115 | // when
116 | String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
117 | .withBody(BODY_REQUEST)
118 | .withCaCertificates(readFile("ca.crt"))
119 | .post();
120 |
121 | // then
122 | assertEquals(BODY_RESPONSE, result);
123 | }
124 |
125 | private String readFile(String filename) {
126 | return readFileContents(pathTo(filename));
127 | }
128 |
129 | private String pathTo(String filename) {
130 | return new File(getClass().getClassLoader().getResource(filename).getFile()).getAbsolutePath();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/KubernetesApiEndpointResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.config.NetworkConfig;
20 | import com.hazelcast.kubernetes.KubernetesClient.Endpoint;
21 | import com.hazelcast.logging.ILogger;
22 | import com.hazelcast.cluster.Address;
23 | import com.hazelcast.spi.discovery.DiscoveryNode;
24 | import com.hazelcast.spi.discovery.SimpleDiscoveryNode;
25 |
26 | import java.net.InetAddress;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 |
30 | class KubernetesApiEndpointResolver
31 | extends HazelcastKubernetesDiscoveryStrategy.EndpointResolver {
32 |
33 | private final String serviceName;
34 | private final String serviceLabel;
35 | private final String serviceLabelValue;
36 | private final String podLabel;
37 | private final String podLabelValue;
38 | private final Boolean resolveNotReadyAddresses;
39 | private final int port;
40 | private final KubernetesClient client;
41 |
42 | KubernetesApiEndpointResolver(ILogger logger, String serviceName, int port,
43 | String serviceLabel, String serviceLabelValue, String podLabel, String podLabelValue,
44 | Boolean resolveNotReadyAddresses, KubernetesClient client) {
45 |
46 | super(logger);
47 |
48 | this.serviceName = serviceName;
49 | this.port = port;
50 | this.serviceLabel = serviceLabel;
51 | this.serviceLabelValue = serviceLabelValue;
52 | this.podLabel = podLabel;
53 | this.podLabelValue = podLabelValue;
54 | this.resolveNotReadyAddresses = resolveNotReadyAddresses;
55 | this.client = client;
56 | }
57 |
58 | @Override
59 | List resolve() {
60 | if (serviceName != null && !serviceName.isEmpty()) {
61 | logger.fine("Using service name to discover nodes.");
62 | return getSimpleDiscoveryNodes(client.endpointsByName(serviceName));
63 | } else if (serviceLabel != null && !serviceLabel.isEmpty()) {
64 | logger.fine("Using service label to discover nodes.");
65 | return getSimpleDiscoveryNodes(client.endpointsByServiceLabel(serviceLabel, serviceLabelValue));
66 | } else if (podLabel != null && !podLabel.isEmpty()) {
67 | logger.fine("Using pod label to discover nodes.");
68 | return getSimpleDiscoveryNodes(client.endpointsByPodLabel(podLabel, podLabelValue));
69 | }
70 | return getSimpleDiscoveryNodes(client.endpoints());
71 | }
72 |
73 | private List getSimpleDiscoveryNodes(List endpoints) {
74 | List discoveredNodes = new ArrayList();
75 | for (Endpoint address : endpoints) {
76 | addAddress(discoveredNodes, address);
77 | }
78 | return discoveredNodes;
79 | }
80 |
81 | private void addAddress(List discoveredNodes, Endpoint endpoint) {
82 | if (Boolean.TRUE.equals(resolveNotReadyAddresses) || endpoint.isReady()) {
83 | Address privateAddress = createAddress(endpoint.getPrivateAddress());
84 | Address publicAddress = createAddress(endpoint.getPublicAddress());
85 | discoveredNodes
86 | .add(new SimpleDiscoveryNode(privateAddress, publicAddress, endpoint.getAdditionalProperties()));
87 | if (logger.isFinestEnabled()) {
88 | logger.finest(String.format("Found node service with addresses (private, public): %s, %s ", privateAddress,
89 | publicAddress));
90 | }
91 | }
92 | }
93 |
94 | private Address createAddress(KubernetesClient.EndpointAddress address) {
95 | if (address == null) {
96 | return null;
97 | }
98 | String ip = address.getIp();
99 | InetAddress inetAddress = mapAddress(ip);
100 | int port = port(address);
101 | return new Address(inetAddress, port);
102 | }
103 |
104 | private int port(KubernetesClient.EndpointAddress address) {
105 | if (this.port > 0) {
106 | return this.port;
107 | }
108 | if (address.getPort() != null) {
109 | return address.getPort();
110 | }
111 | return NetworkConfig.DEFAULT_PORT;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/DnsEndpointResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.config.NetworkConfig;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.cluster.Address;
22 | import com.hazelcast.spi.discovery.DiscoveryNode;
23 | import com.hazelcast.spi.discovery.SimpleDiscoveryNode;
24 |
25 | import java.net.InetAddress;
26 | import java.net.UnknownHostException;
27 | import java.util.ArrayList;
28 | import java.util.Collections;
29 | import java.util.HashSet;
30 | import java.util.List;
31 | import java.util.Set;
32 | import java.util.concurrent.Callable;
33 | import java.util.concurrent.ExecutionException;
34 | import java.util.concurrent.ExecutorService;
35 | import java.util.concurrent.Executors;
36 | import java.util.concurrent.Future;
37 | import java.util.concurrent.TimeUnit;
38 | import java.util.concurrent.TimeoutException;
39 |
40 | final class DnsEndpointResolver
41 | extends HazelcastKubernetesDiscoveryStrategy.EndpointResolver {
42 | // executor service for dns lookup calls
43 | private static final ExecutorService DNS_LOOKUP_SERVICE = Executors.newCachedThreadPool();
44 |
45 | private final String serviceDns;
46 | private final int port;
47 | private final int serviceDnsTimeout;
48 |
49 | DnsEndpointResolver(ILogger logger, String serviceDns, int port, int serviceDnsTimeout) {
50 | super(logger);
51 | this.serviceDns = serviceDns;
52 | this.port = port;
53 | this.serviceDnsTimeout = serviceDnsTimeout;
54 | }
55 |
56 | List resolve() {
57 | try {
58 | return lookup();
59 | } catch (TimeoutException e) {
60 | logger.warning(String.format("DNS lookup for serviceDns '%s' failed: DNS resolution timeout", serviceDns));
61 | return Collections.emptyList();
62 | } catch (UnknownHostException e) {
63 | logger.warning(String.format("DNS lookup for serviceDns '%s' failed: unknown host", serviceDns));
64 | return Collections.emptyList();
65 | } catch (Exception e) {
66 | logger.warning(String.format("DNS lookup for serviceDns '%s' failed", serviceDns), e);
67 | return Collections.emptyList();
68 | }
69 | }
70 |
71 | private List lookup()
72 | throws UnknownHostException, InterruptedException, ExecutionException, TimeoutException {
73 | Set addresses = new HashSet();
74 |
75 | Future future = DNS_LOOKUP_SERVICE.submit(new Callable() {
76 | @Override
77 | public InetAddress[] call() throws Exception {
78 | return getAllInetAddresses();
79 | }
80 | });
81 |
82 | try {
83 | for (InetAddress address : future.get(serviceDnsTimeout, TimeUnit.SECONDS)) {
84 | if (addresses.add(address.getHostAddress()) && logger.isFinestEnabled()) {
85 | logger.finest("Found node service with address: " + address);
86 | }
87 | }
88 | } catch (ExecutionException e) {
89 | if (e.getCause() instanceof UnknownHostException) {
90 | throw (UnknownHostException) e.getCause();
91 | } else {
92 | throw e;
93 | }
94 | } catch (TimeoutException e) {
95 | // cancel DNS lookup
96 | future.cancel(true);
97 | throw e;
98 | }
99 |
100 | if (addresses.size() == 0) {
101 | logger.warning("Could not find any service for serviceDns '" + serviceDns + "'");
102 | return Collections.emptyList();
103 | }
104 |
105 | List result = new ArrayList();
106 | for (String address : addresses) {
107 | result.add(new SimpleDiscoveryNode(new Address(address, getHazelcastPort(port))));
108 | }
109 | return result;
110 | }
111 |
112 | /**
113 | * Do the actual lookup
114 | * @return array of resolved inet addresses
115 | * @throws UnknownHostException
116 | */
117 | private InetAddress[] getAllInetAddresses() throws UnknownHostException {
118 | return InetAddress.getAllByName(serviceDns);
119 | }
120 |
121 | private static int getHazelcastPort(int port) {
122 | if (port > 0) {
123 | return port;
124 | }
125 | return NetworkConfig.DEFAULT_PORT;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/RetryUtilsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.core.HazelcastException;
20 | import org.junit.Test;
21 |
22 | import java.util.Collections;
23 | import java.util.concurrent.Callable;
24 |
25 | import static com.hazelcast.kubernetes.RetryUtils.BACKOFF_MULTIPLIER;
26 | import static com.hazelcast.kubernetes.RetryUtils.INITIAL_BACKOFF_MS;
27 | import static java.util.Arrays.asList;
28 | import static org.junit.Assert.assertEquals;
29 | import static org.junit.Assert.assertTrue;
30 | import static org.mockito.BDDMockito.given;
31 | import static org.mockito.Mockito.mock;
32 | import static org.mockito.Mockito.times;
33 | import static org.mockito.Mockito.verify;
34 |
35 | public class RetryUtilsTest {
36 | private static final Integer RETRIES = 1;
37 | private static final String RESULT = "result string";
38 | private static final String NON_RETRYABLE_KEYWORD = "\"reason\":\"Forbidden\"";
39 |
40 | private Callable callable = mock(Callable.class);
41 |
42 | @Test
43 | public void retryNoRetries()
44 | throws Exception {
45 | // given
46 | given(callable.call()).willReturn(RESULT);
47 |
48 | // when
49 | String result = RetryUtils.retry(callable, RETRIES, Collections.emptyList());
50 |
51 | // then
52 | assertEquals(RESULT, result);
53 | verify(callable).call();
54 | }
55 |
56 | @Test
57 | public void retryRetriesSuccessful()
58 | throws Exception {
59 | // given
60 | given(callable.call()).willThrow(new RuntimeException()).willReturn(RESULT);
61 |
62 | // when
63 | String result = RetryUtils.retry(callable, RETRIES, Collections.emptyList());
64 |
65 | // then
66 | assertEquals(RESULT, result);
67 | verify(callable, times(2)).call();
68 | }
69 |
70 | @Test(expected = RuntimeException.class)
71 | public void retryRetriesFailed()
72 | throws Exception {
73 | // given
74 | given(callable.call()).willThrow(new RuntimeException()).willThrow(new RuntimeException()).willReturn(RESULT);
75 |
76 | // when
77 | RetryUtils.retry(callable, RETRIES, Collections.emptyList());
78 |
79 | // then
80 | // throws exception
81 | }
82 |
83 | @Test(expected = HazelcastException.class)
84 | public void retryRetriesFailedUncheckedException()
85 | throws Exception {
86 | // given
87 | given(callable.call()).willThrow(new Exception()).willThrow(new Exception()).willReturn(RESULT);
88 |
89 | // when
90 | RetryUtils.retry(callable, RETRIES, Collections.emptyList());
91 |
92 | // then
93 | // throws exception
94 | }
95 |
96 | @Test
97 | public void retryRetriesWaitExponentialBackoff()
98 | throws Exception {
99 | // given
100 | double twoBackoffIntervalsMs = INITIAL_BACKOFF_MS + (BACKOFF_MULTIPLIER * INITIAL_BACKOFF_MS);
101 | given(callable.call()).willThrow(new RuntimeException()).willThrow(new RuntimeException()).willReturn(RESULT);
102 |
103 | // when
104 | long startTimeMs = System.currentTimeMillis();
105 | RetryUtils.retry(callable, 5, Collections.emptyList());
106 | long endTimeMs = System.currentTimeMillis();
107 |
108 | // then
109 | assertTrue(twoBackoffIntervalsMs < (endTimeMs - startTimeMs));
110 | }
111 |
112 | @Test(expected = NonRetryableException.class)
113 | public void retryNonRetryableKeyword()
114 | throws Exception {
115 | // given
116 | given(callable.call()).willThrow(new NonRetryableException()).willReturn(RESULT);
117 |
118 | // when
119 | RetryUtils.retry(callable, RETRIES, asList(NON_RETRYABLE_KEYWORD));
120 |
121 | // then
122 | // throws exception
123 | }
124 |
125 | @Test(expected = RuntimeException.class)
126 | public void retryNonRetryableKeywordOnCause()
127 | throws Exception {
128 | // given
129 | given(callable.call()).willThrow(new RuntimeException(new NonRetryableException())).willReturn(RESULT);
130 |
131 | // when
132 | RetryUtils.retry(callable, RETRIES, asList(NON_RETRYABLE_KEYWORD));
133 |
134 | // then
135 | // throws exception
136 | }
137 |
138 | private static class NonRetryableException
139 | extends RuntimeException {
140 | private NonRetryableException() {
141 | super(String.format("Message: %s", NON_RETRYABLE_KEYWORD));
142 | }
143 | }
144 | }
--------------------------------------------------------------------------------
/src/main/java/com/hazelcast/kubernetes/HazelcastKubernetesDiscoveryStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.kubernetes.KubernetesConfig.DiscoveryMode;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.spi.discovery.AbstractDiscoveryStrategy;
22 | import com.hazelcast.spi.discovery.DiscoveryNode;
23 | import com.hazelcast.spi.partitiongroup.PartitionGroupMetaData;
24 |
25 | import java.net.InetAddress;
26 | import java.net.UnknownHostException;
27 | import java.util.HashMap;
28 | import java.util.List;
29 | import java.util.Map;
30 |
31 | final class HazelcastKubernetesDiscoveryStrategy
32 | extends AbstractDiscoveryStrategy {
33 | private final KubernetesClient client;
34 | private final EndpointResolver endpointResolver;
35 | private KubernetesConfig config;
36 |
37 | private final Map memberMetadata = new HashMap();
38 |
39 | HazelcastKubernetesDiscoveryStrategy(ILogger logger, Map properties) {
40 | super(logger, properties);
41 |
42 | config = new KubernetesConfig(properties);
43 | logger.info(config.toString());
44 |
45 | client = buildKubernetesClient(config);
46 |
47 | if (DiscoveryMode.DNS_LOOKUP.equals(config.getMode())) {
48 | endpointResolver = new DnsEndpointResolver(logger, config.getServiceDns(), config.getServicePort(),
49 | config.getServiceDnsTimeout());
50 | } else {
51 | endpointResolver = new KubernetesApiEndpointResolver(logger, config.getServiceName(), config.getServicePort(),
52 | config.getServiceLabelName(), config.getServiceLabelValue(),
53 | config.getPodLabelName(), config.getPodLabelValue(),
54 | config.isResolveNotReadyAddresses(), client);
55 | }
56 |
57 | logger.info("Kubernetes Discovery activated with mode: " + config.getMode().name());
58 | }
59 |
60 | private static KubernetesClient buildKubernetesClient(KubernetesConfig config) {
61 | return new KubernetesClient(config.getNamespace(), config.getKubernetesMasterUrl(), config.getKubernetesApiToken(),
62 | config.getKubernetesCaCertificate(), config.getKubernetesApiRetries(), config.isUseNodeNameAsExternalAddress());
63 | }
64 |
65 | public void start() {
66 | endpointResolver.start();
67 | }
68 |
69 | @Override
70 | public Map discoverLocalMetadata() {
71 | if (memberMetadata.isEmpty()) {
72 | memberMetadata.put(PartitionGroupMetaData.PARTITION_GROUP_ZONE, discoverZone());
73 | memberMetadata.put("hazelcast.partition.group.node", discoverNodeName());
74 | }
75 | return memberMetadata;
76 | }
77 |
78 | /**
79 | * Discovers the availability zone in which the current Hazelcast member is running.
80 | *
81 | * Note: ZONE_AWARE is available only for the Kubernetes API Mode.
82 | */
83 | private String discoverZone() {
84 | if (DiscoveryMode.KUBERNETES_API.equals(config.getMode())) {
85 | try {
86 | String zone = client.zone(podName());
87 | if (zone != null) {
88 | getLogger().info(String.format("Kubernetes plugin discovered availability zone: %s", zone));
89 | return zone;
90 | }
91 | } catch (Exception e) {
92 | // only log the exception and the message, Hazelcast should still start
93 | getLogger().finest(e);
94 | }
95 | getLogger().info("Cannot fetch the current zone, ZONE_AWARE feature is disabled");
96 | }
97 | return "unknown";
98 | }
99 |
100 | /**
101 | * Discovers the name of the node which the current Hazelcast member pod is running on.
102 | *
103 | * Note: NODE_AWARE is available only for the Kubernetes API Mode.
104 | */
105 | private String discoverNodeName() {
106 | if (DiscoveryMode.KUBERNETES_API.equals(config.getMode())) {
107 | try {
108 | String nodeName = client.nodeName(podName());
109 | if (nodeName != null) {
110 | getLogger().info(String.format("Kubernetes plugin discovered node name: %s", nodeName));
111 | return nodeName;
112 | }
113 | } catch (Exception e) {
114 | // only log the exception and the message, Hazelcast should still start
115 | getLogger().finest(e);
116 | }
117 | getLogger().warning("Cannot fetch name of the node, NODE_AWARE feature is disabled");
118 | }
119 | return "unknown";
120 | }
121 |
122 | private String podName() throws UnknownHostException {
123 | String podName = System.getenv("POD_NAME");
124 | if (podName == null) {
125 | podName = System.getenv("HOSTNAME");
126 | }
127 | if (podName == null) {
128 | podName = InetAddress.getLocalHost().getHostName();
129 | }
130 | return podName;
131 | }
132 |
133 | @Override
134 | public Iterable discoverNodes() {
135 | return endpointResolver.resolve();
136 | }
137 |
138 | public void destroy() {
139 | endpointResolver.destroy();
140 | }
141 |
142 | abstract static class EndpointResolver {
143 | protected final ILogger logger;
144 |
145 | EndpointResolver(ILogger logger) {
146 | this.logger = logger;
147 | }
148 |
149 | abstract List resolve();
150 |
151 | void start() {
152 | }
153 |
154 | void destroy() {
155 | }
156 |
157 | protected InetAddress mapAddress(String address) {
158 | if (address == null) {
159 | return null;
160 | }
161 | try {
162 | return InetAddress.getByName(address);
163 | } catch (UnknownHostException e) {
164 | logger.warning("Address '" + address + "' could not be resolved");
165 | }
166 | return null;
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/KubernetesApiEndpointResolverTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.kubernetes.KubernetesClient.Endpoint;
20 | import com.hazelcast.logging.ILogger;
21 | import com.hazelcast.logging.NoLogFactory;
22 | import com.hazelcast.spi.discovery.DiscoveryNode;
23 | import org.junit.Before;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 | import org.mockito.Mock;
27 | import org.powermock.api.mockito.PowerMockito;
28 | import org.powermock.core.classloader.annotations.PrepareForTest;
29 | import org.powermock.modules.junit4.PowerMockRunner;
30 |
31 | import java.util.Collections;
32 | import java.util.List;
33 |
34 | import static java.util.Arrays.asList;
35 | import static org.junit.Assert.assertEquals;
36 | import static org.mockito.BDDMockito.given;
37 |
38 | @RunWith(PowerMockRunner.class)
39 | @PrepareForTest(KubernetesApiEndpointResolver.class)
40 | public class KubernetesApiEndpointResolverTest {
41 | private static final ILogger LOGGER = new NoLogFactory().getLogger("no");
42 | private static final String SERVICE_NAME = "serviceName";
43 | private static final String SERVICE_LABEL = "serviceLabel";
44 | private static final String SERVICE_LABEL_VALUE = "serviceLabelValue";
45 | private static final String POD_LABEL = "podLabel";
46 | private static final String POD_LABEL_VALUE = "podLabelValue";
47 | private static final Boolean RESOLVE_NOT_READY_ADDRESSES = true;
48 |
49 | @Mock
50 | private KubernetesClient client;
51 |
52 | @Before
53 | public void setup()
54 | throws Exception {
55 | PowerMockito.whenNew(KubernetesClient.class).withAnyArguments().thenReturn(client);
56 | }
57 |
58 | @Test
59 | public void resolveWhenNodeInFound() {
60 | // given
61 | List endpoints = Collections.emptyList();
62 | given(client.endpoints()).willReturn(endpoints);
63 |
64 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, null, 0, null, null, null, null, null, client);
65 |
66 | // when
67 | List nodes = sut.resolve();
68 |
69 | // then
70 | assertEquals(0, nodes.size());
71 | }
72 |
73 | @Test
74 | public void resolveWithServiceNameWhenNodeInNamespace() {
75 | resolveWithServiceNameWhenNodeInNamespace(0, 1); // expected port 1 is the kubernetes discovery endpoint port
76 | }
77 |
78 | @Test
79 | public void resolveWithServiceNameWhenNodeInNamespaceAndCustomPort() {
80 | resolveWithServiceNameWhenNodeInNamespace(333, 333);
81 | }
82 |
83 | private void resolveWithServiceNameWhenNodeInNamespace(final int port, final int expectedPort) {
84 | // given
85 | List endpoints = createEndpoints(1);
86 | given(client.endpointsByName(SERVICE_NAME)).willReturn(endpoints);
87 |
88 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, SERVICE_NAME, port, null, null, null, null, null,
89 | client);
90 |
91 | // when
92 | List nodes = sut.resolve();
93 |
94 | // then
95 | assertEquals(1, nodes.size());
96 | assertEquals(expectedPort, nodes.get(0).getPrivateAddress().getPort());
97 | }
98 |
99 | @Test
100 | public void resolveWithServiceLabelWhenNodeWithServiceLabel() {
101 | // given
102 | List endpoints = createEndpoints(2);
103 | given(client.endpointsByServiceLabel(SERVICE_LABEL, SERVICE_LABEL_VALUE)).willReturn(endpoints);
104 |
105 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, null, 0, SERVICE_LABEL, SERVICE_LABEL_VALUE,
106 | null, null, null, client);
107 |
108 | // when
109 | List nodes = sut.resolve();
110 |
111 | // then
112 | assertEquals(1, nodes.size());
113 | assertEquals(2, nodes.get(0).getPrivateAddress().getPort());
114 | }
115 |
116 | @Test
117 | public void resolveWithPodLabelWhenNodeWithPodLabel() {
118 | // given
119 | List endpoints = createEndpoints(2);
120 | given(client.endpointsByPodLabel(POD_LABEL, POD_LABEL_VALUE)).willReturn(endpoints);
121 |
122 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, null, 0, null, null,
123 | POD_LABEL, POD_LABEL_VALUE, null, client);
124 |
125 | // when
126 | List nodes = sut.resolve();
127 |
128 | // then
129 | assertEquals(1, nodes.size());
130 | assertEquals(2, nodes.get(0).getPrivateAddress().getPort());
131 | }
132 |
133 | @Test
134 | public void resolveWithServiceNameWhenNotReadyAddressesAndNotReadyEnabled() {
135 | // given
136 | List endpoints = createNotReadyEndpoints(2);
137 | given(client.endpointsByName(SERVICE_NAME)).willReturn(endpoints);
138 |
139 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, SERVICE_NAME, 0, null, null,
140 | null, null, RESOLVE_NOT_READY_ADDRESSES, client);
141 |
142 | // when
143 | List nodes = sut.resolve();
144 |
145 | // then
146 | assertEquals(1, nodes.size());
147 | }
148 |
149 | @Test
150 | public void resolveWithServiceNameWhenNotReadyAddressesAndNotReadyDisabled() {
151 | // given
152 | List endpoints = createNotReadyEndpoints(2);
153 | given(client.endpointsByName(SERVICE_NAME)).willReturn(endpoints);
154 |
155 | KubernetesApiEndpointResolver sut = new KubernetesApiEndpointResolver(LOGGER, SERVICE_NAME, 0, null, null, null, null, null,
156 | client);
157 |
158 | // when
159 | List nodes = sut.resolve();
160 |
161 | // then
162 | assertEquals(0, nodes.size());
163 | }
164 |
165 | private static List createEndpoints(int customPort) {
166 | return asList(createEntrypointAddress(customPort, true));
167 | }
168 |
169 | private static List createNotReadyEndpoints(int customPort) {
170 | return asList(createEntrypointAddress(customPort, false));
171 | }
172 |
173 | private static Endpoint createEntrypointAddress(int customPort, boolean isReady) {
174 | String ip = "1.1.1.1";
175 | return new Endpoint(new KubernetesClient.EndpointAddress(ip, customPort), isReady);
176 | }
177 | }
--------------------------------------------------------------------------------
/src/test/java/com/hazelcast/kubernetes/DnsEndpointResolverTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hazelcast.kubernetes;
18 |
19 | import com.hazelcast.logging.ILogger;
20 | import com.hazelcast.logging.NoLogFactory;
21 | import com.hazelcast.spi.discovery.DiscoveryNode;
22 | import org.junit.Before;
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 | import org.mockito.invocation.InvocationOnMock;
26 | import org.mockito.stubbing.Answer;
27 | import org.powermock.api.mockito.PowerMockito;
28 | import org.powermock.core.classloader.annotations.PrepareForTest;
29 | import org.powermock.modules.junit4.PowerMockRunner;
30 |
31 | import java.net.InetAddress;
32 | import java.net.UnknownHostException;
33 | import java.util.HashSet;
34 | import java.util.List;
35 | import java.util.Set;
36 |
37 | import static org.junit.Assert.assertEquals;
38 | import static org.mockito.Mockito.any;
39 | import static org.mockito.Mockito.anyString;
40 | import static org.mockito.Mockito.mock;
41 | import static org.mockito.Mockito.never;
42 | import static org.mockito.Mockito.verify;
43 | import static org.mockito.Mockito.when;
44 |
45 | @RunWith(PowerMockRunner.class)
46 | @PrepareForTest(DnsEndpointResolver.class)
47 | public class DnsEndpointResolverTest {
48 | private static final ILogger LOGGER = new NoLogFactory().getLogger("no");
49 |
50 | private static final String SERVICE_DNS = "my-release-hazelcast.default.svc.cluster.local";
51 | private static final int DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS = 5;
52 | private static final int TEST_DNS_TIMEOUT_SECONDS = 1;
53 | private static final int UNSET_PORT = 0;
54 | private static final int DEFAULT_PORT = 5701;
55 | private static final int CUSTOM_PORT = 5702;
56 | private static final String IP_SERVER_1 = "192.168.0.5";
57 | private static final String IP_SERVER_2 = "192.168.0.6";
58 |
59 | @Before
60 | public void setUp()
61 | throws Exception {
62 | PowerMockito.mockStatic(InetAddress.class);
63 |
64 | InetAddress address1 = mock(InetAddress.class);
65 | InetAddress address2 = mock(InetAddress.class);
66 | when(address1.getHostAddress()).thenReturn(IP_SERVER_1);
67 | when(address2.getHostAddress()).thenReturn(IP_SERVER_2);
68 | PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenReturn(new InetAddress[]{address1, address2});
69 | }
70 |
71 | @Test
72 | public void resolve() {
73 | // given
74 | DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
75 |
76 | // when
77 | List result = dnsEndpointResolver.resolve();
78 |
79 | // then
80 |
81 | Set> resultAddresses = setOf(result.get(0).getPrivateAddress().getHost(), result.get(1).getPrivateAddress().getHost());
82 | Set> resultPorts = setOf(result.get(0).getPrivateAddress().getPort(), result.get(1).getPrivateAddress().getPort());
83 | assertEquals(setOf(IP_SERVER_1, IP_SERVER_2), resultAddresses);
84 | assertEquals(setOf(DEFAULT_PORT), resultPorts);
85 | }
86 |
87 | @Test
88 | public void resolveCustomPort() {
89 | // given
90 | DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, CUSTOM_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
91 |
92 | // when
93 | List result = dnsEndpointResolver.resolve();
94 |
95 | // then
96 |
97 | Set> resultAddresses = setOf(result.get(0).getPrivateAddress().getHost(), result.get(1).getPrivateAddress().getHost());
98 | Set> resultPorts = setOf(result.get(0).getPrivateAddress().getPort(), result.get(1).getPrivateAddress().getPort());
99 | assertEquals(setOf(IP_SERVER_1, IP_SERVER_2), resultAddresses);
100 | assertEquals(setOf(CUSTOM_PORT), resultPorts);
101 | }
102 |
103 | @Test
104 | public void resolveException()
105 | throws Exception {
106 | // given
107 | ILogger logger = mock(ILogger.class);
108 | PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenThrow(new UnknownHostException());
109 | DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(logger, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
110 |
111 | // when
112 | List result = dnsEndpointResolver.resolve();
113 |
114 | // then
115 | assertEquals(0, result.size());
116 | verify(logger).warning(String.format("DNS lookup for serviceDns '%s' failed: unknown host", SERVICE_DNS));
117 | verify(logger, never()).warning(anyString(), any(Throwable.class));
118 | }
119 |
120 | @Test
121 | public void resolveNotFound()
122 | throws Exception {
123 | // given
124 | PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenReturn(new InetAddress[0]);
125 | DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
126 |
127 | // when
128 | List result = dnsEndpointResolver.resolve();
129 |
130 | // then
131 | assertEquals(0, result.size());
132 | }
133 |
134 | @Test
135 | public void resolveTimeout()
136 | throws Exception {
137 | // given
138 | ILogger logger = mock(ILogger.class);
139 | PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).then(waitAndAnswer());
140 | DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(logger, SERVICE_DNS, UNSET_PORT, TEST_DNS_TIMEOUT_SECONDS);
141 |
142 | // when
143 | List result = dnsEndpointResolver.resolve();
144 |
145 | // then
146 | assertEquals(0, result.size());
147 | verify(logger).warning(String.format("DNS lookup for serviceDns '%s' failed: DNS resolution timeout", SERVICE_DNS));
148 | verify(logger, never()).warning(anyString(), any(Throwable.class));
149 | }
150 |
151 | private static Answer waitAndAnswer() {
152 | return new Answer() {
153 | @Override
154 | public InetAddress[] answer(InvocationOnMock invocation) throws Throwable {
155 | Thread.sleep(TEST_DNS_TIMEOUT_SECONDS * 5 * 1000);
156 | return new InetAddress[0];
157 | }
158 | };
159 | }
160 |
161 | private static Set> setOf(Object... objects) {
162 | Set