blob: f09a3ec023aeab1bdea594a01cd4bcd97201328c [file] [log] [blame]
#! /usr/bin/python
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
#
from __future__ import print_function
import fnmatch
import inspect
import json
import logging
import os
import re
import ssl
import sys
import storage.fs_adapter
import vault.db
if sys.version_info >= (3, 0):
#python 3
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
from configparser import ConfigParser
import urllib.parse as urlparse
else:
#python 2
from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
from ConfigParser import ConfigParser
import urlparse
class RequestHandler(BaseHTTPRequestHandler):
"""
An HTTP server, emulating RIAK behavior on the calls done by traffic-ops.
The class implements BaseHTTPRequestHandler functions
"""
def do_GET(self):
"""
Base class function implementation - to be called upon HTTP GET/HEAD requests
"""
try:
self._do_GET()
except Exception as e:
logger.exception("do_GET exception")
do_HEAD = do_GET
def do_POST(self):
"""
Base class function implementation - to be called upon HTTP POST/PUT requests
"""
try:
self._do_POST()
except Exception as e:
logger.exception("do_POST exception")
do_PUT = do_POST
def do_DELETE(self):
"""
Base class function implementation - to be called upon HTTP DEL request
"""
try:
self._do_DELETE()
except Exception as e:
logger.exception("do_DELETE exception")
def _do_GET(self):
"""
Actual GET logic
:raises: Exception
"""
logger.debug("GET %s", self.path)
parsed_path = urlparse.urlparse(self.path)
if parsed_path.path == "/ping":
logger.info("Ping")
self._do_GET_ping()
elif parsed_path.path == "/search/query/sslkeys":
logger.info("Search SSL: path=%s query=%s", parsed_path.path, parsed_path.query)
self._do_GET_sslkeys(parsed_path)
else:
self._do_GET_general(parsed_path)
def _do_GET_ping(self):
"""
Actual GET logic for ping request
:raises: Exception
"""
success, value = db.ping()
if not success:
self.send_response(503)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write("Failure".encode())
return
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(value.encode())
def _do_GET_sslkeys(self, parsed_path):
"""
Actual GET logic for ssl keys request
:param parsed_path: key's url path
:type parsed_path: str
:raises: Exception
"""
filters = {}
keyFilters = {}
cdnFind = re.search(".*q=cdn:([^&]*).*", parsed_path.query)
if cdnFind:
def cdnFilter(key,val):
try:
data = json.loads(val)
return data['cdn']==cdnFind.group(1)
except Exception as e:
return False
filters['cdn'] = cdnFilter
dsFind = re.search(".*q=deliveryservice:([^&]*).*", parsed_path.query)
if dsFind:
def dsFilter(key,val):
try:
data = json.loads(val)
return data['deliveryservice']==dsFind.group(1)
except Exception as e:
return False
filters['ds'] = dsFilter
keyFind = re.search(".*q=_yz_rk:([^&]*).*", parsed_path.query)
if keyFind:
keyFilters['key-match'] = lambda key: fnmatch.fnmatch(os.path.basename(key), keyFind.group(1))
success, parameters = db.searchParameters("/riak/ssl/", keyFilters=keyFilters, filters=filters)
if not success:
self.send_response(503)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Failure"}'.encode())
return
docs = [json.loads(val) for val in parameters.values()]
toReturn = {"response":{"numFound":len(docs),"start":0, "docs":docs}}
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(toReturn).encode())
def _do_GET_general(self, parsed_path):
"""
Actual GET logic for general variable request
:param parsed_path: key's url path
:type parsed_path: str
:raises: Exception
"""
success, value = db.getParameter(parsed_path.path)
if not success:
self.send_response(503)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Failure"}'.encode())
return
if value is None:
self.send_response(404)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Not found"}'.encode())
return
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(value.encode())
def _do_POST(self):
"""
Actual POST request logic
"""
logger.info("POST %s", self.path)
parsed_path = urlparse.urlparse(self.path)
content_len = int(self.headers.getheader('content-length'))
post_body = self.rfile.read(content_len)
data = json.loads(post_body)
#mimic vault beahvior
if parsed_path.path.startswith('/riak/ssl/'):
certificate_data = data.get('certificate', {})
data.update({"certificate.%s"%key: value for key, value in certificate_data.items()})
success = db.setParameter(parsed_path.path, json.dumps(data))
if not success:
self.send_response(503)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Failure"}'.encode())
return
self.send_response(204)
self.send_header('Content-Type', 'application/json')
self.end_headers()
return
def _do_DELETE(self):
"""
Actual DEL request logic
"""
logger.info("DELETE %s", self.path)
parsed_path = urlparse.urlparse(self.path)
success = db.deleteParameter(parsed_path.path)
if not success:
self.send_response(503)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Failure"}'.encode())
return
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write('{"Deleted"}'.encode())
return
def main():
progPath = inspect.stack()[-1][1]
progAbsPath = os.path.abspath( progPath )
progAbsPath = os.path.dirname(os.path.normpath(progAbsPath))
confDir = os.path.join(progAbsPath, "conf")
confFile = os.path.join(confDir, "cfg.ini")
try:
config = ConfigParser()
config.read(confFile)
except IOError as e:
print("Failed to read configuration - I/O error({0}): {1}".format(e.errno, e.strerror), file=sys.stderr)
return 1
except Exception as e:
print("Failed to read configuration: {0}".format(e), file=sys.stderr)
return 1
generalCfg = dict(config.items("general")) if config.has_section("general") else {}
logDir = generalCfg.get("log-dir", os.path.join(progAbsPath, "var/log"))
debugLogFile = os.path.join(logDir, "traffic-ops-vault-debug.log")
mainLogFile = os.path.join(logDir, "traffic-ops-vault.log")
try:
if not os.path.exists(logDir):
os.makedirs(logDir)
except IOError as e:
print("Failed to create log dir - I/O error({0}): {1}".format(e.errno, e.strerror), file=sys.stderr)
return 1
except Exception as e:
print("Failed to create log dir: {0}".format(e), file=sys.stderr)
return 1
global logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# create file handler which logs even debug messages
fhd = logging.FileHandler(debugLogFile)
fhd.setLevel(logging.DEBUG)
fhm = logging.FileHandler(mainLogFile)
fhm.setLevel(logging.INFO)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fhm.setFormatter(formatter)
fhd.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fhm)
logger.addHandler(fhd)
httpServerCfg = dict(config.items("http-server")) if config.has_section("http-server") else {}
listenIP = httpServerCfg.get("listen-ip", "0.0.0.0")
try:
listenPort = int(httpServerCfg.get("listen-port", "8088"))
except Exception as e:
logger.exception("Failed %s integer conversion failed", "listen-port")
return 1
use_ssl_val = httpServerCfg.get("use-ssl", "True")
if use_ssl_val in ["False", "false", "0"]:
use_ssl=False
elif use_ssl_val in ["True", "true", "1"]:
use_ssl=True
else:
logger.error("Invalid %s value", "use-ssl")
return 1
if use_ssl:
sslKey = httpServerCfg.get("ssl-key-path")
if not sslKey:
print("Missing configuration: {0}/{1}".format("http-server", "ssl-key-path"), file=sys.stderr)
return 1
sslCert = httpServerCfg.get("ssl-cert-path")
if not sslCert:
print("Missing configuration: {0}/{1}".format("http-server", "ssl-cert-path"), file=sys.stderr)
return 1
storageAdapterType = generalCfg.get("storage-adapter-type")
if not storageAdapterType:
logger.error("Missing storage adapter type cfg")
return 1
elif storageAdapterType == "fs":
storageAdapter = storage.fs_adapter.FsAdapter(logger=logger)
else:
logger.error("Invalid storage adapter type '%s'", storageAdapterType)
return 1
if not storageAdapter.init_cfg(config):
logger.error("Failed storage adapter initialization")
return 1
if not storageAdapter.init():
logger.error("Failed storage adaper initialization")
return 1
global db
db = vault.db.Db(logger, storageAdapter)
server = HTTPServer((listenIP, listenPort), RequestHandler)
if use_ssl:
try:
server.socket = ssl.wrap_socket (server.socket, keyfile=sslKey, certfile=sslCert, server_side=True)
except Exception as e:
logger.exception("Failed on SSL init")
return 1
msg = 'Starting server at http%s://%s:%d'%("s" if use_ssl else "", listenIP, listenPort)
print(msg, file=sys.stderr)
logger.info(msg)
server.serve_forever()
return 0
if __name__ == '__main__':
sys.exit(main())