示例#1
0
    def setUp(self):
        self.codalab_manager = CodaLabManager()
        self.codalab_manager.config['server']['class'] = 'SQLiteModel'
        self.bundle_manager = BundleManager(self.codalab_manager)
        self.download_manager = self.codalab_manager.download_manager()
        self.upload_manager = self.codalab_manager.upload_manager()

        # Create a standard user
        self.user_id = generate_uuid()
        self.bundle_manager._model.add_user(
            "codalab_standard",
            "*****@*****.**",
            "Test",
            "User",
            "password",
            "Stanford",
            user_id=self.user_id,
        )

        # Create a root user
        self.root_user_id = self.codalab_manager.root_user_id()
        self.bundle_manager._model.add_user(
            "codalab_root",
            "*****@*****.**",
            "Test",
            "User",
            "password",
            "Stanford",
            user_id=self.root_user_id,
        )
 def __init__(self, args):
     self.args = args
     self.codalab_manager = CodaLabManager()
     self.codalab_client = self.codalab_manager.client(args.server)
     self.staged_uuids = []
     self.last_worker_start_time = 0
     logger.info('Started worker manager.')
示例#3
0
 def _create_cli(self, worksheet_uuid):
     manager = CodaLabManager(
         temporary=True,
         clients={settings.BUNDLE_SERVICE_URL: self.client})
     manager.set_current_worksheet_uuid(self.client, worksheet_uuid)
     cli = bundle_cli.BundleCLI(manager, headless=True)
     return cli
示例#4
0
def main(args):
    manager = CodaLabManager()
    model = manager.model()

    # Get the the message
    subject = args.subject
    with open(args.body_file) as f:
        body_template = f.read()
    mime_type = 'html' if args.body_file.endswith('.html') else 'plain'

    # Figure out who we want to send
    to_send_list = get_to_send_list(model, args.threshold)
    sent_list = get_sent_list(args.sent_file)
    sent_emails = set(info['email'] for info in sent_list)
    pending_to_send_list = [info for info in to_send_list if info['email'] not in sent_emails]
    print 'Already sent %d emails, %d to go' % (len(sent_list), len(pending_to_send_list))

    for i, info in enumerate(pending_to_send_list):
        if args.only_email and args.only_email != info['email']:
            continue

        # Derived fields
        info['greeting_name'] = info['first_name'] or info['last_name'] or info['user_name']
        info['full_name'] = ' '.join([x for x in [info['first_name'], info['last_name']] if x])
        info['email_description'] = '%s <%s>' % (info['full_name'], info['email']) if info['full_name'] else info['email']
        info['sent_time'] = time.time()

        print 'Sending %s/%s (%s>=%s, doit=%s): [%s] %s' % \
            (i, len(pending_to_send_list), info['notifications'], args.threshold, args.doit, info['user_name'], info['email_description'])

        # Apply template to get body of message
        body = body_template
        for field, value in info.items():
            body = body.replace('{{' + field + '}}', unicode(value or ''))

        if args.verbose >= 1:
            print 'To      : %s' % info['email_description']
            print 'Subject : %s' % subject
            print body
            print '-------'

        if not args.doit:
            continue

        # Send the actual email
        manager.emailer.send_email(
            recipient=info['email_description'],
            subject=subject,
            body=body,
            mime_type=mime_type,
        )

        # Record that we sent
        with open(args.sent_file, 'a') as f:
            print >>f, json.dumps(info)
            f.flush()
示例#5
0
 def _create_cli(self, worksheet_uuid):
     output_buffer = StringIO()
     manager = CodaLabManager(temporary=True,
                              clients={'local': self.client})
     manager.set_current_worksheet_uuid(self.client, worksheet_uuid)
     cli = bundle_cli.BundleCLI(manager,
                                headless=True,
                                stdout=output_buffer,
                                stderr=output_buffer)
     return cli, output_buffer
 def setUp(self):
     self.codalab_manager = CodaLabManager()
     self.codalab_manager.config['server']['class'] = 'SQLiteModel'
     self.bundle_manager = BundleManager(self.codalab_manager)
     self.user_id = generate_uuid()
     self.bundle_manager._model.add_user(
         "codalab",
         "*****@*****.**",
         "Test",
         "User",
         "password",
         "Stanford",
         user_id=self.user_id,
     )
示例#7
0
    def __init__(
        self,
        docker_image,
        initial_command="",
        manager=None,
        dependencies=[],
        bundle_locations={},
        verbose=False,
        stdout=sys.stdout,
        stderr=sys.stderr,
    ):
        # Instantiate a CodaLabManager if one is not passed in
        self._manager = manager if manager else CodaLabManager()
        self._docker_image = docker_image
        self._initial_command = initial_command

        InteractiveSession._validate_bundle_locations(bundle_locations,
                                                      dependencies)
        self._dependencies = dependencies
        self._bundle_locations = bundle_locations

        self._docker_client = docker.from_env(
            timeout=InteractiveSession._MAX_SESSION_TIMEOUT)
        self._session_uuid = generate_uuid()

        self._verbose = verbose
        self._stdout = stdout
        self._stderr = stderr
示例#8
0
def main():
    cli = BundleCLI(CodaLabManager())
    try:
        cli.do_command(sys.argv[1:])
    except KeyboardInterrupt:
        print('Terminated by Ctrl-C')
        sys.exit(130)
示例#9
0
def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    manager = CodaLabManager()
    url = manager.model().engine.url
    context.configure(url=url, target_metadata=target_metadata)

    with context.begin_transaction():
        context.run_migrations()
示例#10
0
def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    manager = CodaLabManager()
    engine = manager.model().engine

    connection = engine.connect()
    context.configure(connection=connection, target_metadata=target_metadata)

    try:
        with context.begin_transaction():
            context.run_migrations()
    finally:
        connection.close()
示例#11
0
def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    manager = CodaLabManager()
    url = manager.model().engine.url
    context.configure(url=url, target_metadata=target_metadata)

    with context.begin_transaction():
        context.run_migrations()
示例#12
0
 def test_temp_codalab_manager(self):
     manager: CodaLabManager = CodaLabManager(temporary=True)
     self.assertEqual(manager.state, {'auth': {}, 'sessions': {}})
     manager.save_state()
     self.assertFalse(
         os.path.exists(manager.state_path),
         msg=
         'Assert that the current state is not written out to state_path for a temporary CodaLabManager',
     )
示例#13
0
def find_default_editor():
    manager = CodaLabManager()
    editor = os.getenv('EDITOR')
    if editor:
        return editor
    # If not yet set, use a sane default.
    if sys.platform == 'win32':
        editor = 'notepad'
    else:
        editor = 'vi'
    return editor
示例#14
0
def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    manager = CodaLabManager()
    engine = manager.model().engine

    connection = engine.connect()
    context.configure(
                connection=connection,
                target_metadata=target_metadata
                )

    try:
        with context.begin_transaction():
            context.run_migrations()
    finally:
        connection.close()
示例#15
0
    def _create_cli(self, worksheet_uuid):
        """
        Create an instance of the CLI.

        The CLI uses JsonApiClient to communicate back to the REST API.
        This is admittedly not ideal since now the REST API is essentially
        making HTTP requests back to itself. Future potential solutions might
        include creating a subclass of JsonApiClient that can reroute HTTP
        requests directly to the appropriate Bottle view functions.
        """
        output_buffer = StringIO()
        rest_client = JsonApiClient(self._rest_url(), lambda: get_user_token())
        manager = CodaLabManager(
            temporary=True,
            config=local.config,
            clients={
                self._rest_url(): rest_client
            })
        manager.set_current_worksheet_uuid(self._rest_url(), worksheet_uuid)
        cli = bundle_cli.BundleCLI(manager, headless=True, stdout=output_buffer, stderr=output_buffer)
        return cli, output_buffer
示例#16
0
 def test_temp_codalab_manager(self):
     """
     A codalab manager with temporary state should initialize its state from an existing
     state.json file if it is present.
     """
     manager: CodaLabManager = CodaLabManager(temporary=True)
     self.assertEqual(manager.state, {'auth': {}, 'sessions': {}})
     manager.save_state()
     self.assertFalse(
         os.path.exists(manager.state_path),
         msg=
         'Assert that the current state is not written out to state_path for a temporary CodaLabManager',
     )
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--sleep-time',
        help='Number of seconds to wait between successive actions.',
        type=int,
        default=0.5,
    )
    args = parser.parse_args()

    manager = BundleManager(CodaLabManager())
    # Register a signal handler to ensure safe shutdown.
    for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]:
        signal.signal(sig, lambda signup, frame: manager.signal())

    manager.run(args.sleep_time)
示例#18
0
    def test_temp_codalab_manager_initialize_state(self):
        initial_state: Dict = {
            'auth': {
                "https://worksheets.codalab.org": {
                    "token_info": {
                        "access_token": "secret"
                    }
                }
            },
            'sessions': {},
        }

        cache_file = tempfile.NamedTemporaryFile(delete=False)
        with open(cache_file.name, "w") as f:
            json.dump(initial_state, f)
        os.environ["CODALAB_STATE"] = cache_file.name

        manager: CodaLabManager = CodaLabManager(temporary=True)

        self.assertEqual(manager.state, initial_state)
        os.remove(cache_file.name)
示例#19
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--sleep-time',
        help='Number of seconds to wait between successive actions.',
        type=int,
        default=0.5,
    )
    parser.add_argument(
        '--worker-timeout-seconds',
        help=
        'Number of seconds to wait after a worker check-in before determining a worker is offline',
        type=int,
        default=60,
    )
    args = parser.parse_args()

    manager = BundleManager(CodaLabManager(), args.worker_timeout_seconds)
    # Register a signal handler to ensure safe shutdown.
    for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]:
        signal.signal(sig, lambda signup, frame: manager.signal())

    manager.run(args.sleep_time)
示例#20
0
def create_rest_app(manager=CodaLabManager()):
    """Creates and returns a rest app."""
    install(SaveEnvironmentPlugin(manager))
    install(CheckJsonPlugin())
    install(oauth2_provider.check_oauth())
    install(CookieAuthenticationPlugin())
    install(UserVerifiedPlugin())
    install(PublicUserPlugin())
    install(ErrorAdapter())

    # Replace default JSON plugin with one that handles datetime objects
    # Note: ErrorAdapter must come before JSONPlugin to catch serialization errors
    uninstall(JSONPlugin())
    install(JSONPlugin(json_dumps=DatetimeEncoder().encode))

    # JsonApiPlugin must come after JSONPlugin, to inspect and modify response
    # dicts before they are serialized into JSON
    install(JsonApiPlugin())

    for code in range(100, 600):
        default_app().error(code)(error_handler)

    root_app = Bottle()
    root_app.mount('/rest', default_app())

    # Look for templates in codalab-worksheets/views
    bottle.TEMPLATE_PATH = [
        os.path.join(
            os.path.dirname(
                os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
            'views')
    ]

    # Increase the request body size limit to 8 MiB
    bottle.BaseRequest.MEMFILE_MAX = 8 * 1024 * 1024
    return root_app
示例#21
0
        observer = Observer()
        observer.schedule(event_handler, path, recursive=True)
        observer.start()
        try:
            while True:
                time.sleep(10)
        except KeyboardInterrupt:
            observer.stop()
        observer.join()
    else:
        from codalab.server.bundle_rpc_server import BundleRPCServer
        rpc_server = BundleRPCServer(manager)
        rpc_server.serve_forever()

def run_cli():
    from codalab.lib.bundle_cli import BundleCLI
    cli = BundleCLI(manager)
    cli.do_command(sys.argv[1:])

if __name__ == '__main__':
    manager = CodaLabManager()
    # Either start the server or the client.
    try:
        if len(sys.argv) > 1 and sys.argv[1] == 'server':
            run_server()
        else:
            run_cli()
    except KeyboardInterrupt:
        print 'Terminated by Ctrl-C'
        sys.exit(130)
class WorkerManager(object):
    """
    The abstract class for a worker manager.  Different backends like AWS,
    Azure, Slurm should override `get_worker_jobs` and `start_worker_job`.

    The basic architecture of the WorkerManager is extremely simple: to a
    first-order approximation, it simply launches `cl-worker`s as AWS/Azure
    batch jobs as long as there are staged bundles.

    The simplicity means that we don't need to manage state about how workers
    and bundles are related - that logic is complex and is done by the usual
    worker system, and we don't want to duplicate that for every possible
    worker backend.

    More specifically, a worker manager will monitor a job queue to see how
    many worker jobs are running, and try to keep that between `min_workers`
    and `max_workers`.  It will also monitor the staged bundles that satisfy a
    certain `search` criterion.  If there are staged bundles then it will issue
    a `start_worker_job()` call, provided some other conditions are met (e.g.,
    don't start workers too fast).

    The WorkerManager is all client-side code, so it can be customized as one
    sees fit.

    Notes:
    - The worker manager is not visible via CodaLab (i.e., CodaLab has no
      notion of a worker manager or what it's trying to do - all it sees is
      bundles and workers).  One needs to monitor the AWS/Azure Batch system
      separately.
    - Resource handling is not currently supported.  Generally, the safe thing
      is to create a separate queue for different resource needs and put the
      burden of deciding on the user.
    """

    # Subcommand name to use for this worker manager type
    NAME = 'worker-manager'
    DESCRIPTION = 'Base class for Worker Managers, please implement for your deployment'

    @staticmethod
    def add_arguments_to_subparser(subparser):
        """
        Add any arguments specific to this worker manager to the given subparser
        """
        raise NotImplementedError

    def __init__(self, args):
        self.args = args
        self.codalab_manager = CodaLabManager()
        self.codalab_client = self.codalab_manager.client(args.server)
        self.staged_uuids = []
        self.last_worker_start_time = 0
        logger.info('Started worker manager.')

    def get_worker_jobs(self):
        """Return a list of `WorkerJob`s."""
        raise NotImplementedError

    def start_worker_job(self):
        """Start a new `WorkerJob`."""
        raise NotImplementedError

    def build_command(self, worker_id, work_dir):
        command = [
            self.args.worker_executable,
            '--server',
            self.args.server,
            '--verbose',
            '--exit-when-idle',
            '--idle-seconds',
            str(self.args.worker_idle_seconds),
            '--work-dir',
            work_dir,
            '--id',
            f'$(hostname -s)-{worker_id}',
            '--network-prefix',
            'cl_worker_{}_network'.format(worker_id),
        ]

        # Additional optional arguments
        if self.args.worker_tag:
            command.extend(['--tag', self.args.worker_tag])
        if self.args.worker_group:
            command.extend(['--group', self.args.worker_group])
        if self.args.worker_exit_after_num_runs and self.args.worker_exit_after_num_runs > 0:
            command.extend([
                '--exit-after-num-runs',
                str(self.args.worker_exit_after_num_runs)
            ])
        if self.args.worker_max_work_dir_size:
            command.extend(
                ['--max-work-dir-size', self.args.worker_max_work_dir_size])
        if self.args.worker_delete_work_dir_on_exit:
            command.extend(['--delete-work-dir-on-exit'])
        if self.args.worker_exit_on_exception:
            command.extend(['--exit-on-exception'])
        if self.args.worker_tag_exclusive:
            command.extend(['--tag-exclusive'])
        if self.args.worker_pass_down_termination:
            command.extend(['--pass-down-termination'])

        return command

    def run_loop(self):
        while True:
            try:
                self.run_one_iteration()
            except (
                    urllib.error.URLError,
                    http.client.HTTPException,
                    socket.error,
                    NotFoundError,
                    JsonApiException,
            ):
                # Sometimes, network errors occur when running the WorkerManager . These are often
                # transient exceptions, and retrying the command would lead to success---as a result,
                # we ignore these network-based exceptions (rather than fatally exiting from the
                # WorkerManager )
                traceback.print_exc()
            if self.args.once:
                break
            logger.debug('Sleeping {} seconds'.format(self.args.sleep_time))
            time.sleep(self.args.sleep_time)

    def run_one_iteration(self):
        # Get staged bundles for the current user. The principle here is that we want to get all of
        # the staged bundles can be run by this user.
        keywords = ['state=' + State.STAGED] + self.args.search
        # If the current user is "codalab", don't filter by .mine because the workers owned
        # by "codalab" can be shared by all users. But, for all other users, we only
        # want to see their staged bundles.
        if os.environ.get('CODALAB_USERNAME') != "codalab":
            keywords += [".mine"]
        # The keywords below search for `request_queue=<worker tag>` OR `request_queue=tag=<worker tag>`
        # If support for this is removed so that 'request_queue' is always set to be '<worker tag>'
        # (and not tag=<worker tag>) this search query can again be simplified.
        # NOTE: server/bundle_manager.py has the server-side matching logic that should be synced
        # with this search request.
        if self.args.worker_tag_exclusive and self.args.worker_tag:
            keywords += [
                "request_queue=%s,tag=%s" %
                (self.args.worker_tag, self.args.worker_tag)
            ]

        bundles = self.codalab_client.fetch('bundles',
                                            params={
                                                'worksheet': None,
                                                'keywords': keywords,
                                                'include': ['owner']
                                            })
        new_staged_uuids = [bundle['uuid'] for bundle in bundles]
        old_staged_uuids = self.staged_uuids
        # Bundles that were staged but now aren't
        removed_uuids = [
            uuid for uuid in old_staged_uuids if uuid not in new_staged_uuids
        ]
        self.staged_uuids = new_staged_uuids
        logger.info('Staged bundles [{}]: {}'.format(
            ' '.join(keywords), ' '.join(self.staged_uuids) or '(none)'))

        # Get worker jobs
        worker_jobs = self.get_worker_jobs()
        pending_worker_jobs, active_worker_jobs = [], []

        for job in worker_jobs:
            (active_worker_jobs
             if job.active else pending_worker_jobs).append(job)

        # Print status
        logger.info(
            '{} staged bundles ({} removed since last time), {} worker jobs (min={}, max={}) ({} active, {} pending)'
            .format(
                len(self.staged_uuids),
                len(removed_uuids),
                len(worker_jobs),
                self.args.min_workers,
                self.args.max_workers,
                len(active_worker_jobs),
                len(pending_worker_jobs),
            ))

        want_workers = False

        # There is a staged bundle AND there aren't any workers that are still booting up/starting
        if len(self.staged_uuids) > 0:
            logger.info(
                'Want to launch a worker because we have {} > 0 staged bundles'
                .format(len(self.staged_uuids)))
            want_workers = True

        if want_workers:
            # Make sure we don't launch workers too quickly.
            seconds_since_last_worker = int(time.time() -
                                            self.last_worker_start_time)
            if seconds_since_last_worker < self.args.min_seconds_between_workers:
                logger.info(
                    'Don\'t launch because waited {} < {} seconds since last worker'
                    .format(seconds_since_last_worker,
                            self.args.min_seconds_between_workers))
                want_workers = False

            # Make sure we don't queue up more workers than staged UUIDs if there are
            # more workers still booting up than staged bundles
            if len(pending_worker_jobs) >= len(self.staged_uuids):
                logger.info(
                    'Don\'t launch because still more pending workers than staged bundles ({} >= {})'
                    .format(len(pending_worker_jobs), len(self.staged_uuids)))
                want_workers = False

            # Don't launch more than `max_workers`.
            # For now, only the number of workers is used to determine what workers
            # we launch.
            if len(worker_jobs) >= self.args.max_workers:
                logger.info(
                    'Don\'t launch because too many workers already ({} >= {})'
                    .format(len(worker_jobs), self.args.max_workers))
                want_workers = False

        # We have fewer than min_workers, so launch one regardless of other constraints
        if len(worker_jobs) < self.args.min_workers:
            logger.info(
                'Launch a worker because we are under the minimum ({} < {})'.
                format(len(worker_jobs), self.args.min_workers))
            want_workers = True

        if want_workers:
            logger.info('Starting a worker!')
            self.start_worker_job()
            self.last_worker_start_time = time.time()
def main(args):
    manager = CodaLabManager()
    model = manager.model()

    # Get the the message
    subject = args.subject
    with open(args.body_file) as f:
        body_template = f.read()
    mime_type = 'html' if args.body_file.endswith('.html') else 'plain'

    # Figure out who we want to send
    to_send_list = get_to_send_list(model, args.threshold)
    sent_list = get_sent_list(args.sent_file)
    sent_emails = set(info['email'] for info in sent_list)
    pending_to_send_list = [info for info in to_send_list if info['email'] not in sent_emails]
    print('Already sent %d emails, %d to go' % (len(sent_list), len(pending_to_send_list)))

    for i, info in enumerate(pending_to_send_list):
        if args.only_email and args.only_email != info['email']:
            continue

        # Derived fields
        info['greeting_name'] = info['first_name'] or info['last_name'] or info['user_name']
        info['full_name'] = ' '.join([x for x in [info['first_name'], info['last_name']] if x])
        info['email_description'] = (
            '%s <%s>' % (info['full_name'], info['email']) if info['full_name'] else info['email']
        )
        info['sent_time'] = time.time()

        print(
            'Sending %s/%s (%s>=%s, doit=%s): [%s] %s'
            % (
                i,
                len(pending_to_send_list),
                info['notifications'],
                args.threshold,
                args.doit,
                info['user_name'],
                info['email_description'],
            )
        )

        # Apply template to get body of message
        body = body_template
        for field, value in info.items():
            body = body.replace('{{' + field + '}}', unicode(value or ''))

        if args.verbose >= 1:
            print('To      : %s' % info['email_description'])
            print('Subject : %s' % subject)
            print(body)
            print('-------')

        if not args.doit:
            continue

        # Send the actual email
        manager.emailer.send_email(
            recipient=info['email_description'], subject=subject, body=body, mime_type=mime_type
        )

        # Record that we sent
        with open(args.sent_file, 'a') as f:
            print >> f, json.dumps(info)
            f.flush()
def run_command(
    args,
    expected_exit_code=0,
    max_output_chars=1024,
    env=None,
    include_stderr=False,
    binary=False,
    force_subprocess=False,
    cwd=None,
):
    # We import the following imports here because codalab_service.py imports TestModule from
    # this file. If we kept the imports at the top, then anyone who ran codalab_service.py
    # would also have to install all the dependencies that BundleCLI and CodaLabManager use.
    from codalab.lib.bundle_cli import BundleCLI
    from codalab.lib.codalab_manager import CodaLabManager

    def sanitize(string, max_chars=256):
        # Sanitize and truncate output so it can be printed on the command line.
        # Don't print out binary.
        if isinstance(string, bytes):
            string = '<binary>'
        if len(string) > max_chars:
            string = string[:max_chars] + ' (...more...)'
        return string

    # If we don't care about the exit code, set `expected_exit_code` to None.
    print(">>", *map(str, args), sep=" ")
    sys.stdout.flush()

    try:
        kwargs = dict(env=env)
        if not binary:
            kwargs = dict(kwargs, encoding="utf-8")
        if include_stderr:
            kwargs = dict(kwargs, stderr=subprocess.STDOUT)
        if cwd:
            kwargs = dict(kwargs, cwd=cwd)
        if not force_subprocess:
            # In this case, run the Codalab CLI directly, which is much faster
            # than opening a new subprocess to do so.
            stderr = io.StringIO()  # Not used; we just don't want to redirect cli.stderr to stdout.
            stdout = FakeStdout()
            cli = BundleCLI(CodaLabManager(), stdout=stdout, stderr=stderr)
            try:
                cli.do_command(args[1:])
                exitcode = 0
            except SystemExit as e:
                exitcode = e.code
            output = stdout.getvalue()
        else:
            output = subprocess.check_output([a.encode() for a in args], **kwargs)
            exitcode = 0
    except subprocess.CalledProcessError as e:
        output = e.output
        exitcode = e.returncode
    except Exception:
        output = traceback.format_exc()
        exitcode = 'test-cli exception'

    if expected_exit_code is not None and exitcode != expected_exit_code:
        colorize = Colorizer.red
        extra = ' BAD'
    else:
        colorize = Colorizer.cyan
        extra = ''
    print(colorize(" (exit code %s, expected %s%s)" % (exitcode, expected_exit_code, extra)))
    sys.stdout.flush()
    print(sanitize(output, max_output_chars))
    sys.stdout.flush()
    assert expected_exit_code == exitcode, 'Exit codes don\'t match'
    return output.rstrip()
示例#25
0
class WorkerManager(object):
    """
    The abstract class for a worker manager.  Different backends like AWS,
    Azure, Slurm should override `get_worker_jobs` and `start_worker_job`.

    The basic architecture of the WorkerManager is extremely simple: to a
    first-order approximation, it simply launches `cl-worker`s as AWS/Azure
    batch jobs as long as there are staged bundles.

    The simplicity means that we don't need to manage state about how workers
    and bundles are related - that logic is complex and is done by the usual
    worker system, and we don't want to duplicate that for every possible
    worker backend.

    More specifically, a worker manager will monitor a job queue to see how
    many worker jobs are running, and try to keep that between `min_workers`
    and `max_workers`.  It will also monitor the staged bundles that satisfy a
    certain `search` criterion.  If there are staged bundles then it will issue
    a `start_worker_job()` call, provided some other conditions are met (e.g.,
    don't start workers too fast).

    The WorkerManager is all client-side code, so it can be customized as one
    sees fit.

    Notes:
    - The worker manager is not visible via CodaLab (i.e., CodaLab has no
      notion of a worker manager or what it's trying to do - all it sees is
      bundles and workers).  One needs to monitor the AWS/Azure Batch system
      separately.
    - Resource handling is not currently supported.  Generally, the safe thing
      is to create a separate queue for different resource needs and put the
      burden of deciding on the user.
    """

    # Subcommand name to use for this worker manager type
    NAME = 'worker-manager'
    DESCRIPTION = 'Base class for Worker Managers, please implement for your deployment'

    @staticmethod
    def add_arguments_to_subparser(subparser):
        """
        Add any arguments specific to this worker manager to the given subparser
        """
        raise NotImplementedError

    def __init__(self, args):
        self.args = args
        self.codalab_manager = CodaLabManager()
        self.codalab_client = self.codalab_manager.client(args.server)
        self.staged_uuids = []
        self.last_worker_start_time = 0
        logger.info('Started worker manager.')

    def get_worker_jobs(self):
        """Return a list of `WorkerJob`s."""
        raise NotImplementedError

    def start_worker_job(self):
        """Start a new `WorkerJob`."""
        raise NotImplementedError

    def run_loop(self):
        while True:
            self.run_one_iteration()
            if self.args.once:
                break
            logger.debug('Sleeping {} seconds'.format(self.args.sleep_time))
            time.sleep(self.args.sleep_time)

    def run_one_iteration(self):
        # Get staged bundles for the current user.
        keywords = ['state=' + State.STAGED] + [".mine"] + self.args.search
        if self.args.worker_tag:
            keywords.append('request_queue=tag=' + self.args.worker_tag)
        bundles = self.codalab_client.fetch('bundles',
                                            params={
                                                'worksheet': None,
                                                'keywords': keywords,
                                                'include': ['owner']
                                            })
        new_staged_uuids = [bundle['uuid'] for bundle in bundles]
        old_staged_uuids = self.staged_uuids
        # Bundles that were staged but now aren't
        removed_uuids = [
            uuid for uuid in old_staged_uuids if uuid not in new_staged_uuids
        ]
        self.staged_uuids = new_staged_uuids
        logger.info('Staged bundles [{}]: {}'.format(
            ' '.join(keywords), ' '.join(self.staged_uuids) or '(none)'))

        # Get worker jobs
        worker_jobs = self.get_worker_jobs()
        pending_worker_jobs, active_worker_jobs = [], []

        for job in worker_jobs:
            (active_worker_jobs
             if job.active else pending_worker_jobs).append(job)

        # Print status
        logger.info(
            '{} staged bundles ({} removed since last time), {} worker jobs (min={}, max={}) ({} active, {} pending)'
            .format(
                len(self.staged_uuids),
                len(removed_uuids),
                len(worker_jobs),
                self.args.min_workers,
                self.args.max_workers,
                len(active_worker_jobs),
                len(pending_worker_jobs),
            ))

        want_workers = False

        # There is a staged bundle AND there aren't any workers that are still booting up/starting
        if len(self.staged_uuids) > 0:
            logger.info(
                'Want to launch a worker because we have {} > 0 staged bundles'
                .format(len(self.staged_uuids)))
            want_workers = True

        if want_workers:
            # Make sure we don't launch workers too quickly.
            seconds_since_last_worker = int(time.time() -
                                            self.last_worker_start_time)
            if seconds_since_last_worker < self.args.min_seconds_between_workers:
                logger.info(
                    'Don\'t launch becaused waited {} < {} seconds since last worker'
                    .format(seconds_since_last_worker,
                            self.args.min_seconds_between_workers))
                want_workers = False

            # Make sure we don't queue up more workers than staged UUIDs if there are
            # more workers still booting up than staged bundles
            if len(pending_worker_jobs) >= len(self.staged_uuids):
                logger.info(
                    'Don\'t launch because still more pending workers than staged bundles ({} >= {})'
                    .format(len(pending_worker_jobs), len(self.staged_uuids)))
                want_workers = False

            # Don't launch more than `max_workers`.
            # For now, only the number of workers is used to determine what workers
            # we launch.
            if len(worker_jobs) >= self.args.max_workers:
                logger.info(
                    'Don\'t launch because too many workers already ({} >= {})'
                    .format(len(worker_jobs), self.args.max_workers))
                want_workers = False

        # We have fewer than min_workers, so launch one regardless of other constraints
        if len(worker_jobs) < self.args.min_workers:
            logger.info(
                'Launch a worker because we are under the minimum ({} < {})'.
                format(len(worker_jobs), self.args.min_workers))
            want_workers = True

        if want_workers:
            logger.info('Starting a worker!')
            self.start_worker_job()
            self.last_worker_start_time = time.time()
示例#26
0

class DryRunAbort(Exception):
    """Raised at end of transaction of dry run."""
    def __str__(self):
        return """
        This was a dry run, no migration occurred. To perform full migration,
        run again with `-f':

            %s -f
        """.rstrip() % sys.argv[0]


dry_run = False if len(sys.argv) > 1 and sys.argv[1] == '-f' else True

manager = CodaLabManager()
model = manager.model()
CODALAB_HOME = manager.codalab_home

# Turn on query logging
model.engine.echo = True


###############################################################
# Configure connection to Django database
###############################################################
django_config = read_json_or_die(os.path.join(CODALAB_HOME,
                                              'website-config.json'))

# Use default settings as defined in codalab-worksheets
if 'database' not in django_config:
示例#27
0
#!./venv/bin/python
"""
Script that creates the root user.
"""
import sys

sys.path.append('.')

import getpass

from codalab.lib import crypt_util
from codalab.lib.codalab_manager import CodaLabManager
from codalab.objects.user import User

manager = CodaLabManager()
model = manager.model()

username = manager.root_user_name()
user_id = manager.root_user_id()

if len(sys.argv) == 2:
    password = sys.argv[1]
else:
    while True:
        password = getpass.getpass()
        if getpass.getpass('Config password: '******'Passwords don\'t match. Try again.'
        print
示例#28
0
 def _create_cli(self, worksheet_uuid):
     output_buffer = StringIO()
     manager = CodaLabManager(temporary=True, clients={'local': self.client})
     manager.set_current_worksheet_uuid(self.client, worksheet_uuid)
     cli = bundle_cli.BundleCLI(manager, headless=True, stdout=output_buffer, stderr=output_buffer)
     return cli, output_buffer
示例#29
0
文件: cl.py 项目: Adama94/codalab-cli

@Commands.command(
    'bundle-manager',
    help='Start the bundle manager that executes run and make bundles.',
    arguments=(Commands.Argument(
        '--sleep-time',
        help='Number of seconds to wait between successive actions.',
        type=int,
        default=0.5), ),
)
def do_bundle_manager_command(bundle_cli, args):
    bundle_cli._fail_if_headless(args)
    from codalab.worker.bundle_manager import BundleManager
    manager = BundleManager.create(bundle_cli.manager)

    # Register a signal handler to ensure safe shutdown.
    for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]:
        signal.signal(sig, lambda signup, frame: manager.signal())

    manager.run(args.sleep_time)


if __name__ == '__main__':
    cli = BundleCLI(CodaLabManager())
    try:
        cli.do_command(sys.argv[1:])
    except KeyboardInterrupt:
        print 'Terminated by Ctrl-C'
        sys.exit(130)
#!./venv/bin/python
"""
Script that creates the default CodaLab OAuth2 clients.

 - codalab_cli_client for the Bundle CLI clients authenticating through the Password Grant
 - codalab_worker_client for workers authenticating through the Password Grant

TODO(skoo): Create row for the web client given a redirect url.
"""
import sys
sys.path.append('.')

from codalab.lib.codalab_manager import CodaLabManager
from codalab.objects.oauth2 import OAuth2Client

manager = CodaLabManager()
model = manager.model()

if not model.get_oauth2_client('codalab_cli_client'):
    model.save_oauth2_client(OAuth2Client(
        model,
        client_id='codalab_cli_client',
        secret=None,
        name='Codalab CLI',
        user_id=None,
        grant_type='password',
        response_type='token',
        scopes='default',
        redirect_uris='',
    ))
Indexes the contents of the bundle store. Used during the launch of the new
worker system.

TODO(klopyrev): Delete once it's launched.
"""
import sys
sys.path.append('.')

from codalab.common import State
from codalab.lib.codalab_manager import CodaLabManager
from codalab.model.tables import bundle as cl_bundle, bundle_contents_index as cl_bundle_contents_index
from sqlalchemy import distinct, select
from worker.file_util import index_contents


manager = CodaLabManager()
bundle_store = manager.bundle_store()
model = manager.model()
engine = model.engine

with engine.begin() as conn:
    bundles = conn.execute(
        select([cl_bundle.c.uuid])
        .where(cl_bundle.c.state.in_([State.READY, State.FAILED]))
    ).fetchall()
    indexed_bundles = conn.execute(
        select([distinct(cl_bundle_contents_index.c.bundle_uuid)])
    ).fetchall()

uuids_to_index = (set(bundle.uuid for bundle in bundles) -
                  set(bundle.bundle_uuid for bundle in indexed_bundles))
示例#32
0

class DryRunAbort(Exception):
    """Raised at end of transaction of dry run."""
    def __str__(self):
        return ("""
        This was a dry run, no migration occurred. To perform full migration,
        run again with `-f':

            %s -f
        """.rstrip() % sys.argv[0])


dry_run = False if len(sys.argv) > 1 and sys.argv[1] == '-f' else True

manager = CodaLabManager()
model = manager.model()
CODALAB_HOME = manager.codalab_home

# Turn on query logging
model.engine.echo = True

###############################################################
# Configure connection to Django database
###############################################################
django_config = read_json_or_die(
    os.path.join(CODALAB_HOME, 'website-config.json'))

# Use default settings as defined in codalab-worksheets
if 'database' not in django_config:
    django_config['database'] = {
示例#33
0
class TestBase:
    """
    Base class for BundleManager tests with a CodaLab Manager hitting an in-memory SQLite database.
    """
    def setUp(self):
        self.codalab_manager = CodaLabManager()
        self.codalab_manager.config['server']['class'] = 'SQLiteModel'
        self.bundle_manager = BundleManager(self.codalab_manager)
        self.download_manager = self.codalab_manager.download_manager()
        self.upload_manager = self.codalab_manager.upload_manager()
        self.user_id = generate_uuid()
        self.bundle_manager._model.add_user(
            "codalab",
            "*****@*****.**",
            "Test",
            "User",
            "password",
            "Stanford",
            user_id=self.user_id,
        )

    def create_make_bundle(self, state=State.MAKING):
        """Creates a MakeBundle with the given state."""
        bundle = MakeBundle.construct(
            targets=[],
            command='',
            metadata=BASE_METADATA_MAKE_BUNDLE,
            owner_id=self.user_id,
            uuid=generate_uuid(),
            state=state,
        )
        return bundle

    def save_bundle(self, bundle):
        """Saves the given bundle to the database."""
        self.bundle_manager._model.save_bundle(bundle)

    def read_bundle(self, bundle, extra_path=""):
        """Retrieves the given bundle from the bundle store and returns
        its contents.
        Args:
            extra_path: path appended to bundle store location from which to read the file.
        Returns:
            Bundle contents
        """
        with open(
                os.path.join(
                    self.codalab_manager.bundle_store().get_bundle_location(
                        bundle.uuid), extra_path),
                "r",
        ) as f:
            return f.read()

    def write_bundle(self, bundle, contents=""):
        """Writes the given contents to the location of the given bundle.
        Args:
            bundle: bundle to write
            contents: string to write
        Returns:
            None
        """
        with open(
                self.codalab_manager.bundle_store().get_bundle_location(
                    bundle.uuid), "w+") as f:
            f.write(contents)

    def update_bundle(self, bundle, update):
        return self.bundle_manager._model.update_bundle(bundle, update)

    def create_run_bundle(self, state=State.CREATED, metadata=None):
        """Creates a RunBundle.
        Args:
            state: state for the new bundle
            metadata: additional metadata to add to the bundle.
        """
        bundle = RunBundle.construct(
            targets=[],
            command='',
            metadata=dict(BASE_METADATA, **(metadata or {})),
            owner_id=self.user_id,
            uuid=generate_uuid(),
            state=state,
        )
        bundle.is_anonymous = False
        return bundle

    def create_bundle_single_dep(self,
                                 parent_state=State.READY,
                                 bundle_state=State.CREATED,
                                 bundle_type=RunBundle):
        """Creates a bundle with a single dependency, which is mounted at path "src" of the
        new bundle.

        Args:
            parent_state: State of the parent bundle. Defaults to State.READY.
            bundle_state: State of the new bundle. Defaults to State.CREATED.
            bundle_type: Type of child bundle to create; valid values are RunBundle and MakeBundle. Defaults to RunBundle.

        Returns:
            (bundle, parent)
        """
        parent = self.create_run_bundle(parent_state)
        self.write_bundle(parent, FILE_CONTENTS_1)
        bundle = (self.create_run_bundle(bundle_state) if bundle_type
                  == RunBundle else self.create_make_bundle(bundle_state))
        bundle.dependencies = [
            Dependency({
                "parent_uuid": parent.uuid,
                "parent_path": "",
                "child_uuid": bundle.uuid,
                "child_path": "src",
            })
        ]
        return bundle, parent

    def create_bundle_two_deps(self):
        """Create a bundle with two dependencies. The first dependency is mounted at path "src1"
        and the second is mounted at path "src2" of the new bundle.

        Returns:
            (bundle, parent1, parent2)
        """
        parent1 = self.create_run_bundle(state=State.READY)
        self.write_bundle(parent1, FILE_CONTENTS_1)
        parent2 = self.create_run_bundle(state=State.READY)
        self.write_bundle(parent2, FILE_CONTENTS_2)
        bundle = MakeBundle.construct(
            targets=[],
            command='',
            metadata=BASE_METADATA_MAKE_BUNDLE,
            owner_id=self.user_id,
            uuid=generate_uuid(),
            state=State.STAGED,
        )
        bundle.dependencies = [
            Dependency({
                "parent_uuid": parent1.uuid,
                "parent_path": "",
                "child_uuid": bundle.uuid,
                "child_path": "src1",
            }),
            Dependency({
                "parent_uuid": parent2.uuid,
                "parent_path": "",
                "child_uuid": bundle.uuid,
                "child_path": "src2",
            }),
        ]
        return bundle, parent1, parent2

    def mock_worker_checkin(self,
                            cpus=0,
                            gpus=0,
                            memory_bytes=0,
                            free_disk_bytes=0,
                            tag=None,
                            user_id=None):
        """Perform a mock check-in of a new worker."""
        worker_id = generate_uuid()
        self.bundle_manager._worker_model.worker_checkin(
            user_id=user_id
            or self.bundle_manager._model.root_user_id,  # codalab-owned worker
            worker_id=worker_id,
            tag=tag,
            group_name=None,
            cpus=cpus,
            gpus=gpus,
            memory_bytes=memory_bytes,
            free_disk_bytes=free_disk_bytes,
            dependencies=[],
            shared_file_system=False,
            tag_exclusive=False,
            exit_after_num_runs=999999999,
            is_terminating=False,
        )
        # Mock a reply from the worker
        self.bundle_manager._worker_model.send_json_message = Mock(
            return_value=True)
        return worker_id

    def mock_bundle_checkin(self, bundle, worker_id, user_id=None):
        """Mock a worker checking in with the latest state of a bundle.

        Args:
            bundle: Bundle to check in.
            worker_id ([type]): worker id of the worker that performs the checkin.
            user_id (optional): user id that performs the checkin. Defaults to the default user id.
        """
        worker_run = BundleCheckinState(
            uuid=bundle.uuid,
            run_status="",
            bundle_start_time=0,
            container_time_total=0,
            container_time_user=0,
            container_time_system=0,
            docker_image="",
            state=bundle.state,
            remote="",
            exitcode=0,
            failure_message="",
            cpu_usage=0.0,
            memory_limit=0,
        )
        self.bundle_manager._model.bundle_checkin(bundle, worker_run, user_id
                                                  or self.user_id, worker_id)
示例#34
0
#!./venv/bin/python
"""
Script that creates the default CodaLab OAuth2 clients.

 - codalab_cli_client for the Bundle CLI clients authenticating through the Password Grant
 - codalab_worker_client for workers authenticating through the Password Grant

TODO(skoo): Create row for the web client given a redirect url.
"""
import sys
sys.path.append('.')

from codalab.lib.codalab_manager import CodaLabManager
from codalab.objects.oauth2 import OAuth2Client

manager = CodaLabManager()
model = manager.model()

if not model.get_oauth2_client('codalab_cli_client'):
    model.save_oauth2_client(
        OAuth2Client(
            model,
            client_id='codalab_cli_client',
            secret=None,
            name='Codalab CLI',
            user_id=None,
            grant_type='password',
            response_type='token',
            scopes='default',
            redirect_uris='',
        ))
示例#35
0
#!./venv/bin/python
"""
Script that creates the root user.
"""
import sys

sys.path.append('.')

import getpass

from codalab.lib import crypt_util
from codalab.lib.codalab_manager import CodaLabManager
from codalab.objects.user import User

manager = CodaLabManager()
model = manager.model()

username = manager.root_user_name()
user_id = manager.root_user_id()

if len(sys.argv) == 2:
    password = sys.argv[1]
else:
    while True:
        password = getpass.getpass('Password for %s(%s): ' % (username, user_id))
        if getpass.getpass('Confirm password: '******'Passwords don\'t match. Try again.')

if model.get_user(user_id=user_id, check_active=False):
    update = {
示例#36
0
文件: bundles.py 项目: abmnv/codalab
 def _create_cli(self, worksheet_uuid):
     manager = CodaLabManager(temporary=True, clients={settings.BUNDLE_SERVICE_URL: self.client})
     manager.set_current_worksheet_uuid(self.client, worksheet_uuid)
     cli = bundle_cli.BundleCLI(manager, headless=True)
     return cli
def main(args):
    manager = CodaLabManager()

    # Get the the message
    subject = args.subject
    with open(args.body_file) as f:
        body_template = f.read()

    if args.body_file.endswith('.md'):
        body_template = f"""
            <div style='margin:auto; width: 100%; max-width: 600px'>
                <img src=https://worksheets.codalab.org/img/codalab-logo.png style='max-width: 100%;' />
                <h1>CodaLab Worksheets</h1>
                {markdown2.markdown(body_template)}<br><br>
                <small>If you'd like stop receiving these emails, please <a href='https://worksheets.codalab.org/account/profile'>update your account settings on CodaLab</a>.</small>
            </div>
        """

    mime_type = ('html' if args.body_file.endswith('.html')
                 or args.body_file.endswith('.md') else 'plain')

    # Figure out who we want to send
    to_send_list = ([{
        'email': e,
        'first_name': '',
        'last_name': '',
        'user_name': '',
        'notifications': 2
    } for e in args.emails.split(",")] if args.emails else get_to_send_list(
        manager.model(), args.threshold))
    sent_list = get_sent_list(args.sent_file)
    sent_emails = set(info['email'] for info in sent_list)
    pending_to_send_list = [
        info for info in to_send_list if info['email'] not in sent_emails
    ]
    print('Already sent %d emails, %d to go' %
          (len(sent_list), len(pending_to_send_list)))

    for i, info in enumerate(pending_to_send_list):
        if args.only_email and args.only_email != info['email']:
            continue

        # Derived fields
        info['greeting_name'] = info['first_name'] or info[
            'last_name'] or info['user_name']
        info['full_name'] = ' '.join(
            [x for x in [info['first_name'], info['last_name']] if x])
        info['email_description'] = ('%s <%s>' %
                                     (info['full_name'], info['email'])
                                     if info['full_name'] else info['email'])
        info['sent_time'] = time.time()

        print(('Sending %s/%s (%s>=%s, doit=%s): [%s] %s' % (
            i,
            len(pending_to_send_list),
            info['notifications'],
            args.threshold,
            args.doit,
            info['user_name'],
            info['email_description'],
        )))

        # Apply template to get body of message
        body = body_template
        for field, value in info.items():
            body = body.replace('{{' + field + '}}', str(value or ''))

        if args.verbose >= 1:
            print('To      : %s' % info['email_description'])
            print('Subject : %s' % subject)
            print(body)
            print('-------')

        if not args.doit:
            continue

        # Send the actual email
        manager.emailer.send_email(recipient=info['email_description'],
                                   subject=subject,
                                   body=body,
                                   mime_type=mime_type)

        # Record that we sent
        with open(args.sent_file, 'a') as f:
            print(json.dumps(info), file=f)
            f.flush()