@@ -5,10 +5,10 @@
55{-# LANGUAGE GeneralizedNewtypeDeriving #-}
66{-# LANGUAGE MultiParamTypeClasses #-}
77-- {-# LANGUAGE NoMonomorphismRestriction #-}
8+{-# LANGUAGE NamedFieldPuns #-}
89{-# LANGUAGE OverloadedStrings #-}
910{-# LANGUAGE RecordWildCards #-}
1011{-# LANGUAGE UndecidableInstances #-}
11−{-# LANGUAGE NamedFieldPuns #-}
1212
1313-------------------------------------------------------------------------------
1414-- |
@@ -69,6 +69,9 @@ module Database.Bloodhound.Types
6969 , AllocationPolicy(..)
7070 , ReplicaBounds(..)
7171 , Bytes(..)
72+ , gigabytes
73+ , megabytes
74+ , kilobytes
7275 , FSType(..)
7376 , InitialShardCount(..)
7477 , NodeAttrFilter(..)
@@ -235,6 +238,21 @@ module Database.Bloodhound.Types
235238 , CollectionMode(..)
236239 , TermOrder(..)
237240 , TermInclusion(..)
241+ , SnapshotRepoSelection(..)
242+ , GenericSnapshotRepo(..)
243+ , SnapshotRepo(..)
244+ , SnapshotRepoConversionError(..)
245+ , SnapshotRepoType(..)
246+ , GenericSnapshotRepoSettings(..)
247+ , SnapshotRepoUpdateSettings(..)
248+ , defaultSnapshotRepoUpdateSettings
249+ , SnapshotRepoName(..)
250+ , SnapshotRepoPattern(..)
251+ , SnapshotVerification(..)
252+ , SnapshotNodeVerification(..)
253+ , FullNodeId(..)
254+ , NodeName(..)
255+ , FsSnapshotRepo(..)
238256
239257 , Aggregation(..)
240258 , Aggregations
@@ -275,7 +293,7 @@ module Database.Bloodhound.Types
275293 , EsPassword(..)
276294 ) where
277295
278−import Control.Applicative
296+import Control.Applicative as A
279297import Control.Monad.Catch
280298import Control.Monad.Except
281299import Control.Monad.Reader
@@ -283,7 +301,7 @@ import Control.Monad.State
283301import Control.Monad.Writer
284302import Data.Aeson
285303import Data.Aeson.Types (Pair, Parser, emptyObject,
286− parseMaybe)
304+ parseEither, parseMaybe)
287305import qualified Data.ByteString.Lazy.Char8 as L
288306import Data.Char
289307import Data.Hashable (Hashable)
@@ -332,7 +350,7 @@ mkBHEnv s m = BHEnv s m return
332350newtype BH m a = BH {
333351 unBH :: ReaderT BHEnv m a
334352 } deriving ( Functor
335− , Applicative
353+ , A.Applicative
336354 , Monad
337355 , MonadIO
338356 , MonadState s
@@ -483,8 +501,31 @@ data ReplicaBounds = ReplicasBounded Int Int
483501 | ReplicasUnbounded
484502 deriving (Eq, Read, Show, Generic, Typeable)
485503
504+-- | A measure of bytes used for various configurations. You may want
505+-- to use smart constructors like 'gigabytes' for larger values.
506+--
507+-- >>> gigabytes 9
508+-- Bytes 9000000000
509+--
510+-- >>> megabytes 9
511+-- Bytes 9000000
512+--
513+-- >>> kilobytes 9
514+-- Bytes 9000
486515newtype Bytes = Bytes Int deriving (Eq, Read, Show, Generic, Typeable, Ord, ToJSON, FromJSON)
487516
517+gigabytes :: Int -> Bytes
518+gigabytes n = megabytes (1000 * n)
519+
520+
521+megabytes :: Int -> Bytes
522+megabytes n = kilobytes (1000 * n)
523+
524+
525+kilobytes :: Int -> Bytes
526+kilobytes n = Bytes (1000 * n)
527+
528+
488529data FSType = FSSimple
489530 | FSBuffered deriving (Eq, Read, Show, Generic, Typeable, Ord)
490531
@@ -3625,3 +3666,149 @@ newtype EsUsername = EsUsername { esUsername :: Text } deriving (Read, Show, Eq)
36253666
36263667-- | Password type used for HTTP Basic authentication. See 'basicAuthHook'.
36273668newtype EsPassword = EsPassword { esPassword :: Text } deriving (Read, Show, Eq)
3669+
3670+
3671+data SnapshotRepoSelection = SnapshotRepoList (NonEmpty SnapshotRepoPattern)
3672+ | AllSnapshotRepos deriving (Eq, Generic, Show, Typeable)
3673+
3674+
3675+-- | Either specifies an exact repo name or one with globs in it,
3676+-- e.g. @RepoPattern "foo*"@ __NOTE__: Patterns are not supported on ES < 1.7
3677+data SnapshotRepoPattern = ExactRepo SnapshotRepoName
3678+ | RepoPattern Text
3679+ deriving (Eq, Generic, Show, Typeable)
3680+
3681+-- | The unique name of a snapshot repository.
3682+newtype SnapshotRepoName = SnapshotRepoName { snapshotRepoName :: Text }
3683+ deriving (Eq, Generic, Show, Typeable, ToJSON, FromJSON)
3684+
3685+
3686+-- | A generic representation of a snapshot repo. This is what gets
3687+-- sent to and parsed from the server. For repo types enabled by
3688+-- plugins that aren't exported by this library, consider making a
3689+-- custom type which implements 'SnapshotRepo'. If it is a common repo
3690+-- type, consider submitting a pull request to have it included in the
3691+-- library proper
3692+data GenericSnapshotRepo = GenericSnapshotRepo {
3693+ gSnapshotRepoName :: SnapshotRepoName
3694+ , gSnapshotRepoType :: SnapshotRepoType
3695+ , gSnapshotRepoSettings :: GenericSnapshotRepoSettings
3696+ } deriving (Eq, Generic, Show, Typeable)
3697+
3698+
3699+newtype SnapshotRepoType = SnapshotRepoType { snapshotRepoType :: Text }
3700+ deriving (Eq, Generic, Show, Typeable, ToJSON, FromJSON)
3701+
3702+
3703+-- | Opaque representation of snapshot repo settings. Instances of
3704+-- 'SnapshotRepo' will produce this.
3705+newtype GenericSnapshotRepoSettings = GenericSnapshotRepoSettings { gSnapshotRepoSettingsObject :: Object }
3706+ deriving (Eq, Generic, Show, Typeable, ToJSON, FromJSON)
3707+
3708+
3709+-- | The result of running 'verifySnapshotRepo'. --TODO: more detail once you know what a failure looks like
3710+newtype SnapshotVerification = SnapshotVerification { snapshotNodeVerifications :: [SnapshotNodeVerification] }
3711+ deriving (Eq, Generic, Show, Typeable)
3712+
3713+
3714+instance FromJSON SnapshotVerification where
3715+ parseJSON = withObject "SnapshotVerification" parse
3716+ where
3717+ parse o = do
3718+ o2 <- o .: "nodes"
3719+ SnapshotVerification <$> mapM (uncurry parse') (HM.toList o2)
3720+ parse' rawFullId = withObject "SnapshotNodeVerification" $ \o ->
3721+ SnapshotNodeVerification (FullNodeId rawFullId) <$> o .: "name"
3722+
3723+
3724+data SnapshotNodeVerification = SnapshotNodeVerification {
3725+ snvFullId :: FullNodeId
3726+ , snvNodeName :: NodeName
3727+ } deriving (Eq, Generic, Show, Typeable)
3728+
3729+
3730+-- | Unique, automatically-generated name assigned to nodes that are
3731+-- usually returned in node-oriented APIs.
3732+newtype FullNodeId = FullNodeId { fullNodeId :: Text }
3733+ deriving (Eq, Generic, Show, Typeable, FromJSON)
3734+
3735+
3736+newtype NodeName = NodeName { nodeName :: Text }
3737+ deriving (Eq, Generic, Show, Typeable, FromJSON)
3738+
3739+
3740+data SnapshotRepoUpdateSettings = SnapshotRepoUpdateSettings {
3741+ repoUpdateVerify :: Bool
3742+ -- ^ After creation/update, synchronously check that nodes can
3743+ -- write to this repo. Defaults to True. You may use False if you
3744+ -- need a faster response and plan on verifying manually later
3745+ -- with 'verifySnapshotRepo'.
3746+ } deriving (Eq, Show, Generic, Typeable)
3747+
3748+
3749+-- | Reasonable defaults for repo creation/update
3750+--
3751+-- * repoUpdateVerify True
3752+defaultSnapshotRepoUpdateSettings :: SnapshotRepoUpdateSettings
3753+defaultSnapshotRepoUpdateSettings = SnapshotRepoUpdateSettings True
3754+
3755+
3756+-- | A filesystem-based snapshot repo that ships with elasticsearch.
3757+data FsSnapshotRepo = FsSnapshotRepo {
3758+ fsrName :: SnapshotRepoName
3759+ , fsrLocation :: FilePath
3760+ , fsrCompressMetadata :: Bool
3761+ , fsrChunkSize :: Maybe Bytes
3762+ , fsrMaxRestoreBytesPerSec :: Maybe Bytes
3763+ , fsrMaxSnapshotBytesPerSec :: Maybe Bytes
3764+ } deriving (Eq, Generic, Show, Typeable)
3765+
3766+
3767+instance SnapshotRepo FsSnapshotRepo where
3768+ toGSnapshotRepo FsSnapshotRepo {..} =
3769+ GenericSnapshotRepo fsrName fsRepoType (GenericSnapshotRepoSettings settings)
3770+ where
3771+ Object settings = object $ [ "location" .= fsrLocation
3772+ , "compress" .= fsrCompressMetadata
3773+ ] ++ optionalPairs
3774+ optionalPairs = catMaybes [ ("chunk_size" .=) <$> fsrChunkSize
3775+ , ("max_restore_bytes_per_sec" .=) <$> fsrMaxRestoreBytesPerSec
3776+ , ("max_snapshot_bytes_per_sec" .=) <$> fsrMaxSnapshotBytesPerSec
3777+ ]
3778+ fromGSnapshotRepo GenericSnapshotRepo {..}
3779+ | gSnapshotRepoType == fsRepoType = do
3780+ let o = gSnapshotRepoSettingsObject gSnapshotRepoSettings
3781+ parseRepo $ do
3782+ FsSnapshotRepo gSnapshotRepoName <$> o .: "location"
3783+ <*> o .:? "compress" .!= False
3784+ <*> o .:? "chunk_size"
3785+ <*> o .:? "max_restore_bytes_per_sec"
3786+ <*> o .:? "max_snapshot_bytes_per_sec"
3787+ | otherwise = Left (RepoTypeMismatch fsRepoType gSnapshotRepoType)
3788+
3789+
3790+--TODO: test what error case looks like
3791+parseRepo :: Parser a -> Either SnapshotRepoConversionError a
3792+parseRepo parser = case parseEither (const parser) () of
3793+ Left e -> Left (OtherRepoConversionError (T.pack e))
3794+ Right a -> Right a
3795+
3796+
3797+fsRepoType :: SnapshotRepoType
3798+fsRepoType = SnapshotRepoType "fs"
3799+
3800+-- | Law: fromGSnapshotRepo (toGSnapshotRepo r) == Right r
3801+class SnapshotRepo r where
3802+ toGSnapshotRepo :: r -> GenericSnapshotRepo
3803+ fromGSnapshotRepo :: GenericSnapshotRepo -> Either SnapshotRepoConversionError r
3804+
3805+
3806+data SnapshotRepoConversionError = RepoTypeMismatch SnapshotRepoType
3807+ -- ^ Expected type
3808+ SnapshotRepoType
3809+ -- ^ Actual type
3810+ | OtherRepoConversionError Text
3811+ deriving (Show, Eq, Generic, Typeable)
3812+
3813+
3814+instance Exception SnapshotRepoConversionError