Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 48 additions & 140 deletions microservices/gatewayApi/v2/routes/gw_status.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import requests
import sys
import traceback
import urllib3
import certifi
import socket
from urllib.parse import urlparse
from flask import Blueprint, jsonify, request, Response, make_response, abort, g, current_app as app
from werkzeug.exceptions import HTTPException

from v2.auth.auth import admin_jwt, uma_enforce

from v2.services.namespaces import NamespaceService
from utils.get_data_plane import get_data_plane
from clients.kong import get_services_by_ns, get_routes_by_ns

gw_status = Blueprint('gw_status_v2', 'gw_status')
Expand All @@ -23,138 +21,48 @@ def get_statuses(namespace: str) -> object:

log.info("Get status for %s" % namespace)

services = get_services_by_ns (namespace)
routes = get_routes_by_ns (namespace)

response = []

for service in services:
url = build_url (service)
status = "UP"
reason = ""

actual_host = None
host = None
for route in routes:
if route['service']['id'] == service['id'] and 'hosts' in route:
actual_host = route['hosts'][0]
if route['preserve_host']:
host = clean_host(actual_host)

try:
addr = socket.gethostbyname(service['host'])
log.info("Address = %s" % addr)
except:
status = "DOWN"
reason = "DNS"

if status == "UP":
try:
headers = {}
if host is None or service['host'].endswith('.svc'):
r = requests.get(url, headers=headers, timeout=3.0)
status_code = r.status_code
else:
u = urlparse(url)

if host is None:
headers['Host'] = u.hostname
else:
headers['Host'] = host

log.info("GET %-30s %s" % ("%s://%s" % (u.scheme, u.netloc), headers))

urllib3.disable_warnings()
if u.scheme == "https":
pool = urllib3.HTTPSConnectionPool(
"%s" % (u.netloc),
assert_hostname=host,
server_hostname=host,
cert_reqs='CERT_NONE',
ca_certs=certifi.where()
)
else:
pool = urllib3.HTTPConnectionPool(
"%s" % (u.netloc)
)
req = pool.urlopen(
"GET",
u.path,
headers={"Host": host},
assert_same_host=False,
timeout=1.0,
retries=False
)

status_code = req.status

log.info("Result received!! %d" % status_code)
if status_code < 400:
status = "UP"
reason = "%d Response" % status_code
elif status_code == 401 or status_code == 403:
status = "UP"
reason = "AUTH %d" % status_code
else:
status = "DOWN"
reason = "%d Response" % status_code
except requests.exceptions.Timeout as ex:
status = "DOWN"
reason = "TIMEOUT"
except urllib3.exceptions.ConnectTimeoutError as ex:
status = "DOWN"
reason = "TIMEOUT"
except requests.exceptions.ConnectionError as ex:
log.error("ConnError %s" % ex)
status = "DOWN"
reason = "CONNECTION"
except requests.exceptions.SSLError as ex:
status = "DOWN"
reason = "SSL"
except urllib3.exceptions.NewConnectionError as ex:
log.error("NewConnError %s" % ex)
status = "DOWN"
reason = "CON_ERR"
except urllib3.exceptions.SSLError as ex:
log.error(ex)
status = "DOWN"
reason = "SSL_URLLIB3"
except Exception as ex:
log.error(ex)
traceback.print_exc(file=sys.stdout)
status = "DOWN"
reason = "UNKNOWN"

log.info("GET %-30s %s" % (url,reason))
response.append({"name": service['name'], "upstream": url, "status": status, "reason": reason, "host": host, "env_host": actual_host})

return make_response(jsonify(response))

def build_url (s):
schema = default(s, "protocol", "http")
defaultPort = 80
if schema == "https":
defaultPort = 443
host = s['host']
port = default(s, "port", defaultPort)
path = default(s, "path", "/")
if 'url' in s:
return s['url']
else:
return "%s://%s:%d%s" % (schema, host, port, path)


def default (s, key, val):
if key in s and s[key] is not None:
return s[key]
else:
return val


def clean_host (host):
conf = app.config['hostTransformation']
if conf['enabled'] is True:
conf = app.config['hostTransformation']
return host.replace(conf['baseUrl'], 'gov.bc.ca').replace('-data-gov-bc-ca', '.data').replace('-api-gov-bc-ca', '.api').replace('-apps-gov-bc-ca', '.apps')
else:
return host
services = get_services_by_ns(namespace)
routes = get_routes_by_ns(namespace)

ns_svc = NamespaceService()
ns_attributes = ns_svc.get_namespace_attributes(namespace)

res = []

try:
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})
service_payload = {
"services": services,
"routes": routes,
"conf": app.config['hostTransformation'],
}

dp = get_data_plane(ns_attributes)
rqst_url = app.config['data_planes'][dp]['kube-api']
log.debug("[%s] - Initiating request to kube API" % (dp))

res = session.get(rqst_url + "/namespaces/%s/service-status" % namespace,
json=service_payload,
auth=(app.config['kubeApiCreds']['kubeApiUser'], app.config['kubeApiCreds']['kubeApiPass']))

log.debug("[%s] - The kube API responded with %s" % (dp, res.status_code))

if res.status_code != 200:
log.debug("[%s] - The kube API could not process the request" % (dp))
raise Exception("[%s] - Failed to get services: %s" % (dp, str(res.text)))

except HTTPException as ex:
log.error("Error getting status of services. %s" % str(ex))
return make_response(jsonify({"error": "HTTP exception occurred"}), 500)
except requests.exceptions.RequestException as ex:
log.error("Request error: %s" % str(ex))
return make_response(jsonify({"error": "Request exception occurred"}), 500)
except Exception as ex:
log.error("Unexpected error occurred: %s" % str(ex))
traceback.print_exc()
return make_response(jsonify({"error": "Unexpected error occurred"}), 500)
finally:
session.close()

return make_response(jsonify(res.json().get("services_status", {})))
6 changes: 3 additions & 3 deletions microservices/kubeApi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ This API manages resources in Kubernetes environment.
```bash
brew update
brew install pyenv
pyenv install 3.9
pyenv global 3.9
pyenv install 3.11
pyenv global 3.11
curl -sSL https://install.python-poetry.org | python3 -
```

#### Requirements

```bash
poetry env use 3.9 # (optional)
poetry env use 3.11 # (optional)
poetry install
```

Expand Down
150 changes: 149 additions & 1 deletion microservices/kubeApi/routers/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
from fastapi.logger import logger
from config import settings
from typing import Optional
import socket
import requests
from urllib.parse import urlparse
import urllib3
import certifi

router = APIRouter(
prefix="",
Expand Down Expand Up @@ -47,6 +52,12 @@ class BulkSyncRequest(BaseModel):
# SSL Certificate serial number for custom domains
sslCertificateSerialNumber: str


class ServiceStatusRequest(BaseModel):
services: list
routes: list
conf: dict


@router.put("/namespaces/{namespace}/routes", status_code=201, dependencies=[Depends(verify_credentials)])
def add_routes(namespace: str, route: OCPRoute):
Expand Down Expand Up @@ -157,6 +168,118 @@ def get_tls(namespace: str):
logger.debug("[%s] returning %d certs" % (namespace, len(kong_certs)))
return kong_certs

@router.get("/namespaces/{namespace}/service-status", status_code=200, dependencies=[Depends(verify_credentials)])
def get_service_status(namespace: str, service_payload: ServiceStatusRequest):
logger.debug("[%s] get_service_status" % namespace)

services = service_payload.services
routes = service_payload.routes
conf = service_payload.conf

response = []

for service in services:
url = build_url (service)
status = "UP"
reason = ""

actual_host = None
host = None
for route in routes:
if route['service']['id'] == service['id'] and 'hosts' in route:
actual_host = route['hosts'][0]
if route['preserve_host']:
host = clean_host(actual_host, conf)

try:
addr = socket.gethostbyname(service['host'])
logger.info("Address = %s" % addr)
except:
status = "DOWN"
reason = "DNS"

if status == "UP":
try:
headers = {}
if host is None or service['host'].endswith('.svc'):
r = requests.get(url, headers=headers, timeout=3.0)
status_code = r.status_code
else:
u = urlparse(url)

if host is None:
headers['Host'] = u.hostname
else:
headers['Host'] = host

logger.info("GET %-30s %s" % ("%s://%s" % (u.scheme, u.netloc), headers))

urllib3.disable_warnings()
if u.scheme == "https":
pool = urllib3.HTTPSConnectionPool(
"%s" % (u.netloc),
assert_hostname=host,
server_hostname=host,
cert_reqs='CERT_NONE',
ca_certs=certifi.where()
)
else:
pool = urllib3.HTTPConnectionPool(
"%s" % (u.netloc)
)
req = pool.urlopen(
"GET",
u.path,
headers={"Host": host},
assert_same_host=False,
timeout=1.0,
retries=False
)

status_code = req.status

logger.info("Result received!! %d" % status_code)
if status_code < 400:
status = "UP"
reason = "%d Response" % status_code
elif status_code == 401 or status_code == 403:
status = "UP"
reason = "AUTH %d" % status_code
else:
status = "DOWN"
reason = "%d Response" % status_code
except requests.exceptions.Timeout as ex:
status = "DOWN"
reason = "TIMEOUT"
except urllib3.exceptions.ConnectTimeoutError as ex:
status = "DOWN"
reason = "TIMEOUT"
except requests.exceptions.ConnectionError as ex:
logger.error("ConnError %s" % ex)
status = "DOWN"
reason = "CONNECTION"
except requests.exceptions.SSLError as ex:
status = "DOWN"
reason = "SSL"
except urllib3.exceptions.NewConnectionError as ex:
logger.error("NewConnError %s" % ex)
status = "DOWN"
reason = "CON_ERR"
except urllib3.exceptions.SSLError as ex:
logger.error(ex)
status = "DOWN"
reason = "SSL_URLLIB3"
except Exception as ex:
logger.error(ex)
traceback.print_exc(file=sys.stdout)
status = "DOWN"
reason = "UNKNOWN"

logger.info("GET %-30s %s" % (url,reason))
response.append({"name": service['name'], "upstream": url, "status": status, "reason": reason, "host": host, "env_host": actual_host})

return JSONResponse(content={"services_status": response}, status_code=200)

@router.post("/namespaces/{namespace}/routes/sync", status_code=200, dependencies=[Depends(verify_credentials)])
async def verify_and_create_routes(namespace: str, request: Request):

Expand Down Expand Up @@ -276,4 +399,29 @@ def in_list_by_name(match, list):
for item in list:
if item['name'] == match['name']:
return True
return False
return False

def build_url (s):
schema = default(s, "protocol", "http")
defaultPort = 80
if schema == "https":
defaultPort = 443
host = s['host']
port = default(s, "port", defaultPort)
path = default(s, "path", "/")
if 'url' in s:
return s['url']
else:
return "%s://%s:%d%s" % (schema, host, port, path)

def default (s, key, val):
if key in s and s[key] is not None:
return s[key]
else:
return val

def clean_host (host, conf):
if conf['enabled'] is True:
return host.replace(conf['baseUrl'], 'gov.bc.ca').replace('-data-gov-bc-ca', '.data').replace('-api-gov-bc-ca', '.api').replace('-apps-gov-bc-ca', '.apps')
else:
return host
Loading
Loading