Ejemplo n.º 1
0
def main():
    parser = argparse.ArgumentParser(description="Stress tester for CMS")
    parser.add_argument(
        "-c", "--contest-id", action="store", type=int, required=True,
        help="ID of the contest to test against")
    parser.add_argument(
        "-n", "--actor-num", action="store", type=int,
        help="the number of actors to spawn")
    parser.add_argument(
        "-s", "--sort-actors", action="store_true",
        help="sort usernames alphabetically before slicing them")
    parser.add_argument(
        "-u", "--base-url", action="store", type=utf8_decoder,
        help="base contest URL for placing HTTP requests "
             "(without trailing slash)")
    parser.add_argument(
        "-S", "--submissions-path", action="store", type=utf8_decoder,
        help="base path for submission to send")
    parser.add_argument(
        "-p", "--prepare-path", action="store", type=utf8_decoder,
        help="file to put contest info to")
    parser.add_argument(
        "-r", "--read-from", action="store", type=utf8_decoder,
        help="file to read contest info from")
    parser.add_argument(
        "-t", "--time-coeff", action="store", type=float, default=10.0,
        help="average wait between actions")
    parser.add_argument(
        "-o", "--only-submit", action="store_true",
        help="whether the actor only submits solutions")
    args = parser.parse_args()

    # If prepare_path is specified we only need to save some useful
    # contest data and exit.
    if args.prepare_path is not None:
        users, tasks = harvest_contest_data(args.contest_id)
        contest_data = dict()
        contest_data['users'] = users
        contest_data['tasks'] = tasks
        with open(args.prepare_path, "wt", encoding="utf-8") as file_:
            file_.write("%s" % contest_data)
        return

    assert args.time_coeff > 0.0
    assert not (args.only_submit and len(args.submissions_path) == 0)

    users = []
    tasks = []

    # If read_from is not specified, read contest data from database
    # if it is specified - read contest data from the file
    if args.read_from is None:
        users, tasks = harvest_contest_data(args.contest_id)
    else:
        with open(args.read_from, "rt", encoding="utf-8") as file_:
            contest_data = ast.literal_eval(file_.read())
        users = contest_data['users']
        tasks = contest_data['tasks']

    if len(users) == 0:
        print("No viable users, terminating.")
        return

    if args.actor_num is not None:
        user_items = list(users.items())
        if args.sort_actors:
            user_items.sort()
        else:
            random.shuffle(user_items)
        users = dict(user_items[:args.actor_num])

    # If the base URL is not specified, we try to guess it; anyway,
    # the guess code isn't very smart...
    if args.base_url is not None:
        base_url = args.base_url
    else:
        base_url = "http://%s:%d/" % \
            (get_service_address(ServiceCoord('ContestWebServer', 0))[0],
             config.contest_listen_port[0])

    metrics = DEFAULT_METRICS
    metrics["time_coeff"] = args.time_coeff
    actor_class = RandomActor
    if args.only_submit:
        actor_class = SubmitActor
    actors = [actor_class(username, data['password'], metrics, tasks,
                          log=RequestLog(log_dir=os.path.join('./test_logs',
                                                              username)),
                          base_url=base_url,
                          submissions_path=args.submissions_path)
              for username, data in users.items()]
    for actor in actors:
        actor.start()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Taking down actors", file=sys.stderr)
        for actor in actors:
            actor.die = True

    # Uncomment to turn on some memory profiling.
    # from meliae import scanner
    # print("Dumping")
    # scanner.dump_all_objects('objects.json')
    # print("Dump finished")

    for actor in actors:
        actor.join()

    print("Test finished", file=sys.stderr)

    great_log = RequestLog()
    for actor in actors:
        great_log.merge(actor.log)

    great_log.print_stats()
Ejemplo n.º 2
0
 def test_success(self):
     """Test success cases."""
     self.assertEqual(get_service_address(ServiceCoord("Service", 0)),
                      Address("0.0.0.0", 0))
     self.assertEqual(get_service_address(ServiceCoord("Service", 1)),
                      Address("0.0.0.1", 1))
Ejemplo n.º 3
0
    def __init__(self, shard, contest_id=None):
        parameters = {
            "static_files": [("cms.server", "static"),
                             ("cms.server.contest", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "is_proxy_used":
            config.is_proxy_used,
            "num_proxies_used":
            config.num_proxies_used,
            "xsrf_cookies":
            True,
            "xsrf_cookie_kwargs": {
                "samesite": "Strict"
            },
        }

        try:
            listen_address = config.contest_listen_address[shard]
            listen_port = config.contest_listen_port[shard]
        except IndexError:
            raise ConfigError("Wrong shard number for %s, or missing "
                              "address/port configuration. Please check "
                              "contest_listen_address and contest_listen_port "
                              "in cms.conf." % __name__)

        self.contest_id = contest_id

        if self.contest_id is None:
            HANDLERS.append((r"", MainHandler))
            handlers = [(r'/', ContestListHandler)]
            for h in HANDLERS:
                handlers.append((r'/([^/]+)' + h[0], ) + h[1:])
        else:
            HANDLERS.append((r"/", MainHandler))
            handlers = HANDLERS

        super().__init__(listen_port,
                         handlers,
                         parameters,
                         shard=shard,
                         listen_address=listen_address)

        self.wsgi_app = SharedDataMiddleware(
            self.wsgi_app, {
                "/stl": config.stl_path,
                "/py-sl": config.py_sl_path
            },
            cache=True,
            cache_timeout=SECONDS_IN_A_YEAR,
            fallback_mimetype="application/octet-stream")

        self.jinja2_environment = CWS_ENVIRONMENT

        # This is a dictionary (indexed by username) of pending
        # notification. Things like "Yay, your submission went
        # through.", not things like "Your question has been replied",
        # that are handled by the db. Each username points to a list
        # of tuples (timestamp, subject, text).
        self.notifications = {}

        # Retrieve the available translations.
        self.translations = get_translations()

        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        printing_enabled = config.printer is not None
        self.printing_service = self.connect_to(
            ServiceCoord("PrintingService", 0),
            must_be_present=printing_enabled)
Ejemplo n.º 4
0
    def __init__(self, shard, contest):
        parameters = {
            "login_url":
            "/",
            "template_path":
            pkg_resources.resource_filename("cms.server.contest", "templates"),
            "static_files": [("cms.server", "static"),
                             ("cms.server.contest", "static")],
            "cookie_secret":
            base64.b64encode(config.secret_key),
            "debug":
            config.tornado_debug,
            "is_proxy_used":
            config.is_proxy_used,
            "num_proxies_used":
            config.num_proxies_used,
        }

        try:
            listen_address = config.contest_listen_address[shard]
            listen_port = config.contest_listen_port[shard]
        except IndexError:
            raise ConfigError("Wrong shard number for %s, or missing "
                              "address/port configuration. Please check "
                              "contest_listen_address and contest_listen_port "
                              "in cms.conf." % __name__)

        super(ContestWebServer, self).__init__(listen_port,
                                               HANDLERS,
                                               parameters,
                                               shard=shard,
                                               listen_address=listen_address)

        self.contest = contest

        # This is a dictionary (indexed by username) of pending
        # notification. Things like "Yay, your submission went
        # through.", not things like "Your question has been replied",
        # that are handled by the db. Each username points to a list
        # of tuples (timestamp, subject, text).
        self.notifications = {}

        # Retrieve the available translations.
        self.langs = {
            lang_code: wrap_translations_for_tornado(trans)
            for lang_code, trans in get_translations().iteritems()
        }

        self.file_cacher = FileCacher(self)
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        printing_enabled = config.printer is not None
        self.printing_service = self.connect_to(
            ServiceCoord("PrintingService", 0),
            must_be_present=printing_enabled)
Ejemplo n.º 5
0
def maybe_send_notification(submission_id):
    """Non-blocking attempt to notify a running ES of the submission"""
    rs = RemoteServiceClient(ServiceCoord("EvaluationService", 0))
    rs.connect()
    rs.new_submission(submission_id=submission_id)
    rs.disconnect()
Ejemplo n.º 6
0
 def __init__(self):
     self._log_service = RemoteService(None, ServiceCoord("LogService", 0))
     self.operation = ""
     self._my_coord = None
Ejemplo n.º 7
0
 def test_method_return_list(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.echo(value=["Hello", 42, "World"])
     result.wait()
     self.assertTrue(result.successful())
     self.assertEqual(result.value, ["Hello", 42, "World"])
Ejemplo n.º 8
0
 def test_method_not_rpc_callable(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.not_rpc_callable()
     result.wait()
     self.assertFalse(result.successful())
     self.assertIsInstance(result.exception, RPCError)
Ejemplo n.º 9
0
 def test_method_return_int(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.echo(value=42)
     result.wait()
     self.assertTrue(result.successful())
     self.assertEqual(result.value, 42)
Ejemplo n.º 10
0
 def test_method_return_string(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.echo(value="Hello World")
     result.wait()
     self.assertTrue(result.successful())
     self.assertEqual(result.value, "Hello World")
Ejemplo n.º 11
0
 def test_method_return_bool(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.echo(value=True)
     result.wait()
     self.assertTrue(result.successful())
     self.assertIs(result.value, True)
Ejemplo n.º 12
0
 def test_method_return_unencodable(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.return_unencodable()
     result.wait(timeout=0.002)
     self.assertFalse(result.ready())
Ejemplo n.º 13
0
 def test_method_send_unencodable(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.echo(value=RuntimeError())
     result.wait()
     self.assertFalse(result.successful())
     self.assertIsInstance(result.exception, RPCError)
Ejemplo n.º 14
0
 def test_service_not_present(self):
     """Test failure when the service is invalid."""
     with self.assertRaises(KeyError):
         get_service_address(ServiceCoord("ServiceNotPresent", 0))
Ejemplo n.º 15
0
 def test_double_connect_client(self):
     # Check that asking an already-connected client to connect
     # again causes an error.
     client = self.get_client(ServiceCoord("Foo", 0))
     self.assertRaises(Exception, client.connect)
Ejemplo n.º 16
0
def main():
    parser = optparse.OptionParser(usage="usage: %prog [options]")
    parser.add_option("-c", "--contest",
                      help="contest ID to export", dest="contest_id",
                      action="store", type="int", default=None)
    parser.add_option("-n", "--actor-num",
                      help="the number of actors to spawn", dest="actor_num",
                      action="store", type="int", default=None)
    parser.add_option("-s", "--sort-actors",
                      help="sort usernames alphabetically "
                      "instead of randomizing before slicing them",
                      action="store_true", default=False, dest="sort_actors")
    parser.add_option("-u", "--base-url",
                      help="base URL for placing HTTP requests",
                      action="store", default=None, dest="base_url")
    parser.add_option("-S", "--submissions-path",
                      help="base path for submission to send",
                      action="store", default=None, dest="submissions_path")
    parser.add_option("-p", "--prepare-path",
                      help="file to put contest info to",
                      action="store", default=None, dest="prepare_path")
    parser.add_option("-r", "--read-from",
                      help="file to read contest info from",
                      action="store", default=None, dest="read_from")
    options = parser.parse_args()[0]

    # If prepare_path is specified we only need to save some useful
    # contest data and exit.
    if options.prepare_path is not None:
        users, tasks = harvest_contest_data(options.contest_id)
        contest_data = dict()
        contest_data['users'] = users
        contest_data['tasks'] = tasks
        with open(options.prepare_path, "w") as file_:
            file_.write(str(contest_data))
        return

    users = []
    tasks = []

    # If read_from is not specified, read contest data from database
    # if it is specified - read contest data from the file
    if options.read_from is None:
        users, tasks = harvest_contest_data(options.contest_id)
    else:
        with open(options.read_from, "r") as file_:
            contest_data = ast.literal_eval(file_.read())
        users = contest_data['users']
        tasks = contest_data['tasks']

    if options.actor_num is not None:
        user_items = users.items()
        if options.sort_actors:
            user_items.sort()
        else:
            random.shuffle(user_items)
        users = dict(user_items[:options.actor_num])

    # If the base URL is not specified, we try to guess it; anyway,
    # the guess code isn't very smart...
    if options.base_url is not None:
        base_url = options.base_url
    else:
        base_url = "http://%s:%d/" % \
            (get_service_address(ServiceCoord('ContestWebServer', 0))[0],
             config.contest_listen_port[0])

    actors = [RandomActor(username, data['password'], DEFAULT_METRICS, tasks,
                          log=RequestLog(log_dir=os.path.join('./test_logs',
                                                              username)),
                          base_url=base_url,
                          submissions_path=options.submissions_path)
              for username, data in users.iteritems()]
    for actor in actors:
        actor.start()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Taking down actors", file=sys.stderr)
        for actor in actors:
            actor.die = True

    # Uncomment to turn on some memory profiling.
    # from meliae import scanner
    # print("Dumping")
    # scanner.dump_all_objects('objects.json')
    # print("Dump finished")

    finished = False
    while not finished:
        for actor in actors:
            actor.join()
        else:
            finished = True

    print("Test finished", file=sys.stderr)

    great_log = RequestLog()
    for actor in actors:
        great_log.merge(actor.log)

    great_log.print_stats()
Ejemplo n.º 17
0
 def test_double_connect_server(self):
     # Check that asking an already-connected server to initialize
     # again its connection causes an error.
     self.get_client(ServiceCoord("Foo", 0))
     self.sleep()
     self.assertRaises(Exception, self.servers[0].initialize, "foo")
Ejemplo n.º 18
0
 def test_method_raise_exception(self):
     client = self.get_client(ServiceCoord("Foo", 0))
     result = client.raise_exception()
     result.wait()
     self.assertFalse(result.successful())
     self.assertIsInstance(result.exception, RPCError)
Ejemplo n.º 19
0
    def wsgi_app(self, environ, start_response):
        """Execute this instance as a WSGI application.

        See the PEP for the meaning of parameters. The separation of
        __call__ and wsgi_app eases the insertion of middlewares.

        """
        urls = self._url_map.bind_to_environ(environ)
        try:
            endpoint, args = urls.match()
        except HTTPException as exc:
            return exc

        assert endpoint == "rpc"

        response = Response()

        if self._auth is not None and not self._auth(environ):
            response.status_code = 403
            response.mimetype = "plain/text"
            response.data = "Request not allowed."
            return response

        request = Request(environ)
        request.encoding_errors = "strict"

        remote_service = ServiceCoord(args['service'], args['shard'])

        if remote_service not in self._service.remote_services:
            return NotFound()

        # TODO Check content_encoding and content_md5.

        if request.mimetype != "application/json":
            return UnsupportedMediaType()

        if request.accept_mimetypes.quality("application/json") <= 0:
            return NotAcceptable()

        try:
            data = json.load(request.stream, encoding='utf-8')
        except ValueError:
            return BadRequest()

        if not self._service.remote_services[remote_service].connected:
            return ServiceUnavailable()

        result = self._service.remote_services[remote_service].execute_rpc(
            args['method'], data)

        # XXX We could set a timeout on the .wait().
        result.wait()

        response.status_code = 200
        response.mimetype = "application/json"
        response.data = json.dumps({
            "data":
            result.value,
            "error":
            None if result.successful() else "%s" % result.exception
        })

        return response