#!/usr/bin/env python2 ### # ============LICENSE_START======================================================= # ONAP CLAMP # ================================================================================ # Copyright (C) 2018 AT&T Intellectual Property. All rights # reserved. # ================================================================================ # 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. # ============LICENSE_END============================================ # =================================================================== # ### import json import requests import os import errno import sys import SimpleHTTPServer import SocketServer import argparse import tempfile import signal import uuid import shutil parser = argparse.ArgumentParser(description="3rd party Cache & Replay") parser.add_argument("--username", "-u", type=str, help="Set the username for contacting 3rd party - only used for GET") parser.add_argument("--password", "-p", type=str, help="Set the password for contacting 3rd party - only used for GET") parser.add_argument("--root", "-r", default=tempfile.mkdtemp(), type=str, help="Root folder for the proxy cache") parser.add_argument("--temp", "-t", default=tempfile.mkdtemp(), type=str, help="Temp folder for the generated content") parser.add_argument("--proxy" , type=str, help="Url of the Act as a proxy. If not set, this script only uses the cache and will return a 404 if files aren't found") parser.add_argument("--port", "-P", type=int, default="8081", help="Port on which the proxy should listen to") parser.add_argument("--verbose", "-v", type=bool, help="Print more information in case of error") parser.add_argument("--proxyaddress","-a", type=str, help="Address of this proxy, generally either third-party-proxy:8085 or localhost:8085 depending if started with docker-compose or not") options = parser.parse_args() PORT = options.port HOST = options.proxy AUTH = (options.username, options.password) HEADERS = {'X-ECOMP-InstanceID':'CLAMP'} CACHE_ROOT = str(options.root) TMP_ROOT = str(options.temp) PROXY_ADDRESS=str(options.proxyaddress) def signal_handler(signal_sent, frame): global httpd if signal_sent == signal.SIGINT: print('Got Ctrl-C (SIGINT)') httpd.socket.close() httpd.shutdown() httpd.server_close() class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): def print_headers(self): for header,value in self.headers.items(): print("header: %s : %s" % (header, value)) def check_credentials(self): pass def _send_content(self, header_file, content_file): self.send_response(200) with open(header_file, 'rb') as f: headers = json.load(f) for key,value in headers.items(): if key in ('Transfer-Encoding',): continue self.send_header(key, value) self.end_headers() with open(content_file,'rb') as f: fc = f.read() self.wfile.write(fc) def _write_cache(self,cached_file_folder, header_file, content_file, response): os.makedirs(cached_file_folder, 0777) with open(content_file, 'w') as f: f.write(response.raw.read()) with open(header_file, 'w') as f: json.dump(dict(response.raw.headers), f) # Entry point of the code def _get_cached_file_folder_name(self,folder): cached_file_folder = '%s/%s' % (folder, self.path,) print("Cached file name before escaping : %s" % cached_file_folder) cached_file_folder = cached_file_folder.replace('<','<').replace('>','>').replace('?','?').replace('*','*').replace('\\','*').replace(':',':').replace('|','|') print("Cached file name after escaping (used for cache storage) : %s" % cached_file_folder) return cached_file_folder def _get_cached_content_file_name(self,cached_file_folder): return "%s/.file" % (cached_file_folder,) def _get_cached_header_file_name(self,cached_file_folder): return "%s/.header" % (cached_file_folder,) def _execute_content_generated_cases(self,http_type): print("Testing special cases, cache files will be sent to :" +TMP_ROOT) cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) if self.path.startswith("/dcae-service-types?asdcResourceId=") and http_type == "GET": if not _file_available: print "self.path start with /dcae-service-types?asdcResourceId=, generating response json..." uuidGenerated = str(uuid.uuid4()) typeId = "typeId-" + uuidGenerated typeName = "typeName-" + uuidGenerated print "typeId generated: " + typeName + " and typeName: "+ typeId jsonGenerated = "{\"totalCount\":1, \"items\":[{\"typeId\":\"" + typeId + "\", \"typeName\":\"" + typeName +"\"}]}" print "jsonGenerated: " + jsonGenerated os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: f.write(jsonGenerated) return True elif self.path.startswith("/dcae-operationstatus/install") and http_type == "GET": if not _file_available: print "self.path start with /dcae-operationstatus/install, generating response json..." jsonGenerated = "{\"operationType\": \"install\", \"status\": \"succeeded\"}" print "jsonGenerated: " + jsonGenerated try: os.makedirs(cached_file_folder, 0777) except OSError as e: if e.errno != errno.EEXIST: raise print(cached_file_folder+" already exists") with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: f.write(jsonGenerated) return True elif self.path.startswith("/dcae-operationstatus/uninstall") and http_type == "GET": if not _file_available: print "self.path start with /dcae-operationstatus/uninstall, generating response json..." jsonGenerated = "{\"operationType\": \"uninstall\", \"status\": \"succeeded\"}" print "jsonGenerated: " + jsonGenerated try: os.makedirs(cached_file_folder, 0777) except OSError as e: if e.errno != errno.EEXIST: raise print(cached_file_folder+" already exists") with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: f.write(jsonGenerated) return True elif self.path.startswith("/sdc/v1/catalog/services/") and http_type == "POST": if not _file_available: print "self.path start with /sdc/v1/catalog/services/, generating response json..." jsondata = json.loads(self.data_string) jsonGenerated = "{\"artifactName\":\"" + jsondata['artifactName'] + "\",\"artifactType\":\"" + jsondata['artifactType'] + "\",\"artifactURL\":\"" + self.path + "\",\"artifactDescription\":\"" + jsondata['description'] + "\",\"artifactChecksum\":\"ZjJlMjVmMWE2M2M1OTM2MDZlODlmNTVmZmYzNjViYzM=\",\"artifactUUID\":\"" + str(uuid.uuid4()) + "\",\"artifactVersion\":\"1\"}" print "jsonGenerated: " + jsonGenerated os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: f.write(jsonGenerated) return True elif self.path.startswith("/dcae-deployments/") and http_type == "PUT": print "self.path start with /dcae-deployments/ DEPLOY, generating response json..." #jsondata = json.loads(self.data_string) jsonGenerated = "{\"operationType\":\"install\",\"status\":\"processing\",\"links\":{\"status\":\"http:\/\/" + PROXY_ADDRESS + "\/dcae-operationstatus/install\"}}" print "jsonGenerated: " + jsonGenerated if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w+') as f: f.write(jsonGenerated) return True elif self.path.startswith("/dcae-deployments/") and http_type == "DELETE": print "self.path start with /dcae-deployments/ UNDEPLOY, generating response json..." #jsondata = json.loads(self.data_string) jsonGenerated = "{\"operationType\":\"uninstall\",\"status\":\"processing\",\"links\":{\"status\":\"http:\/\/" + PROXY_ADDRESS + "\/dcae-operationstatus/uninstall\"}}" print "jsonGenerated: " + jsonGenerated if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w+') as f: f.write(jsonGenerated) return True elif (self.path.startswith("/pdp/api/") and (http_type == "PUT" or http_type == "DELETE")) or (self.path.startswith("/pdp/api/policyEngineImport") and http_type == "POST"): print "self.path start with /pdp/api/, copying body to response ..." if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len(self.data_string)) + "\", \"Content-Type\": \""+str(self.headers['Content-Type'])+"\"}") with open(cached_file_content, 'w+') as f: f.write(self.data_string) return True elif self.path.startswith("/policy/api/v1/policytypes/") and http_type == "POST": print "self.path start with POST new policy API /pdp/api/, copying body to response ..." if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len(self.data_string)) + "\", \"Content-Type\": \""+str(self.headers['Content-Type'])+"\"}") with open(cached_file_content, 'w+') as f: f.write(self.data_string) return True elif self.path.startswith("/policy/api/v1/policytypes/") and http_type == "DELETE": print "self.path start with DELETE new policy API /policy/api/v1/policytypes/ ..." if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len("")) + "\", \"Content-Type\": \""+str("")+"\"}") with open(cached_file_content, 'w+') as f: f.write(self.data_string) return True elif self.path.startswith("/policy/pap/v1/pdps/policies") and http_type == "POST": print "self.path start with POST new policy API /policy/pap/v1/pdps/ ..." if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len("")) + "\", \"Content-Type\": \""+str("")+"\"}") with open(cached_file_content, 'w+') as f: f.write(self.data_string) return True elif (self.path.startswith("/policy/api/v1/policytypes/") or self.path.startswith("/policy/pap/v1/policies/deployed/")) and http_type == "GET": print "self.path start with /policy/api/v1/policytypes/, generating response json..." jsonGenerated = "{\"policyTypeId\": \"onap.policies.controlloop.operational\",\"policyTypeVersion\": \"1.0.0\",\"policyId\": \"OPERATIONAL_z711F_v1_0_ResourceInstanceName1_tca\"}" print "jsonGenerated: " + jsonGenerated if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: f.write(jsonGenerated) return True else: return False def do_GET(self): cached_file_folder = "" cached_file_content ="" cached_file_header="" print("\n\n\nGot a GET request for %s " % self.path) self.print_headers() self.check_credentials() # Verify if it's a special case is_special = self._execute_content_generated_cases("GET") if is_special: cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) else: cached_file_folder = self._get_cached_file_folder_name(CACHE_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) if not _file_available: print("Request for data currently not present in cache: %s" % (cached_file_folder,)) if not HOST: self.send_response(404) self.end_headers() self.wfile.write('404 Not found, no remote HOST specified on the emulator !!!') return "404 Not found, no remote HOST specified on the emulator !!!" url = '%s%s' % (HOST, self.path) response = requests.get(url, auth=AUTH, headers=HEADERS, stream=True) if response.status_code == 200: self._write_cache(cached_file_folder, cached_file_header, cached_file_content, response) else: print('Error when requesting file :') print('Requested url : %s' % (url,)) print('Status code : %s' % (response.status_code,)) print('Content : %s' % (response.content,)) self.send_response(response.status_code) self.end_headers() self.wfile.write('404 Not found, nothing found on the remote server !!!') return response.content else: print("Request for data currently present in cache: %s" % (cached_file_folder,)) self._send_content(cached_file_header, cached_file_content) if self.path.startswith("/dcae-service-types?asdcResourceId="): print "DCAE case deleting folder created " + cached_file_folder shutil.rmtree(cached_file_folder, ignore_errors=False, onerror=None) else: print "NOT in DCAE case deleting folder created " + cached_file_folder def do_POST(self): cached_file_folder = "" cached_file_content ="" cached_file_header="" print("\n\n\nGot a POST for %s" % self.path) self.check_credentials() self.data_string = self.rfile.read(int(self.headers['Content-Length'])) print("data-string:\n %s" % self.data_string) print("self.headers:\n %s" % self.headers) is_special = self._execute_content_generated_cases("POST") if is_special: cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) else: cached_file_folder = self._get_cached_file_folder_name(CACHE_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) if not _file_available: if not HOST: self.send_response(404) self.end_headers() self.wfile.write('404 Not found, no remote HOST specified on the emulator !!!') return "404 Not found, no remote HOST specified on the emulator !!!" print("Request for data currently not present in cache: %s" % (cached_file_folder,)) url = '%s%s' % (HOST, self.path) print("url: %s" % (url,)) response = requests.post(url, data=self.data_string, headers=self.headers, stream=True) if response.status_code == 200: self._write_cache(cached_file_folder, cached_file_header, cached_file_content, response) else: print('Error when requesting file :') print('Requested url : %s' % (url,)) print('Status code : %s' % (response.status_code,)) print('Content : %s' % (response.content,)) self.send_response(response.status_code) self.end_headers() self.wfile.write('404 Not found, nothing found on the remote server !!!') return response.content else: print("Request for data present in cache: %s" % (cached_file_folder,)) self._send_content(cached_file_header, cached_file_content) def do_PUT(self): cached_file_folder = "" cached_file_content ="" cached_file_header="" print("\n\n\nGot a PUT for %s " % self.path) self.check_credentials() self.data_string = self.rfile.read(int(self.headers['Content-Length'])) print("data-string:\n %s" % self.data_string) print("self.headers:\n %s" % self.headers) is_special = self._execute_content_generated_cases("PUT") if is_special: cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) else: cached_file_folder = self._get_cached_file_folder_name(CACHE_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) if not _file_available: if not HOST: self.send_response(404) self.end_headers() self.wfile.write('404 Not found, no remote HOST specified on the emulator !!!') return "404 Not found, no remote HOST specified on the emulator !!!" print("Request for data currently not present in cache: %s" % (cached_file_folder,)) url = '%s%s' % (HOST, self.path) print("url: %s" % (url,)) response = requests.put(url, data=self.data_string, headers=self.headers, stream=True) if response.status_code == 200: self._write_cache(cached_file_folder, cached_file_header, cached_file_content, response) else: print('Error when requesting file :') print('Requested url : %s' % (url,)) print('Status code : %s' % (response.status_code,)) print('Content : %s' % (response.content,)) self.send_response(response.status_code) self.end_headers() self.wfile.write('404 Not found, nothing found on the remote server !!!') return response.content else: print("Request for data present in cache: %s" % (cached_file_folder,)) self._send_content(cached_file_header, cached_file_content) def do_DELETE(self): cached_file_folder = "" cached_file_content ="" cached_file_header="" print("\n\n\nGot a DELETE for %s " % self.path) self.check_credentials() if self.headers.get('Content-Length') is not None: self.data_string = self.rfile.read(int(self.headers['Content-Length'])) else: self.data_string = "empty generated" print("self.headers:\n %s" % self.headers) is_special = self._execute_content_generated_cases("DELETE") if is_special: cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) else: cached_file_folder = self._get_cached_file_folder_name(CACHE_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) if not _file_available: if not HOST: self.send_response(404) self.end_headers() self.wfile.write('404 Not found, no remote HOST specified on the emulator !!!') return "404 Not found, no remote HOST specified on the emulator !!!" print("Request for data currently not present in cache: %s" % (cached_file_folder,)) url = '%s%s' % (HOST, self.path) print("url: %s" % (url,)) response = requests.put(url, data=self.data_string, headers=self.headers, stream=True) if response.status_code == 200: self._write_cache(cached_file_folder, cached_file_header, cached_file_content, response) else: print('Error when requesting file :') print('Requested url : %s' % (url,)) print('Status code : %s' % (response.status_code,)) print('Content : %s' % (response.content,)) self.send_response(response.status_code) self.end_headers() self.wfile.write('404 Not found, nothing found on the remote server !!!') return response.content else: print("Request for data present in cache: %s" % (cached_file_folder,)) self._send_content(cached_file_header, cached_file_content) # Main code that start the HTTP server httpd = SocketServer.ForkingTCPServer(('', PORT), Proxy) httpd.allow_reuse_address = True print "Listening on port "+ str(PORT) + "(Press Ctrl+C/Ctrl+Z to stop HTTPD Caching script)" print "Caching folder " + CACHE_ROOT + ", Tmp folder for generated files " + TMP_ROOT signal.signal(signal.SIGINT, signal_handler) httpd.serve_forever()