Reservation

Introduction

After the director set up a job on the SD and after running any run before scripts, it will then issue a “use” command to tell the SD what storage device it wants to use. The SD then checks the list of devices the director sent and tries to reserve a suitable device. In the end the SD either tells the director which device to use or that there is no device available for the job.

There is a so-called reservation context that is passed to every function that has a swimlane in this diagram. This context is basically a bunch of flags that are set and cleared to change the behaviour of the rest of the code.

For example CanReserveDevice() from the second diagram sets the flag low_use_drive which is then evaluated in UseDeviceCmd().

Diagram Conventions

Each swimlane denotes a separate function.

.. uml::
  :caption: Diagram legend

  @startuml
  #aqua:reservation context is read or set;
  detach
  #limegreen:Decision based on configuration directive;
  detach
  #tomato:Control flow broken;
  detach
  (M)
  note right
    goto or jump-mark
  end note
  detach
  :Read read data from the network<
  detach
  :Send data to the network>
  detach
  :Call another function|
  detach
  :Remote procedure call/

  @enduml

UseDeviceCmd

This function reads the list of storages and devices the director is willing to use for this job. Afterwards several different methods of finding a device to reseve are used. If no device could be reserved the function waits for up to a minute or until ReleaseDeviceCond() is called and then tries again.

.. uml::
  :caption: Control flow of UseDeviceCmd()

  @startuml
  'BEGIN macros
  !definelong RESERVE_DEVICE()
  :ReserveDevice()|
  !enddefinelong

  !definelong AUTOCHANGER()
  partition autochanger {
    while (with each device in autochanger device)
      #limegreen:if (device autoselect?) then (yes)
        RESERVE_DEVICE()
        if (have_device?) then (yes)
          #tomato:return;
          detach
        endif
      endif
    endwhile
  }
  !enddefinelong

  !definelong PLAIN_DEVICE()
  partition plain_device {
  RESERVE_DEVICE()
  note right
    try to reserve the named device
  end note
  if (have_device?) then (yes)
    #tomato:return;
    detach
  endif
  #limegreen:if (DeviceReserveByMediatype) then(yes)
    while (each device with matching media_type)
      RESERVE_DEVICE()
      if (have_device?) then (yes)
        #tomato:return;
        detach
      endif
    endwhile
  endif
  }
  !enddefinelong

  !definelong MOUNTED_VOLUMES()
  partition mounted_volumes {
  if (append?) then(yes)
    while(with each mounted volume)
      :ask director if volume is ok/
      if (volume ok?) then(yes)
        :find device for volume;
        RESERVE_DEVICE()
        if (have_device?) then (yes)
          #tomato:return;
          detach
        endif
      endif
    endwhile
  endif
  }
  !enddefinelong
  'END macros
  |UseDeviceCmd|
  start
  partition read_data_from_director {
    repeat
      :storage specification<
      repeat
        :device specification<
      repeat while (more devices?)
    repeat while (more storages?)
  }

    repeat
    #aqua:clear suitable_device]
    #aqua:clear have_volume]
    #aqua:clear VolumeName]
    #aqua:clear any_drive]
    #limegreen:if (PreferMountedVols) then (no)
        #aqua:clear low_use_drive]
        #aqua:clear PreferMountedVols]
        #aqua:clear exact_match]
        #aqua:set autochanger_only]
        |FindSuitableDeviceForJob|
        while (with every device)
          |SearchResForDevice|
          AUTOCHANGER()
          |FindSuitableDeviceForJob|
        endwhile
        |UseDeviceCmd|
        if (have_device) then (yes)
          stop
        else (no)
          #aqua:if (low_use_drive set?) then (yes)
            #aqua:set try_low_use_drive]
            |FindSuitableDeviceForJob|
            while (with every device)
              |SearchResForDevice|
              AUTOCHANGER()
              |FindSuitableDeviceForJob|
            endwhile
            |UseDeviceCmd|
            if (have_device) then (yes)
              stop
            endif
            #aqua:clear try_low_use_drive]
          endif
          #aqua:clear autochanger_only]
          |FindSuitableDeviceForJob|
          while (with every device)
            |SearchResForDevice|
            AUTOCHANGER()
            PLAIN_DEVICE()
            |FindSuitableDeviceForJob|
          endwhile
          |UseDeviceCmd|
          if (have_device) then (yes)
            stop
          endif
        endif
      else (yes)
      endif
      #aqua:set PreferMountedVols]
      #aqua:set exact_match]
      #aqua:clear autochanger_only]
      |FindSuitableDeviceForJob|
      MOUNTED_VOLUMES()
          while (with every device)
            |SearchResForDevice|
            AUTOCHANGER()
            PLAIN_DEVICE()
            |FindSuitableDeviceForJob|
          endwhile
      |UseDeviceCmd|
      if (have_device) then (yes)
        stop
      else (no)
        #aqua:clear exact_match]
        |FindSuitableDeviceForJob|
      MOUNTED_VOLUMES()
          while (with every device)
            |SearchResForDevice|
            AUTOCHANGER()
            PLAIN_DEVICE()
            |FindSuitableDeviceForJob|
          endwhile
        |UseDeviceCmd|
        if (have_device) then (yes)
          stop
        else (no)
          #aqua:set any_drive]
          |FindSuitableDeviceForJob|
      MOUNTED_VOLUMES()
          while (with every device)
            |SearchResForDevice|
            AUTOCHANGER()
            PLAIN_DEVICE()
            |FindSuitableDeviceForJob|
          endwhile
          |UseDeviceCmd|
          if (have_device) then (yes)
            stop
          else (no)
            if (attempt 3+?) then (yes)
              :wait 30 seconds;
            else (no)
              #aqua:if (suitable_device set?) then (yes)
                :WaitForDevice()|
                note right
                  This will acquire a mutex to queue up
                  multiple jobs waiting for a device.
                  Then it waits up to 60 seconds for some
                  other thread to call ReleaseDeviceCond()
                end note
              else (no)
                (F)
                detach
              endif
            endif
          endif
        endif
      endif
    repeat while (repeat forever)
    detach
  partition failed_to_reserve {
    (F)
    :no device message>
    stop
  }
  @enduml

ReserveDevice

Here we see wether the media type matches and actually try to open the device. The actual reservation is delegated to ReserveDeviceForRead() or ReserveDeviceForAppend(). While the first one is more or less trivial, the latter one is really complicated.

.. uml::
  :caption: Control flow of ReserveDevice()

  @startuml
  |ReserveDevice|
  start
  #limegreen:if (device matches requested media_type?) then (yes)
    if (device initialized?) then (no)
      :InitDev()|
    endif
    if (device exists and is open?) then (yes)
      #aqua:set suitable_device]
      :initialize DCR;
      if (append?) then (no)
        :ReserveDeviceForRead()|
      else (yes)
        |ReserveDeviceForAppend|
        if (CanRead()) then (no)
          if (IsDeviceUnmounted()) then (no)
            |CanReserveDrive|
            if (IsMaxJobsOk()) then (yes)
              #aqua:if (any_drive set?) then (no)
                #aqua:if (try_low_use_drive set?) then (yes)
                  #aqua:if (low_use_drive is current drive?) then (yes)
                    (S)
                    detach
                  endif
                endif
                #aqua:if (PreferMountedVols set?) then (no)
                  if (isBusy()) then (yes)
                    #aqua:if (dev.num_writers + NumReserved() < num_writers) then (yes)
                      #aqua:set num_writers := dev.numwriters + NumReserved()]
                      #aqua:set low_use_drive := dev]
                    endif
                    (X)
                    detach
                  endif
                else (yes)
                  if (IsTape() but no volume) then (yes)
                    (X)
                    detach
                  endif
                endif
                #aqua:if(exact_match set? and have_volume set?) then (yes)
                  #aqua:if(VolumeName matches mounted volume) then (no)
                    (X)
                    detach
                  endif
                  if (Can_i_use_volume()) then (no)
                    (X)
                    detach
                  endif
                endif
                :line 1080;

                :line 1152;
              endif
              #aqua:if (autochanger_only) then (yes)
                if (IsBusy()) then (no)
                  if (volume in drive) then (no)
                    (S)
                    detach
                  endif
                endif
              endif
              if (num_writers == 0) then (yes)
                if (NumReserved()) then (yes)
                  if (IsPoolOk()) then (yes)
                    (S)
                    detach
                  else (no)
                    (X)
                    detach
                  endif
                elseif (CanAppend()) then (yes)
                  if (IsPoolOk()) then (no)
                    :UnloadAutoChanger()|
                  endif
                  (S)
                  detach
                endif
              endif
              if (num_writers > 0 || CanAppend()) then (yes)
                if (IsPoolOk()) then (yes)
                  (S)
                  #tomato:return success;
                else (no)
                  (X)
                  detach
                endif
              else (no)
                #tomato:cancel job (M_FATAL);
                detach
              endif
            else (no)
              (X)
              #tomato:return failure;
            endif
            |ReserveDeviceForAppend|
            if (success?) then (yes)
              :reserve device;
              :set have_device]
            endif
          endif
        endif
        |ReserveDevice|
        if (have_device?) then (yes)
          #aqua:if (have_volume set?) then (yes)
            if (reserve_volume()) then (no)
              (F)
              detach
            endif
          else (no)
            if (DirFindNextAppendableVolume()) then (yes)
              #aqua:set have_volume]
              #aqua:set VolumeName]
            else (no)
              #aqua:clear have_volume]
              #aqua:clear VolumeName]
              if (FoundInUse()) then (yes)
                #aqua:if(PreferMountedVols set?) then (no)
                  #aqua:set PreferMountedVols]
                  if (dcr has volume?) then (yes)
                    :UnreserveDevice()|
                  endif
                  (F)
                  detach
                endif
              endif
              if (num_writers != 0) then (yes)
                  if (dcr has volume?) then (yes)
                    :UnreserveDevice()|
                  endif
                  (F)
                  detach
              endif
            endif
          endif
        else (no)
          (F)
          detach
        endif
      endif
  if (have_device?) then (yes)
    :OK device message>
  else (no)
    (F)
    #aqua:clear have_volume]
    #aqua:clear VolumeName]
  endif
    endif
  endif

  @enduml