Merge branch 'p2phttp-multi'

This commit is contained in:
Joey Hess 2024-11-21 15:16:06 -04:00
commit 757f93203a
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
6 changed files with 276 additions and 117 deletions

View file

@ -23,6 +23,8 @@ git-annex (10.20241032) UNRELEASED; urgency=medium
unnecessary duplicate password prompts.
* git-remote-annex: Require git version 2.31 or newer, since old
ones had a buggy git bundle command.
* p2phttp: Added --directory option which serves multiple git-annex
repositories located inside a directory.
-- Joey Hess <id@joeyh.name> Mon, 11 Nov 2024 12:26:00 -0400

View file

@ -511,15 +511,19 @@ jsonProgressOption =
-- action in `allowConcurrentOutput`.
jobsOption :: [AnnexOption]
jobsOption =
[ annexOption (setAnnexState . setConcurrency . ConcurrencyCmdLine) $
option (maybeReader parseConcurrency)
( long "jobs" <> short 'J'
<> metavar (paramNumber `paramOr` "cpus")
<> help "enable concurrent jobs"
<> hidden
)
[ annexOption (setAnnexState . setConcurrency . ConcurrencyCmdLine)
jobsOptionParser
]
jobsOptionParser :: Parser Concurrency
jobsOptionParser =
option (maybeReader parseConcurrency)
( long "jobs" <> short 'J'
<> metavar (paramNumber `paramOr` "cpus")
<> help "enable concurrent jobs"
<> hidden
)
timeLimitOption :: [AnnexOption]
timeLimitOption =
[ annexOption settimelimit $ option (eitherReader parseDuration)

View file

@ -11,11 +11,16 @@
module Command.P2PHttp where
import Command
import Command hiding (jobsOption)
import P2P.Http.Server
import P2P.Http.Url
import qualified P2P.Protocol as P2P
import Utility.Env
import Annex.UUID
import qualified Git
import qualified Git.Construct
import qualified Annex
import Types.Concurrency
import Servant
import qualified Network.Wai.Handler.Warp as Warp
@ -23,12 +28,14 @@ import qualified Network.Wai.Handler.WarpTLS as Warp
import Network.Socket (PortNumber)
import qualified Data.Map as M
import Data.String
import Control.Concurrent.STM
cmd :: Command
cmd = noMessages $ withAnnexOptions [jobsOption] $
command "p2phttp" SectionPlumbing
"communicate in P2P protocol over http"
paramNothing (seek <$$> optParser)
cmd = noMessages $ dontCheck repoExists $
noRepo (startIO <$$> optParser) $
command "p2phttp" SectionPlumbing
"communicate in P2P protocol over http"
paramNothing (startAnnex <$$> optParser)
data Options = Options
{ portOption :: Maybe PortNumber
@ -43,7 +50,9 @@ data Options = Options
, unauthNoLockingOption :: Bool
, wideOpenOption :: Bool
, proxyConnectionsOption :: Maybe Integer
, jobsOption :: Maybe Concurrency
, clusterJobsOption :: Maybe Int
, directoryOption :: [FilePath]
}
optParser :: CmdParamsDesc -> Parser Options
@ -96,32 +105,80 @@ optParser _ = Options
( long "proxyconnections" <> metavar paramNumber
<> help "maximum number of idle connections when proxying"
))
<*> optional jobsOptionParser
<*> optional (option auto
( long "clusterjobs" <> metavar paramNumber
<> help "number of concurrent node accesses per connection"
))
<*> many (strOption
( long "directory" <> metavar paramPath
<> help "serve repositories in subdirectories of a directory"
))
seek :: Options -> CommandSeek
seek o = getAnnexWorkerPool $ \workerpool ->
withP2PConnections workerpool
(fromMaybe 1 $ proxyConnectionsOption o)
(fromMaybe 1 $ clusterJobsOption o)
(go workerpool)
where
go workerpool acquireconn = liftIO $ do
startAnnex :: Options -> Annex ()
startAnnex o
| null (directoryOption o) = ifM ((/=) NoUUID <$> getUUID)
( do
authenv <- liftIO getAuthEnv
st <- mkServerState o authenv
liftIO $ runServer o st
-- Run in a git repository that is not a git-annex repository.
, liftIO $ startIO o
)
| otherwise = liftIO $ startIO o
startIO :: Options -> IO ()
startIO o
| null (directoryOption o) =
giveup "Use the --directory option to specify which git-annex repositories to serve."
| otherwise = do
authenv <- getAuthEnv
st <- mkP2PHttpServerState acquireconn workerpool $
mkGetServerMode authenv o
st <- mkst authenv mempty
runServer o st
where
mkst authenv oldst = do
repos <- findRepos o
sts <- forM repos $ \r -> do
strd <- Annex.new r
Annex.eval strd (mkstannex authenv oldst)
return (mconcat sts)
{ updateRepos = updaterepos authenv
}
mkstannex authenv oldst = do
u <- getUUID
if u == NoUUID
then return mempty
else case M.lookup u (servedRepos oldst) of
Nothing -> mkServerState o authenv
Just old -> return $ P2PHttpServerState
{ servedRepos = M.singleton u old
, serverShutdownCleanup = mempty
, updateRepos = mempty
}
updaterepos authenv oldst = do
newst <- mkst authenv oldst
return $ newst
{ serverShutdownCleanup =
serverShutdownCleanup newst
<> serverShutdownCleanup oldst
}
runServer :: Options -> P2PHttpServerState -> IO ()
runServer o mst = go `finally` serverShutdownCleanup mst
where
go = do
let settings = Warp.setPort port $ Warp.setHost host $
Warp.defaultSettings
mstv <- newTMVarIO mst
case (certFileOption o, privateKeyFileOption o) of
(Nothing, Nothing) -> Warp.runSettings settings (p2pHttpApp st)
(Nothing, Nothing) -> Warp.runSettings settings (p2pHttpApp mstv)
(Just certfile, Just privatekeyfile) -> do
let tlssettings = Warp.tlsSettingsChain
certfile (chainFileOption o) privatekeyfile
Warp.runTLS tlssettings settings (p2pHttpApp st)
Warp.runTLS tlssettings settings (p2pHttpApp mstv)
_ -> giveup "You must use both --certfile and --privatekeyfile options to enable HTTPS."
port = maybe
(fromIntegral defaultP2PHttpProtocolPort)
fromIntegral
@ -131,6 +188,15 @@ seek o = getAnnexWorkerPool $ \workerpool ->
fromString
(bindOption o)
mkServerState :: Options -> M.Map Auth P2P.ServerMode -> Annex P2PHttpServerState
mkServerState o authenv =
withAnnexWorkerPool (jobsOption o) $
mkP2PHttpServerState
(mkGetServerMode authenv o)
return
(fromMaybe 1 $ proxyConnectionsOption o)
(fromMaybe 1 $ clusterJobsOption o)
mkGetServerMode :: M.Map Auth P2P.ServerMode -> Options -> GetServerMode
mkGetServerMode _ o _ Nothing
| wideOpenOption o = ServerMode
@ -197,3 +263,11 @@ getAuthEnv = do
case M.lookup user permmap of
Nothing -> (auth, P2P.ServeReadWrite)
Just perms -> (auth, perms)
findRepos :: Options -> IO [Git.Repo]
findRepos o = do
files <- map toRawFilePath . concat
<$> mapM dirContents (directoryOption o)
map Git.Construct.newFrom . catMaybes
<$> mapM Git.Construct.checkForRepo files

View file

@ -45,10 +45,10 @@ import Control.Concurrent
import System.IO.Unsafe
import Data.Either
p2pHttpApp :: P2PHttpServerState -> Application
p2pHttpApp :: TMVar P2PHttpServerState -> Application
p2pHttpApp = serve p2pHttpAPI . serveP2pHttp
serveP2pHttp :: P2PHttpServerState -> Server P2PHttpAPI
serveP2pHttp :: TMVar P2PHttpServerState -> Server P2PHttpAPI
serveP2pHttp st
= serveGet st
:<|> serveGet st
@ -91,7 +91,7 @@ serveP2pHttp st
:<|> serveGetGeneric st
serveGetGeneric
:: P2PHttpServerState
:: TMVar P2PHttpServerState
-> B64UUID ServerSide
-> B64Key
-> Maybe (B64UUID ClientSide)
@ -109,7 +109,7 @@ serveGetGeneric st su@(B64UUID u) k mcu bypass =
serveGet
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> B64Key
@ -120,8 +120,8 @@ serveGet
-> IsSecure
-> Maybe Auth
-> Handler (Headers '[DataLengthHeader] (S.SourceT IO B.ByteString))
serveGet st su apiver (B64Key k) cu bypass baf startat sec auth = do
conn <- getP2PConnection apiver st cu su bypass sec auth ReadAction id
serveGet mst su apiver (B64Key k) cu bypass baf startat sec auth = do
(conn, st) <- getP2PConnection apiver mst cu su bypass sec auth ReadAction id
bsv <- liftIO newEmptyTMVarIO
endv <- liftIO newEmptyTMVarIO
validityv <- liftIO newEmptyTMVarIO
@ -222,7 +222,7 @@ serveGet st su apiver (B64Key k) cu bypass baf startat sec auth = do
serveCheckPresent
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> B64Key
@ -233,14 +233,14 @@ serveCheckPresent
-> Handler CheckPresentResult
serveCheckPresent st su apiver (B64Key k) cu bypass sec auth = do
res <- withP2PConnection apiver st cu su bypass sec auth ReadAction id
$ \conn -> liftIO $ proxyClientNetProto conn $ checkPresent k
$ \(conn, _) -> liftIO $ proxyClientNetProto conn $ checkPresent k
case res of
Right b -> return (CheckPresentResult b)
Left err -> throwError $ err500 { errBody = encodeBL err }
serveRemove
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> (RemoveResultPlus -> t)
-> B64UUID ServerSide
-> v
@ -252,7 +252,7 @@ serveRemove
-> Handler t
serveRemove st resultmangle su apiver (B64Key k) cu bypass sec auth = do
res <- withP2PConnection apiver st cu su bypass sec auth RemoveAction id
$ \conn ->
$ \(conn, _) ->
liftIO $ proxyClientNetProto conn $ remove Nothing k
case res of
(Right b, plusuuids) -> return $ resultmangle $
@ -262,7 +262,7 @@ serveRemove st resultmangle su apiver (B64Key k) cu bypass sec auth = do
serveRemoveBefore
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> B64Key
@ -274,7 +274,7 @@ serveRemoveBefore
-> Handler RemoveResultPlus
serveRemoveBefore st su apiver (B64Key k) cu bypass (Timestamp ts) sec auth = do
res <- withP2PConnection apiver st cu su bypass sec auth RemoveAction id
$ \conn ->
$ \(conn, _) ->
liftIO $ proxyClientNetProto conn $
removeBeforeRemoteEndTime ts k
case res of
@ -285,7 +285,7 @@ serveRemoveBefore st su apiver (B64Key k) cu bypass (Timestamp ts) sec auth = do
serveGetTimestamp
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> B64UUID ClientSide
@ -295,7 +295,7 @@ serveGetTimestamp
-> Handler GetTimestampResult
serveGetTimestamp st su apiver cu bypass sec auth = do
res <- withP2PConnection apiver st cu su bypass sec auth ReadAction id
$ \conn ->
$ \(conn, _) ->
liftIO $ proxyClientNetProto conn getTimestamp
case res of
Right ts -> return $ GetTimestampResult (Timestamp ts)
@ -304,7 +304,7 @@ serveGetTimestamp st su apiver cu bypass sec auth = do
servePut
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> (PutResultPlus -> t)
-> B64UUID ServerSide
-> v
@ -319,28 +319,28 @@ servePut
-> IsSecure
-> Maybe Auth
-> Handler t
servePut st resultmangle su apiver (Just True) _ k cu bypass baf _ _ sec auth = do
res <- withP2PConnection' apiver st cu su bypass sec auth WriteAction
servePut mst resultmangle su apiver (Just True) _ k cu bypass baf _ _ sec auth = do
res <- withP2PConnection' apiver mst cu su bypass sec auth WriteAction
(\cst -> cst { connectionWaitVar = False }) (liftIO . protoaction)
servePutResult resultmangle res
where
protoaction conn = servePutAction st conn k baf $ \_offset -> do
protoaction conn = servePutAction conn k baf $ \_offset -> do
net $ sendMessage DATA_PRESENT
checkSuccessPlus
servePut st resultmangle su apiver _datapresent (DataLength len) k cu bypass baf moffset stream sec auth = do
servePut mst resultmangle su apiver _datapresent (DataLength len) k cu bypass baf moffset stream sec auth = do
validityv <- liftIO newEmptyTMVarIO
let validitycheck = local $ runValidityCheck $
liftIO $ atomically $ readTMVar validityv
tooshortv <- liftIO newEmptyTMVarIO
content <- liftIO $ S.unSourceT stream (gather validityv tooshortv)
res <- withP2PConnection' apiver st cu su bypass sec auth WriteAction
(\cst -> cst { connectionWaitVar = False }) $ \conn -> do
res <- withP2PConnection' apiver mst cu su bypass sec auth WriteAction
(\cst -> cst { connectionWaitVar = False }) $ \(conn, st) -> do
liftIO $ void $ async $ checktooshort conn tooshortv
liftIO (protoaction conn content validitycheck)
liftIO (protoaction conn st content validitycheck)
servePutResult resultmangle res
where
protoaction conn content validitycheck =
servePutAction st conn k baf $ \offset' ->
protoaction conn st content validitycheck =
servePutAction (conn, st) k baf $ \offset' ->
let offsetdelta = offset' - offset
in case compare offset' offset of
EQ -> sendContent' nullMeterUpdate (Len len)
@ -396,13 +396,12 @@ servePut st resultmangle su apiver _datapresent (DataLength len) k cu bypass baf
closeP2PConnection conn
servePutAction
:: P2PHttpServerState
-> P2PConnectionPair
:: (P2PConnectionPair, PerRepoServerState)
-> B64Key
-> Maybe B64FilePath
-> (P2P.Protocol.Offset -> Proto (Maybe [UUID]))
-> IO (Either SomeException (Either ProtoFailure (Maybe [UUID])))
servePutAction st conn (B64Key k) baf a = inAnnexWorker st $
servePutAction (conn, st) (B64Key k) baf a = inAnnexWorker st $
enteringStage (TransferStage Download) $
runFullProto (clientRunState conn) (clientP2PConnection conn) $
put' k af a
@ -422,7 +421,7 @@ servePutResult resultmangle res = case res of
servePut'
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> (PutResultPlus -> t)
-> B64UUID ServerSide
-> v
@ -440,7 +439,7 @@ servePut' st resultmangle su v = servePut st resultmangle su v Nothing
servePutOffset
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> (PutOffsetResultPlus -> t)
-> B64UUID ServerSide
-> v
@ -452,7 +451,7 @@ servePutOffset
-> Handler t
servePutOffset st resultmangle su apiver (B64Key k) cu bypass sec auth = do
res <- withP2PConnection apiver st cu su bypass sec auth WriteAction
(\cst -> cst { connectionWaitVar = False }) $ \conn ->
(\cst -> cst { connectionWaitVar = False }) $ \(conn, _) ->
liftIO $ proxyClientNetProto conn $ getPutOffset k af
case res of
Right offset -> return $ resultmangle $
@ -464,7 +463,7 @@ servePutOffset st resultmangle su apiver (B64Key k) cu bypass sec auth = do
serveLockContent
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> B64Key
@ -473,8 +472,8 @@ serveLockContent
-> IsSecure
-> Maybe Auth
-> Handler LockResult
serveLockContent st su apiver (B64Key k) cu bypass sec auth = do
conn <- getP2PConnection apiver st cu su bypass sec auth LockAction id
serveLockContent mst su apiver (B64Key k) cu bypass sec auth = do
(conn, st) <- getP2PConnection apiver mst cu su bypass sec auth LockAction id
let lock = do
lockresv <- newEmptyTMVarIO
unlockv <- newEmptyTMVarIO
@ -501,7 +500,7 @@ serveLockContent st su apiver (B64Key k) cu bypass sec auth = do
serveKeepLocked
:: APIVersion v
=> P2PHttpServerState
=> TMVar P2PHttpServerState
-> B64UUID ServerSide
-> v
-> LockID
@ -513,15 +512,15 @@ serveKeepLocked
-> Maybe KeepAlive
-> S.SourceT IO UnlockRequest
-> Handler LockResult
serveKeepLocked st _su _apiver lckid _cu _bypass sec auth _ _ unlockrequeststream = do
checkAuthActionClass st sec auth LockAction $ \_ -> do
serveKeepLocked mst su _apiver lckid _cu _bypass sec auth _ _ unlockrequeststream = do
checkAuthActionClass mst su sec auth LockAction $ \st _ -> do
liftIO $ keepingLocked lckid st
_ <- liftIO $ S.unSourceT unlockrequeststream go
_ <- liftIO $ S.unSourceT unlockrequeststream (go st)
return (LockResult False Nothing)
where
go S.Stop = dropLock lckid st
go (S.Error _err) = dropLock lckid st
go (S.Skip s) = go s
go (S.Effect ms) = ms >>= go
go (S.Yield (UnlockRequest False) s) = go s
go (S.Yield (UnlockRequest True) _) = dropLock lckid st
go st S.Stop = dropLock lckid st
go st (S.Error _err) = dropLock lckid st
go st (S.Skip s) = go st s
go st (S.Effect ms) = ms >>= go st
go st (S.Yield (UnlockRequest False) s) = go st s
go st (S.Yield (UnlockRequest True) _) = dropLock lckid st

View file

@ -26,6 +26,8 @@ import Types.NumCopies
import Types.WorkerPool
import Annex.WorkerPool
import Annex.BranchState
import Annex.Concurrent
import Types.Concurrency
import Types.Cluster
import CmdLine.Action (startConcurrency)
import Utility.ThreadScheduler
@ -42,8 +44,37 @@ import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Control.Concurrent.Async
import Data.Time.Clock.POSIX
import qualified Data.Semigroup as Sem
import Prelude
data P2PHttpServerState = P2PHttpServerState
{ servedRepos :: M.Map UUID PerRepoServerState
, serverShutdownCleanup :: IO ()
, updateRepos :: UpdateRepos
}
type UpdateRepos = P2PHttpServerState -> IO P2PHttpServerState
instance Monoid P2PHttpServerState where
mempty = P2PHttpServerState
{ servedRepos = mempty
, serverShutdownCleanup = noop
, updateRepos = const mempty
}
instance Sem.Semigroup P2PHttpServerState where
a <> b = P2PHttpServerState
{ servedRepos = servedRepos a <> servedRepos b
, serverShutdownCleanup = do
serverShutdownCleanup a
serverShutdownCleanup b
, updateRepos = \st -> do
a' <- updateRepos a st
b' <- updateRepos b st
return (a' <> b')
}
data PerRepoServerState = PerRepoServerState
{ acquireP2PConnection :: AcquireP2PConnection
, annexWorkerPool :: AnnexWorkerPool
, getServerMode :: GetServerMode
@ -62,8 +93,8 @@ data ServerMode
}
| CannotServeRequests
mkP2PHttpServerState :: AcquireP2PConnection -> AnnexWorkerPool -> GetServerMode -> IO P2PHttpServerState
mkP2PHttpServerState acquireconn annexworkerpool getservermode = P2PHttpServerState
mkPerRepoServerState :: AcquireP2PConnection -> AnnexWorkerPool -> GetServerMode -> IO PerRepoServerState
mkPerRepoServerState acquireconn annexworkerpool getservermode = PerRepoServerState
<$> pure acquireconn
<*> pure annexworkerpool
<*> pure getservermode
@ -75,7 +106,7 @@ data ActionClass = ReadAction | WriteAction | RemoveAction | LockAction
withP2PConnection
:: APIVersion v
=> v
-> P2PHttpServerState
-> TMVar P2PHttpServerState
-> B64UUID ClientSide
-> B64UUID ServerSide
-> [B64UUID Bypass]
@ -83,10 +114,10 @@ withP2PConnection
-> Maybe Auth
-> ActionClass
-> (ConnectionParams -> ConnectionParams)
-> (P2PConnectionPair -> Handler (Either ProtoFailure a))
-> ((P2PConnectionPair, PerRepoServerState) -> Handler (Either ProtoFailure a))
-> Handler a
withP2PConnection apiver st cu su bypass sec auth actionclass fconnparams connaction =
withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction'
withP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams connaction =
withP2PConnection' apiver mst cu su bypass sec auth actionclass fconnparams connaction'
where
connaction' conn = connaction conn >>= \case
Right r -> return r
@ -96,7 +127,7 @@ withP2PConnection apiver st cu su bypass sec auth actionclass fconnparams connac
withP2PConnection'
:: APIVersion v
=> v
-> P2PHttpServerState
-> TMVar P2PHttpServerState
-> B64UUID ClientSide
-> B64UUID ServerSide
-> [B64UUID Bypass]
@ -104,17 +135,17 @@ withP2PConnection'
-> Maybe Auth
-> ActionClass
-> (ConnectionParams -> ConnectionParams)
-> (P2PConnectionPair -> Handler a)
-> ((P2PConnectionPair, PerRepoServerState) -> Handler a)
-> Handler a
withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction = do
conn <- getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams
connaction conn
withP2PConnection' apiver mst cu su bypass sec auth actionclass fconnparams connaction = do
(conn, st) <- getP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams
connaction (conn, st)
`finally` liftIO (releaseP2PConnection conn)
getP2PConnection
:: APIVersion v
=> v
-> P2PHttpServerState
-> TMVar P2PHttpServerState
-> B64UUID ClientSide
-> B64UUID ServerSide
-> [B64UUID Bypass]
@ -122,16 +153,16 @@ getP2PConnection
-> Maybe Auth
-> ActionClass
-> (ConnectionParams -> ConnectionParams)
-> Handler P2PConnectionPair
getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams =
checkAuthActionClass st sec auth actionclass go
-> Handler (P2PConnectionPair, PerRepoServerState)
getP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams =
checkAuthActionClass mst su sec auth actionclass go
where
go servermode = liftIO (acquireP2PConnection st cp) >>= \case
go st servermode = liftIO (acquireP2PConnection st cp) >>= \case
Left (ConnectionFailed err) ->
throwError err502 { errBody = encodeBL err }
Left TooManyConnections ->
throwError err503
Right v -> return v
Right v -> return (v, st)
where
cp = fconnparams $ ConnectionParams
{ connectionProtocolVersion = protocolVersion apiver
@ -142,30 +173,51 @@ getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams =
, connectionWaitVar = True
}
getPerRepoServerState :: TMVar P2PHttpServerState -> B64UUID ServerSide -> IO (Maybe PerRepoServerState)
getPerRepoServerState mstv su = do
mst <- atomically $ readTMVar mstv
case lookupst mst of
Just st -> return (Just st)
Nothing -> do
mst' <- atomically $ takeTMVar mstv
mst'' <- updateRepos mst' mst'
debug "P2P.Http" $
"Rescanned for repositories, now serving UUIDs: "
++ show (M.keys (servedRepos mst''))
atomically $ putTMVar mstv mst''
return $ lookupst mst''
where
lookupst mst = M.lookup (fromB64UUID su) (servedRepos mst)
checkAuthActionClass
:: P2PHttpServerState
:: TMVar P2PHttpServerState
-> B64UUID ServerSide
-> IsSecure
-> Maybe Auth
-> ActionClass
-> (P2P.ServerMode -> Handler a)
-> (PerRepoServerState -> P2P.ServerMode -> Handler a)
-> Handler a
checkAuthActionClass st sec auth actionclass go =
case (sm, actionclass) of
checkAuthActionClass mstv su sec auth actionclass go =
liftIO (getPerRepoServerState mstv su) >>= \case
Just st -> select st
Nothing -> throwError err404
where
select st = case (sm, actionclass) of
(ServerMode { serverMode = P2P.ServeReadWrite }, _) ->
go P2P.ServeReadWrite
go st P2P.ServeReadWrite
(ServerMode { unauthenticatedLockingAllowed = True }, LockAction) ->
go P2P.ServeReadOnly
go st P2P.ServeReadOnly
(ServerMode { serverMode = P2P.ServeAppendOnly }, RemoveAction) ->
throwError $ forbiddenWithoutAuth sm
(ServerMode { serverMode = P2P.ServeAppendOnly }, _) ->
go P2P.ServeAppendOnly
go st P2P.ServeAppendOnly
(ServerMode { serverMode = P2P.ServeReadOnly }, ReadAction) ->
go P2P.ServeReadOnly
go st P2P.ServeReadOnly
(ServerMode { serverMode = P2P.ServeReadOnly }, _) ->
throwError $ forbiddenWithoutAuth sm
(CannotServeRequests, _) -> throwError basicAuthRequired
where
sm = getServerMode st sec auth
where
sm = getServerMode st sec auth
forbiddenAction :: ServerError
forbiddenAction = err403
@ -204,13 +256,14 @@ type AcquireP2PConnection
= ConnectionParams
-> IO (Either ConnectionProblem P2PConnectionPair)
withP2PConnections
:: AnnexWorkerPool
mkP2PHttpServerState
:: GetServerMode
-> UpdateRepos
-> ProxyConnectionPoolSize
-> ClusterConcurrency
-> (AcquireP2PConnection -> Annex a)
-> Annex a
withP2PConnections workerpool proxyconnectionpoolsize clusterconcurrency a = do
-> AnnexWorkerPool
-> Annex P2PHttpServerState
mkP2PHttpServerState getservermode updaterepos proxyconnectionpoolsize clusterconcurrency workerpool = do
enableInteractiveBranchAccess
myuuid <- getUUID
myproxies <- M.lookup myuuid <$> getProxies
@ -223,7 +276,13 @@ withP2PConnections workerpool proxyconnectionpoolsize clusterconcurrency a = do
let endit = do
liftIO $ atomically $ putTMVar endv ()
liftIO $ wait asyncservicer
a (acquireconn reqv) `finally` endit
let servinguuids = myuuid : map proxyRemoteUUID (maybe [] S.toList myproxies)
st <- liftIO $ mkPerRepoServerState (acquireconn reqv) workerpool getservermode
return $ P2PHttpServerState
{ servedRepos = M.fromList $ zip servinguuids (repeat st)
, serverShutdownCleanup = endit
, updateRepos = updaterepos
}
where
acquireconn reqv connparams = do
respvar <- newEmptyTMVarIO
@ -487,13 +546,13 @@ mkLocker lock unlock = do
wait locktid
return Nothing
storeLock :: LockID -> Locker -> P2PHttpServerState -> IO ()
storeLock :: LockID -> Locker -> PerRepoServerState -> IO ()
storeLock lckid locker st = atomically $ do
m <- takeTMVar (openLocks st)
let !m' = M.insert lckid locker m
putTMVar (openLocks st) m'
keepingLocked :: LockID -> P2PHttpServerState -> IO ()
keepingLocked :: LockID -> PerRepoServerState -> IO ()
keepingLocked lckid st = do
m <- atomically $ readTMVar (openLocks st)
case M.lookup lckid m of
@ -502,7 +561,7 @@ keepingLocked lckid st = do
atomically $ void $
tryPutTMVar (lockerTimeoutDisable locker) ()
dropLock :: LockID -> P2PHttpServerState -> IO ()
dropLock :: LockID -> PerRepoServerState -> IO ()
dropLock lckid st = do
v <- atomically $ do
m <- takeTMVar (openLocks st)
@ -520,13 +579,15 @@ dropLock lckid st = do
Nothing -> return ()
Just locker -> wait (lockerThread locker)
getAnnexWorkerPool :: (AnnexWorkerPool -> Annex a) -> Annex a
getAnnexWorkerPool a = startConcurrency transferStages $
Annex.getState Annex.workers >>= \case
Nothing -> giveup "Use -Jn or set annex.jobs to configure the number of worker threads."
Just wp -> a wp
withAnnexWorkerPool :: (Maybe Concurrency) -> (AnnexWorkerPool -> Annex a) -> Annex a
withAnnexWorkerPool mc a = do
maybe noop (setConcurrency . ConcurrencyCmdLine) mc
startConcurrency transferStages $
Annex.getState Annex.workers >>= \case
Nothing -> giveup "Use -Jn or set annex.jobs to configure the number of worker threads."
Just wp -> a wp
inAnnexWorker :: P2PHttpServerState -> Annex a -> IO (Either SomeException a)
inAnnexWorker :: PerRepoServerState -> Annex a -> IO (Either SomeException a)
inAnnexWorker st = inAnnexWorker' (annexWorkerPool st)
inAnnexWorker' :: AnnexWorkerPool -> Annex a -> IO (Either SomeException a)

View file

@ -12,8 +12,12 @@ This is a HTTP server for the git-annex API.
It is the git-annex equivilant of git-http-backend(1), for serving
a repository over HTTP with write access for authenticated users.
This does not serve the git repository over HTTP, only the git-annex
API.
This does not serve a git repository over HTTP, only the git-annex
API.
By default, this serves the git-annex API for the git-annex repository
in the current working directory. It can also serve more than one
repository, see the `--directory` parameter.
Typically a remote will have `remote.name.url` set to a http url
as usual, and `remote.name.annexUrl` set to an annex+http url such as
@ -35,10 +39,25 @@ convenient way to download the content of any key, by using the path
# OPTIONS
* `--directory=path`
Serve each git-annex repository found in immediate
subdirectories of a directory.
This option can be provided more than once to serve several directories
full of git-annex repositories.
New git-annex repositories can be added to the directory, and will be
noticed and served immediately. There is no need to restart the server.
When a git-annex repository is removed from the directory, the server
will stop serving it as well. This may not be immediate, as some files
in the deleted repository may still be open.
* `--jobs=N` `-JN`
This or annex.jobs must be set to configure the number of worker
threads that serve connections to the webserver.
threads, per repository served, that serve connections to the webserver.
Since the webserver itself also uses one of these threads,
this needs to be set to 2 or more.
@ -47,15 +66,15 @@ convenient way to download the content of any key, by using the path
* `--proxyconnections=N`
When this command is run in a repository that is configured to act as a
proxy for some of its remotes, this is the maximum number of idle
connections to keep open to proxied remotes.
When serving a repository that is configured to act as a proxy for some
of its remotes, this is the maximum number of idle connections to keep
open to proxied remotes.
The default is 1.
* `--clusterjobs=N`
When this command is run in a repository that is a gateway for a cluster,
When serving a repository that is a gateway for a cluster,
this is the number of concurrent jobs to use to access nodes of the
cluster, per connection to the webserver.