def test_report_result(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) wu = csclient.WorkUnit() wu.plugin = Testlib.TEST_PLUGIN_NAME cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234' try: fd, wu.test_file = tempfile.mkstemp() os.close(fd) with open(wu.test_file, 'wb') as fp: fp.write(os.urandom(32) * 1024) with Testlib.TestServer(port) as srv: srv.set_mode('report') response = cl.report_result(count=1, defect='BADf00D', failure='DEADBEEF', file_name=wu.test_file, log='', plugin=wu.plugin, name='test') self.assertTrue(response) finally: if os.path.isfile(wu.test_file): os.remove(wu.test_file)
def report_work(self, wu): """ report_work in this example will populate the WorkUnit with dummy data and report the work back to the CrashStash server. """ # populate the dummy work unit wu.duration = wu.duration if wu.duration else random.randint(1, 1200) wu.iterations = wu.iterations if wu.iterations else random.randint( 1, 40000) log.info('Reporting WorkUnit...') log.info('Plugin: %s', wu.plugin) log.info('Duration: %d', wu.duration) log.info('Iterations: %d', wu.iterations) log.info('Contacting server with work report...') c = csclient.Client(addr=self._ip_addr, cert=self._cert, port=self._port, scheme=self._scheme, debug=True) # attempt to report work unit to server if c.report_work(wu): log.info('Work reported') else: # TODO: a retry mechanism should be added sc = c.get_status_code() if sc is not None and sc != 200: log.warn('Status code: %d', sc) log.warn('Failed to report work unit')
def test_request_bad_url(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) with Testlib.TestServer(port): wu = cl.request('csclient/bad_url1234') self.assertIsNone(wu)
def test_request_server_hang(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', timeout=0.1, version_file=self._test_version_file) with Testlib.TestServer(port) as srv: srv.set_mode('server_hang') wu = cl.request() self.assertIsNone(wu)
def request(self, plugin_name=None): """ Makes a request to the CrashStash server for a work unit. plugin_name is used to request work for a specific plugin. If None is used work for any active plugin can be assigned. Returns a WorkUnit or None if an update for the client or a plugin was received instead of a WorkUnit. None is also returned if there is a problem communicating with the server. None typically means try again. """ c = csclient.Client(addr=self._ip_addr, cert=self._cert, port=self._port, scheme=self._scheme, debug=True) log.info('The current client version is: %s', c.get_client_version()) log.info('Contacting server with work request...') if plugin_name is not None: log.info('Plugin manually requested: %s...', plugin_name) wu = c.request(plugin=plugin_name) sc = c.get_status_code() if sc is not None and sc != 200: log.warn('Status code: %d', sc) if c.required_client_update(): # a client update is available, unpack it and return None # so another work request is made. log.info('Looks like the client needs to be updated.') new_version, file_name = c.required_client_update() self._fake_client_update(new_version, file_name) return None if c.required_plugin_update(): # a plugin update is available, unpack it and return None # so another work request is made. pl_name, pl_version, pl_file = c.required_plugin_update() log.info('Looks like there is an update for %s.', pl_name) self._fake_plugin_update(pl_name, pl_version, pl_file) return None if not type(wu) == type(csclient.WorkUnit()): log.warn('Something went wrong expected a WorkUnit.') log.warn('Has this client been approved?') # wait to avoid hammering the server time.sleep(5) return None log.info('Received a work unit') log.info('plugin: %s', wu.plugin) log.info('for %d seconds or %d iterations (0 == no limit)', wu.duration, wu.iterations) log.info('use this test case: %s', wu.test_file) log.info('it should have this sha1 hash: %s', wu.test_hash) if wu.allow_fuzzing: log.info('it is %sfuzzable', '' if wu.allow_fuzzing else 'NOT ') os.remove(wu.test_file) return wu
def test_request_client_update(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) with Testlib.TestServer(port) as srv: srv.set_mode('client_update') wu = cl.request() self.assertIsNone(wu) self.assertEqual(cl.get_client_version(), '') self.assertTrue(os.path.isfile(cl._client_update)) os.remove(cl._client_update) self.assertEqual(cl._versions['client_pending_update'], '20140918_110500')
def test_report_work_unit(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) wu = csclient.WorkUnit() wu.plugin = Testlib.TEST_PLUGIN_NAME wu.duration = 1200 wu.iterations = 4567 cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234' with Testlib.TestServer(port) as srv: srv.set_mode('report') response = cl.report_work(wu) self.assertTrue(response)
def test_request_fuzz(self): for _ in range(10): # increase this to 1000 for a longer run port = random.randint(6000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) with Testlib.TestServer(port) as srv: srv.set_mode('fuzz') wu = cl.request() if wu and os.path.isfile(wu.test_file): os.remove(wu.test_file) if os.path.isfile(cl._plugin_update): os.remove(cl._plugin_update) if os.path.isfile(cl._client_update): os.remove(cl._client_update)
def report_result(self, wu): """ report_result in this example will create a dummy result and report it back to the CrashStash server. """ log.info('Generating a result') # generate random dummy test case fd, test_file = tempfile.mkstemp() os.close(fd) with open(test_file, 'wb') as fp: fp.write(os.urandom(2**random.randint(0, 20))) try: # generate random dummy result result = { 'classification': random.choice(['UNKNOWN', 'EXPLOITABLE', 'TIMEOUT']), 'count': random.randint(1, 10), 'defect': hashlib.sha1(os.urandom(random.randint(0, 1))).hexdigest(), 'failure': hashlib.sha1(os.urandom(random.randint(1, 3))).hexdigest(), 'file_name': test_file, 'log': 'stuff happened...\nstack()\ntrace()\nfoo()\n', 'name': 'test_file.bin', 'plugin': wu.plugin } c = csclient.Client(addr=self._ip_addr, cert=self._cert, port=self._port, scheme=self._scheme, debug=True) log.info('Reporting Result...') if c.report_result(**result): log.info('Result reported') else: # TODO: a retry mechanism should be added sc = c.get_status_code() if sc is not None and sc != 200: log.warn('Status code: %d', sc) log.warn('Failed to report result') finally: os.remove(test_file)
def test_request(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) with Testlib.TestServer(port) as srv: srv.set_mode('request') wu = cl.request() self.assertIsNone(cl.required_client_update()) self.assertIsNone(cl.required_plugin_update()) self.assertIsNotNone(wu) self.assertEqual(wu.allow_fuzzing, True) self.assertEqual(wu.duration, 1200) self.assertEqual(wu.iterations, 0) self.assertEqual(wu.plugin, Testlib.TEST_PLUGIN_NAME) self.assertEqual(wu.test_name, 'test_case_fn.bin') self.assertTrue(os.path.isfile(wu.test_file)) with open(wu.test_file, 'rb') as fp: file_hash = hashlib.sha1(fp.read()).hexdigest() os.remove(wu.test_file) self.assertEqual(wu.test_hash, file_hash)
def test_report_work_unit_fuzz(self): for _ in range(10): # increase this to 1000 for a longer test port = random.randint(6000, 9000) cl = csclient.Client(port=port, scheme='http', timeout=random.choice([0.001, 0.01]) if Testlib.Fuzz.dice(20) else None, version_file=self._test_version_file) wu = csclient.WorkUnit() if Testlib.Fuzz.dice(): wu.allow_fuzzing = Testlib.Fuzz.string(None) elif not Testlib.Fuzz.dice(): wu.allow_fuzzing = random.choice(['true', '']) if Testlib.Fuzz.dice(): wu.duration = Testlib.Fuzz.int() elif not Testlib.Fuzz.dice(): wu.duration = 1200 if Testlib.Fuzz.dice(): wu.iterations = Testlib.Fuzz.int() elif not Testlib.Fuzz.dice(): wu.iterations = 1200 if Testlib.Fuzz.dice(): wu.plugin = Testlib.Fuzz.string(None) elif not Testlib.Fuzz.dice(): wu.plugin = Testlib.TEST_PLUGIN_NAME cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234' if Testlib.Fuzz.dice(): wu.test_name = Testlib.Fuzz.string(None) elif not Testlib.Fuzz.dice(): wu.test_name = 'test_name' if Testlib.Fuzz.dice(20): wu = None with Testlib.TestServer(port) as srv: srv.set_mode('report') response = cl.report_work(wu)
def test_request_project_update(self): port = random.randint(8000, 9000) cl = csclient.Client(port=port, scheme='http', version_file=self._test_version_file) with Testlib.TestServer(port) as srv: srv.set_mode('plugin_update') wu = cl.request() self.assertIsNone(wu) self.assertTrue(os.path.isfile(cl._plugin_update)) self.assertEqual(cl.get_plugin_version(Testlib.TEST_PLUGIN_NAME), '') self.assertEqual( cl.required_plugin_update(), (Testlib.TEST_PLUGIN_NAME, '20140918_110500', cl._plugin_update)) self.assertEqual(cl._versions['plugin_pending_update'], (Testlib.TEST_PLUGIN_NAME, '20140918_110500')) cl.update_plugin() self.assertIsNone(cl.required_plugin_update()) self.assertEqual(cl.get_plugin_version(Testlib.TEST_PLUGIN_NAME), '20140918_110500') self.assertTrue(os.path.isdir(Testlib.TEST_PLUGIN_NAME)) shutil.rmtree(Testlib.TEST_PLUGIN_NAME)
def run(self): """ This is the main loop. Operations managed here include: - communication with the server - requesting work - reporting work and results - client and plugin updates - preparation to perform work - post work cleanup """ log.info('Ctrl+C to quit') try: base_dir = os.getcwd() done = False while not done: log.info('Current time: %s', time.strftime('%Y/%m/%d %H:%M:%S')) if self._scheme != 'https': log.warning('Not using HTTPS') elif self._cert is None: log.warning('Server certificate NOT provided') log.warning('Server verification will NOT be performed') conn = csclient.Client(addr=self._ip_addr, cert=self._cert, port=self._port, scheme=self._scheme, version_file=self._version_file) log.info('Client version: %s', conn.get_client_version()) log.info('Contacting %s with work request...', self._ip_addr) if self._requested_pl is not None: log.info('Plugin manually requested: %s', self._requested_pl) w_unit = conn.request(plugin=self._requested_pl) sc = conn.get_status_code() if sc is not None and sc != 200: log.warning('Status code: %d', sc) log.warning('Has this client been approved?') if self._test_mode: return False log.warning('Waiting %d seconds', self._retry_delay) time.sleep(self._retry_delay) continue if conn.required_client_update(): cl_version, update_file = conn.required_client_update() log.info('Client requires update to version: %s', cl_version) self._unpack_archive(update_file) conn.update_client_version(cl_version) return True if conn.required_plugin_update(): pl_name = conn.required_plugin_update()[0] log.info('%s will now be updated', pl_name) conn.update_plugin(dest_path='projects') continue if not type(w_unit) == type(csclient.WorkUnit()): log.warning('Server transaction failed') log.warning('Waiting %d seconds', self._retry_delay) time.sleep(self._retry_delay) continue log.info('Got work for %s', w_unit.plugin) working_dir = '%s_%s' % (time.strftime('%Y%m%d-%H-%M-%S'), w_unit.plugin) os.mkdir(working_dir) try: self._perform_work(w_unit, working_dir) except KeyboardInterrupt: done = True log.info('User interrupted work') log.info('Will exit following work report') log.info('Reporting work') while not conn.report_work(self._report_unit): log.warning('Failed to report work waiting %d seconds', self._retry_delay) time.sleep(self._retry_delay) log.info('%d result(s) to report', len(self._results)) f_ids = list(self._results.keys()) while f_ids: if conn.report_result(**self._results[f_ids[0]]): log.info('Result reported') f_ids.pop(0) else: log.warning( 'Failed to report result waiting %d seconds', self._retry_delay) time.sleep(self._retry_delay) log.info('Work report complete') os.chdir(base_dir) self._report_unit = None self._results = None alf.delete(w_unit.test_file) alf.delete(os.path.abspath(working_dir)) self._do_deletes() except KeyboardInterrupt: log.info('User interrupted') done = True finally: pass #TODO: report errors return not done # indicate relaunch should be performed
def test_request_no_server(self): cl = csclient.Client(port=random.randint(8000, 9000), scheme='http', version_file=self._test_version_file) wu = cl.request() self.assertIsNone(wu)
def test_report_result_fuzz(self): for _ in range(10): # increase this to 1000 for a longer test port = random.randint(6000, 9000) cl = csclient.Client(port=port, scheme='http', timeout=random.choice([0.001, 0.01]) if Testlib.Fuzz.dice(20) else None, version_file=self._test_version_file) wu = csclient.WorkUnit() args = {} if Testlib.Fuzz.dice(): args['classification'] = Testlib.Fuzz.string(None) else: args['classification'] = 'UNKNOWN' if Testlib.Fuzz.dice(): args['count'] = Testlib.Fuzz.int() else: args['count'] = 1 if Testlib.Fuzz.dice(): args['defect'] = Testlib.Fuzz.string(None) else: args['defect'] = 'BADf00D' if Testlib.Fuzz.dice(): args['failure'] = Testlib.Fuzz.string(None) else: args['failure'] = 'DEADBEEF' if Testlib.Fuzz.dice(): args['log'] = Testlib.Fuzz.string(None) else: args['log'] = 'test' if Testlib.Fuzz.dice(): wu.plugin = Testlib.Fuzz.string(None) else: wu.plugin = Testlib.TEST_PLUGIN_NAME cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234' if Testlib.Fuzz.dice(): wu.test_name = Testlib.Fuzz.string(None) else: wu.test_name = 'test_name' try: fd, wu.test_file = tempfile.mkstemp() os.close(fd) with open(wu.test_file, 'wb') as fp: fp.write(os.urandom(random.choice([0, 1, 32]))) with Testlib.TestServer(port) as srv: srv.set_mode('report') cl.report_result( count=args['count'], defect=args['defect'], failure=args['failure'], file_name=wu.test_file if not Testlib.Fuzz.dice() else Testlib.Fuzz.string(None), log=args['log'], plugin=wu.plugin, name=wu.test_name) finally: if os.path.isfile(wu.test_file): os.remove(wu.test_file)