def Restore(self, request):
    """Restore this device .

    Args:
      request: an API request.

    Returns:
      an updated DeviceInfo
    Raises:
      endpoints.NotFoundException: If the given device does not exist.
    """
    device = device_manager.GetDevice(
        device_serial=request.device_serial, hostname=request.hostname)
    if not device:
      raise endpoints.NotFoundException("Device {0} {1} does not exist.".format(
          request.hostname, request.device_serial))
    device = device_manager.RestoreDevice(
        device_serial=device.device_serial, hostname=device.hostname)
    return datastore_entities.ToMessage(device)
  def ListHistories(self, request):
    """List histories of a device.

    Args:
      request: an API request.

    Returns:
      an api_messages.DeviceInfoHistoryCollection object.
    """
    device = device_manager.GetDevice(device_serial=request.device_serial)
    query = (
        datastore_entities.DeviceInfoHistory.query(ancestor=device.key).order(
            -datastore_entities.DeviceInfoHistory.timestamp))
    histories, prev_cursor, next_cursor = datastore_util.FetchPage(
        query, request.count, request.cursor, backwards=request.backwards)
    history_msgs = [
        datastore_entities.ToMessage(entity) for entity in histories
    ]
    return api_messages.DeviceInfoHistoryCollection(
        histories=history_msgs,
        next_cursor=next_cursor,
        prev_cursor=prev_cursor)
  def GetDevice(self, request):
    """Fetches the information and notes of a given device.

    Args:
      request: an API request.

    Returns:
      a DeviceInfo object.
    Raises:
      endpoints.NotFoundException: If the given device does not exist.
    """
    device_serial = request.device_serial
    device = device_manager.GetDevice(
        hostname=request.hostname, device_serial=device_serial)
    if not device:
      raise endpoints.NotFoundException(
          "Device {0} does not exist.".format(device_serial))

    device_info = datastore_entities.ToMessage(device)

    # TODO: deprecate "include_notes".
    if request.include_notes:
      device_notes = (
          datastore_entities.Note.query().filter(
              datastore_entities.Note.type == common.NoteType.DEVICE_NOTE)
          .filter(datastore_entities.Note.device_serial == device_serial).order(
              -datastore_entities.Note.timestamp))
      device_info.notes = [
          datastore_entities.ToMessage(note) for note in device_notes
      ]
    if request.include_history:
      histories = device_manager.GetDeviceStateHistory(device.hostname,
                                                       device_serial)
      device_info.history = [datastore_entities.ToMessage(h) for h in histories]
    if request.include_utilization:
      utilization = device_manager.CalculateDeviceUtilization(device_serial)
      device_info.utilization = utilization
    return device_info
  def BatchUpdateNotesWithPredefinedMessage(self, request):
    """Batch update notes with the same predefined message.

    Args:
      request: an API request.

    Returns:
      an api_messages.NoteCollection object.
    """
    time_now = datetime.datetime.utcnow()

    device_note_entities = []
    for note in request.notes:
      note_id = int(note.id) if note.id is not None else None
      device_note_entity = datastore_util.GetOrCreateEntity(
          datastore_entities.Note,
          entity_id=note_id,
          device_serial=note.device_serial,
          hostname=note.hostname,
          type=common.NoteType.DEVICE_NOTE)
      device_note_entity.populate(
          user=request.user,
          message=request.message,
          timestamp=time_now,
          event_time=request.event_time)
      device_note_entities.append(device_note_entity)

    try:
      offline_reason_entity = note_manager.PreparePredefinedMessageForNote(
          common.PredefinedMessageType.DEVICE_OFFLINE_REASON,
          message_id=request.offline_reason_id,
          lab_name=request.lab_name,
          content=request.offline_reason,
          delta_count=len(device_note_entities))
    except note_manager.InvalidParameterError as err:
      raise endpoints.BadRequestException("Invalid offline reason: [%s]" % err)
    if offline_reason_entity:
      for device_note_entity in device_note_entities:
        device_note_entity.offline_reason = offline_reason_entity.content
      offline_reason_entity.put()

    try:
      recovery_action_entity = note_manager.PreparePredefinedMessageForNote(
          common.PredefinedMessageType.DEVICE_RECOVERY_ACTION,
          message_id=request.recovery_action_id,
          lab_name=request.lab_name,
          content=request.recovery_action,
          delta_count=len(device_note_entities))
    except note_manager.InvalidParameterError as err:
      raise endpoints.BadRequestException("Invalid recovery action: [%s]" % err)
    if recovery_action_entity:
      for device_note_entity in device_note_entities:
        device_note_entity.recovery_action = recovery_action_entity.content
      recovery_action_entity.put()

    note_keys = ndb.put_multi(device_note_entities)
    device_note_entities = ndb.get_multi(note_keys)
    note_msgs = []
    for device_note_entity in device_note_entities:
      device_note_msg = datastore_entities.ToMessage(device_note_entity)
      note_msgs.append(device_note_msg)

      device = device_manager.GetDevice(
          device_serial=device_note_entity.device_serial)
      device_note_event_msg = api_messages.NoteEvent(
          note=device_note_msg,
          hostname=device.hostname,
          lab_name=device.lab_name,
          run_target=device.run_target)
      note_manager.PublishMessage(
          device_note_event_msg, common.PublishEventType.DEVICE_NOTE_EVENT)

    for request_note, updated_note_key in zip(request.notes, note_keys):
      if not request_note.id:
        # If ids are not provided, then a new note is created, we should create
        # a history snapshot.
        device_manager.CreateAndSaveDeviceInfoHistoryFromDeviceNote(
            request_note.device_serial, updated_note_key.id())

    return api_messages.NoteCollection(
        notes=note_msgs, more=False, next_cursor=None, prev_cursor=None)
  def AddOrUpdateNote(self, request):
    """Add or update a device note.

    Args:
      request: an API request.

    Returns:
      an api_messages.Note.
    """
    time_now = datetime.datetime.utcnow()

    device_note_entity = datastore_util.GetOrCreateEntity(
        datastore_entities.Note,
        entity_id=request.id,
        device_serial=request.device_serial,
        hostname=request.hostname,
        type=common.NoteType.DEVICE_NOTE)
    device_note_entity.populate(
        user=request.user,
        message=request.message,
        timestamp=time_now,
        event_time=request.event_time)
    entities_to_update = [device_note_entity]

    try:
      offline_reason_entity = note_manager.PreparePredefinedMessageForNote(
          common.PredefinedMessageType.DEVICE_OFFLINE_REASON,
          message_id=request.offline_reason_id,
          lab_name=request.lab_name,
          content=request.offline_reason)
    except note_manager.InvalidParameterError as err:
      raise endpoints.BadRequestException("Invalid offline reason: [%s]" % err)
    if offline_reason_entity:
      device_note_entity.offline_reason = offline_reason_entity.content
      entities_to_update.append(offline_reason_entity)

    try:
      recovery_action_entity = note_manager.PreparePredefinedMessageForNote(
          common.PredefinedMessageType.DEVICE_RECOVERY_ACTION,
          message_id=request.recovery_action_id,
          lab_name=request.lab_name,
          content=request.recovery_action)
    except note_manager.InvalidParameterError as err:
      raise endpoints.BadRequestException("Invalid recovery action: [%s]" % err)
    if recovery_action_entity:
      device_note_entity.recovery_action = recovery_action_entity.content
      entities_to_update.append(recovery_action_entity)

    keys = ndb.put_multi(entities_to_update)
    device_note_msg = datastore_entities.ToMessage(device_note_entity)

    device = device_manager.GetDevice(
        device_serial=device_note_entity.device_serial)
    device_note_event_msg = api_messages.NoteEvent(
        note=device_note_msg,
        hostname=device.hostname,
        lab_name=device.lab_name,
        run_target=device.run_target)
    note_manager.PublishMessage(device_note_event_msg,
                                common.PublishEventType.DEVICE_NOTE_EVENT)

    note_key = keys[0]
    if request.id != note_key.id():
      # If ids are different, then a new note is created, we should create
      # a history snapshot.
      device_manager.CreateAndSaveDeviceInfoHistoryFromDeviceNote(
          request.device_serial, note_key.id())

    return device_note_msg