def ensure_api_server(project_id):
    """ Make sure there is a running API server for a project.

  Args:
    project_id: A string specifying the project ID.
  Returns:
    An integer specifying the API server port.
  """
    global api_servers
    if project_id in api_servers:
        raise gen.Return(api_servers[project_id])

    server_port = MAX_API_SERVER_PORT
    for port in api_servers.values():
        if port <= server_port:
            server_port = port - 1

    zk_locations = appscale_info.get_zk_node_ips()
    start_cmd = ' '.join([
        API_SERVER_LOCATION, '--port',
        str(server_port), '--project-id', project_id, '--zookeeper-locations',
        ' '.join(zk_locations)
    ])

    watch = ''.join([API_SERVER_PREFIX, project_id])
    full_watch = '-'.join([watch, str(server_port)])
    pidfile = os.path.join(VAR_DIR, '{}.pid'.format(full_watch))
    monit_app_configuration.create_config_file(
        watch,
        start_cmd,
        pidfile,
        server_port,
        max_memory=DEFAULT_MAX_APPSERVER_MEMORY,
        check_port=True)

    monit_operator = MonitOperator()
    yield monit_operator.reload(thread_pool)
    yield monit_operator.send_command_retry_process(full_watch, 'start')

    api_servers[project_id] = server_port
    raise gen.Return(server_port)
def start_app(version_key, config):
    """ Starts a Google App Engine application on this machine. It
      will start it up and then proceed to fetch the main page.

  Args:
    version_key: A string specifying a version key.
    config: a dictionary that contains
      app_port: An integer specifying the port to use.
      login_server: The server address the AppServer will use for login urls.
  """
    if 'app_port' not in config:
        raise BadConfigurationException('app_port is required')
    if 'login_server' not in config or not config['login_server']:
        raise BadConfigurationException('login_server is required')

    login_server = config['login_server']

    project_id, service_id, version_id = version_key.split(
        VERSION_PATH_SEPARATOR)

    if not misc.is_app_name_valid(project_id):
        raise BadConfigurationException(
            'Invalid project ID: {}'.format(project_id))

    try:
        service_manager = projects_manager[project_id][service_id]
        version_details = service_manager[version_id].version_details
    except KeyError:
        raise BadConfigurationException('Version not found')

    runtime = version_details['runtime']
    env_vars = version_details.get('envVariables', {})
    runtime_params = deployment_config.get_config('runtime_parameters')
    max_memory = runtime_params.get('default_max_appserver_memory',
                                    DEFAULT_MAX_APPSERVER_MEMORY)
    if 'instanceClass' in version_details:
        max_memory = INSTANCE_CLASSES.get(version_details['instanceClass'],
                                          max_memory)

    revision_key = VERSION_PATH_SEPARATOR.join(
        [project_id, service_id, version_id,
         str(version_details['revision'])])
    source_archive = version_details['deployment']['zip']['sourceUrl']

    api_server_port = yield ensure_api_server(project_id)
    yield source_manager.ensure_source(revision_key, source_archive, runtime)

    logging.info('Starting {} application {}'.format(runtime, project_id))

    pidfile = PIDFILE_TEMPLATE.format(revision=revision_key,
                                      port=config['app_port'])

    if runtime == constants.GO:
        env_vars['GOPATH'] = os.path.join(UNPACK_ROOT, revision_key, 'gopath')
        env_vars['GOROOT'] = os.path.join(GO_SDK, 'goroot')

    watch = ''.join([MONIT_INSTANCE_PREFIX, revision_key])
    if runtime in (constants.PYTHON27, constants.GO, constants.PHP):
        start_cmd = create_python27_start_cmd(project_id, login_server,
                                              config['app_port'], pidfile,
                                              revision_key, api_server_port)
        env_vars.update(create_python_app_env(login_server, project_id))
    elif runtime == constants.JAVA:
        # Account for MaxPermSize (~170MB), the parent process (~50MB), and thread
        # stacks (~20MB).
        max_heap = max_memory - 250
        if max_heap <= 0:
            raise BadConfigurationException(
                'Memory for Java applications must be greater than 250MB')

        start_cmd = create_java_start_cmd(project_id, config['app_port'],
                                          login_server, max_heap, pidfile,
                                          revision_key, api_server_port)

        env_vars.update(create_java_app_env(project_id))
    else:
        raise BadConfigurationException('Unknown runtime {} for {}'.format(
            runtime, project_id))

    logging.info("Start command: " + str(start_cmd))
    logging.info("Environment variables: " + str(env_vars))

    monit_app_configuration.create_config_file(watch,
                                               start_cmd,
                                               pidfile,
                                               config['app_port'],
                                               env_vars,
                                               max_memory,
                                               options.syslog_server,
                                               check_port=True,
                                               kill_exceeded_memory=True)

    full_watch = '{}-{}'.format(watch, config['app_port'])

    monit_operator = MonitOperator()
    yield monit_operator.reload(thread_pool)
    yield monit_operator.send_command_retry_process(full_watch, 'start')

    # Make sure the version node exists.
    zk_client.ensure_path('/'.join([VERSION_REGISTRATION_NODE, version_key]))

    # Since we are going to wait, possibly for a long time for the
    # application to be ready, we do it later.
    IOLoop.current().spawn_callback(add_routing,
                                    Instance(revision_key, config['app_port']))

    if project_id == DASHBOARD_PROJECT_ID:
        log_size = DASHBOARD_LOG_SIZE
    else:
        log_size = APP_LOG_SIZE

    if not setup_logrotate(project_id, log_size):
        logging.error(
            "Error while setting up log rotation for application: {}".format(
                project_id))