-
Notifications
You must be signed in to change notification settings - Fork 110
Forward all machine generated traffic to port 8443 for cmsweb services. #10330
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
Merged
amaltaro
merged 3 commits into
dmwm:master
from
todor-ivanov:feature_WMCore_portUsage_fix-10119
Mar 19, 2021
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,127 @@ | ||
| #!/usr/bin/env python | ||
| """ | ||
| _PortForward_ | ||
|
|
||
| A decorator for swapping ports in an url | ||
| """ | ||
| from __future__ import print_function, division | ||
| from future import standard_library | ||
| standard_library.install_aliases() | ||
|
|
||
| from builtins import str | ||
|
|
||
| import logging | ||
| from urllib.parse import urlparse, ParseResult | ||
|
|
||
|
|
||
| def portForward(port): | ||
| """ | ||
| Decorator wrapper function for port forwarding of the REST calls of any | ||
| function to a given port. | ||
|
|
||
| Currently there are three constraints for applying this decorator. | ||
| 1. The function to be decorated must be defined within a class and not being a static method. | ||
| The reason for that is because we need to be sure the function's signature will | ||
| always include the class instance as its first argument. | ||
| 2. The url argument must be present as the second one in the positional argument list | ||
| of the decorated function (right after the class instance argument). | ||
| 3. The url must follow the syntax specifications in RFC 1808: | ||
| https://tools.ietf.org/html/rfc1808.html | ||
|
|
||
| If all of the above constraints are fulfilled and the url is part of the | ||
| urlMangleList, then the url is parsed and the port is substituted with the | ||
| one provided as an argument to the decorator's wrapper function. | ||
|
|
||
| param port: The port to which the REST call should be forwarded. | ||
| """ | ||
| def portForwardDecorator(callFunc): | ||
| """ | ||
| The actual decorator | ||
| """ | ||
| urlMangleList = ['https://tivanov', | ||
| 'https://alancc', | ||
| 'https://cmsweb'] | ||
|
|
||
| def portMangle(callObj, url, *args, **kwargs): | ||
| """ | ||
| Function used to check if the url coming with the current argument list | ||
| is to be forwarded and if so change the port to the one provided as an | ||
| argument to the decorator wrapper. | ||
|
|
||
| :param classObj: This is the class object (slef from within the class) | ||
| which is always to be present in the signature of a | ||
| public method. We will never use this argument, but | ||
| we need it there for not breaking the positional | ||
| argument order | ||
| :param url: This is the actual url to be (eventually) forwarded | ||
| :param *args: The positional argument list coming from the original function | ||
| :param *kwargs: The keywords argument list coming from the original function | ||
| """ | ||
| # As a first step try to get a logger from the calling object: | ||
| if callable(getattr(callObj, 'logger', None)): | ||
| logger = callObj.logger | ||
| else: | ||
| logger = logging.getLogger() | ||
|
|
||
| forwarded = False | ||
| try: | ||
| oldUrl = urlparse(url) | ||
| found = False | ||
| if isinstance(url, str): | ||
| for mUrl in urlMangleList: | ||
| if url.startswith(mUrl): | ||
| netlocStr = u'%s:%d' % (oldUrl.hostname, port) | ||
| found = True | ||
| break | ||
| elif isinstance(url, bytes): | ||
| for mUrl in urlMangleList: | ||
| if url.startswith(mUrl.encode('utf-8')): | ||
| netlocStr = b'%s:%d' % (oldUrl.hostname, port) | ||
| found = True | ||
| break | ||
| if found: | ||
| newUrl = ParseResult(scheme=oldUrl.scheme, | ||
| netloc=netlocStr, | ||
| path=oldUrl.path, | ||
| params=oldUrl.params, | ||
| query=oldUrl.query, | ||
| fragment=oldUrl.fragment) | ||
| newUrl = newUrl.geturl() | ||
| forwarded = True | ||
| except Exception as ex: | ||
| msg = "Failed to forward url: %s to port: %s due to ERROR: %s" | ||
| logger.exception(msg, url, port, str(ex)) | ||
| if forwarded: | ||
| return callFunc(callObj, newUrl, *args, **kwargs) | ||
| else: | ||
| return callFunc(callObj, url, *args, **kwargs) | ||
| return portMangle | ||
| return portForwardDecorator | ||
|
|
||
|
|
||
| class PortForward(): | ||
| """ | ||
| A class with a call method implementing a simple way to use the functionality | ||
| provided by the protForward decorator as a pure functional call: | ||
| EXAMPLE: | ||
| from Utils.PortForward import PortForward | ||
|
|
||
| portForwarder = PortForward(8443) | ||
| url = 'https://cmsweb-testbed.cern.ch/couchdb' | ||
| url = portForwarder(url) | ||
| """ | ||
| def __init__(self, port): | ||
| """ | ||
| The init method for the PortForward call class. This one is supposed | ||
| to simply provide an initial class instance with a logger. | ||
| """ | ||
| self.logger = logging.getLogger() | ||
| self.port = port | ||
|
|
||
| def __call__(self, url): | ||
| """ | ||
| The call method for the PortForward class | ||
| """ | ||
| def dummyCall(self, url): | ||
| return url | ||
| return portForward(self.port)(dummyCall)(self, url) |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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,75 @@ | ||
| #!/usr/bin/env python | ||
| """ | ||
| Unittests for PortForward | ||
| """ | ||
|
|
||
| from __future__ import division, print_function | ||
|
|
||
| import unittest | ||
|
|
||
| from Utils.PortForward import portForward, PortForward | ||
|
|
||
|
|
||
| class RequestHandler(object): | ||
| def __init__(self, config=None, logger=None): | ||
| super(RequestHandler, self).__init__() | ||
| if not config: | ||
| config = {} | ||
|
|
||
| @portForward(8443) | ||
| def request(self, url, params=None, headers=None, verb='GET', | ||
| verbose=0, ckey=None, cert=None, doseq=True, | ||
| encode=False, decode=False, cookie=None, uri=None): | ||
| return url | ||
|
|
||
|
|
||
| class PortForwardTests(unittest.TestCase): | ||
| """ | ||
| Unittest for PortForward decorator and class | ||
| """ | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super(PortForwardTests, self).__init__(*args, **kwargs) | ||
| self.urlTestList = ['https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| 'https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| 'https://cmsweb.cern.ch:8443/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| 'https://cmsweb.cern.ch:443/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents', | ||
| u'https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| u'https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| u'https://cmsweb.cern.ch/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| u'https://cmsweb.cern.ch/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents', | ||
| b'https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| b'https://cmsweb.cern.ch/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| b'https://cmsweb.cern.ch/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| b'https://cmsweb.cern.ch/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents'] | ||
|
|
||
| self.urlExpectedtList = ['https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| 'https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| 'https://cmsweb.cern.ch:8443/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| 'https://cmsweb.cern.ch:8443/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents', | ||
| u'https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| u'https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| u'https://cmsweb.cern.ch:8443/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| u'https://cmsweb.cern.ch:8443/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents', | ||
| b'https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bydate?descending=true&limit=1', | ||
| b'https://cmsweb.cern.ch:8443/couchdb/reqmgr_workload_cache/_design/ReqMgr/_view/bystatusandtime?startkey=%5B%22announced%22%2C+0%5D&endkey=%5B%22announced%22%2C+1616016936%5D&descending=false&stale=update_after&include_docs=false', | ||
| b'https://cmsweb.cern.ch:8443/reqmgr2/js/?f=utils.js&f=ajax_utils.js&f=md5.js&f=task_splitting.js', | ||
| b'https://cmsweb.cern.ch:8443/wmstatsserver/data/filtered_requests?mask=RequestStatus&mask=RequestType&mask=RequestPriority&mask=Campaign&mask=RequestNumEvents'] | ||
|
|
||
| def testDecorator(self): | ||
| requesHandler = RequestHandler() | ||
| self.urlResultList = [] | ||
| for url in self.urlTestList: | ||
| self.urlResultList.append(requesHandler.request(url)) | ||
| self.assertItemsEqual(self.urlResultList, self.urlExpectedtList) | ||
|
|
||
| def testCallClass(self): | ||
| portForwarder = PortForward(8443) | ||
| self.urlResultList = [] | ||
| for url in self.urlTestList: | ||
| self.urlResultList.append(portForwarder(url)) | ||
| self.assertItemsEqual(self.urlResultList, self.urlExpectedtList) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.