Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLOUD-3200] - querying k8s api to discover generated routes #87

Merged
merged 1 commit into from
May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}