-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alex Mattson
committed
Oct 22, 2020
1 parent
fd45446
commit fa040b5
Showing
22 changed files
with
826 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,4 +106,6 @@ venv.bak/ | |
lib/ | ||
include/ | ||
bin/ | ||
pyvenv.cfg | ||
pyvenv.cfg | ||
.DS_Store | ||
.gitignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,159 +1,108 @@ | ||
import requests | ||
from flask import Flask, request, Response, jsonify | ||
import logging | ||
import json | ||
import sys | ||
import socket | ||
import os | ||
from datetime import datetime | ||
import emoji | ||
import random | ||
from flask_cors import CORS | ||
import whereami_payload | ||
|
||
METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/' | ||
METADATA_HEADERS = {'Metadata-Flavor': 'Google'} | ||
from concurrent import futures | ||
import multiprocessing | ||
|
||
import grpc | ||
|
||
from grpc_reflection.v1alpha import reflection | ||
from grpc_health.v1 import health | ||
from grpc_health.v1 import health_pb2 | ||
from grpc_health.v1 import health_pb2_grpc | ||
|
||
import whereami_pb2 | ||
import whereami_pb2_grpc | ||
|
||
app = Flask(__name__) | ||
app.config['JSON_AS_ASCII'] = False # otherwise our emojis get hosed | ||
CORS(app) # enable CORS | ||
|
||
# set up emoji list | ||
emoji_list = list(emoji.unicode_codes.UNICODE_EMOJI.keys()) | ||
|
||
# define Whereami object | ||
whereami_payload = whereami_payload.WhereamiPayload() | ||
|
||
@app.route('/healthz') # healthcheck endpoint | ||
def i_am_healthy(): | ||
return ('OK') | ||
|
||
# create gRPC class | ||
class WhereamigRPC(whereami_pb2_grpc.WhereamiServicer): | ||
|
||
@app.route('/', defaults={'path': ''}) | ||
@app.route('/<path:path>') | ||
def home(path): | ||
def GetPayload(self, request, context): | ||
payload = whereami_payload.build_payload(None) | ||
return whereami_pb2.WhereamiReply(**payload) | ||
|
||
# define the response payload | ||
payload = {} | ||
|
||
# get GCP project ID | ||
try: | ||
r = requests.get(METADATA_URL + | ||
'project/project-id', | ||
headers=METADATA_HEADERS) | ||
if r.ok: | ||
payload['project_id'] = r.text | ||
except: | ||
|
||
logging.warning("Unable to capture project ID.") | ||
|
||
# get GCP zone | ||
try: | ||
r = requests.get(METADATA_URL + | ||
'instance/zone', | ||
headers=METADATA_HEADERS) | ||
if r.ok: | ||
payload['zone'] = str(r.text.split("/")[3]) | ||
except: | ||
|
||
logging.warning("Unable to capture zone.") | ||
|
||
# get GKE node name | ||
try: | ||
r = requests.get(METADATA_URL + | ||
'instance/hostname', | ||
headers=METADATA_HEADERS) | ||
if r.ok: | ||
payload['node_name'] = str(r.text) | ||
except: | ||
|
||
logging.warning("Unable to capture node name.") | ||
|
||
# get GKE cluster name | ||
try: | ||
r = requests.get(METADATA_URL + | ||
'instance/attributes/cluster-name', | ||
headers=METADATA_HEADERS) | ||
if r.ok: | ||
payload['cluster_name'] = str(r.text) | ||
except: | ||
|
||
logging.warning("Unable to capture GKE cluster name.") | ||
|
||
# get host header | ||
try: | ||
payload['host_header'] = request.headers.get('host') | ||
except: | ||
logging.warning("Unable to capture host header.") | ||
|
||
# get pod name, emoji & datetime | ||
payload['pod_name'] = socket.gethostname() | ||
payload['pod_name_emoji'] = emoji_list[hash(socket.gethostname()) % | ||
len(emoji_list)] | ||
payload['timestamp'] = datetime.now().replace(microsecond=0).isoformat() | ||
|
||
# get namespace, pod ip, and pod service account via downstream API | ||
if os.getenv('POD_NAMESPACE'): | ||
payload['pod_namespace'] = os.getenv('POD_NAMESPACE') | ||
else: | ||
logging.warning("Unable to capture pod namespace.") | ||
|
||
if os.getenv('POD_IP'): | ||
payload['pod_ip'] = os.getenv('POD_IP') | ||
else: | ||
logging.warning("Unable to capture pod IP address.") | ||
# if selected will serve gRPC endpoint on port 9090 | ||
# see https://github.com/grpc/grpc/blob/master/examples/python/xds/server.py | ||
# for reference on code below | ||
def grpc_serve(): | ||
server = grpc.server( | ||
futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count())) | ||
|
||
if os.getenv('POD_SERVICE_ACCOUNT'): | ||
payload['pod_service_account'] = os.getenv('POD_SERVICE_ACCOUNT') | ||
else: | ||
logging.warning("Unable to capture pod KSA.") | ||
# Add the application servicer to the server. | ||
whereami_pb2_grpc.add_WhereamiServicer_to_server(WhereamigRPC(), server) | ||
|
||
# get the whereami METADATA envvar | ||
metadata = os.getenv('METADATA') | ||
if os.getenv('METADATA'): | ||
payload['metadata'] = os.getenv('METADATA') | ||
else: | ||
logging.warning("Unable to capture metadata.") | ||
# Create a health check servicer. We use the non-blocking implementation | ||
# to avoid thread starvation. | ||
health_servicer = health.HealthServicer( | ||
experimental_non_blocking=True, | ||
experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=1)) | ||
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server) | ||
|
||
# should we call a backend service? | ||
call_backend = os.getenv('BACKEND_ENABLED') | ||
# Create a tuple of all of the services we want to export via reflection. | ||
services = tuple( | ||
service.full_name | ||
for service in whereami_pb2.DESCRIPTOR.services_by_name.values()) + ( | ||
reflection.SERVICE_NAME, health.SERVICE_NAME) | ||
|
||
if call_backend == 'True': | ||
# Add the reflection service to the server. | ||
reflection.enable_server_reflection(services, server) | ||
server.add_insecure_port('[::]:9090') | ||
server.start() | ||
|
||
backend_service = os.getenv('BACKEND_SERVICE') | ||
# Mark all services as healthy. | ||
overall_server_health = "" | ||
for service in services + (overall_server_health,): | ||
health_servicer.set(service, health_pb2.HealthCheckResponse.SERVING) | ||
|
||
try: | ||
r = requests.get('http://' + backend_service) | ||
if r.ok: | ||
backend_result = r.json() | ||
else: | ||
backend_result = None | ||
except: | ||
# Park the main application thread. | ||
server.wait_for_termination() | ||
|
||
print(sys.exc_info()[0]) | ||
backend_result = None | ||
|
||
payload['backend_result'] = backend_result | ||
|
||
echo_headers = os.getenv('ECHO_HEADERS') | ||
|
||
if echo_headers == 'True': | ||
|
||
try: | ||
# HTTP heathcheck | ||
@app.route('/healthz') # healthcheck endpoint | ||
def i_am_healthy(): | ||
return ('OK') | ||
|
||
payload['headers'] = {k:v for k, v in request.headers.items()} | ||
|
||
except: | ||
# default HTTP service | ||
@app.route('/', defaults={'path': ''}) | ||
@app.route('/<path:path>') | ||
def home(path): | ||
|
||
logging.warning("Unable to capture inbound headers.") | ||
payload = whereami_payload.build_payload(request.headers) | ||
|
||
return jsonify(payload) | ||
|
||
|
||
if __name__ == '__main__': | ||
out_hdlr = logging.StreamHandler(sys.stdout) | ||
fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | ||
fmt = logging.Formatter( | ||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s") | ||
out_hdlr.setFormatter(fmt) | ||
out_hdlr.setLevel(logging.INFO) | ||
logging.getLogger().addHandler(out_hdlr) | ||
logging.getLogger().setLevel(logging.INFO) | ||
app.logger.handlers = [] | ||
app.logger.propagate = True | ||
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080))) | ||
|
||
# decision point - HTTP or gRPC? | ||
if os.getenv('GRPC_ENABLED') == "True": | ||
logging.info("gRPC server listening on port 9090") | ||
grpc_serve() | ||
|
||
else: | ||
app.run( | ||
host='0.0.0.0', port=int(os.environ.get('PORT', 8080)), | ||
threaded=True) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: whereami-grpc-configmap | ||
data: | ||
BACKEND_ENABLED: "False" | ||
BACKEND_SERVICE: "whereami-grpc-backend" # substitute with corresponding service name - this example assumes both services are in the same namespace | ||
METADATA: "grpc-backend" | ||
GRPC_ENABLED: "True" |
Oops, something went wrong.