2014-08-01 19:09:49 +00:00
|
|
|
{- git-annex command
|
|
|
|
-
|
2020-01-14 16:35:08 +00:00
|
|
|
- Copyright 2014-2020 Joey Hess <id@joeyh.name>
|
2014-08-01 19:09:49 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2014-08-01 19:09:49 +00:00
|
|
|
-}
|
|
|
|
|
2020-04-29 17:16:43 +00:00
|
|
|
{-# LANGUAGE RankNTypes #-}
|
|
|
|
|
2014-08-01 19:09:49 +00:00
|
|
|
module Command.TestRemote where
|
|
|
|
|
|
|
|
import Command
|
|
|
|
import qualified Annex
|
|
|
|
import qualified Remote
|
2014-08-01 20:50:24 +00:00
|
|
|
import qualified Types.Remote as Remote
|
2016-04-20 17:21:56 +00:00
|
|
|
import qualified Types.Backend as Backend
|
2014-08-01 19:09:49 +00:00
|
|
|
import Types.KeySource
|
|
|
|
import Annex.Content
|
2019-01-17 16:39:29 +00:00
|
|
|
import Annex.WorkTree
|
2014-08-01 19:09:49 +00:00
|
|
|
import Backend
|
2019-01-17 16:39:29 +00:00
|
|
|
import Logs.Location
|
2014-08-01 19:09:49 +00:00
|
|
|
import qualified Backend.Hash
|
|
|
|
import Utility.Tmp
|
|
|
|
import Utility.Metered
|
2014-08-01 20:50:24 +00:00
|
|
|
import Utility.DataUnits
|
2014-08-01 21:16:20 +00:00
|
|
|
import Utility.CopyFile
|
2014-08-01 19:09:49 +00:00
|
|
|
import Types.Messages
|
2017-11-08 18:22:05 +00:00
|
|
|
import Types.Export
|
2020-01-14 16:35:08 +00:00
|
|
|
import Types.RemoteConfig
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
import Types.ProposedAccepted
|
2020-01-15 15:22:36 +00:00
|
|
|
import Annex.SpecialRemote.Config (exportTreeField)
|
2014-08-01 20:50:24 +00:00
|
|
|
import Remote.Helper.Chunked
|
2020-01-15 15:30:45 +00:00
|
|
|
import Remote.Helper.Encryptable (describeEncryption, encryptionField, highRandomQualityField)
|
2015-07-11 04:42:32 +00:00
|
|
|
import Git.Types
|
2014-08-01 19:09:49 +00:00
|
|
|
|
|
|
|
import Test.Tasty
|
|
|
|
import Test.Tasty.Runners
|
|
|
|
import Test.Tasty.HUnit
|
|
|
|
import "crypto-api" Crypto.Random
|
|
|
|
import qualified Data.ByteString as B
|
2014-08-01 21:16:20 +00:00
|
|
|
import qualified Data.ByteString.Lazy as L
|
2014-08-01 20:50:24 +00:00
|
|
|
import qualified Data.Map as M
|
2020-04-28 21:19:07 +00:00
|
|
|
import Control.Concurrent.STM hiding (check)
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2015-07-08 16:33:27 +00:00
|
|
|
cmd :: Command
|
2015-07-11 04:42:32 +00:00
|
|
|
cmd = command "testremote" SectionTesting
|
|
|
|
"test transfers to/from a remote"
|
|
|
|
paramRemote (seek <$$> optParser)
|
2014-08-01 20:50:24 +00:00
|
|
|
|
2015-07-11 04:42:32 +00:00
|
|
|
data TestRemoteOptions = TestRemoteOptions
|
|
|
|
{ testRemote :: RemoteName
|
|
|
|
, sizeOption :: ByteSize
|
2019-01-17 16:39:29 +00:00
|
|
|
, testReadonlyFile :: [FilePath]
|
2015-07-11 04:42:32 +00:00
|
|
|
}
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2015-07-11 04:42:32 +00:00
|
|
|
optParser :: CmdParamsDesc -> Parser TestRemoteOptions
|
|
|
|
optParser desc = TestRemoteOptions
|
|
|
|
<$> argument str ( metavar desc )
|
|
|
|
<*> option (str >>= maybe (fail "parse error") return . readSize dataUnits)
|
|
|
|
( long "size" <> metavar paramSize
|
|
|
|
<> value (1024 * 1024)
|
|
|
|
<> help "base key size (default 1MiB)"
|
|
|
|
)
|
2019-01-17 16:39:29 +00:00
|
|
|
<*> many testreadonly
|
|
|
|
where
|
|
|
|
testreadonly = option str
|
|
|
|
( long "test-readonly" <> metavar paramFile
|
|
|
|
<> help "readonly test object"
|
|
|
|
)
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2015-07-11 04:42:32 +00:00
|
|
|
seek :: TestRemoteOptions -> CommandSeek
|
2019-01-17 16:39:29 +00:00
|
|
|
seek = commandAction . start
|
2015-07-11 04:42:32 +00:00
|
|
|
|
2019-01-17 16:39:29 +00:00
|
|
|
start :: TestRemoteOptions -> CommandStart
|
make CommandStart return a StartMessage
The goal is to be able to run CommandStart in the main thread when -J is
used, rather than unncessarily passing it off to a worker thread, which
incurs overhead that is signficant when the CommandStart is going to
quickly decide to stop.
To do that, the message it displays needs to be displayed in the worker
thread, after the CommandStart has run.
Also, the change will mean that CommandStart will no longer necessarily
run with the same Annex state as CommandPerform. While its docs already
said it should avoid modifying Annex state, I audited all the
CommandStart code as part of the conversion. (Note that CommandSeek
already sometimes runs with a different Annex state, and that has not been
a source of any problems, so I am not too worried that this change will
lead to breakage going forward.)
The only modification of Annex state I found was it calling
allowMessages in some Commands that default to noMessages. Dealt with
that by adding a startCustomOutput and a startingUsualMessages.
This lets a command start with noMessages and then select the output it
wants for each CommandStart.
One bit of breakage: onlyActionOn has been removed from commands that used it.
The plan is that, since a StartMessage contains an ActionItem,
when a Key can be extracted from that, the parallel job runner can
run onlyActionOn' automatically. Then commands won't need to worry about
this detail. Future work.
Otherwise, this was a fairly straightforward process of making each
CommandStart compile again. Hopefully other behavior changes were mostly
avoided.
In a few cases, a command had a CommandStart that called a CommandPerform
that then called showStart multiple times. I have collapsed those
down to a single start action. The main command to perhaps suffer from it
is Command.Direct, which used to show a start for each file, and no
longer does.
Another minor behavior change is that some commands used showStart
before, but had an associated file and a Key available, so were changed
to ShowStart with an ActionItemAssociatedFile. That will not change the
normal output or behavior, but --json output will now include the key.
This should not break it for anyone using a real json parser.
2019-06-06 19:42:30 +00:00
|
|
|
start o = starting "testremote" (ActionItemOther (Just (testRemote o))) $ do
|
2014-08-03 22:08:34 +00:00
|
|
|
fast <- Annex.getState Annex.fast
|
2019-01-17 16:39:29 +00:00
|
|
|
r <- either giveup disableExportTree =<< Remote.byName' (testRemote o)
|
|
|
|
ks <- case testReadonlyFile o of
|
|
|
|
[] -> if Remote.readonly r
|
|
|
|
then giveup "This remote is readonly, so you need to use the --test-readonly option."
|
|
|
|
else do
|
|
|
|
showAction "generating test keys"
|
|
|
|
mapM randKey (keySizes basesz fast)
|
|
|
|
fs -> mapM (getReadonlyKey r) fs
|
|
|
|
let r' = if null (testReadonlyFile o)
|
|
|
|
then r
|
|
|
|
else r { Remote.readonly = True }
|
|
|
|
rs <- if Remote.readonly r'
|
|
|
|
then return [r']
|
2020-04-29 19:48:09 +00:00
|
|
|
else remoteVariants r' basesz fast
|
2019-01-17 16:39:29 +00:00
|
|
|
unavailrs <- catMaybes <$> mapM Remote.mkUnavailable [r']
|
|
|
|
exportr <- if Remote.readonly r'
|
|
|
|
then return Nothing
|
|
|
|
else exportTreeVariant r'
|
make CommandStart return a StartMessage
The goal is to be able to run CommandStart in the main thread when -J is
used, rather than unncessarily passing it off to a worker thread, which
incurs overhead that is signficant when the CommandStart is going to
quickly decide to stop.
To do that, the message it displays needs to be displayed in the worker
thread, after the CommandStart has run.
Also, the change will mean that CommandStart will no longer necessarily
run with the same Annex state as CommandPerform. While its docs already
said it should avoid modifying Annex state, I audited all the
CommandStart code as part of the conversion. (Note that CommandSeek
already sometimes runs with a different Annex state, and that has not been
a source of any problems, so I am not too worried that this change will
lead to breakage going forward.)
The only modification of Annex state I found was it calling
allowMessages in some Commands that default to noMessages. Dealt with
that by adding a startCustomOutput and a startingUsualMessages.
This lets a command start with noMessages and then select the output it
wants for each CommandStart.
One bit of breakage: onlyActionOn has been removed from commands that used it.
The plan is that, since a StartMessage contains an ActionItem,
when a Key can be extracted from that, the parallel job runner can
run onlyActionOn' automatically. Then commands won't need to worry about
this detail. Future work.
Otherwise, this was a fairly straightforward process of making each
CommandStart compile again. Hopefully other behavior changes were mostly
avoided.
In a few cases, a command had a CommandStart that called a CommandPerform
that then called showStart multiple times. I have collapsed those
down to a single start action. The main command to perhaps suffer from it
is Command.Direct, which used to show a start for each file, and no
longer does.
Another minor behavior change is that some commands used showStart
before, but had an associated file and a Key available, so were changed
to ShowStart with an ActionItemAssociatedFile. That will not change the
normal output or behavior, but --json output will now include the key.
This should not break it for anyone using a real json parser.
2019-06-06 19:42:30 +00:00
|
|
|
perform rs unavailrs exportr ks
|
2019-01-17 16:39:29 +00:00
|
|
|
where
|
|
|
|
basesz = fromInteger $ sizeOption o
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2020-04-29 19:48:09 +00:00
|
|
|
remoteVariants :: Remote -> ByteSize -> Bool -> Annex [Remote]
|
|
|
|
remoteVariants r basesz fast = do
|
|
|
|
rs <- catMaybes <$> mapM (adjustChunkSize r) (chunkSizes basesz fast)
|
|
|
|
concat <$> mapM encryptionVariants rs
|
|
|
|
|
2017-11-08 18:22:05 +00:00
|
|
|
perform :: [Remote] -> [Remote] -> Maybe Remote -> [Key] -> CommandPerform
|
|
|
|
perform rs unavailrs exportr ks = do
|
2020-04-28 21:19:07 +00:00
|
|
|
st <- liftIO . newTVarIO =<< Annex.getState id
|
2020-04-29 19:48:09 +00:00
|
|
|
let tests = testGroup "Remote Tests" $ mkTestTrees
|
2020-04-29 17:16:43 +00:00
|
|
|
(runTestCase st)
|
|
|
|
(map (\r -> Described (descr r) (pure r)) rs)
|
|
|
|
(map (\r -> Described (descr r) (pure r)) unavailrs)
|
|
|
|
(fmap pure exportr)
|
|
|
|
(map (\k -> Described (desck k) (pure k)) ks)
|
2014-08-01 19:09:49 +00:00
|
|
|
ok <- case tryIngredients [consoleTestReporter] mempty tests of
|
|
|
|
Nothing -> error "No tests found!?"
|
|
|
|
Just act -> liftIO act
|
2014-08-01 20:50:24 +00:00
|
|
|
next $ cleanup rs ks ok
|
2014-08-01 19:09:49 +00:00
|
|
|
where
|
2020-04-29 17:16:43 +00:00
|
|
|
descr r = intercalate "; " $ map unwords
|
|
|
|
[ [ show (getChunkConfig (Remote.config r)) ]
|
|
|
|
, ["encryption", describeEncryption (Remote.config r)]
|
2017-11-08 18:22:05 +00:00
|
|
|
]
|
2020-04-29 17:16:43 +00:00
|
|
|
desck k = unwords [ "key size", show (fromKey keySize k) ]
|
2014-08-01 20:50:24 +00:00
|
|
|
|
|
|
|
adjustChunkSize :: Remote -> Int -> Annex (Maybe Remote)
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
adjustChunkSize r chunksize = adjustRemoteConfig r $
|
|
|
|
M.insert chunkField (Proposed (show chunksize))
|
2014-08-01 21:52:40 +00:00
|
|
|
|
|
|
|
-- Variants of a remote with no encryption, and with simple shared
|
|
|
|
-- encryption. Gpg key based encryption is not tested.
|
|
|
|
encryptionVariants :: Remote -> Annex [Remote]
|
|
|
|
encryptionVariants r = do
|
2020-01-14 16:35:08 +00:00
|
|
|
noenc <- adjustRemoteConfig r $
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
M.insert encryptionField (Proposed "none")
|
2014-08-01 21:52:40 +00:00
|
|
|
sharedenc <- adjustRemoteConfig r $
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
M.insert encryptionField (Proposed "shared") .
|
|
|
|
M.insert highRandomQualityField (Proposed "false")
|
2014-08-01 21:52:40 +00:00
|
|
|
return $ catMaybes [noenc, sharedenc]
|
|
|
|
|
2017-11-08 18:22:05 +00:00
|
|
|
-- Variant of a remote with exporttree disabled.
|
|
|
|
disableExportTree :: Remote -> Annex Remote
|
2018-03-27 20:10:28 +00:00
|
|
|
disableExportTree r = maybe (error "failed disabling exportree") return
|
2020-01-15 15:22:36 +00:00
|
|
|
=<< adjustRemoteConfig r (M.delete exportTreeField)
|
2017-11-08 18:22:05 +00:00
|
|
|
|
|
|
|
-- Variant of a remote with exporttree enabled.
|
|
|
|
exportTreeVariant :: Remote -> Annex (Maybe Remote)
|
|
|
|
exportTreeVariant r = ifM (Remote.isExportSupported r)
|
|
|
|
( adjustRemoteConfig r $
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
M.insert encryptionField (Proposed "none") .
|
|
|
|
M.insert exportTreeField (Proposed "yes")
|
2017-11-08 18:22:05 +00:00
|
|
|
, return Nothing
|
|
|
|
)
|
|
|
|
|
2014-08-01 21:52:40 +00:00
|
|
|
-- Regenerate a remote with a modified config.
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
adjustRemoteConfig :: Remote -> (Remote.RemoteConfig -> Remote.RemoteConfig) -> Annex (Maybe Remote)
|
2018-06-04 18:31:55 +00:00
|
|
|
adjustRemoteConfig r adjustconfig = do
|
|
|
|
repo <- Remote.getRepo r
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
let ParsedRemoteConfig _ origc = Remote.config r
|
2018-06-04 18:31:55 +00:00
|
|
|
Remote.generate (Remote.remotetype r)
|
|
|
|
repo
|
|
|
|
(Remote.uuid r)
|
fix encryption of content to gcrypt and git-lfs
Fix serious regression in gcrypt and encrypted git-lfs remotes.
Since version 7.20200202.7, git-annex incorrectly stored content
on those remotes without encrypting it.
Problem was, Remote.Git enumerates all git remotes, including git-lfs
and gcrypt. It then dispatches to those. So, Remote.List used the
RemoteConfigParser from Remote.Git, instead of from git-lfs or gcrypt,
and that parser does not know about encryption fields, so did not
include them in the ParsedRemoteConfig. (Also didn't include other
fields specific to those remotes, perhaps chunking etc also didn't
get through.)
To fix, had to move RemoteConfig parsing down into the generate methods
of each remote, rather than doing it in Remote.List.
And a consequence of that was that ParsedRemoteConfig had to change to
include the RemoteConfig that got parsed, so that testremote can
generate a new remote based on an existing remote.
(I would have rather fixed this just inside Remote.Git, but that was not
practical, at least not w/o re-doing work that Remote.List already did.
Big ugly mostly mechanical patch seemed preferable to making git-annex
slower.)
2020-02-26 21:20:56 +00:00
|
|
|
(adjustconfig origc)
|
2018-06-04 18:31:55 +00:00
|
|
|
(Remote.gitconfig r)
|
add RemoteStateHandle
This solves the problem of sameas remotes trampling over per-remote
state. Used for:
* per-remote state, of course
* per-remote metadata, also of course
* per-remote content identifiers, because two remote implementations
could in theory generate the same content identifier for two different
peices of content
While chunk logs are per-remote data, they don't use this, because the
number and size of chunks stored is a common property across sameas
remotes.
External special remote had a complication, where it was theoretically
possible for a remote to send SETSTATE or GETSTATE during INITREMOTE or
EXPORTSUPPORTED. Since the uuid of the remote is typically generate in
Remote.setup, it would only be possible to pass a Maybe
RemoteStateHandle into it, and it would otherwise have to construct its
own. Rather than go that route, I decided to send an ERROR in this case.
It seems unlikely that any existing external special remote will be
affected. They would have to make up a git-annex key, and set state for
some reason during INITREMOTE. I can imagine such a hack, but it doesn't
seem worth complicating the code in such an ugly way to support it.
Unfortunately, both TestRemote and Annex.Import needed the Remote
to have a new field added that holds its RemoteStateHandle.
2019-10-14 16:33:27 +00:00
|
|
|
(Remote.remoteStateHandle r)
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2020-04-29 17:16:43 +00:00
|
|
|
data Described t = Described
|
|
|
|
{ getDesc :: String
|
|
|
|
, getVal :: t
|
|
|
|
}
|
|
|
|
|
|
|
|
type RunAnnex = forall a. Annex a -> IO a
|
|
|
|
|
|
|
|
runTestCase :: TVar Annex.AnnexState -> RunAnnex
|
|
|
|
runTestCase stv a = do
|
|
|
|
st <- atomically $ readTVar stv
|
|
|
|
(r, st') <- Annex.run st $ do
|
|
|
|
Annex.setOutput QuietOutput
|
|
|
|
a
|
|
|
|
atomically $ writeTVar stv st'
|
|
|
|
return r
|
|
|
|
|
|
|
|
-- Note that the same remotes and keys should be produced each time
|
|
|
|
-- the provided actions are called.
|
2020-04-29 19:48:09 +00:00
|
|
|
mkTestTrees
|
2020-04-29 17:16:43 +00:00
|
|
|
:: RunAnnex
|
|
|
|
-> [Described (Annex Remote)]
|
|
|
|
-> [Described (Annex Remote)]
|
|
|
|
-> Maybe (Annex Remote)
|
|
|
|
-> [Described (Annex Key)]
|
2020-04-29 19:48:09 +00:00
|
|
|
-> [TestTree]
|
|
|
|
mkTestTrees runannex mkrs mkunavailrs mkexportr mkks = concat $
|
2020-04-29 17:16:43 +00:00
|
|
|
[ [ testGroup "unavailable remote" (testUnavailable runannex (getVal mkr) (getVal (Prelude.head mkks))) | mkr <- mkunavailrs ]
|
|
|
|
, [ testGroup (desc mkr mkk) (test runannex (getVal mkr) (getVal mkk)) | mkk <- mkks, mkr <- mkrs ]
|
|
|
|
, [ testGroup (descexport mkk1 mkk2) (testExportTree runannex mkexportr (getVal mkk1) (getVal mkk2)) | mkk1 <- take 2 mkks, mkk2 <- take 2 (reverse mkks) ]
|
|
|
|
]
|
|
|
|
where
|
|
|
|
desc r k = intercalate "; " $ map unwords
|
|
|
|
[ [ getDesc k ]
|
|
|
|
, [ getDesc r ]
|
|
|
|
]
|
|
|
|
descexport k1 k2 = intercalate "; " $ map unwords
|
|
|
|
[ [ "exporttree=yes" ]
|
|
|
|
, [ getDesc k1 ]
|
|
|
|
, [ getDesc k2 ]
|
|
|
|
]
|
|
|
|
|
|
|
|
test :: RunAnnex -> Annex Remote -> Annex Key -> [TestTree]
|
|
|
|
test runannex mkr mkk =
|
|
|
|
[ check "removeKey when not present" $ \r k ->
|
|
|
|
whenwritable r $ remove r k
|
|
|
|
, check ("present " ++ show False) $ \r k ->
|
|
|
|
whenwritable r $ present r k False
|
|
|
|
, check "storeKey" $ \r k ->
|
|
|
|
whenwritable r $ store r k
|
|
|
|
, check ("present " ++ show True) $ \r k ->
|
|
|
|
whenwritable r $ present r k True
|
|
|
|
, check "storeKey when already present" $ \r k ->
|
|
|
|
whenwritable r $ store r k
|
|
|
|
, check ("present " ++ show True) $ \r k -> present r k True
|
|
|
|
, check "retrieveKeyFile" $ \r k -> do
|
2015-10-09 19:48:02 +00:00
|
|
|
lockContentForRemoval k removeAnnex
|
2020-04-29 17:16:43 +00:00
|
|
|
get r k
|
|
|
|
, check "fsck downloaded object" fsck
|
|
|
|
, check "retrieveKeyFile resume from 33%" $ \r k -> do
|
2019-12-11 18:12:22 +00:00
|
|
|
loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k)
|
2014-08-01 21:16:20 +00:00
|
|
|
tmp <- prepTmp k
|
|
|
|
partial <- liftIO $ bracket (openBinaryFile loc ReadMode) hClose $ \h -> do
|
|
|
|
sz <- hFileSize h
|
|
|
|
L.hGet h $ fromInteger $ sz `div` 3
|
|
|
|
liftIO $ L.writeFile tmp partial
|
2015-10-09 19:48:02 +00:00
|
|
|
lockContentForRemoval k removeAnnex
|
2020-04-29 17:16:43 +00:00
|
|
|
get r k
|
|
|
|
, check "fsck downloaded object" fsck
|
|
|
|
, check "retrieveKeyFile resume from 0" $ \r k -> do
|
2014-08-01 21:16:20 +00:00
|
|
|
tmp <- prepTmp k
|
|
|
|
liftIO $ writeFile tmp ""
|
2015-10-09 19:48:02 +00:00
|
|
|
lockContentForRemoval k removeAnnex
|
2020-04-29 17:16:43 +00:00
|
|
|
get r k
|
|
|
|
, check "fsck downloaded object" fsck
|
|
|
|
, check "retrieveKeyFile resume from end" $ \r k -> do
|
2019-12-11 18:12:22 +00:00
|
|
|
loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k)
|
2014-08-01 21:16:20 +00:00
|
|
|
tmp <- prepTmp k
|
2014-08-27 00:06:43 +00:00
|
|
|
void $ liftIO $ copyFileExternal CopyAllMetaData loc tmp
|
2015-10-09 19:48:02 +00:00
|
|
|
lockContentForRemoval k removeAnnex
|
2020-04-29 17:16:43 +00:00
|
|
|
get r k
|
|
|
|
, check "fsck downloaded object" fsck
|
|
|
|
, check "removeKey when present" $ \r k ->
|
|
|
|
whenwritable r $ remove r k
|
|
|
|
, check ("present " ++ show False) $ \r k ->
|
|
|
|
whenwritable r $ present r k False
|
2014-08-01 19:09:49 +00:00
|
|
|
]
|
|
|
|
where
|
2020-04-29 17:16:43 +00:00
|
|
|
whenwritable r a
|
|
|
|
| Remote.readonly r = return True
|
|
|
|
| otherwise = a
|
|
|
|
check desc a = testCase desc $ do
|
|
|
|
let a' = do
|
|
|
|
r <- mkr
|
|
|
|
k <- mkk
|
|
|
|
a r k
|
|
|
|
runannex a' @? "failed"
|
|
|
|
present r k b = (== Right b) <$> Remote.hasKey r k
|
|
|
|
fsck _ k = case maybeLookupBackendVariety (fromKey keyVariety k) of
|
2014-08-01 21:16:20 +00:00
|
|
|
Nothing -> return True
|
2016-04-20 17:21:56 +00:00
|
|
|
Just b -> case Backend.verifyKeyContent b of
|
2014-08-01 21:16:20 +00:00
|
|
|
Nothing -> return True
|
2019-01-14 17:03:35 +00:00
|
|
|
Just verifier -> verifier k (serializeKey k)
|
2020-04-29 17:16:43 +00:00
|
|
|
get r k = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest ->
|
2017-03-10 17:12:24 +00:00
|
|
|
Remote.retrieveKeyFile r k (AssociatedFile Nothing)
|
|
|
|
dest nullMeterUpdate
|
2020-04-29 17:16:43 +00:00
|
|
|
store r k = Remote.storeKey r k (AssociatedFile Nothing) nullMeterUpdate
|
|
|
|
remove r k = Remote.removeKey r k
|
2020-04-28 21:19:07 +00:00
|
|
|
|
2020-04-29 17:16:43 +00:00
|
|
|
testExportTree :: RunAnnex -> Maybe (Annex Remote) -> Annex Key -> Annex Key -> [TestTree]
|
|
|
|
testExportTree _ Nothing _ _ = []
|
|
|
|
testExportTree runannex (Just mkr) mkk1 mkk2 =
|
|
|
|
[ check "check present export when not present" $ \ea k1 _k2 ->
|
|
|
|
not <$> checkpresentexport ea k1
|
|
|
|
, check "remove export when not present" $ \ea k1 _k2 ->
|
|
|
|
removeexport ea k1
|
|
|
|
, check "store export" $ \ea k1 _k2 ->
|
|
|
|
storeexport ea k1
|
|
|
|
, check "check present export after store" $ \ea k1 _k2 ->
|
|
|
|
checkpresentexport ea k1
|
|
|
|
, check "store export when already present" $ \ea k1 _k2 ->
|
|
|
|
storeexport ea k1
|
|
|
|
, check "retrieve export" $ \ea k1 _k2 ->
|
|
|
|
retrieveexport ea k1
|
|
|
|
, check "store new content to export" $ \ea _k1 k2 ->
|
|
|
|
storeexport ea k2
|
|
|
|
, check "check present export after store of new content" $ \ea _k1 k2 ->
|
|
|
|
checkpresentexport ea k2
|
|
|
|
, check "retrieve export new content" $ \ea _k1 k2 ->
|
|
|
|
retrieveexport ea k2
|
|
|
|
, check "remove export" $ \ea _k1 k2 ->
|
|
|
|
removeexport ea k2
|
|
|
|
, check "check present export after remove" $ \ea _k1 k2 ->
|
|
|
|
not <$> checkpresentexport ea k2
|
|
|
|
, check "retrieve export fails after removal" $ \ea _k1 k2 ->
|
|
|
|
not <$> retrieveexport ea k2
|
|
|
|
, check "remove export directory" $ \ea _k1 _k2 ->
|
|
|
|
removeexportdirectory ea
|
|
|
|
, check "remove export directory that is already removed" $ \ea _k1 _k2 ->
|
|
|
|
removeexportdirectory ea
|
2017-11-08 18:22:05 +00:00
|
|
|
-- renames are not tested because remotes do not need to support them
|
|
|
|
]
|
|
|
|
where
|
|
|
|
testexportdirectory = "testremote-export"
|
2019-12-05 18:36:43 +00:00
|
|
|
testexportlocation = mkExportLocation (toRawFilePath (testexportdirectory </> "location"))
|
2020-04-29 17:16:43 +00:00
|
|
|
check desc a = testCase desc $ do
|
|
|
|
let a' = do
|
|
|
|
ea <- Remote.exportActions <$> mkr
|
|
|
|
k1 <- mkk1
|
|
|
|
k2 <- mkk2
|
|
|
|
a ea k1 k2
|
|
|
|
runannex a' @? "failed"
|
|
|
|
storeexport ea k = do
|
2019-12-11 18:12:22 +00:00
|
|
|
loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k)
|
2017-11-08 18:22:05 +00:00
|
|
|
Remote.storeExport ea loc k testexportlocation nullMeterUpdate
|
2020-04-29 17:16:43 +00:00
|
|
|
retrieveexport ea k = withTmpFile "exported" $ \tmp h -> do
|
2017-11-08 18:22:05 +00:00
|
|
|
liftIO $ hClose h
|
|
|
|
ifM (Remote.retrieveExport ea k testexportlocation tmp nullMeterUpdate)
|
2018-06-21 17:34:11 +00:00
|
|
|
( verifyKeyContent RetrievalAllKeysSecure AlwaysVerify UnVerified k tmp
|
2017-11-08 18:22:05 +00:00
|
|
|
, return False
|
|
|
|
)
|
2020-04-29 17:16:43 +00:00
|
|
|
checkpresentexport ea k = Remote.checkPresentExport ea k testexportlocation
|
|
|
|
removeexport ea k = Remote.removeExport ea k testexportlocation
|
|
|
|
removeexportdirectory ea = case Remote.removeExportDirectory ea of
|
2017-11-08 18:22:05 +00:00
|
|
|
Nothing -> return True
|
2019-12-05 18:36:43 +00:00
|
|
|
Just a -> a (mkExportDirectory (toRawFilePath testexportdirectory))
|
2017-11-08 18:22:05 +00:00
|
|
|
|
2020-04-29 17:16:43 +00:00
|
|
|
testUnavailable :: RunAnnex -> Annex Remote -> Annex Key -> [TestTree]
|
|
|
|
testUnavailable runannex mkr mkk =
|
|
|
|
[ check (== Right False) "removeKey" $ \r k ->
|
2014-08-10 18:52:58 +00:00
|
|
|
Remote.removeKey r k
|
2020-04-29 17:16:43 +00:00
|
|
|
, check (== Right False) "storeKey" $ \r k ->
|
2017-03-10 17:12:24 +00:00
|
|
|
Remote.storeKey r k (AssociatedFile Nothing) nullMeterUpdate
|
2020-04-29 17:16:43 +00:00
|
|
|
, check (`notElem` [Right True, Right False]) "checkPresent" $ \r k ->
|
2014-08-10 18:52:58 +00:00
|
|
|
Remote.checkPresent r k
|
2020-04-29 17:16:43 +00:00
|
|
|
, check (== Right False) "retrieveKeyFile" $ \r k ->
|
2018-06-21 17:34:11 +00:00
|
|
|
getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest ->
|
2017-03-10 17:12:24 +00:00
|
|
|
Remote.retrieveKeyFile r k (AssociatedFile Nothing) dest nullMeterUpdate
|
2020-04-29 17:16:43 +00:00
|
|
|
, check (== Right False) "retrieveKeyFileCheap" $ \r k ->
|
2018-06-21 17:34:11 +00:00
|
|
|
getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest -> unVerified $
|
2017-03-10 17:12:24 +00:00
|
|
|
Remote.retrieveKeyFileCheap r k (AssociatedFile Nothing) dest
|
2014-08-10 18:52:58 +00:00
|
|
|
]
|
|
|
|
where
|
|
|
|
check checkval desc a = testCase desc $ do
|
2020-04-29 17:16:43 +00:00
|
|
|
v <- runannex $ do
|
|
|
|
r <- mkr
|
|
|
|
k <- mkk
|
|
|
|
either (Left . show) Right <$> tryNonAsync (a r k)
|
2014-08-10 18:52:58 +00:00
|
|
|
checkval v @? ("(got: " ++ show v ++ ")")
|
|
|
|
|
2014-08-01 20:50:24 +00:00
|
|
|
cleanup :: [Remote] -> [Key] -> Bool -> CommandCleanup
|
2019-01-17 16:39:29 +00:00
|
|
|
cleanup rs ks ok
|
|
|
|
| all Remote.readonly rs = return ok
|
|
|
|
| otherwise = do
|
|
|
|
forM_ rs $ \r -> forM_ ks (Remote.removeKey r)
|
|
|
|
forM_ ks $ \k -> lockContentForRemoval k removeAnnex
|
|
|
|
return ok
|
2014-08-01 19:09:49 +00:00
|
|
|
|
2014-08-03 22:08:34 +00:00
|
|
|
chunkSizes :: Int -> Bool -> [Int]
|
|
|
|
chunkSizes base False =
|
2014-08-01 20:50:24 +00:00
|
|
|
[ 0 -- no chunking
|
|
|
|
, base `div` 100
|
|
|
|
, base `div` 1000
|
|
|
|
, base
|
|
|
|
]
|
2014-08-04 12:24:06 +00:00
|
|
|
chunkSizes _ True =
|
2014-08-03 22:08:34 +00:00
|
|
|
[ 0
|
|
|
|
]
|
2014-08-01 20:50:24 +00:00
|
|
|
|
2014-08-03 22:08:34 +00:00
|
|
|
keySizes :: Int -> Bool -> [Int]
|
|
|
|
keySizes base fast = filter want
|
2014-08-01 19:09:49 +00:00
|
|
|
[ 0 -- empty key is a special case when chunking
|
2014-08-01 20:50:24 +00:00
|
|
|
, base
|
|
|
|
, base + 1
|
|
|
|
, base - 1
|
|
|
|
, base * 2
|
2014-08-01 19:09:49 +00:00
|
|
|
]
|
2014-08-03 22:08:34 +00:00
|
|
|
where
|
|
|
|
want sz
|
|
|
|
| fast = sz <= base && sz > 0
|
|
|
|
| otherwise = sz > 0
|
2014-08-01 19:09:49 +00:00
|
|
|
|
|
|
|
randKey :: Int -> Annex Key
|
|
|
|
randKey sz = withTmpFile "randkey" $ \f h -> do
|
|
|
|
gen <- liftIO (newGenIO :: IO SystemRandom)
|
|
|
|
case genBytes sz gen of
|
|
|
|
Left e -> error $ "failed to generate random key: " ++ show e
|
|
|
|
Right (rand, _) -> liftIO $ B.hPut h rand
|
|
|
|
liftIO $ hClose h
|
|
|
|
let ks = KeySource
|
2020-02-21 13:34:59 +00:00
|
|
|
{ keyFilename = toRawFilePath f
|
|
|
|
, contentLocation = toRawFilePath f
|
2014-08-01 19:09:49 +00:00
|
|
|
, inodeCache = Nothing
|
|
|
|
}
|
|
|
|
k <- fromMaybe (error "failed to generate random key")
|
2019-06-25 15:37:52 +00:00
|
|
|
<$> Backend.getKey Backend.Hash.testKeyBackend ks nullMeterUpdate
|
2017-02-28 16:49:17 +00:00
|
|
|
_ <- moveAnnex k f
|
2014-08-01 19:09:49 +00:00
|
|
|
return k
|
2019-01-17 16:39:29 +00:00
|
|
|
|
|
|
|
getReadonlyKey :: Remote -> FilePath -> Annex Key
|
2019-12-05 18:36:43 +00:00
|
|
|
getReadonlyKey r f = lookupFile (toRawFilePath f) >>= \case
|
2019-01-17 16:39:29 +00:00
|
|
|
Nothing -> giveup $ f ++ " is not an annexed file"
|
|
|
|
Just k -> do
|
|
|
|
unlessM (inAnnex k) $
|
|
|
|
giveup $ f ++ " does not have its content locally present, cannot test it"
|
|
|
|
unlessM ((Remote.uuid r `elem`) <$> loggedLocations k) $
|
|
|
|
giveup $ f ++ " is not stored in the remote being tested, cannot test it"
|
|
|
|
return k
|