Example #1
0
class VirtualEventManager(object):
    """
        This is the manager that manages the live virtual event.
    """
    def __init__(self,
                 queue_url,
                 aws_region='us-east-1',
                 race_duration=180,
                 number_of_trials=3,
                 number_of_resets=10000,
                 penalty_seconds=2.0,
                 off_track_penalty=2.0,
                 collision_penalty=5.0,
                 is_continuous=False,
                 race_type="TIME_TRIAL"):
        # constructor arguments
        self._model_updater = ModelUpdater.get_instance()
        self._deepracer_path = rospkg.RosPack().get_path(
            DeepRacerPackages.DEEPRACER_SIMULATION_ENVIRONMENT)
        body_shell_path = os.path.join(self._deepracer_path, "meshes", "f1")
        self._valid_body_shells = \
            set(".".join(f.split(".")[:-1]) for f in os.listdir(body_shell_path) if os.path.isfile(
                os.path.join(body_shell_path, f)))
        self._valid_body_shells.add(const.BodyShellType.DEFAULT.value)
        self._valid_car_colors = set(e.value for e in const.CarColorType
                                     if "f1" not in e.value)
        self._num_sectors = int(rospy.get_param("NUM_SECTORS", "3"))
        self._queue_url = queue_url
        self._region = aws_region
        self._number_of_trials = number_of_trials
        self._number_of_resets = number_of_resets
        self._penalty_seconds = penalty_seconds
        self._off_track_penalty = off_track_penalty
        self._collision_penalty = collision_penalty
        self._is_continuous = is_continuous
        self._race_type = race_type
        self._is_save_simtrace_enabled = False
        self._is_save_mp4_enabled = False
        self._is_event_end = False
        self._done_condition = any
        self._race_duration = race_duration
        self._enable_domain_randomization = False

        # sqs client
        # The boto client errors out after polling for 1 hour.
        self._sqs_client = SQSClient(queue_url=self._queue_url,
                                     region_name=self._region,
                                     max_num_of_msg=MAX_NUM_OF_SQS_MESSAGE,
                                     wait_time_sec=SQS_WAIT_TIME_SEC,
                                     session=refreshed_session(self._region))
        self._s3_client = S3Client(region_name=self._region)
        # tracking current state information
        self._track_data = TrackData.get_instance()
        self._start_lane = self._track_data.center_line
        # keep track of the racer specific info, e.g. s3 locations, alias, car color etc.
        self._current_racer = None
        # keep track of the current race car we are using. It is always "racecar".
        car_model_state = ModelState()
        car_model_state.model_name = "racecar"
        self._current_car_model_state = car_model_state
        self._last_body_shell_type = None
        self._last_sensors = None
        self._racecar_model = AgentModel()
        # keep track of the current control agent we are using
        self._current_agent = None
        # keep track of the current control graph manager
        self._current_graph_manager = None
        # Keep track of previous model's name
        self._prev_model_name = None
        self._hide_position_idx = 0
        self._hide_positions = get_hide_positions(race_car_num=1)
        self._run_phase_subject = RunPhaseSubject()
        self._simtrace_video_s3_writers = []

        self._local_model_directory = './checkpoint'

        # virtual event only have single agent, so set agent_name to "agent"
        self._agent_name = "agent"

        # camera manager
        self._camera_manager = CameraManager.get_instance()

        # setting up virtual event top and follow camera in CameraManager
        # virtual event configure camera does not need to wait for car to spawm because
        # follow car camera is not tracking any car initially
        self._main_cameras, self._sub_camera = configure_camera(
            namespaces=[VIRTUAL_EVENT], is_wait_for_model=False)
        self._spawn_cameras()

        # pop out all cameras after configuration to prevent camera from moving
        self._camera_manager.pop(namespace=VIRTUAL_EVENT)

        dummy_metrics_s3_config = {
            MetricsS3Keys.METRICS_BUCKET.value: "dummy-bucket",
            MetricsS3Keys.METRICS_KEY.value: "dummy-key",
            MetricsS3Keys.REGION.value: self._region
        }

        self._eval_metrics = EvalMetrics(
            agent_name=self._agent_name,
            s3_dict_metrics=dummy_metrics_s3_config,
            is_continuous=self._is_continuous,
            pause_time_before_start=PAUSE_TIME_BEFORE_START)

        # upload a default best sector time for all sectors with time inf for each sector
        # if there is not best sector time existed in s3

        # use the s3 bucket and prefix for yaml file stored as environment variable because
        # here is SimApp use only. For virtual event there is no s3 bucket and prefix past
        # through yaml file. All are past through sqs. For simplicity, reuse the yaml s3 bucket
        # and prefix environment variable.
        virtual_event_best_sector_time = VirtualEventBestSectorTime(
            bucket=os.environ.get("YAML_S3_BUCKET", ''),
            s3_key=get_s3_key(os.environ.get("YAML_S3_PREFIX", ''),
                              SECTOR_TIME_S3_POSTFIX),
            region_name=os.environ.get("APP_REGION", "us-east-1"),
            local_path=SECTOR_TIME_LOCAL_PATH)
        response = virtual_event_best_sector_time.list()
        # this is used to handle situation such as robomaker job crash, so the next robomaker job
        # can catch the best sector time left over from crashed job
        if "Contents" not in response:
            virtual_event_best_sector_time.persist(
                body=json.dumps({
                    SECTOR_X_FORMAT.format(idx + 1): float("inf")
                    for idx in range(self._num_sectors)
                }),
                s3_kms_extra_args=utils.get_s3_kms_extra_args())

        # ROS service to indicate all the robomaker markov packages are ready for consumption
        signal_robomaker_markov_package_ready()

        PhaseObserver('/agent/training_phase', self._run_phase_subject)

        # setup mp4 services
        self._setup_mp4_services()

    @property
    def current_racer(self):
        """ Get the current racer object.

        Returns:
            RacerInformation: Information about current racer that was passed-in from the queue.
        """
        return self._current_racer

    @property
    def is_event_end(self):
        """Return True if the service has signaled event end

        Returns:
            boolean: Is it the time to kill everything and die
        """
        return self._is_event_end

    def _spawn_cameras(self):
        '''helper method for initializing cameras
        '''
        # virtual event configure camera does not need to wait for car to spawm because
        # follow car camera is not tracking any car initially
        camera_manager = CameraManager.get_instance()

        # pop all camera under virtual event namespace
        camera_manager.pop(namespace=VIRTUAL_EVENT)

        # Spawn the follow car camera
        LOG.info(
            "[virtual event manager] Spawning virtual event follow car camera model"
        )
        initial_pose = self._track_data.get_racecar_start_pose(
            racecar_idx=0,
            racer_num=1,
            start_position=get_start_positions(1)[0])
        self._main_cameras[VIRTUAL_EVENT].spawn_model(
            initial_pose,
            os.path.join(self._deepracer_path, "models", "camera",
                         "model.sdf"))

        LOG.info("[virtual event manager] Spawning sub camera model")
        # Spawn the top camera model
        self._sub_camera.spawn_model(
            None,
            os.path.join(self._deepracer_path, "models", "top_camera",
                         "model.sdf"))

    def poll_next_racer(self):
        """
            Poll from sqs the next racer information.
        """
        received_racer = False
        while not received_racer:
            # Polling MAX_NUM_OF_SQS_MESSAGE=1 message from sqs
            # with wait time specified in SQS_WAIT_TIME_SEC
            response = self._sqs_client.get_messages()
            # valid response is non-empty list
            if response:
                message_body = response[0]
                try:
                    # validate the current racer information.
                    validate_json_input(message_body, RACER_INFO_JSON_SCHEMA)
                    # Parse JSON into an racer information object
                    # with attributes corresponding to dict keys
                    self._current_racer = json.loads(
                        message_body,
                        object_hook=lambda d: namedtuple(
                            RACER_INFO_OBJECT, d.keys())(*d.values()))
                    # only set received_racer to True after making sure the message is valid.
                    received_racer = True
                    LOG.info(
                        "[virtual event manager] Received next racer's information %s",
                        self._current_racer)
                except GenericNonFatalException as ex:
                    ex.log_except_and_continue()

    def setup_race(self):
        """
            Setting up the race for the current racer.

        Returns:
            bool: True if setup race is successful.
                  False is a non fatal exception occurred.
        """

        LOG.info("[virtual event manager] Setting up race for racer")
        try:
            self._model_updater.unpause_physics()
            LOG.info(
                "[virtual event manager] Unpause physics in current world to setup race."
            )
            # step 1: hide the racecar to a position that camera cannot see
            self._hide_racecar_model(
                model_name=self._current_car_model_state.model_name)

            # step 2: set camera to starting position after previous car is deleted
            initial_pose = self._track_data.get_racecar_start_pose(
                racecar_idx=0,
                racer_num=1,
                start_position=get_start_positions(1)[0])
            self._main_cameras[VIRTUAL_EVENT].reset_pose(car_pose=initial_pose)
            LOG.info("[virtual event manager] Reset camera to starting line.")

            # step 3: download model metadata from s3
            sensors, version, model_metadata = self._download_model_metadata()

            # step 4: check whether body shell and sensors have been updated
            # to decide whether need to delete and re-spawn. Then, update
            # shell or color accordingly
            if hasattr(self._current_racer, "carConfig") and \
                    hasattr(self._current_racer.carConfig, "bodyShellType"):
                body_shell_type = self._current_racer.carConfig.bodyShellType \
                    if self._current_racer.carConfig.bodyShellType in self._valid_body_shells \
                    else const.BodyShellType.DEFAULT.value
            else:
                body_shell_type = const.BodyShellType.DEFAULT.value

            # check whether need to delete and respawn racecar
            # re-spawn if sensor or body shell type changed
            if self._last_body_shell_type != body_shell_type or \
                    self._last_sensors != sensors:
                # delete last racecar
                self._racecar_model.delete()
                # respawn a new racecar
                hide_pose = Pose()
                hide_pose.position.x = self._hide_positions[
                    self._hide_position_idx][0]
                hide_pose.position.y = self._hide_positions[
                    self._hide_position_idx][1]
                self._racecar_model.spawn(
                    name=self._current_car_model_state.model_name,
                    pose=hide_pose,
                    include_second_camera="true"
                    if Input.STEREO.value in sensors else "false",
                    include_lidar_sensor=str(
                        any(["lidar" in sensor.lower()
                             for sensor in sensors])).lower(),
                    body_shell_type=body_shell_type,
                    lidar_360_degree_sample=str(LIDAR_360_DEGREE_SAMPLE),
                    lidar_360_degree_horizontal_resolution=str(
                        LIDAR_360_DEGREE_HORIZONTAL_RESOLUTION),
                    lidar_360_degree_min_angle=str(LIDAR_360_DEGREE_MIN_ANGLE),
                    lidar_360_degree_max_angle=str(LIDAR_360_DEGREE_MAX_ANGLE),
                    lidar_360_degree_min_range=str(LIDAR_360_DEGREE_MIN_RANGE),
                    lidar_360_degree_max_range=str(LIDAR_360_DEGREE_MAX_RANGE),
                    lidar_360_degree_range_resolution=str(
                        LIDAR_360_DEGREE_RANGE_RESOLUTION),
                    lidar_360_degree_noise_mean=str(
                        LIDAR_360_DEGREE_NOISE_MEAN),
                    lidar_360_degree_noise_stddev=str(
                        LIDAR_360_DEGREE_NOISE_STDDEV))
                self._last_body_shell_type = body_shell_type
                self._last_sensors = sensors

            # step 5: download checkpoint, setup simtrace, mp4, clear metrics, and setup graph manager
            # download checkpoint from s3
            checkpoint = self._download_checkpoint(version)
            # setup the simtrace and mp4 writers if the s3 locations are available
            self._setup_simtrace_mp4_writers()
            # reset the metrics s3 location for the current racer
            self._reset_metrics_loc()
            # setup agents
            agent_list = self._get_agent_list(model_metadata, version)
            # after _setup_graph_manager finishes, physics is paused
            # physics will be unpaused again when race start
            self._setup_graph_manager(checkpoint, agent_list)
            LOG.info(
                "[virtual event manager] Graph manager successfully created the graph: setup race successful."
            )

            # step 6: update body shell or color
            # treat amazon van digital reward specially by also hiding the collision wheel
            visuals = self._model_updater.get_model_visuals(
                self._current_car_model_state.model_name)
            if const.F1 in body_shell_type:
                self._model_updater.hide_visuals(
                    visuals=visuals,
                    ignore_keywords=["f1_body_link"] if "with_wheel"
                    in body_shell_type.lower() else ["wheel", "f1_body_link"])
            else:
                if hasattr(self._current_racer, "carConfig") and \
                        hasattr(self._current_racer.carConfig, "carColor"):
                    car_color = self._current_racer.carConfig.carColor if self._current_racer.carConfig.carColor in self._valid_car_colors \
                        else DEFAULT_COLOR
                else:
                    car_color = DEFAULT_COLOR
                self._model_updater.update_color(visuals, car_color)
            return True
        except GenericNonFatalException as ex:
            ex.log_except_and_continue()
            self.upload_race_status(status_code=ex.error_code,
                                    error_name=ex.error_name,
                                    error_details=ex.error_msg)
            self._clean_up_race()
            return False
        except Exception as ex:
            log_and_exit(
                "[virtual event manager] Something really wrong happened when setting up the race. {}"
                .format(ex), SIMAPP_VIRTUAL_EVENT_RACE_EXCEPTION,
                SIMAPP_EVENT_ERROR_CODE_500)

    def _download_checkpoint(self, version):
        """Setup the Checkpoint object and selete the best checkpoint.

        Args:
            version (float): SimApp version

        Returns:
            Checkpoint: Checkpoint class instance
        """
        # download checkpoint from s3
        checkpoint = Checkpoint(
            bucket=self._current_racer.inputModel.s3BucketName,
            s3_prefix=self._current_racer.inputModel.s3KeyPrefix,
            region_name=self._region,
            agent_name=self._agent_name,
            checkpoint_dir=self._local_model_directory)
        # make coach checkpoint compatible
        if version < SIMAPP_VERSION_2 and not checkpoint.rl_coach_checkpoint.is_compatible(
        ):
            checkpoint.rl_coach_checkpoint.make_compatible(
                checkpoint.syncfile_ready)
        # get best model checkpoint string
        model_checkpoint_name = checkpoint.deepracer_checkpoint_json.get_deepracer_best_checkpoint(
        )
        # Select the best checkpoint model by uploading rl coach .coach_checkpoint file
        model_kms = self._current_racer.inputModel.s3KmsKeyArn if hasattr(
            self._current_racer.inputModel, 's3KmsKeyArn') else None
        checkpoint.rl_coach_checkpoint.update(
            model_checkpoint_name=model_checkpoint_name,
            s3_kms_extra_args=utils.get_s3_extra_args(model_kms))
        return checkpoint

    def _download_model_metadata(self):
        """ Attempt to download model metadata from s3.

        Raises:
            GenericNonFatalException: An non fatal exception which we will
                                      catch and proceed with work loop.

        Returns:
            sensors, version, model_metadata: The needed information from model metadata.
        """
        model_metadata_s3_key = get_s3_key(
            self._current_racer.inputModel.s3KeyPrefix,
            MODEL_METADATA_S3_POSTFIX)
        try:
            model_metadata = ModelMetadata(
                bucket=self._current_racer.inputModel.s3BucketName,
                s3_key=model_metadata_s3_key,
                region_name=self._region,
                local_path=MODEL_METADATA_LOCAL_PATH_FORMAT.format(
                    self._agent_name))
            model_metadata_info = model_metadata.get_model_metadata_info()
            sensors = model_metadata_info[ModelMetadataKeys.SENSOR.value]
            simapp_version = model_metadata_info[
                ModelMetadataKeys.VERSION.value]
        except botocore.exceptions.ClientError as err:
            error_msg = "[s3] Client Error: Failed to download model_metadata file: \
                        s3_bucket: {}, s3_key: {}, {}.".format(
                self._current_racer.inputModel.s3BucketName,
                model_metadata_s3_key, err)
            raise GenericNonFatalException(
                error_msg=error_msg,
                error_code=SIMAPP_EVENT_ERROR_CODE_400,
                error_name=SIMAPP_EVENT_USER_ERROR)
        except Exception as err:
            error_msg = "[s3] System Error: Failed to download model_metadata file: \
                        s3_bucket: {}, s3_key: {}, {}.".format(
                self._current_racer.inputModel.s3BucketName,
                model_metadata_s3_key, err)
            raise GenericNonFatalException(
                error_msg=error_msg,
                error_code=SIMAPP_EVENT_ERROR_CODE_500,
                error_name=SIMAPP_EVENT_SYSTEM_ERROR)
        return sensors, simapp_version, model_metadata

    def start_race(self):
        """
            Start the race (evaluation) for the current racer.
        """
        LOG.info("[virtual event manager] Starting race for racer %s",
                 self._current_racer.racerAlias)
        # send request
        if self._is_save_mp4_enabled:
            # racecar_color is not used for virtual event image editing, so simply pass default "Black"
            self._subscribe_to_save_mp4(
                VirtualEventVideoEditSrvRequest(
                    display_name=self._current_racer.racerAlias,
                    racecar_color=DEFAULT_COLOR))

        configure_environment_randomizer()
        self._model_updater.unpause_physics()
        LOG.info("[virtual event manager] Unpaused physics in current world.")

        if self._is_continuous:
            self._evaluate()
        else:
            self._evaluate()
            for _ in range(self._number_of_trials - 1):
                self._current_graph_manager.evaluate(EnvironmentSteps(1))

    def _evaluate(self):
        """Evaluate: step order is important and do not change

        First, reset internal state such as reset metrics, wait for
        sensor to be ready and reset the agent to the starting position.
        Second, it adds the virtual event camera to camera manager to follow specific agent.
        Third, call evaluate without reset again.

        This order is critical important and should not be changed for below reason.

        In the first step, a manual reset is done. In this manual reset step. It will
            1. reset agent metrics such as add pause second to total race second
            and clean up previous left over parameters.
            2. wait for sensor topics to become ready. Especially, when use LIDAR,
            LIDAR topic can take longer to come up.
            3. reset agent to the start line
        After the first step, camera is then attached to racecar through camera manager add.
        We should not add camera into camera manager before graph manager manual reset internal
        state. The reason is because racecar is initially spawn at a hide location. If we add
        camera before reset_internal_state, camera will record video at hide position.

        Last, we will start evaluate without reset again because we have already manually reset.
        It is critical here to NOT reset again. If we reset for a second time, we will double count
        the prepare time before the race
        """
        self._current_graph_manager.reset_internal_state(
            force_environment_reset=True)
        # Update CameraManager by adding cameras into the current namespace. By doing so
        # a single follow car camera will follow the current active racecar.
        self._camera_manager.add(self._main_cameras[VIRTUAL_EVENT],
                                 self._current_car_model_state.model_name)
        self._camera_manager.add(self._sub_camera,
                                 self._current_car_model_state.model_name)

        self._current_graph_manager.evaluate(EnvironmentSteps(1),
                                             reset_before_eval=False)

    def finish_race(self):
        """
            Finish the race for the current racer.
        """
        # pause physics of the world
        self._model_updater.pause_physics()
        time.sleep(1)
        # unsubscribe mp4
        if self._is_save_mp4_enabled:
            self._unsubscribe_from_save_mp4(EmptyRequest())
        self._track_data.remove_object(
            name=self._current_car_model_state.model_name)
        LOG.info("[virtual event manager] Finish Race - remove object %s.",
                 self._current_car_model_state.model_name)
        # pop out current racecar from camera namespace to prevent camera from moving
        self._camera_manager.pop(
            namespace=self._current_car_model_state.model_name)
        # upload simtrace and mp4 into s3 bucket
        self._save_simtrace_mp4()
        self.upload_race_status(status_code=200)
        # keep track of the previous model name
        self._prev_model_name = self._current_car_model_state.model_name
        # clean up local trace of current race
        self._clean_up_race()

    def _clean_up_race(self):
        """Helper function to clean up everything related to the ex-racer
           and get ready for the next racer.
        """
        self._simtrace_video_s3_writers = []
        # clean up local checkpoints etc.
        self._clean_local_directory()
        # reset the tensorflow graph to avoid errors with the global session
        tf.reset_default_graph()
        # reset the current racer
        self._current_racer = None
        self._current_agent = None
        self._current_graph_manager = None
        self._hide_position_idx = 0

    def _save_simtrace_mp4(self):
        """Get the appropriate kms key and save the simtrace and mp4 files.
        """
        # TODO: It might be theorically possible to have different kms keys for simtrace and mp4
        # but we are using the same key now since that's what happens in real life
        # consider refactor the simtrace_video_s3_writers later.
        if hasattr(self._current_racer.outputMp4, 's3KmsKeyArn'):
            simtrace_mp4_kms = self._current_racer.outputMp4.s3KmsKeyArn
        elif hasattr(self._current_racer.outputSimTrace, 's3KmsKeyArn'):
            simtrace_mp4_kms = self._current_racer.outputSimTrace.s3KmsKeyArn
        else:
            simtrace_mp4_kms = None
        for s3_writer in self._simtrace_video_s3_writers:
            s3_writer.persist(utils.get_s3_extra_args(simtrace_mp4_kms))

    def _reset_metrics_loc(self):
        """Reset the metrics location as new racer is loaded.
        """
        metrics_s3_config = {
            MetricsS3Keys.METRICS_BUCKET.value:
            self._current_racer.outputMetrics.s3BucketName,
            MetricsS3Keys.METRICS_KEY.value:
            self._current_racer.outputMetrics.s3KeyPrefix,
            MetricsS3Keys.REGION.value: self._region
        }
        self._eval_metrics.reset_metrics(
            s3_dict_metrics=metrics_s3_config,
            is_save_simtrace_enabled=self._is_save_simtrace_enabled)

    def _setup_simtrace_mp4_writers(self):
        """Setup the simtrace and mp4 writers if the locations are passed in.
        """
        self._is_save_simtrace_enabled = False
        self._is_save_mp4_enabled = False
        if hasattr(self._current_racer, 'outputSimTrace'):
            self._simtrace_video_s3_writers.append(
                SimtraceVideo(
                    upload_type=SimtraceVideoNames.SIMTRACE_EVAL.value,
                    bucket=self._current_racer.outputSimTrace.s3BucketName,
                    s3_prefix=self._current_racer.outputSimTrace.s3KeyPrefix,
                    region_name=self._region,
                    local_path=SIMTRACE_EVAL_LOCAL_PATH_FORMAT.format(
                        self._agent_name)))
            self._is_save_simtrace_enabled = True
        if hasattr(self._current_racer, 'outputMp4'):
            self._simtrace_video_s3_writers.extend([
                SimtraceVideo(
                    upload_type=SimtraceVideoNames.PIP.value,
                    bucket=self._current_racer.outputMp4.s3BucketName,
                    s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                    region_name=self._region,
                    local_path=CAMERA_PIP_MP4_LOCAL_PATH_FORMAT.format(
                        self._agent_name)),
                SimtraceVideo(
                    upload_type=SimtraceVideoNames.DEGREE45.value,
                    bucket=self._current_racer.outputMp4.s3BucketName,
                    s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                    region_name=self._region,
                    local_path=CAMERA_45DEGREE_LOCAL_PATH_FORMAT.format(
                        self._agent_name)),
                SimtraceVideo(
                    upload_type=SimtraceVideoNames.TOPVIEW.value,
                    bucket=self._current_racer.outputMp4.s3BucketName,
                    s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                    region_name=self._region,
                    local_path=CAMERA_TOPVIEW_LOCAL_PATH_FORMAT.format(
                        self._agent_name))
            ])
            self._is_save_mp4_enabled = True

    def _setup_graph_manager(self, checkpoint, agent_list):
        """Sets up graph manager based on the checkpoint file and agents list.

        Args:
            checkpoint (Checkpoint): The model checkpoints we just downloaded.
            agent_list (list[Agent]): List of agents we want to setup graph manager for.
        """
        sm_hyperparams_dict = {}
        self._current_graph_manager, _ = get_graph_manager(
            hp_dict=sm_hyperparams_dict,
            agent_list=agent_list,
            run_phase_subject=self._run_phase_subject,
            enable_domain_randomization=self._enable_domain_randomization,
            done_condition=self._done_condition,
            pause_physics=self._model_updater.pause_physics_service,
            unpause_physics=self._model_updater.unpause_physics_service)
        checkpoint_dict = dict()
        checkpoint_dict[self._agent_name] = checkpoint
        ds_params_instance = S3BotoDataStoreParameters(
            checkpoint_dict=checkpoint_dict)

        self._current_graph_manager.data_store = S3BotoDataStore(
            params=ds_params_instance,
            graph_manager=self._current_graph_manager,
            ignore_lock=True,
            log_and_cont=True)
        self._current_graph_manager.env_params.seed = 0

        self._current_graph_manager.data_store.wait_for_checkpoints()
        self._current_graph_manager.data_store.modify_checkpoint_variables()

        task_parameters = TaskParameters()
        task_parameters.checkpoint_restore_path = self._local_model_directory

        self._current_graph_manager.create_graph(
            task_parameters=task_parameters,
            stop_physics=self._model_updater.pause_physics_service,
            start_physics=self._model_updater.unpause_physics_service,
            empty_service_call=EmptyRequest)

    def _get_agent_list(self, model_metadata, version):
        """Setup agent and get the agents list.

        Args:
            model_metadata (ModelMetadata): Current racer's model metadata
            version (str): The current racer's simapp version in the model metadata

        Returns:
            agent_list (list): The list of agents for the current racer
        """
        # setup agent
        agent_config = {
            'model_metadata': model_metadata,
            ConfigParams.CAR_CTRL_CONFIG.value: {
                ConfigParams.LINK_NAME_LIST.value: [
                    link_name.replace('racecar',
                                      self._current_car_model_state.model_name)
                    for link_name in LINK_NAMES
                ],
                ConfigParams.VELOCITY_LIST.value: [
                    velocity_topic.replace(
                        'racecar', self._current_car_model_state.model_name)
                    for velocity_topic in VELOCITY_TOPICS
                ],
                ConfigParams.STEERING_LIST.value: [
                    steering_topic.replace(
                        'racecar', self._current_car_model_state.model_name)
                    for steering_topic in STEERING_TOPICS
                ],
                ConfigParams.CHANGE_START.value:
                utils.str2bool(rospy.get_param('CHANGE_START_POSITION',
                                               False)),
                ConfigParams.ALT_DIR.value:
                utils.str2bool(
                    rospy.get_param('ALTERNATE_DRIVING_DIRECTION', False)),
                ConfigParams.MODEL_METADATA.value:
                model_metadata,
                ConfigParams.REWARD.value:
                reward_function,
                ConfigParams.AGENT_NAME.value:
                self._current_car_model_state.model_name,
                ConfigParams.VERSION.value:
                version,
                ConfigParams.NUMBER_OF_RESETS.value:
                self._number_of_resets,
                ConfigParams.PENALTY_SECONDS.value:
                self._penalty_seconds,
                ConfigParams.NUMBER_OF_TRIALS.value:
                self._number_of_trials,
                ConfigParams.IS_CONTINUOUS.value:
                self._is_continuous,
                ConfigParams.RACE_TYPE.value:
                self._race_type,
                ConfigParams.COLLISION_PENALTY.value:
                self._collision_penalty,
                ConfigParams.OFF_TRACK_PENALTY.value:
                self._off_track_penalty,
                ConfigParams.START_POSITION.value:
                get_start_positions(1)
                [0],  # hard-coded to the first start position
                ConfigParams.DONE_CONDITION.value:
                self._done_condition,
                ConfigParams.IS_VIRTUAL_EVENT.value:
                True,
                ConfigParams.RACE_DURATION.value:
                self._race_duration
            }
        }

        agent_list = list()
        agent_list.append(
            create_rollout_agent(agent_config, self._eval_metrics,
                                 self._run_phase_subject))
        agent_list.append(create_obstacles_agent())
        agent_list.append(
            create_bot_cars_agent(
                pause_time_before_start=PAUSE_TIME_BEFORE_START))
        return agent_list

    def _setup_mp4_services(self):
        """
        Setting up the mp4 ros services if mp4s need to be saved.
        """
        mp4_sub = "/{}/save_mp4/subscribe_to_save_mp4".format(VIRTUAL_EVENT)
        mp4_unsub = "/{}/save_mp4/unsubscribe_from_save_mp4".format(
            VIRTUAL_EVENT)
        rospy.wait_for_service(mp4_sub)
        rospy.wait_for_service(mp4_unsub)
        self._subscribe_to_save_mp4 = ServiceProxyWrapper(
            mp4_sub, VirtualEventVideoEditSrv)
        self._unsubscribe_from_save_mp4 = ServiceProxyWrapper(mp4_unsub, Empty)

    def _clean_local_directory(self):
        """Clean up the local directory after race ends.
        """
        LOG.info(
            "[virtual event manager] cleaning up the local directory after race ends."
        )
        for root, _, files in os.walk(self._local_model_directory):
            for f in files:
                os.remove(os.path.join(root, f))

    def _hide_racecar_model(self, model_name):
        """hide racecar model into hide location

        Args:
            model_name (str): model name
        """
        # set the car at the pit parking position
        yaw = 0.0 if self._track_data.is_ccw else math.pi
        self._model_updater.set_model_position(
            model_name,
            self._hide_positions[self._hide_position_idx],
            yaw,
            is_blocking=True)
        LOG.info("[virtual event manager] hide {}".format(model_name))

    def upload_race_status(self,
                           status_code,
                           error_name=None,
                           error_details=None):
        """Upload race status into s3.

        Args:
            status_code (str): Status code for race.
            error_name (str, optional): The name of the error if is 4xx or 5xx.
                                        Defaults to "".
            error_details (str, optional): The detail message of the error
                                           if is 4xx or 5xx.
                                           Defaults to "".
        """
        # persist s3 status file
        if error_name is not None and error_details is not None:
            status = {
                RaceStatusKeys.STATUS_CODE.value: status_code,
                RaceStatusKeys.ERROR_NAME.value: error_name,
                RaceStatusKeys.ERROR_DETAILS.value: error_details
            }
        else:
            status = {RaceStatusKeys.STATUS_CODE.value: status_code}
        status_json = json.dumps(status)
        s3_key = os.path.normpath(
            os.path.join(self._current_racer.outputStatus.s3KeyPrefix,
                         S3_RACE_STATUS_FILE_NAME))
        race_status_kms = self._current_racer.outputStatus.s3KmsKeyArn if \
            hasattr(self._current_racer.outputStatus, 's3KmsKeyArn') else None
        self._s3_client.upload_fileobj(
            bucket=self._current_racer.outputStatus.s3BucketName,
            s3_key=s3_key,
            fileobj=io.BytesIO(status_json.encode()),
            s3_kms_extra_args=utils.get_s3_extra_args(race_status_kms))
        LOG.info(
            "[virtual event manager] Successfully uploaded race status file to \
                 s3 bucket {} with s3 key {}.".format(
                self._current_racer.outputStatus.s3BucketName, s3_key))
Example #2
0
    def __init__(self,
                 queue_url,
                 aws_region='us-east-1',
                 race_duration=180,
                 number_of_trials=3,
                 number_of_resets=10000,
                 penalty_seconds=2.0,
                 off_track_penalty=2.0,
                 collision_penalty=5.0,
                 is_continuous=False,
                 race_type="TIME_TRIAL"):
        # constructor arguments
        self._model_updater = ModelUpdater.get_instance()
        self._deepracer_path = rospkg.RosPack().get_path(
            DeepRacerPackages.DEEPRACER_SIMULATION_ENVIRONMENT)
        body_shell_path = os.path.join(self._deepracer_path, "meshes", "f1")
        self._valid_body_shells = \
            set(".".join(f.split(".")[:-1]) for f in os.listdir(body_shell_path) if os.path.isfile(
                os.path.join(body_shell_path, f)))
        self._valid_body_shells.add(const.BodyShellType.DEFAULT.value)
        self._valid_car_colors = set(e.value for e in const.CarColorType
                                     if "f1" not in e.value)
        self._num_sectors = int(rospy.get_param("NUM_SECTORS", "3"))
        self._queue_url = queue_url
        self._region = aws_region
        self._number_of_trials = number_of_trials
        self._number_of_resets = number_of_resets
        self._penalty_seconds = penalty_seconds
        self._off_track_penalty = off_track_penalty
        self._collision_penalty = collision_penalty
        self._is_continuous = is_continuous
        self._race_type = race_type
        self._is_save_simtrace_enabled = False
        self._is_save_mp4_enabled = False
        self._is_event_end = False
        self._done_condition = any
        self._race_duration = race_duration
        self._enable_domain_randomization = False

        # sqs client
        # The boto client errors out after polling for 1 hour.
        self._sqs_client = SQSClient(queue_url=self._queue_url,
                                     region_name=self._region,
                                     max_num_of_msg=MAX_NUM_OF_SQS_MESSAGE,
                                     wait_time_sec=SQS_WAIT_TIME_SEC,
                                     session=refreshed_session(self._region))
        self._s3_client = S3Client(region_name=self._region)
        # tracking current state information
        self._track_data = TrackData.get_instance()
        self._start_lane = self._track_data.center_line
        # keep track of the racer specific info, e.g. s3 locations, alias, car color etc.
        self._current_racer = None
        # keep track of the current race car we are using. It is always "racecar".
        car_model_state = ModelState()
        car_model_state.model_name = "racecar"
        self._current_car_model_state = car_model_state
        self._last_body_shell_type = None
        self._last_sensors = None
        self._racecar_model = AgentModel()
        # keep track of the current control agent we are using
        self._current_agent = None
        # keep track of the current control graph manager
        self._current_graph_manager = None
        # Keep track of previous model's name
        self._prev_model_name = None
        self._hide_position_idx = 0
        self._hide_positions = get_hide_positions(race_car_num=1)
        self._run_phase_subject = RunPhaseSubject()
        self._simtrace_video_s3_writers = []

        self._local_model_directory = './checkpoint'

        # virtual event only have single agent, so set agent_name to "agent"
        self._agent_name = "agent"

        # camera manager
        self._camera_manager = CameraManager.get_instance()

        # setting up virtual event top and follow camera in CameraManager
        # virtual event configure camera does not need to wait for car to spawm because
        # follow car camera is not tracking any car initially
        self._main_cameras, self._sub_camera = configure_camera(
            namespaces=[VIRTUAL_EVENT], is_wait_for_model=False)
        self._spawn_cameras()

        # pop out all cameras after configuration to prevent camera from moving
        self._camera_manager.pop(namespace=VIRTUAL_EVENT)

        dummy_metrics_s3_config = {
            MetricsS3Keys.METRICS_BUCKET.value: "dummy-bucket",
            MetricsS3Keys.METRICS_KEY.value: "dummy-key",
            MetricsS3Keys.REGION.value: self._region
        }

        self._eval_metrics = EvalMetrics(
            agent_name=self._agent_name,
            s3_dict_metrics=dummy_metrics_s3_config,
            is_continuous=self._is_continuous,
            pause_time_before_start=PAUSE_TIME_BEFORE_START)

        # upload a default best sector time for all sectors with time inf for each sector
        # if there is not best sector time existed in s3

        # use the s3 bucket and prefix for yaml file stored as environment variable because
        # here is SimApp use only. For virtual event there is no s3 bucket and prefix past
        # through yaml file. All are past through sqs. For simplicity, reuse the yaml s3 bucket
        # and prefix environment variable.
        virtual_event_best_sector_time = VirtualEventBestSectorTime(
            bucket=os.environ.get("YAML_S3_BUCKET", ''),
            s3_key=get_s3_key(os.environ.get("YAML_S3_PREFIX", ''),
                              SECTOR_TIME_S3_POSTFIX),
            region_name=os.environ.get("APP_REGION", "us-east-1"),
            local_path=SECTOR_TIME_LOCAL_PATH)
        response = virtual_event_best_sector_time.list()
        # this is used to handle situation such as robomaker job crash, so the next robomaker job
        # can catch the best sector time left over from crashed job
        if "Contents" not in response:
            virtual_event_best_sector_time.persist(
                body=json.dumps({
                    SECTOR_X_FORMAT.format(idx + 1): float("inf")
                    for idx in range(self._num_sectors)
                }),
                s3_kms_extra_args=utils.get_s3_kms_extra_args())

        # ROS service to indicate all the robomaker markov packages are ready for consumption
        signal_robomaker_markov_package_ready()

        PhaseObserver('/agent/training_phase', self._run_phase_subject)

        # setup mp4 services
        self._setup_mp4_services()
Example #3
0
class VirtualEventManager(object):
    """
        This is the manager that manages the live virtual event.
    """
    def __init__(self, queue_url, aws_region='us-east-1',
                 race_duration=180, number_of_trials=3, number_of_resets=10000,
                 penalty_seconds=2.0, off_track_penalty=2.0, collision_penalty=5.0,
                 is_continuous=False, race_type="TIME_TRIAL",
                 body_shell_type="deepracer"):
        # constructor arguments
        self._body_shell_type = body_shell_type
        self._num_sectors = int(rospy.get_param("NUM_SECTORS", "3"))
        self._queue_url = queue_url
        self._region = aws_region
        self._number_of_trials = number_of_trials
        self._number_of_resets = number_of_resets
        self._penalty_seconds = penalty_seconds
        self._off_track_penalty = off_track_penalty
        self._collision_penalty = collision_penalty
        self._is_continuous = is_continuous
        self._race_type = race_type
        self._is_save_simtrace_enabled = False
        self._is_save_mp4_enabled = False
        self._is_event_end = False
        self._done_condition = any
        self._race_duration = race_duration
        self._enable_domain_randomization = False

        # sqs client
        # The boto client errors out after polling for 1 hour.
        self._sqs_client = SQSClient(queue_url=self._queue_url,
                                     region_name=self._region,
                                     max_num_of_msg=MAX_NUM_OF_SQS_MESSAGE,
                                     wait_time_sec=SQS_WAIT_TIME_SEC,
                                     session=refreshed_session(self._region))
        self._s3_client = S3Client(region_name=self._region)
        self._model_updater = ModelUpdater.get_instance()

        # tracking current state information
        self._track_data = TrackData.get_instance()
        self._start_lane = self._track_data.center_line
        # keep track of the racer specific info, e.g. s3 locations, alias, car color etc.
        self._current_racer = None
        # keep track of the current race car we are using, e.g. racecar_0
        self._current_car_model_state = None
        # keep track of the current control agent we are using
        self._current_agent = None
        # keep track of the current control graph manager
        self._current_graph_manager = None
        # Keep track of previous model's name
        self._prev_model_name = None
        self._park_position_idx = 0
        self._park_positions = get_hide_positions(len(SENSOR_MODEL_MAP))
        self._run_phase_subject = RunPhaseSubject()
        self._simtrace_video_s3_writers = []

        self._local_model_directory = './checkpoint'

        # virtual event only have single agent, so set agent_name to "agent"
        self._agent_name = "agent"

        # camera manager
        self._camera_manager = CameraManager.get_instance()

        # setting up virtual event top and follow camera in CameraManager
        # virtual event configure camera does not need to wait for car to spawm because
        # follow car camera is not tracking any car initially
        self._main_cameras, self._sub_camera = configure_camera(namespaces=[VIRTUAL_EVENT],
                                                                is_wait_for_model=False)

        # pop out all cameras after configuration to prevent camera from moving
        self._camera_manager.pop(namespace=VIRTUAL_EVENT)

        dummy_metrics_s3_config = {MetricsS3Keys.METRICS_BUCKET.value: "dummy-bucket",
                                   MetricsS3Keys.METRICS_KEY.value: "dummy-key",
                                   MetricsS3Keys.REGION.value: self._region}

        self._eval_metrics = EvalMetrics(agent_name=self._agent_name,
                                         s3_dict_metrics=dummy_metrics_s3_config,
                                         is_continuous=self._is_continuous,
                                         pause_time_before_start=PAUSE_TIME_BEFORE_START)

        # upload a default best sector time for all sectors with time inf for each sector
        # if there is not best sector time existed in s3

        # use the s3 bucket and prefix for yaml file stored as environment variable because
        # here is SimApp use only. For virtual event there is no s3 bucket and prefix past
        # through yaml file. All are past through sqs. For simplicity, reuse the yaml s3 bucket
        # and prefix environment variable.
        virtual_event_best_sector_time = VirtualEventBestSectorTime(
            bucket=os.environ.get("YAML_S3_BUCKET", ''),
            s3_key=get_s3_key(os.environ.get("YAML_S3_PREFIX", ''), SECTOR_TIME_S3_POSTFIX),
            region_name=os.environ.get("APP_REGION", "us-east-1"),
            local_path=SECTOR_TIME_LOCAL_PATH)
        response = virtual_event_best_sector_time.list()
        # this is used to handle situation such as robomaker job crash, so the next robomaker job
        # can catch the best sector time left over from crashed job
        if "Contents" not in response:
            virtual_event_best_sector_time.persist(body=json.dumps(
                {SECTOR_X_FORMAT.format(idx + 1): float("inf")
                 for idx in range(self._num_sectors)}),
                s3_kms_extra_args=utils.get_s3_kms_extra_args())

        # ROS service to indicate all the robomaker markov packages are ready for consumption
        signal_robomaker_markov_package_ready()

        PhaseObserver('/agent/training_phase', self._run_phase_subject)

        # setup mp4 services
        self._setup_mp4_services()

    @property
    def current_racer(self):
        """ Get the current racer object.

        Returns:
            RacerInformation: Information about current racer that was passed-in from the queue.
        """
        return self._current_racer

    @property
    def is_event_end(self):
        """Return True if the service has signaled event end

        Returns:
            boolean: Is it the time to kill everything and die
        """
        return self._is_event_end

    def poll_next_racer(self):
        """
            Poll from sqs the next racer information.
        """
        received_racer = False
        error_counter = 0
        while not received_racer:
            # Polling MAX_NUM_OF_SQS_MESSAGE=1 message from sqs
            # with wait time specified in SQS_WAIT_TIME_SEC
            response = self._sqs_client.get_messages()
            # If polling message is successful, it will return a list of payloads
            # If polling message failed, it will return an integer
            # 1=ClientError Or FailedToDeleteMessage
            # 2=SystemError
            if isinstance(response, int):
                error_counter += response
                if error_counter >= MAX_NUM_OF_SQS_ERROR:
                    # something went really wrong with the sqs queue...
                    log_and_exit("[virtual event manager] Too many exceptions (num={}) in \
                                 receiving message from sqs queue: {}".format(error_counter,
                                                                              self._queue_url),
                                 SIMAPP_SQS_RECEIVE_MESSAGE_EXCEPTION,
                                 SIMAPP_EVENT_ERROR_CODE_500)
            elif isinstance(response, list) and len(response) == 1:
                message_body = response[0]
                try:
                    # validate the current racer information.
                    validate_json_input(message_body, RACER_INFO_JSON_SCHEMA)
                    # Parse JSON into an racer information object
                    # with attributes corresponding to dict keys
                    self._current_racer = json.loads(message_body,
                                                     object_hook=lambda d:
                                                     namedtuple(RACER_INFO_OBJECT, d.keys())(*d.values()))
                    # only set received_racer to True after making sure the message is valid.
                    received_racer = True
                    LOG.info("[virtual event manager] Received next racer's information %s", self._current_racer)
                except GenericNonFatalException as ex:
                    ex.log_except_and_continue()

    def setup_race(self):
        """
            Setting up the race for the current racer.

        Returns:
            bool: True if setup race is successful.
                  False is a non fatal exception occurred.
        """

        LOG.info("[virtual event manager] Setting up race for racer")
        try:
            # unpause the physics in current world
            self._model_updater.unpause_physics()
            LOG.info("[virtual event manager] Unpaused physics in current world.")
            # set camera to starting position
            initial_pose = self._track_data.get_racecar_start_pose(
                racecar_idx=0,
                racer_num=1,
                start_position=get_start_positions(1)[0])
            self._main_cameras[VIRTUAL_EVENT].reset_pose(
                car_pose=initial_pose)
            LOG.info("[virtual event manager] Reset camera to starting line.")
            if self._prev_model_name is not None:
                # NOTE: it's by design that we immediately part the previous car to pit
                # location right after unpause physics. This prevents any unwanted
                # leftover behavior to happen
                self._park_at_pit_location(self._prev_model_name)
                LOG.info("[virtual event manager] Parked previous model %s to pit location.",
                         self._prev_model_name)
            self._model_updater.pause_physics()
            LOG.info("[virtual event manager] Paused physics in current world.")
            # download model metadata from s3
            sensors, version, model_metadata = self._download_model_metadata()
            # based on model metadata, select racecar
            self._current_car_model_state = self._get_car_model_state(sensors)
            # download checkpoint from s3
            checkpoint = self._download_checkpoint(version)
            # setup the simtrace and mp4 writers if the s3 locations are available
            self._setup_simtrace_mp4_writers()
            # reset the metrics s3 location for the current racer
            self._reset_metrics_loc()
            # setup agents
            agent_list = self._get_agent_list(model_metadata, version)
            self._setup_graph_manager(checkpoint, agent_list)
            LOG.info("[virtual event manager] Graph manager successfully created the graph: setup race successful.")
            return True
        except GenericNonFatalException as ex:
            ex.log_except_and_continue()
            self.upload_race_status(status_code=ex.error_code,
                                    error_name=ex.error_name,
                                    error_details=ex.error_msg)
            self._clean_up_race()
            return False
        except Exception as ex:
            log_and_exit("[virtual event manager] Something really wrong happened when setting up the race. {}".format(ex),
                         SIMAPP_VIRTUAL_EVENT_RACE_EXCEPTION,
                         SIMAPP_EVENT_ERROR_CODE_500)

    def _download_checkpoint(self, version):
        """Setup the Checkpoint object and selete the best checkpoint.

        Args:
            version (float): SimApp version

        Returns:
            Checkpoint: Checkpoint class instance
        """
        # download checkpoint from s3
        checkpoint = Checkpoint(bucket=self._current_racer.inputModel.s3BucketName,
                                s3_prefix=self._current_racer.inputModel.s3KeyPrefix,
                                region_name=self._region,
                                agent_name=self._agent_name,
                                checkpoint_dir=self._local_model_directory)
        # make coach checkpoint compatible
        if version < SIMAPP_VERSION_2 and not checkpoint.rl_coach_checkpoint.is_compatible():
            checkpoint.rl_coach_checkpoint.make_compatible(checkpoint.syncfile_ready)
        # get best model checkpoint string
        model_checkpoint_name = checkpoint.deepracer_checkpoint_json.get_deepracer_best_checkpoint()
        # Select the best checkpoint model by uploading rl coach .coach_checkpoint file
        model_kms = self._current_racer.inputModel.s3KmsKeyArn if hasattr(self._current_racer.inputModel, 's3KmsKeyArn') else None
        checkpoint.rl_coach_checkpoint.update(
            model_checkpoint_name=model_checkpoint_name,
            s3_kms_extra_args=utils.get_s3_extra_args(model_kms))
        return checkpoint

    def _download_model_metadata(self):
        """ Attempt to download model metadata from s3.

        Raises:
            GenericNonFatalException: An non fatal exception which we will
                                      catch and proceed with work loop.

        Returns:
            sensors, version, model_metadata: The needed information from model metadata.
        """
        model_metadata_s3_key = get_s3_key(self._current_racer.inputModel.s3KeyPrefix,
                                           MODEL_METADATA_S3_POSTFIX)
        try:
            model_metadata = ModelMetadata(bucket=self._current_racer.inputModel.s3BucketName,
                                           s3_key=model_metadata_s3_key,
                                           region_name=self._region,
                                           local_path=MODEL_METADATA_LOCAL_PATH_FORMAT.format(self._agent_name))
            model_metadata_info = model_metadata.get_model_metadata_info()
            sensors = model_metadata_info[ModelMetadataKeys.SENSOR.value]
            simapp_version = model_metadata_info[ModelMetadataKeys.VERSION.value]
        except botocore.exceptions.ClientError as err:
            error_msg = "[s3] Client Error: Failed to download model_metadata file: \
                        s3_bucket: {}, s3_key: {}, {}.".format(self._current_racer.inputModel.s3BucketName,
                                                               model_metadata_s3_key,
                                                               err)
            raise GenericNonFatalException(error_msg=error_msg,
                                           error_code=SIMAPP_EVENT_ERROR_CODE_400,
                                           error_name=SIMAPP_EVENT_USER_ERROR)
        except Exception as err:
            error_msg = "[s3] System Error: Failed to download model_metadata file: \
                        s3_bucket: {}, s3_key: {}, {}.".format(self._current_racer.inputModel.s3BucketName,
                                                               model_metadata_s3_key,
                                                               err)
            raise GenericNonFatalException(error_msg=error_msg,
                                           error_code=SIMAPP_EVENT_ERROR_CODE_500,
                                           error_name=SIMAPP_EVENT_SYSTEM_ERROR)
        return sensors, simapp_version, model_metadata

    def start_race(self):
        """
            Start the race (evaluation) for the current racer.
        """
        LOG.info("[virtual event manager] Starting race for racer %s", self._current_racer.racerAlias)
        # update the car on current model if does not use f1 or tron type of shell
        if const.F1 not in self._body_shell_type.lower():
            self._model_updater.update_model_color(self._current_car_model_state.model_name,
                                                   self._current_racer.carConfig.carColor)
        # send request
        if self._is_save_mp4_enabled:
            self._subscribe_to_save_mp4(VirtualEventVideoEditSrvRequest(
                display_name=self._current_racer.racerAlias,
                racecar_color=self._current_racer.carConfig.carColor))

        # Update CameraManager by adding cameras into the current namespace. By doing so
        # a single follow car camera will follow the current active racecar.
        self._camera_manager.add(self._main_cameras[VIRTUAL_EVENT],
                                 self._current_car_model_state.model_name)
        self._camera_manager.add(self._sub_camera, self._current_car_model_state.model_name)

        configure_environment_randomizer()
        # strip index for park position
        self._park_position_idx = get_racecar_idx(self._current_car_model_state.model_name)
        # set the park position in track and do evaluation
        # Before each evaluation episode (single lap for non-continuous race and complete race for
        # continuous race), a new copy of park_positions needs to be loaded into track_data because
        # a park position will be pop from park_positions when a racer car need to be parked.
        # unpause the physics in current world
        self._model_updater.unpause_physics()
        LOG.info("[virtual event manager] Unpaused physics in current world.")
        if self._prev_model_name is not None and \
           self._prev_model_name != self._current_car_model_state.model_name:
            # disable the links on the prev car
            # we are doing it here because we don't want the car to float around
            # after the link is disabled
            prev_car_model_state = ModelState()
            prev_car_model_state.model_name = self._prev_model_name
        LOG.info("[virtual event manager] Unpaused model for current car.")
        if self._is_continuous:
            self._track_data.park_positions = [self._park_positions[self._park_position_idx]]
            self._current_graph_manager.evaluate(EnvironmentSteps(1))
        else:
            for _ in range(self._number_of_trials):
                self._track_data.park_positions = [self._park_positions[self._park_position_idx]]
                self._current_graph_manager.evaluate(EnvironmentSteps(1))

    def finish_race(self):
        """
            Finish the race for the current racer.
        """
        # pause physics of the world
        self._model_updater.pause_physics()
        time.sleep(1)
        # unsubscribe mp4
        if self._is_save_mp4_enabled:
            self._unsubscribe_from_save_mp4(EmptyRequest())
        self._track_data.remove_object(name=self._current_car_model_state.model_name)
        LOG.info("[virtual event manager] Finish Race - remove object %s.",
                 self._current_car_model_state.model_name)
        # pop out current racecar from camera namespace to prevent camera from moving
        self._camera_manager.pop(namespace=self._current_car_model_state.model_name)
        # upload simtrace and mp4 into s3 bucket
        self._save_simtrace_mp4()
        self.upload_race_status(status_code=200)
        # keep track of the previous model name
        self._prev_model_name = self._current_car_model_state.model_name
        # clean up local trace of current race
        self._clean_up_race()

    def _clean_up_race(self):
        """Helper function to clean up everything related to the ex-racer
           and get ready for the next racer.
        """
        self._simtrace_video_s3_writers = []
        # clean up local checkpoints etc.
        self._clean_local_directory()
        # reset the tensorflow graph to avoid errors with the global session
        tf.reset_default_graph()
        # reset the current racer
        self._current_racer = None
        self._current_agent = None
        self._current_car_model_state = None
        self._current_graph_manager = None
        self._park_position_idx = 0

    def _save_simtrace_mp4(self):
        """Get the appropriate kms key and save the simtrace and mp4 files.
        """
        # TODO: It might be theorically possible to have different kms keys for simtrace and mp4
        # but we are using the same key now since that's what happens in real life
        # consider refactor the simtrace_video_s3_writers later.
        if hasattr(self._current_racer.outputMp4, 's3KmsKeyArn'):
            simtrace_mp4_kms = self._current_racer.outputMp4.s3KmsKeyArn
        elif hasattr(self._current_racer.outputSimTrace, 's3KmsKeyArn'):
            simtrace_mp4_kms = self._current_racer.outputSimTrace.s3KmsKeyArn
        else:
            simtrace_mp4_kms = None
        for s3_writer in self._simtrace_video_s3_writers:
            s3_writer.persist(utils.get_s3_extra_args(simtrace_mp4_kms))

    def _reset_metrics_loc(self):
        """Reset the metrics location as new racer is loaded.
        """
        metrics_s3_config = {MetricsS3Keys.METRICS_BUCKET.value: self._current_racer.outputMetrics.s3BucketName,
                             MetricsS3Keys.METRICS_KEY.value: self._current_racer.outputMetrics.s3KeyPrefix,
                             MetricsS3Keys.REGION.value: self._region}
        self._eval_metrics.reset_metrics(s3_dict_metrics=metrics_s3_config,
                                         is_save_simtrace_enabled=self._is_save_simtrace_enabled)

    def _setup_simtrace_mp4_writers(self):
        """Setup the simtrace and mp4 writers if the locations are passed in.
        """
        self._is_save_simtrace_enabled = False
        self._is_save_mp4_enabled = False
        if hasattr(self._current_racer, 'outputSimTrace'):
            self._simtrace_video_s3_writers.append(
                SimtraceVideo(upload_type=SimtraceVideoNames.SIMTRACE_EVAL.value,
                              bucket=self._current_racer.outputSimTrace.s3BucketName,
                              s3_prefix=self._current_racer.outputSimTrace.s3KeyPrefix,
                              region_name=self._region,
                              local_path=SIMTRACE_EVAL_LOCAL_PATH_FORMAT.format(self._agent_name)))
            self._is_save_simtrace_enabled = True
        if hasattr(self._current_racer, 'outputMp4'):
            self._simtrace_video_s3_writers.extend([
                SimtraceVideo(upload_type=SimtraceVideoNames.PIP.value,
                              bucket=self._current_racer.outputMp4.s3BucketName,
                              s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                              region_name=self._region,
                              local_path=CAMERA_PIP_MP4_LOCAL_PATH_FORMAT.format(self._agent_name)),
                SimtraceVideo(upload_type=SimtraceVideoNames.DEGREE45.value,
                              bucket=self._current_racer.outputMp4.s3BucketName,
                              s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                              region_name=self._region,
                              local_path=CAMERA_45DEGREE_LOCAL_PATH_FORMAT.format(self._agent_name)),
                SimtraceVideo(upload_type=SimtraceVideoNames.TOPVIEW.value,
                              bucket=self._current_racer.outputMp4.s3BucketName,
                              s3_prefix=self._current_racer.outputMp4.s3KeyPrefix,
                              region_name=self._region,
                              local_path=CAMERA_TOPVIEW_LOCAL_PATH_FORMAT.format(self._agent_name))])
            self._is_save_mp4_enabled = True

    def _setup_graph_manager(self, checkpoint, agent_list):
        """Sets up graph manager based on the checkpoint file and agents list.

        Args:
            checkpoint (Checkpoint): The model checkpoints we just downloaded.
            agent_list (list[Agent]): List of agents we want to setup graph manager for.
        """
        sm_hyperparams_dict = {}
        self._current_graph_manager, _ = get_graph_manager(hp_dict=sm_hyperparams_dict, agent_list=agent_list,
                                                           run_phase_subject=self._run_phase_subject,
                                                           enable_domain_randomization=self._enable_domain_randomization,
                                                           done_condition=self._done_condition,
                                                           pause_physics=self._model_updater.pause_physics_service,
                                                           unpause_physics=self._model_updater.unpause_physics_service)
        checkpoint_dict = dict()
        checkpoint_dict[self._agent_name] = checkpoint
        ds_params_instance = S3BotoDataStoreParameters(checkpoint_dict=checkpoint_dict)

        self._current_graph_manager.data_store = S3BotoDataStore(params=ds_params_instance,
                                                                 graph_manager=self._current_graph_manager,
                                                                 ignore_lock=True,
                                                                 log_and_cont=True)
        self._current_graph_manager.env_params.seed = 0

        self._current_graph_manager.data_store.wait_for_checkpoints()
        self._current_graph_manager.data_store.modify_checkpoint_variables()

        task_parameters = TaskParameters()
        task_parameters.checkpoint_restore_path = self._local_model_directory

        self._current_graph_manager.create_graph(task_parameters=task_parameters,
                                                 stop_physics=self._model_updater.pause_physics_service,
                                                 start_physics=self._model_updater.unpause_physics_service,
                                                 empty_service_call=EmptyRequest)

    def _get_agent_list(self, model_metadata, version):
        """Setup agent and get the agents list.

        Args:
            model_metadata (ModelMetadata): Current racer's model metadata
            version (str): The current racer's simapp version in the model metadata

        Returns:
            agent_list (list): The list of agents for the current racer
        """
        # setup agent
        agent_config = {
            'model_metadata': model_metadata,
            ConfigParams.CAR_CTRL_CONFIG.value: {
                ConfigParams.LINK_NAME_LIST.value: [
                    link_name.replace('racecar', self._current_car_model_state.model_name) for link_name in LINK_NAMES],
                ConfigParams.VELOCITY_LIST.value: [
                    velocity_topic.replace('racecar', self._current_car_model_state.model_name) for velocity_topic in VELOCITY_TOPICS],
                ConfigParams.STEERING_LIST.value: [
                    steering_topic.replace('racecar', self._current_car_model_state.model_name) for steering_topic in STEERING_TOPICS],
                ConfigParams.CHANGE_START.value: utils.str2bool(rospy.get_param('CHANGE_START_POSITION', False)),
                ConfigParams.ALT_DIR.value: utils.str2bool(rospy.get_param('ALTERNATE_DRIVING_DIRECTION', False)),
                ConfigParams.MODEL_METADATA.value: model_metadata,
                ConfigParams.REWARD.value: reward_function,
                ConfigParams.AGENT_NAME.value: self._current_car_model_state.model_name,
                ConfigParams.VERSION.value: version,
                ConfigParams.NUMBER_OF_RESETS.value: self._number_of_resets,
                ConfigParams.PENALTY_SECONDS.value: self._penalty_seconds,
                ConfigParams.NUMBER_OF_TRIALS.value: self._number_of_trials,
                ConfigParams.IS_CONTINUOUS.value: self._is_continuous,
                ConfigParams.RACE_TYPE.value: self._race_type,
                ConfigParams.COLLISION_PENALTY.value: self._collision_penalty,
                ConfigParams.OFF_TRACK_PENALTY.value: self._off_track_penalty,
                ConfigParams.START_POSITION.value: get_start_positions(1)[0],  # hard-coded to the first start position
                ConfigParams.DONE_CONDITION.value: self._done_condition,
                ConfigParams.IS_VIRTUAL_EVENT.value: True,
                ConfigParams.RACE_DURATION.value: self._race_duration}}

        agent_list = list()
        agent_list.append(create_rollout_agent(agent_config,
                                               self._eval_metrics,
                                               self._run_phase_subject))
        agent_list.append(create_obstacles_agent())
        agent_list.append(create_bot_cars_agent())
        return agent_list

    def _setup_mp4_services(self):
        """
        Setting up the mp4 ros services if mp4s need to be saved.
        """
        mp4_sub = "/{}/save_mp4/subscribe_to_save_mp4".format(VIRTUAL_EVENT)
        mp4_unsub = "/{}/save_mp4/unsubscribe_from_save_mp4".format(VIRTUAL_EVENT)
        rospy.wait_for_service(mp4_sub)
        rospy.wait_for_service(mp4_unsub)
        self._subscribe_to_save_mp4 = ServiceProxyWrapper(mp4_sub, VirtualEventVideoEditSrv)
        self._unsubscribe_from_save_mp4 = ServiceProxyWrapper(mp4_unsub, Empty)

    def _get_car_model_state(self, sensors: list) -> ModelState:
        """Get the current car model state according to sensors configuration.

        Args:
            sensors (list): sensors in the model metadata

        Returns:
            ModelState: a model state object with the current racecar name
        """
        is_stereo = False
        is_lidar = False
        if Input.STEREO.value in sensors:
            is_stereo = True
            LOG.info("[virtual event manager] stereo camera present")
        if Input.LIDAR.value in sensors or Input.SECTOR_LIDAR.value in sensors:
            is_lidar = True
            LOG.info("[virtual event manager] lidar present")
        car_model_state = ModelState()
        if is_stereo:
            if is_lidar:
                car_model_state.model_name = SENSOR_MODEL_MAP['stereo_camera_lidar']
            else:
                car_model_state.model_name = SENSOR_MODEL_MAP['stereo_camera']
        else:
            if is_lidar:
                car_model_state.model_name = SENSOR_MODEL_MAP['single_camera_lidar']
            else:
                car_model_state.model_name = SENSOR_MODEL_MAP['single_camera']
        return car_model_state

    def _clean_local_directory(self):
        """Clean up the local directory after race ends.
        """
        LOG.info("[virtual event manager] cleaning up the local directory after race ends.")
        for root, _, files in os.walk(self._local_model_directory):
            for f in files:
                os.remove(os.path.join(root, f))

    def _park_at_pit_location(self, model_name):
        """Reset car to inital position.
        """
        # set the car at the pit parking position
        yaw = 0.0 if self._track_data.is_ccw else math.pi
        self._model_updater.set_model_position(model_name,
                                               self._park_positions[self._park_position_idx],
                                               yaw,
                                               is_blocking=True)
        LOG.info("[virtual event manager] parked car to pit position.")

    def upload_race_status(self, status_code, error_name=None, error_details=None):
        """Upload race status into s3.

        Args:
            status_code (str): Status code for race.
            error_name (str, optional): The name of the error if is 4xx or 5xx.
                                        Defaults to "".
            error_details (str, optional): The detail message of the error
                                           if is 4xx or 5xx.
                                           Defaults to "".
        """
        # persist s3 status file
        if error_name is not None and error_details is not None:
            status = {RaceStatusKeys.STATUS_CODE.value: status_code,
                      RaceStatusKeys.ERROR_NAME.value: error_name,
                      RaceStatusKeys.ERROR_DETAILS.value: error_details}
        else:
            status = {RaceStatusKeys.STATUS_CODE.value: status_code}
        status_json = json.dumps(status)
        s3_key = os.path.normpath(os.path.join(self._current_racer.outputStatus.s3KeyPrefix,
                                               S3_RACE_STATUS_FILE_NAME))
        race_status_kms = self._current_racer.outputStatus.s3KmsKeyArn if \
            hasattr(self._current_racer.outputStatus, 's3KmsKeyArn') else None
        self._s3_client.upload_fileobj(bucket=self._current_racer.outputStatus.s3BucketName,
                                       s3_key=s3_key,
                                       fileobj=io.BytesIO(status_json.encode()),
                                       s3_kms_extra_args=utils.get_s3_extra_args(race_status_kms))
        LOG.info("[virtual event manager] Successfully uploaded race status file to \
                 s3 bucket {} with s3 key {}.".format(self._current_racer.outputStatus.s3BucketName,
                                                      s3_key))