Advertisement
hakegawa

Untitled

Aug 23rd, 2017
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.03 KB | None | 0 0
  1. import argparse
  2. import random
  3.  
  4. import aiohttp
  5. import asyncio
  6.  
  7. import getpass
  8. import codecs
  9. import requests
  10. from urlobject import URLObject
  11.  
  12. from lxml import html
  13. from lxml.etree import XPathEvalError
  14.  
  15. from yaml import load_all
  16. try:
  17. from yaml import CLoader as Loader, CDumper as Dumper
  18. except ImportError:
  19. from yaml import Loader, Dumper
  20.  
  21. from urllib import parse as url_parse
  22.  
  23. requests.packages.urllib3.disable_warnings()
  24.  
  25.  
  26. class HostProcessor(object):
  27. status_to_retry = [502, 504]
  28. max_retry_count = 20
  29.  
  30. def __init__(self, name, urls, base_url='', port=None,
  31. verify=None, auth=None, base_host=None, scheme=None, cookies=None,
  32. verbose_success=True, verbose_fail=True, username=None, password=None, override_host=None, **kwargs):
  33. self.name = name
  34. self.base_host = base_host
  35. self.scheme = scheme
  36. self._base_url = URLObject(base_url) or URLObject().with_hostname(base_host)
  37. if self.scheme:
  38. self._base_url = self._base_url.with_scheme(scheme)
  39. self.port = port
  40. self.urls = urls
  41. self.verify = verify
  42. self.auth = auth
  43. self.cookies = cookies or {}
  44. self.save_cookie = False
  45. self._verbose_success = verbose_success
  46. self._verbose_fail = verbose_fail
  47. self.test_username = username
  48. self.test_password = password
  49. self.fail_count = 0
  50. if not (override_host is None):
  51. override_host = URLObject(override_host) or URLObject().with_hostname(override_host)
  52. self.override_host = override_host
  53. self.kwargs = {k: v % dict(base_host=base_host) for k, v in kwargs.items()}
  54.  
  55. @staticmethod
  56. def parse_status(status):
  57. if isinstance(status, int):
  58. return status,
  59. if isinstance(status, str):
  60. return (int(s) for s in status.split(','))
  61. return status
  62.  
  63. @property
  64. def base_url(self):
  65. base_url = URLObject(self._base_url % dict(base_host=self.base_host, **self.kwargs))
  66. if self.port:
  67. base_url = base_url.with_port(self.port)
  68. return base_url.with_scheme(self.scheme) if self.scheme else base_url
  69.  
  70. def verbose_success(self, name, url, method):
  71. if not self._verbose_success:
  72. print('.', end='', flush=True)
  73. return
  74. print('\n{name} {url} {method} - OK'.format(name=name, url=url, method=method,))
  75.  
  76. def verbose_fail(self, name, url, method, error_message):
  77. self.fail_count += 1
  78. if not self._verbose_fail:
  79. print('Err', end='', flush=True)
  80. return
  81. print('\n{name} {url} {method} - ERROR response: {error_message}'.format(
  82. name=name, url=url, method=method, error_message=error_message))
  83.  
  84. async def process_parse(self, name, raw_html, xpath, ensure_count=False, **kwargs):
  85. tree = html.fromstring(raw_html)
  86. try:
  87. tasks = []
  88. url_list = tree.xpath(xpath)
  89. for url in url_list:
  90. url = URLObject(url)
  91. if url.hostname and url.hostname != self.base_url.hostname:
  92. continue
  93. tasks.append(self.process_url(' - '.join([name, url]), url, **kwargs))
  94. if ensure_count and len(url_list) != ensure_count:
  95. print('\n{name} - ERROR parse: got {got} expected {expected}'.format(
  96. name=name, got=len(url_list), expected=ensure_count))
  97. await asyncio.gather(*tasks)
  98. except XPathEvalError:
  99. print('xpath: {}'.format(xpath))
  100. raise
  101.  
  102. async def process_form_data(self, name, url, raw_html, xpath, username=None, password=None,
  103. username_name='username', password_name='password', status=(302,)):
  104. status = self.parse_status(status)
  105. username, password = username or self.test_username, password or self.test_password
  106. tree = html.fromstring(raw_html)
  107. try:
  108. for form in tree.xpath(xpath):
  109. method = form.attrib.get('method', 'POST')
  110. action = URLObject(
  111. form.attrib.get('action', url)
  112. ).with_hostname(self.base_url.hostname).with_scheme(self.base_url.scheme)
  113. params = {}
  114. for input_elem in form.xpath('//input'):
  115. if 'name' not in input_elem.attrib:
  116. continue
  117. if input_elem.attrib['name'] == username_name:
  118. input_elem.value = username or input('Please enter username: ')
  119. elif input_elem.attrib['name'] == password_name:
  120. input_elem.value = password or getpass.getpass()
  121. params[input_elem.attrib['name']] = input_elem.value or ''
  122. response_status = await self.get_cookies(method, action, params)
  123. if response_status not in status:
  124. self.verbose_fail(name, action, method,
  125. 'status {} not in {}'.format(response_status, status))
  126. else:
  127. self.verbose_success(name, url, method)
  128.  
  129. return response_status in status
  130.  
  131. except XPathEvalError:
  132. print('xpath: {}'.format(xpath))
  133. raise
  134.  
  135. async def get_cookies(self, method, url, params, counter=0):
  136. conn = aiohttp.TCPConnector(verify_ssl=False)
  137. async with aiohttp.ClientSession(connector=conn, cookies=self.cookies) as session:
  138. async with session.request(method, url, allow_redirects=False,
  139. params=params) as response:
  140. if (response.status in self.status_to_retry
  141. and counter < self.max_retry_count):
  142. await asyncio.sleep(1)
  143. return await self.get_cookies(method, url, counter+1)
  144. self.cookies.update(response.cookies)
  145. return response.status
  146.  
  147. async def get_raw_html(self, method, url, counter=0):
  148. conn = aiohttp.TCPConnector(verify_ssl=False)
  149. async with aiohttp.ClientSession(connector=conn, cookies=self.cookies) as session:
  150. headers = {}
  151. if not (self.override_host is None):
  152. headers = {'Host': url.hostname}
  153. url = url\
  154. .with_hostname(self.override_host.hostname)\
  155. .with_port(self.override_host.port)\
  156. .with_scheme(self.override_host.scheme)
  157. async with session.request(method, url, allow_redirects=False, headers=headers) as response:
  158. if (response.status in self.status_to_retry
  159. and counter < self.max_retry_count):
  160. await asyncio.sleep(random.randint(1, 3))
  161. return await self.get_raw_html(method, url, counter+1)
  162. raw_html = await response.read()
  163. if self.save_cookie:
  164. self.cookies.update(response.cookies)
  165. return response.status, raw_html
  166.  
  167. def format_url(self, url):
  168. if not (self.override_host is None):
  169. url = '{} (connected to {})'.format(url, self.override_host)
  170.  
  171. return url
  172.  
  173. async def process_url(self, name, url, status=(200,), method='get',
  174. parse=None, form_submit=None):
  175. status = self.parse_status(status)
  176. url = URLObject(url).with_hostname(self.base_url.hostname).with_scheme(self.base_url.scheme)
  177. if self.port:
  178. url = url.with_port(self.port)
  179. url = url.with_query('&'.join(k + ('=' + url_parse.quote_plus(v) if v else '') for k, v in url.query_dict.items()))
  180.  
  181. response_status, raw_html = await self.get_raw_html(method, url)
  182. formatted_url = self.format_url(url)
  183. if response_status not in status:
  184. self.verbose_fail(
  185. name, formatted_url, method, 'status {} not in {}'.format(response_status, status))
  186. else:
  187. self.verbose_success(name, formatted_url, method)
  188.  
  189. tasks = []
  190. if raw_html and parse:
  191. for parse_data in parse:
  192. parse_data['raw_html'] = raw_html
  193. parse_data['name'] = ' - '.join([name, parse_data['name']])
  194. tasks.append(self.process_parse(**parse_data))
  195. await asyncio.gather(*tasks)
  196.  
  197. if form_submit:
  198. return await self.process_form_data(name, url, raw_html, **form_submit)
  199.  
  200. async def process(self):
  201. if self.auth:
  202. self.save_cookie = True
  203. self.auth['name'] = ' - '.join([self.name, self.auth['name']])
  204. if not await self.process_url(**self.auth):
  205. return
  206.  
  207. tasks = []
  208. for url_data in self.urls:
  209. if isinstance(url_data, str):
  210. url_data = {'url': url_data}
  211.  
  212. if 'name' not in url_data:
  213. url_data['name'] = url_data['url']
  214. url_data['name'] = ' - '.join([self.name, url_data['name']])
  215. tasks.append(self.process_url(**url_data))
  216. await asyncio.gather(*tasks)
  217.  
  218.  
  219. async def main(conf_file, global_data):
  220. exit_code = 0
  221.  
  222. with codecs.open(conf_file, 'r', 'utf-8') as conf_file:
  223. for data in load_all(conf_file, Loader=Loader):
  224.  
  225. if isinstance(data, dict):
  226. global_data = dict(data, **global_data)
  227.  
  228. elif isinstance(data, list):
  229. tasks = []
  230. processors = []
  231.  
  232. for host_data in data:
  233. host_data.update(global_data)
  234.  
  235. processor = HostProcessor(**host_data)
  236. processors.append(processor)
  237. tasks.append(processor.process())
  238.  
  239. await asyncio.gather(*tasks)
  240.  
  241. exit_code = 1 if any(p.fail_count > 0 for p in processors) else 0
  242.  
  243. exit(exit_code)
  244.  
  245.  
  246. if __name__ == '__main__':
  247. parser = argparse.ArgumentParser(description='Smoke test.')
  248. parser.add_argument('--test-suite', '-s', dest='conf_file', default='smoke-test.yaml',
  249. help='Test suite yaml file')
  250. parser.add_argument('--test-user', '-u', dest='test_username', help='Test user for auth')
  251. parser.add_argument('--test-password', '-p', dest='test_password',
  252. help='Test password for auth')
  253. parser.add_argument('--base-host', dest='base_host', help='Base host')
  254. parser.add_argument('--override-host', dest='override_host', help='Host to connect to instead of base host')
  255. parser.add_argument('--mobile-host', dest='mobile_host', help='Mobile host')
  256. parser.add_argument('--scheme', dest='scheme', help='Scheme')
  257. args = parser.parse_args()
  258.  
  259. args_dict = vars(parser.parse_args())
  260. args_dict.pop('conf_file')
  261. global_data = {k: v for k, v in args_dict.items() if v is not None}
  262.  
  263. loop = asyncio.get_event_loop()
  264. loop.run_until_complete(main(args.conf_file, global_data))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement