{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module System.Mesos.Types (
  -- * Core Framework & Executor types
  -- ** Masters & Slaves
  MasterInfo(..),
  masterInfo,
  SlaveInfo(..),
  slaveInfo,
  -- ** Frameworks & Executors
  ExecutorInfo(..),
  executorInfo,
  FrameworkInfo(..),
  frameworkInfo,
  -- ** Resource allocation
  Offer(..),
  Request(..),
  Filters(..),
  filters,
  -- ** Launching Tasks
  TaskInfo(..),
  taskInfo,
  TaskExecutionInfo(..),
  CommandInfo(..),
  commandInfo,
  CommandURI(..),
  commandURI,
  CommandValue (..),
  Value(..),
  Resource(..),
  resource,
  -- ** Task & Executor Status Updates
  Status(..),
  TaskStatus(..),
  TaskState(..),
  isTerminal,
  -- ** Identifiers
  FrameworkID(..),
  SlaveID(..),
  OfferID(..),
  TaskID(..),
  ExecutorID(..),
  ContainerID(..),
  -- ** Containerization Support
  ContainerInfo (..),
  Volume (..),
  Mode (..),
  ContainerType (..),
  -- ** Health Checks
  HealthCheck (..),
  HealthCheckStrategy (..),
  -- ** Resource Usage & Performance Statistics
  ResourceStatistics(..),
  ResourceUsage(..),
  PerformanceStatistics(..),
  -- ** Task Status
  -- ** Credentials & ACLs
  Credential(..),
  credential
) where
import           Data.ByteString (ByteString)
import           Data.String
import           Data.Word

isTerminal :: TaskState -> Bool
isTerminal s = case s of
  Finished -> True
  Failed -> True
  Killed -> True
  Lost -> True
  _ -> False

-- | Describes possible task states. IMPORTANT: Mesos assumes tasks that
-- enter terminal states (see below) imply the task is no longer
-- running and thus clean up any thing associated with the task
-- (ultimately offering any resources being consumed by that task to
-- another task).
data TaskState
  = Staging -- ^ Initial state. Framework status updates should not use.
  | Starting
  | TaskRunning
  | Finished -- ^ TERMINAL.
  | Failed -- ^ TERMINAL.
  | Killed -- ^ TERMINAL.
  | Lost -- ^ TERMINAL.
  deriving (Show, Eq)

instance Enum TaskState where
  fromEnum Staging = 6
  fromEnum Starting = 0
  fromEnum TaskRunning = 1
  fromEnum Finished = 2
  fromEnum Failed = 3
  fromEnum Killed = 4
  fromEnum Lost = 5
  toEnum 0 = Starting
  toEnum 1 = TaskRunning
  toEnum 2 = Finished
  toEnum 3 = Failed
  toEnum 4 = Killed
  toEnum 5 = Lost
  toEnum 6 = Staging
  toEnum _ = error "Unsupported task state"

-- | Indicates the state of the scheduler and executor driver
-- after function calls.
data Status = NotStarted | Running | Aborted | Stopped
  deriving (Show, Eq)

instance Enum Status where
  fromEnum Running = 2
  fromEnum NotStarted = 1
  fromEnum Aborted = 3
  fromEnum Stopped = 4
  toEnum 1 = NotStarted
  toEnum 2 = Running
  toEnum 3 = Aborted
  toEnum 4 = Stopped
  toEnum _ = error "Unsupported status"

-- | A unique ID assigned to a framework. A framework can reuse this ID in order to do failover.
newtype FrameworkID = FrameworkID { fromFrameworkID :: ByteString }
  deriving (Show, Eq, IsString)

-- | A unique ID assigned to an offer.
newtype OfferID = OfferID { fromOfferID :: ByteString }
  deriving (Show, Eq, IsString)

-- |  A unique ID assigned to a slave. Currently, a slave gets a new ID whenever it (re)registers with Mesos. Framework writers shouldn't assume any binding between a slave ID and and a hostname.
newtype SlaveID = SlaveID { fromSlaveID :: ByteString }
  deriving (Show, Eq, IsString)

-- |  A framework generated ID to distinguish a task. The ID must remain
-- unique while the task is active. However, a framework can reuse an
-- ID _only_ if a previous task with the same ID has reached a
-- terminal state (e.g., 'Finished', 'Lost', 'Killed', etc.). See 'isTerminal' for a utility function to simplify checking task state.
newtype TaskID = TaskID { fromTaskID :: ByteString }
  deriving (Show, Eq, IsString)

-- |  A framework generated ID to distinguish an executor. Only one
-- executor with the same ID can be active on the same slave at a
-- time.
newtype ExecutorID = ExecutorID { fromExecutorID :: ByteString }
  deriving (Show, Eq, IsString)


-- |  A slave generated ID to distinguish a container. The ID must be unique
-- between any active or completed containers on the slave. In particular,
-- containers for different runs of the same (framework, executor) pair must be
-- unique.
newtype ContainerID = ContainerID { fromContainerID :: ByteString }
  deriving (Show, Eq, IsString)

-- | Describes a framework. If the user field is set to an empty string
-- Mesos will automagically set it to the current user. Note that the
-- ID is only available after a framework has registered, however, it
-- is included here in order to facilitate scheduler failover (i.e.,
-- if it is set then the 'System.Mesos.Scheduler.SchedulerDriver' expects the scheduler is
-- performing failover). The amount of time that the master will wait
-- for the scheduler to failover before removing the framework is
-- specified by 'frameworkFailoverTimeout'.
-- If 'frameworkCheckpoint' is set, framework pid, executor pids and status updates
-- are checkpointed to disk by the slaves.
-- Checkpointing allows a restarted slave to reconnect with old executors
-- and recover status updates, at the cost of disk I/O.
-- The 'frameworkRole' field is used to group frameworks for allocation decisions,
-- depending on the allocation policy being used.
-- If the 'frameworkHostname' field is set to an empty string Mesos will
-- automagically set it to the current hostname.
data FrameworkInfo = FrameworkInfo
  { frameworkUser            :: !ByteString
  , frameworkName            :: !ByteString
  , frameworkID              :: !(Maybe FrameworkID)
  , frameworkFailoverTimeout :: !(Maybe Double)
  , frameworkCheckpoint      :: !(Maybe Bool)
  , frameworkRole            :: !(Maybe ByteString)
  , frameworkHostname        :: !(Maybe ByteString)
  , frameworkPrincipal       :: !(Maybe ByteString)
  }
  deriving (Show, Eq)

frameworkInfo :: ByteString -> ByteString -> FrameworkInfo
frameworkInfo u n = FrameworkInfo u n Nothing Nothing Nothing Nothing Nothing Nothing

data HealthCheckStrategy
   = HTTPCheck
     { httpCheckPort     :: !Word32 -- ^ Port to send the HTTP request.
     , httpCheckPath     :: !(Maybe ByteString) -- ^ HTTP request path. (defaults to @"/"@.
     , httpCheckStatuses :: ![Word32] -- ^ Expected response statuses. Not specifying any statuses implies that any returned status is acceptable.
     }
   | CommandCheck
     { commandCheckCommand :: !CommandInfo -- ^ Command health check.
     } deriving (Show, Eq)

data HealthCheck = HealthCheck
  { healthCheckStrategy            :: !HealthCheckStrategy
  , healthCheckDelaySeconds        :: !(Maybe Double) -- ^ Amount of time to wait until starting the health checks.
  , healthCheckIntervalSeconds     :: !(Maybe Double) -- ^ Interval between health checks.
  , healthCheckTimeoutSeconds      :: !(Maybe Double) -- ^ Amount of time to wait for the health check to complete.
  , healthCheckConsecutiveFailures :: !(Maybe Word32) -- ^ Number of consecutive failures until considered unhealthy.
  , healthCheckGracePeriodSeconds  :: !(Maybe Double) -- ^ Amount of time to allow failed health checks since launch.
  } deriving (Show, Eq)

-- | Describes a command, executed via:
--
-- > /bin/sh -c value
--
-- Any URIs specified are fetched before executing the command.  If the executable field for an
-- uri is set, executable file permission is set on the downloaded file.
-- Otherwise, if the downloaded file has a recognized archive extension
-- (currently [compressed] tar and zip) it is extracted into the executor's
-- working directory.  In addition, any environment variables are set before
-- executing the command (so they can be used to "parameterize" your command).
data CommandInfo = CommandInfo
  { commandInfoURIs    :: ![CommandURI]
  , commandEnvironment :: !(Maybe [(ByteString, ByteString)])
  , commandValue       :: !CommandValue
  -- TODO: Existing field in 0.20, but doesn't actually work
  -- , commandContainer   :: !(Maybe ContainerInfo)

  -- | Enables executor and tasks to run as a specific user. If the user
  -- field is present both in 'FrameworkInfo' and here, the 'CommandInfo'
  -- user value takes precedence.
  , commandUser        :: !(Maybe ByteString)
  }
  deriving (Show, Eq)

commandInfo :: CommandValue -> CommandInfo
commandInfo v = CommandInfo [] Nothing v Nothing

data CommandURI = CommandURI
  { commandURIValue      :: !ByteString
  , commandURIExecutable :: !(Maybe Bool)
  , commandURIExtract    :: !(Maybe Bool)
  }
  deriving (Show, Eq)

commandURI :: ByteString -> CommandURI
commandURI v = CommandURI v Nothing Nothing

{-
TODO: not yet supported (0.20)
data CommandContainerInfo = CommandContainerInfo
  { commandContainerInfoImage   :: !ByteString
  , commandContainerInfoOptions :: ![ByteString]
  }
-}

data CommandValue = ShellCommand !ByteString
                  | RawCommand !ByteString ![ByteString]
                  deriving (Show, Eq)

-- Describes information about an executor. The 'executorData' field can be
-- used to pass arbitrary bytes to an executor.
data ExecutorInfo = ExecutorInfo
  { executorInfoExecutorID    :: !ExecutorID
  , executorInfoFrameworkID   :: !FrameworkID
  , executorInfoCommandInfo   :: !CommandInfo
  , executorInfoContainerInfo :: !(Maybe ContainerInfo)
  -- ^ Executor provided with a container will launch the container
  -- with the executor's 'CommandInfo' and we expect the container to
  -- act as a Mesos executor.
  , executorInfoResources     :: ![Resource]
  , executorName              :: !(Maybe ByteString)
  , executorSource            :: !(Maybe ByteString)
  -- ^ Source is an identifier style string used by frameworks to track
  -- the source of an executor. This is useful when it's possible for
  -- different executor ids to be related semantically.
  --
  -- NOTE: Source is exposed alongside the resource usage of the
  -- executor via JSON on the slave. This allows users to import
  -- usage information into a time series database for monitoring.
  , executorData              :: !(Maybe ByteString)
  }
  deriving (Show, Eq)

executorInfo :: ExecutorID -> FrameworkID -> CommandInfo -> [Resource] -> ExecutorInfo
executorInfo eid fid ci rs = ExecutorInfo eid fid ci Nothing rs Nothing Nothing Nothing

-- | Describes a master. This will probably have more fields in the
-- future which might be used, for example, to link a framework web UI
-- to a master web UI.
data MasterInfo = MasterInfo
  { masterInfoID       :: !ByteString
  , masterInfoIP       :: !Word32
  , masterInfoPort     :: !(Maybe Word32) -- ^ Defaults to 5050
  , masterInfoPID      :: !(Maybe ByteString)
  , masterInfoHostname :: !(Maybe ByteString)
  }
  deriving (Show, Eq)

masterInfo :: ByteString -> Word32 -> MasterInfo
masterInfo id ip = MasterInfo id ip Nothing Nothing Nothing

-- | Describes a slave. Note that the 'slaveInfoSlaveID' field is only available after
-- a slave is registered with the master, and is made available here
-- to facilitate re-registration.  If checkpoint is set, the slave is
-- checkpointing its own information and potentially frameworks'
-- information (if a framework has checkpointing enabled).
data SlaveInfo = SlaveInfo
  { slaveInfoHostname   :: !ByteString
  , slaveInfoPort       :: !(Maybe Word32)
  , slaveInfoResources  :: ![Resource]
  , slaveInfoAttributes :: ![(ByteString, Value)]
  , slaveInfoSlaveID    :: !(Maybe SlaveID)
  , slaveInfoCheckpoint :: !(Maybe Bool)
  }
  deriving (Show, Eq)

slaveInfo :: ByteString -> [Resource] -> [(ByteString, Value)] -> SlaveInfo
slaveInfo hn rs as = SlaveInfo hn Nothing rs as Nothing Nothing

newtype Filters = Filters
  { refuseSeconds :: Maybe Double
  -- ^ Time to consider unused resources refused. Note that all unused
  -- resources will be considered refused and use the default value
  -- (below) regardless of whether Filters was passed to
  -- SchedulerDriver::launchTasks. You MUST pass Filters with this
  -- field set to change this behavior (i.e., get another offer which
  -- includes unused resources sooner or later than the default).
  --
  -- Defaults to 5.0 if not set.
  }
  deriving (Show, Eq)

filters :: Filters
filters = Filters Nothing

credential :: ByteString -> Credential
credential p = Credential p Nothing

data Value
  = Scalar !Double
  | Ranges ![(Word64, Word64)]
  | Set ![ByteString]
  | Text !ByteString
  deriving (Show, Eq)

-- | Describes a resource on a machine. A resource can take on one of
-- three types: scalar (double), a list of finite and discrete ranges
-- (e.g., [1-10, 20-30]), or a set of items.
--
-- N.B. there is a slight deviation from the C++ API: the Haskell bindings convert 'Text' values
-- into a single element 'Set' value in order to avoid having to expose yet another data type.
data Resource = Resource
  { resourceName  :: !ByteString
  , resourceValue :: !Value
  , resourceRole  :: !(Maybe ByteString)
  }
  deriving (Show, Eq)

resource :: ByteString -> Value -> Resource
resource n v = Resource n v Nothing

data ResourceStatistics = ResourceStatistics
  { resourceStatisticsTimestamp          :: !Double
  , resourceStatisticsCPUsUserTimeSecs   :: !(Maybe Double)
  -- ^ Total CPU time spent in user mode
  , resourceStatisticsCPUsSystemTimeSecs :: !(Maybe Double)
  -- ^ Total CPU time spent in kernel mode.
  , resourceCPUsLimit                    :: !Double
  -- ^ Number of CPUs allocated.
  , resourceCPUsPeriods                  :: !(Maybe Word32)
  -- ^ cpu.stat on process throttling (for contention issues).
  , resourceCPUsThrottled                :: !(Maybe Word32)
  -- ^ cpu.stat on process throttling (for contention issues).
  , resourceCPUsThrottledTimeSecs        :: !(Maybe Double)
  -- ^ cpu.stat on process throttling (for contention issues).
  , resourceMemoryResidentSetSize        :: !(Maybe Word64)
  -- ^ Resident set size
  , resourceMemoryLimitBytes             :: !(Maybe Word64)
  -- ^ Amount of memory resources allocated.
  , resourceMemoryFileBytes              :: !(Maybe Word64)
  , resourceMemoryAnonymousBytes         :: !(Maybe Word64)
  , resourceMemoryMappedFileBytes        :: !(Maybe Word64)
  , resourcePerformanceStatistics        :: !(Maybe PerformanceStatistics)
  , resourceNetRxPackets                 :: !(Maybe Word64)
  , resourceNetRxBytes                   :: !(Maybe Word64)
  , resourceNetRxErrors                  :: !(Maybe Word64)
  , resourceNetRxDropped                 :: !(Maybe Word64)
  , resourceNetTxPackets                 :: !(Maybe Word64)
  , resourceNetTxBytes                   :: !(Maybe Word64)
  , resourceNetTxErrors                  :: !(Maybe Word64)
  , resourceNetTxDropped                 :: !(Maybe Word64)
  }
  deriving (Show, Eq)

-- | Describes a snapshot of the resource usage for an executor.
--
-- Resource usage is for an executor. For tasks launched with
-- an explicit executor, the executor id is provided. For tasks
-- launched without an executor, our internal executor will be
-- used. In this case, we provide the task id here instead, in
-- order to make this message easier for schedulers to work with.
data ResourceUsage = ResourceUsage
  { resourceUsageSlaveID      :: !SlaveID
  , resourceUsageFrameworkID  :: !FrameworkID
  , resourceUsageExecutorID   :: !(Maybe ExecutorID) -- ^ If present, this executor was explicitly specified.
  , resourceUsageExecutorName :: !(Maybe ByteString) -- ^ If present, this executor was explicitly specified.
  , resourceUsageTaskID       :: !(Maybe TaskID) -- ^ If present, this task did not have an executor.
  , resourceUsageStatistics   :: !(Maybe ResourceStatistics)
  -- ^  If missing, the isolation module cannot provide resource usage.
  }
  deriving (Show, Eq)

data PerformanceStatistics = PerformanceStatistics
  { performanceStatisticsTimestamp              :: !Double -- ^ Start of sample interval, in seconds since the Epoch.
  , performanceStatisticsDuration               :: !Double -- ^ Duration of sample interval, in seconds.

  -- | Hardware events
  , performanceStatisticsCycles                 :: !(Maybe Word64)
  , performanceStatisticsStalledCyclesFrontend  :: !(Maybe Word64)
  , performanceStatisticsStalledCyclesBackend   :: !(Maybe Word64)
  , performanceStatisticsInstructions           :: !(Maybe Word64)
  , performanceStatisticsCacheReferences        :: !(Maybe Word64)
  , performanceStatisticsCacheMisses            :: !(Maybe Word64)
  , performanceStatisticsBranches               :: !(Maybe Word64)
  , performanceStatisticsBranchMisses           :: !(Maybe Word64)
  , performanceStatisticsBusCycles              :: !(Maybe Word64)
  , performanceStatisticsRefCycles              :: !(Maybe Word64)

  -- | Software events
  , performanceStatisticsCpuClock               :: !(Maybe Double)
  , performanceStatisticsTaskClock              :: !(Maybe Double)
  , performanceStatisticsPageFaults             :: !(Maybe Word64)
  , performanceStatisticsMinorFaults            :: !(Maybe Word64)
  , performanceStatisticsMajorFaults            :: !(Maybe Word64)
  , performanceStatisticsContextSwitches        :: !(Maybe Word64)
  , performanceStatisticsCpuMigrations          :: !(Maybe Word64)
  , performanceStatisticsAlignmentFaults        :: !(Maybe Word64)
  , performanceStatisticsEmulationFaults        :: !(Maybe Word64)

  -- | Hardware cache events
  , performanceStatisticsL1DcacheLoads          :: !(Maybe Word64)
  , performanceStatisticsL1DcacheLoadMisses     :: !(Maybe Word64)
  , performanceStatisticsL1DcacheStores         :: !(Maybe Word64)
  , performanceStatisticsL1DcacheStoreMisses    :: !(Maybe Word64)
  , performanceStatisticsL1DcachePrefetches     :: !(Maybe Word64)
  , performanceStatisticsL1DcachePrefetchMisses :: !(Maybe Word64)
  , performanceStatisticsL1IcacheLoads          :: !(Maybe Word64)
  , performanceStatisticsL1IcacheLoadMisses     :: !(Maybe Word64)
  , performanceStatisticsL1IcachePrefetches     :: !(Maybe Word64)
  , performanceStatisticsL1IcachePrefetchMisses :: !(Maybe Word64)
  , performanceStatisticsLlcLoads               :: !(Maybe Word64)
  , performanceStatisticsLlcLoadMisses          :: !(Maybe Word64)
  , performanceStatisticsLlcStores              :: !(Maybe Word64)
  , performanceStatisticsLlcStoreMisses         :: !(Maybe Word64)
  , performanceStatisticsLlcPrefetches          :: !(Maybe Word64)
  , performanceStatisticsLlcPrefetchMisses      :: !(Maybe Word64)
  , performanceStatisticsDtlbLoads              :: !(Maybe Word64)
  , performanceStatisticsDtlbLoadMisses         :: !(Maybe Word64)
  , performanceStatisticsDtlbStores             :: !(Maybe Word64)
  , performanceStatisticsDtlbStoreMisses        :: !(Maybe Word64)
  , performanceStatisticsDtlbPrefetches         :: !(Maybe Word64)
  , performanceStatisticsDtlbPrefetchMisses     :: !(Maybe Word64)
  , performanceStatisticsItlbLoads              :: !(Maybe Word64)
  , performanceStatisticsItlbLoadMisses         :: !(Maybe Word64)
  , performanceStatisticsBranchLoads            :: !(Maybe Word64)
  , performanceStatisticsBranchLoadMisses       :: !(Maybe Word64)
  , performanceStatisticsNodeLoads              :: !(Maybe Word64)
  , performanceStatisticsNodeLoadMisses         :: !(Maybe Word64)
  , performanceStatisticsNodeStores             :: !(Maybe Word64)
  , performanceStatisticsNodeStoreMisses        :: !(Maybe Word64)
  , performanceStatisticsNodePrefetches         :: !(Maybe Word64)
  , performanceStatisticsNodePrefetchMisses     :: !(Maybe Word64)
  } deriving (Show, Eq)

-- | Describes a request for resources that can be used by a framework
-- to proactively influence the allocator.
data Request = Request
  { requestSlaveID :: !(Maybe SlaveID) -- ^ If value is provided, then this request is assumed to only apply to resources on the given slave.
  , reqResources   :: ![Resource]
  }
  deriving (Show, Eq)

-- | Describes some resources available on a slave. An offer only
-- contains resources from a single slave.
data Offer = Offer
  { offerID          :: !OfferID
  , offerFrameworkID :: !FrameworkID
  , offerSlaveID     :: !SlaveID
  , offerHostname    :: !ByteString
  , offerResources   :: ![Resource]
  , offerAttributes  :: ![(ByteString, Value)]
  , offerExecutorIDs :: ![ExecutorID]
  }
  deriving (Show, Eq)

data TaskExecutionInfo
  = TaskCommand !CommandInfo
  | TaskExecutor !ExecutorInfo
  deriving (Eq, Show)

-- | Describes a task. Passed from the scheduler all the way to an
-- executor (see SchedulerDriver::launchTasks and
-- Executor::launchTask).
--
-- A different executor can be used to launch this task, and subsequent tasks
-- meant for the same executor can reuse the same ExecutorInfo struct.
data TaskInfo = TaskInfo
  { taskInfoName       :: !ByteString
  , taskID             :: !TaskID
  , taskSlaveID        :: !SlaveID
  , taskResources      :: ![Resource]
  , taskImplementation :: !TaskExecutionInfo
  , taskData           :: !(Maybe ByteString)
  -- | Task provided with a container will launch the container as part
  -- of this task paired with the task's CommandInfo.
  , taskContainer      :: !(Maybe ContainerInfo)
  -- | A health check for the task (currently in *alpha* and initial
  -- support will only be for TaskInfo's that have a CommandInfo).
  , taskHealthCheck    :: !(Maybe HealthCheck)
  }
  deriving (Show, Eq)

taskInfo :: ByteString -> TaskID -> SlaveID -> [Resource] -> TaskExecutionInfo -> TaskInfo
taskInfo n t s rs i = TaskInfo n t s rs i Nothing Nothing Nothing

-- | Describes the current status of a task.
data TaskStatus = TaskStatus
  { taskStatusTaskID     :: !TaskID
  , taskStatusState      :: !TaskState
  , taskStatusMessage    :: !(Maybe ByteString) -- ^ Possible message explaining state.
  , taskStatusData       :: !(Maybe ByteString)
  , taskStatusSlaveID    :: !(Maybe SlaveID)
  , taskStatusExecutorID :: !(Maybe ExecutorID)
  , taskStatusTimestamp  :: !(Maybe Double)
  -- | Describes whether the task has been determined to be healthy
  -- (true) or unhealthy (false) according to the HealthCheck field in
  -- the command info.
  , taskStatusHealthy    :: !(Maybe Bool)
  }
  deriving (Show, Eq)

-- | Credential used for authentication.
--
-- NOTE: 'credentialPrincipal' is used for authenticating the framework with
-- the master. This is different from 'frameworkUser'
-- which is used to determine the user under which the framework's
-- executors/tasks are run.
data Credential = Credential
  { credentialPrincipal :: !ByteString
  , credentialSecret    :: !(Maybe ByteString)
  }
  deriving (Show, Eq)


-- | Entity is used to describe a subject(s) or an object(s) of an ACL.
-- NOTE:
-- To allow everyone access to an Entity set its type to 'ANY'.
-- To deny access to an Entity set its type to 'NONE'.
data ACLEntity = Some ![ByteString]
               | Any
               | None
               deriving (Show, Eq)
data RegisterFrameworkACL = RegisterFrameworkACL { registerFrameworkPrincipals :: ACLEntity, registerFrameworkRoles :: ACLEntity } deriving (Show, Eq)
data RunTaskACL = RunTaskACL { runTaskPrincipals :: ACLEntity, runTaskUsers :: ACLEntity } deriving (Show, Eq)
data ShutdownFrameworkACL = ShutdownFrameworkACL { shutdownFrameworkPrincipals :: ACLEntity, shutdownFrameworkFrameworkPrincipals :: ACLEntity } deriving (Show, Eq)

data ACLSettings = ACLSettings
  { aclSettingsPermissive         :: !(Maybe Bool)
  , aclSettingsRegisterFrameworks :: ![RegisterFrameworkACL]
  , aclSettingsRunTasks           :: ![RunTaskACL]
  , aclSettingsShutdownFramework  :: ![ShutdownFrameworkACL]
  } deriving (Show, Eq)

data RateLimit = RateLimit
  { rateLimitQPS       :: !(Maybe Double)
  , rateLimitPrincipal :: !ByteString
  , rateLimitCapacity  :: !(Maybe Word64)
  } deriving (Show, Eq)

data RateLimits = RateLimits
  { rateLimits                         :: ![RateLimit]
  , rateLimitsAggregateDefaultQPS      :: !(Maybe Double)
  , rateLimitsAggregateDefaultCapacity :: !(Maybe Double)
  } deriving (Show, Eq)

data Mode = ReadWrite -- ^ Mount the volume in R/W mode
          | ReadOnly  -- ^ Mount the volume as read-only
  deriving (Show, Eq)

instance Enum Mode where
  fromEnum ReadWrite = 1
  fromEnum ReadOnly = 2
  toEnum 1 = ReadWrite
  toEnum 2 = ReadOnly
  toEnum _ = error "Unsupported volume mode"

data Volume = Volume
  { volumeContainerPath :: !ByteString
  , volumeHostPath      :: !(Maybe ByteString)
  , volumeMode          :: !Mode
  } deriving (Show, Eq)

data ContainerType = Docker { dockerImage :: ByteString }
                   | Unknown Int -- ^ Not technically a container type. Represents the 'type' enum field if we get a container type that isn't Docker (e.g. from Mesos releases > 0.20)
  deriving (Show, Eq)

data ContainerInfo = ContainerInfo
  { containerInfoContainerType :: !ContainerType
  , containerInfoVolumes       :: ![Volume]
  } deriving (Show, Eq)