Skip to content

Commit

Permalink
[CLOUD-3200] - querying k8s api to discover generated routes
Browse files Browse the repository at this point in the history
Signed-off-by: Ricardo Zanini <zanini@redhat.com>
  • Loading branch information
ricardozanini committed May 9, 2019
1 parent 95d3ccf commit a47004a
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 7 deletions.
66 changes: 59 additions & 7 deletions os-eap-sso/added/keycloak.sh
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,10 @@ function configure_client() {
client_config="${client_config},\"publicClient\":\"false\",\"secret\":\"${SSO_SECRET}\""
client_config="${client_config}}"

if [ -z "$SSO_SECRET" ]; then
log_warning "ERROR: SSO_SECRET not set. Make sure to generate a secret in the SSO/Keycloak client '$module_name' configuration and then set the SSO_SECRET variable."
fi

result=`$CURL -H "Content-Type: application/json" -H "Authorization: Bearer ${token}" -X POST -d "${client_config}" ${sso_service}/admin/realms/${SSO_REALM}/clients`

if [ -n "$result" ]; then
Expand Down Expand Up @@ -448,7 +452,7 @@ function read_web_dot_xml {
}

function get_application_routes {

if [ -n "$HOSTNAME_HTTP" ]; then
route="http://${HOSTNAME_HTTP}"
fi
Expand All @@ -457,12 +461,60 @@ function get_application_routes {
secureroute="https://${HOSTNAME_HTTPS}"
fi

if [ -n "$route" ] && [ -n "$secureroute" ]; then
APPLICATION_ROUTES="${route};${secureroute}"
elif [ -n "$route" ]; then
APPLICATION_ROUTES="${route}"
elif [ -n "$secureroute" ]; then
APPLICATION_ROUTES="${secureroute}"
if [ -z "$HOSTNAME_HTTP" ] && [ -z "$HOSTNAME_HTTPS" ]; then
log_warning "HOSTNAME_HTTP and HOSTNAME_HTTPS are not set, trying to discover secure route by querying internal APIs"
APPLICATION_ROUTES=$(discover_routes)
else
if [ -n "$route" ] && [ -n "$secureroute" ]; then
APPLICATION_ROUTES="${route};${secureroute}"
elif [ -n "$route" ]; then
APPLICATION_ROUTES="${route}"
elif [ -n "$secureroute" ]; then
APPLICATION_ROUTES="${secureroute}"
fi
fi
}

# Tries to discover the route using the pod's hostname
function discover_routes() {
local podsuffix=$(python -c "a='${HOSTNAME}'.split('-'); print '-'.join(a[0:len(a)-2])")
echo $(query_routes_from_service $podsuffix)
}

# Verify if the container is on OpenShift. The variable K8S_ENV could be set to emulate this behavior
function is_running_on_openshift() {
if [ -e /var/run/secrets/kubernetes.io/serviceaccount/token ] || [ "${K8S_ENV}" = true ] ; then
return 0
else
return 1
fi
}

# Queries the Routes from the Kubernetes API based on the service name
# ${1} - service name
# see: https://docs.openshift.com/container-platform/3.11/rest_api/apis-route.openshift.io/v1.Route.html#Get-apis-route.openshift.io-v1-routes
function query_routes_from_service() {
local serviceName=${1}
# only execute the following lines if this container is running on OpenShift
if is_running_on_openshift; then
local namespace=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
local token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
local response=$(curl -s -w "%{http_code}" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $token" \
-H 'Accept: application/json' \
${KUBERNETES_SERVICE_PROTOCOL:-https}://${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}:${KUBERNETES_SERVICE_PORT:-443}/apis/route.openshift.io/v1/namespaces/${namespace}/routes?fieldSelector=spec.to.name=${serviceName})
if [[ "${response: -3}" = "200" && "${response::- 3},," = *"items"* ]]; then
routes=$(echo ${response::- 3} | \
python -c 'import json,sys;obj=json.load(sys.stdin); \
routes = [ "https://" + item["spec"]["host"] if "tls" in item["spec"] else "http://" + item["spec"]["host"] for item in obj["items"] ]; \
print(";".join("{}".format(route) for route in routes));')
echo $routes
else
log_warning "Fail to query the Route using the Kubernetes API, the Service Account might not have the necessary privileges."

if [ ! -z "${response}" ]; then
log_warning "Response message: ${response::- 3} - HTTP Status code: ${response: -3}"
fi
fi
fi
}
17 changes: 17 additions & 0 deletions os-eap-sso/tests/bats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Keycloak integration bats tests

These bats tests verify the `keycloak.sh` script use cases.

### The Mock Server

The [`server`](server) dir has a bash utility called [shinatra](https://github.com/benrady/shinatra) to mock a real web server for use cases that require Kubernetes API integration.

The JSON mock responses to test the API calls are in the [`mock_responses`](mock_responses) directory.

To create a new test case, just create a new JSON file from the API you want to test and save it in the `mock_responses` directory. Check the [OpenShift API reference](https://docs.openshift.com/container-platform/3.11/rest_api/) if you are not sure how to call the internal APIs.

Then you can use the [`setup_k8s_api`](common.bash) to fire up the server. Just don't forget to kill the pid and all the child processes. Check the usage of this function in the test suite [`hostname-discovery.bats`](hostname-discovery.bats).

### Hostname Discovery Test Suite

The tests cases in the [`hostname-discovery.bats`](hostname-discovery.bats) file basically verify if the function `query_routes_from_service` on `keycloak.sh` is working as expected simulating scenarios where the API is not available, there's one, multiple or no routes at all.
32 changes: 32 additions & 0 deletions os-eap-sso/tests/bats/common.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
echo $BATS_TEST_DIRNAME
load $BATS_TEST_DIRNAME/../../../test-common/log_utils.bash

export JBOSS_HOME=$BATS_TMPDIR/jboss_home
export K8S_ENV=false
export KUBERNETES_SERVICE_HOST="localhost"
export KUBERNETES_SERVICE_PORT=8080
export KUBERNETES_SERVICE_PROTOCOL="http"

if [ -e /var/run/secrets/kubernetes.io/serviceaccount/token ]; then
K8S_ENV=true
fi

mkdir -p $JBOSS_HOME/bin/launch
mkdir -p $JBOSS_HOME/responses
cp $BATS_TEST_DIRNAME/../../added/keycloak.sh $JBOSS_HOME/bin/launch
cp $BATS_TEST_DIRNAME/../../../test-common/logging.sh $JBOSS_HOME/bin/launch
cp $BATS_TEST_DIRNAME/server/shinatra.sh $JBOSS_HOME/bin/launch
cp $BATS_TEST_DIRNAME/mock_responses/* $JBOSS_HOME/responses

source $JBOSS_HOME/bin/launch/keycloak.sh
source $JBOSS_HOME/bin/launch/shinatra.sh

# Configure the Mocked Kubernetes server based on mocked responses
# {1} mock_response
function setup_k8s_api() {
local mock_response=${1}
local data=$(cat $JBOSS_HOME/responses/${mock_response}.json | tr -d \\n)
start_mock_server ${KUBERNETES_SERVICE_PORT} "${data}" >&2 &
local pid=$!
echo ${pid}
}
50 changes: 50 additions & 0 deletions os-eap-sso/tests/bats/hostname-discovery.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bats

load common

# Runs the base api test
# {1} mock_response
function run_api_test {
local mock_response=${1}
local server_pid=$(setup_k8s_api ${mock_response})
K8S_ENV=true
local routes=$(discover_routes)
pkill -P $server_pid
echo $routes
}

@test "Is nc installed?" {
run nc --version
[ "$status" -eq 0 ]
}

@test "Kubernetes Route API not available" {
local expected=""
if [ "$K8S_ENV" = true ]; then
skip "This test supposed to be run outside a kubernetes environment"
fi
run discover_routes
[ "$status" -eq 0 ]
[ "${lines[0]}" = "${expected}" ]
}

@test "Kubernetes Route API found no routes for the pod" {
local expected=""
local mock_response="no-route"
result=$(run_api_test $mock_response)
[ "${result}" = "$expected" ]
}

@test "Kubernetes Route API found one route for the pod" {
local expected="https://eap-app-bsig-cloud.192.168.99.100.nip.io"
local mock_response="single-route"
result=$(run_api_test $mock_response)
[ "${result}" = "$expected" ]
}

@test "Kubernetes Route API found multiple routes for the pod" {
local expected="http://bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io;https://secure-bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io"
local mock_response="multi-route"
result=$(run_api_test $mock_response)
[ "${result}" = "$expected" ]
}
115 changes: 115 additions & 0 deletions os-eap-sso/tests/bats/mock_responses/multi-route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"kind": "RouteList",
"apiVersion": "route.openshift.io/v1",
"metadata": {
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes",
"resourceVersion": "1633753"
},
"items": [
{
"metadata": {
"name": "bc-authoring-rhpamcentr",
"namespace": "bsig-cloud",
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes/bc-authoring-rhpamcentr",
"uid": "d81990d6-6d27-11e9-b294-080027e8c7bc",
"resourceVersion": "1241102",
"creationTimestamp": "2019-05-02T22:15:50Z",
"labels": {
"app": "rhpam74-authoring",
"application": "bc-authoring",
"rhpam": "1.0",
"service": "bc-authoring-rhpamcentr",
"template": "rhpam74-authoring"
},
"annotations": {
"description": "Route for Business Central's http service.",
"haproxy.router.openshift.io/timeout": "60s",
"openshift.io/generated-by": "OpenShiftNewApp",
"openshift.io/host.generated": "true"
}
},
"spec": {
"host": "bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io",
"to": {
"kind": "Service",
"name": "bc-authoring-rhpamcentr",
"weight": 100
},
"port": {
"targetPort": "http"
},
"wildcardPolicy": "None"
},
"status": {
"ingress": [
{
"host": "bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io",
"routerName": "router",
"conditions": [
{
"type": "Admitted",
"status": "True",
"lastTransitionTime": "2019-05-02T22:15:51Z"
}
],
"wildcardPolicy": "None"
}
]
}
},
{
"metadata": {
"name": "secure-bc-authoring-rhpamcentr",
"namespace": "bsig-cloud",
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes/secure-bc-authoring-rhpamcentr",
"uid": "d8347fb0-6d27-11e9-b294-080027e8c7bc",
"resourceVersion": "1241107",
"creationTimestamp": "2019-05-02T22:15:50Z",
"labels": {
"app": "rhpam74-authoring",
"application": "bc-authoring",
"rhpam": "1.0",
"service": "bc-authoring-rhpamcentr",
"template": "rhpam74-authoring"
},
"annotations": {
"description": "Route for Business Central's https service.",
"haproxy.router.openshift.io/timeout": "60s",
"openshift.io/generated-by": "OpenShiftNewApp",
"openshift.io/host.generated": "true"
}
},
"spec": {
"host": "secure-bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io",
"to": {
"kind": "Service",
"name": "bc-authoring-rhpamcentr",
"weight": 100
},
"port": {
"targetPort": "https"
},
"tls": {
"termination": "passthrough"
},
"wildcardPolicy": "None"
},
"status": {
"ingress": [
{
"host": "secure-bc-authoring-rhpamcentr-bsig-cloud.192.168.99.100.nip.io",
"routerName": "router",
"conditions": [
{
"type": "Admitted",
"status": "True",
"lastTransitionTime": "2019-05-02T22:15:51Z"
}
],
"wildcardPolicy": "None"
}
]
}
}
]
}
9 changes: 9 additions & 0 deletions os-eap-sso/tests/bats/mock_responses/no-route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"kind": "RouteList",
"apiVersion": "route.openshift.io/v1",
"metadata": {
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes",
"resourceVersion": "1633866"
},
"items": []
}
60 changes: 60 additions & 0 deletions os-eap-sso/tests/bats/mock_responses/single-route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"kind": "RouteList",
"apiVersion": "route.openshift.io/v1",
"metadata": {
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes",
"resourceVersion": "1633112"
},
"items": [
{
"metadata": {
"name": "eap-app",
"namespace": "bsig-cloud",
"selfLink": "/apis/route.openshift.io/v1/namespaces/bsig-cloud/routes/eap-app",
"uid": "eb9ed521-6d0a-11e9-8682-080027e8c7bc",
"resourceVersion": "1194535",
"creationTimestamp": "2019-05-02T18:48:48Z",
"labels": {
"app": "eap72-basic-s2i",
"application": "eap-app",
"template": "eap72-basic-s2i",
"xpaas": "1.0.0"
},
"annotations": {
"description": "Route for application's https service.",
"openshift.io/generated-by": "OpenShiftNewApp",
"openshift.io/host.generated": "true"
}
},
"spec": {
"host": "eap-app-bsig-cloud.192.168.99.100.nip.io",
"to": {
"kind": "Service",
"name": "eap-app",
"weight": 100
},
"tls": {
"termination": "edge",
"insecureEdgeTerminationPolicy": "Redirect"
},
"wildcardPolicy": "None"
},
"status": {
"ingress": [
{
"host": "eap-app-bsig-cloud.192.168.99.100.nip.io",
"routerName": "router",
"conditions": [
{
"type": "Admitted",
"status": "True",
"lastTransitionTime": "2019-05-02T18:48:48Z"
}
],
"wildcardPolicy": "None"
}
]
}
}
]
}
2 changes: 2 additions & 0 deletions os-eap-sso/tests/bats/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Based on:
https://github.com/benrady/shinatra
12 changes: 12 additions & 0 deletions os-eap-sso/tests/bats/server/shinatra.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

# Starts the server
# {1} port
# {2} response
function start_mock_server() {
RESPONSE="HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n${2:-"OK"}\r\n"
while { echo -en "$RESPONSE"; } | nc -l "${1:-8080}"; do
echo "==============================="
done
}

0 comments on commit a47004a

Please sign in to comment.