git-annex/Annex/Sim/File.hs
Joey Hess 8e94b75a61
support simulating clusters
Without actually simulating cluster implementation at all. Instead, only
the essential fact that cluster gateways know what changes they have
made to each node of a cluster. That is enough for sims like
sizebalanced_cluster.
2024-09-25 14:06:41 -04:00

279 lines
11 KiB
Haskell

{- sim files
-
- Copyright 2024 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Annex.Sim.File where
import Annex.Sim
import Annex.Common hiding (group)
import Utility.DataUnits
import Types.TrustLevel
import Types.Group
import Git.Config (isTrueFalse)
import Data.Char
import Text.Read
parseSimFile :: String -> Either String [SimCommand]
parseSimFile = go [] . lines
where
go cs [] = Right (reverse cs)
go cs (l:ls) = case parseSimFileLine l of
Right command -> go (command:cs) ls
Left err -> Left err
parseSimFileLine :: String -> Either String SimCommand
parseSimFileLine s
| "#" `isPrefixOf` s = Right (CommandComment s)
| all isSpace s = Right (CommandBlank)
| otherwise = parseSimCommand (words s)
generateSimFile :: [SimCommand] -> String
generateSimFile = unlines . map unwords . go
where
go [] = []
go (CommandInit (RepoName name) : rest) =
["init", name] : go rest
go (CommandInitRemote (RepoName name) : rest) =
["initremote", name] : go rest
go (CommandUse (RepoName name) what : rest) =
["use", name, what] : go rest
go (CommandConnect c : rest) =
("connect":formatConnections c) : go rest
go (CommandDisconnect c : rest) =
("disconnect":formatConnections c) : go rest
go (CommandAddTree (RepoName name) expr : rest) =
["addtree", name, expr] : go rest
go (CommandAdd f sz repos : rest) =
(["add", fromRawFilePath f, showsize sz] ++ map fromRepoName repos) : go rest
go (CommandAddMulti n suffix minsz maxsz repos : rest) =
(["addmulti", show n, suffix, showsize minsz, showsize maxsz] ++ map fromRepoName repos) : go rest
go (CommandStep n : rest) =
["step", show n] : go rest
go (CommandStepStable n : rest) =
["stepstable", show n] : go rest
go (CommandAction act : rest) = formatAction act : go rest
go (CommandSeed n : rest) =
["seed", show n] : go rest
go (CommandPresent (RepoName repo) f : rest) =
["present", repo, fromRawFilePath f] : go rest
go (CommandNotPresent (RepoName repo) f : rest) =
["notpresent", repo, fromRawFilePath f] : go rest
go (CommandNumCopies n : rest) =
["numcopies", show n] : go rest
go (CommandMinCopies n : rest) =
["mincopies", show n] : go rest
go (CommandTrustLevel (RepoName repo) trustlevel : rest) =
["trustlevel", repo, showTrustLevel trustlevel] : go rest
go (CommandGroup (RepoName repo) group : rest) =
["group", repo, fromGroup group] : go rest
go (CommandUngroup (RepoName repo) group : rest) =
["ungroup", repo, fromGroup group] : go rest
go (CommandWanted (RepoName repo) expr : rest) =
["wanted", repo, expr] : go rest
go (CommandRequired (RepoName repo) expr : rest) =
["required", repo, expr] : go rest
go (CommandGroupWanted group expr : rest) =
["groupwanted", fromGroup group, expr] : go rest
go (CommandRandomWanted (RepoName repo) terms : rest) =
("randomwanted" : repo : terms) : go rest
go (CommandRandomRequired (RepoName repo) terms : rest) =
("randomrequired" : repo : terms) : go rest
go (CommandRandomGroupWanted group terms : rest) =
("randomgroupwanted" : fromGroup group : terms) : go rest
go (CommandMaxSize (RepoName repo) maxsize : rest) =
["maxsize", repo, showsize (fromMaxSize maxsize)] : go rest
go (CommandRebalance b : rest) =
["rebalance", if b then "on" else "off"] : go rest
go (CommandClusterNode (RepoName nodename) (RepoName repo) : rest) =
["clusternode", nodename, repo] : go rest
go (CommandVisit (RepoName repo) cmdparams : rest) =
(["visit", repo] ++ cmdparams) : go rest
go (CommandComment s : rest) =
[s] : go rest
go (CommandBlank : rest) =
[""] : go rest
showsize = filter (not . isSpace) . preciseSize storageUnits True
formatAction :: SimAction -> [String]
formatAction (ActionPull (RepoName repo) (RemoteName remote)) =
["action", repo, "pull", remote]
formatAction (ActionPush (RepoName repo) (RemoteName remote)) =
["action", repo, "push", remote]
formatAction (ActionSync (RepoName repo) (RemoteName remote)) =
["action", repo, "sync", remote]
formatAction (ActionGetWanted (RepoName repo) (RemoteName remote)) =
["action", repo, "getwanted", remote]
formatAction (ActionDropUnwanted (RepoName repo) (Just (RemoteName remote))) =
["action", repo, "dropunwantedfrom", remote]
formatAction (ActionDropUnwanted (RepoName repo) Nothing) =
["action", repo, "dropunwanted"]
formatAction (ActionSendWanted (RepoName repo) (RemoteName remote)) =
["action", repo, "sendwanted", remote]
formatAction (ActionGitPush (RepoName repo) (RemoteName remote)) =
["action", repo, "gitpush", remote]
formatAction (ActionGitPull (RepoName repo) (RemoteName remote)) =
["action", repo, "gitpull", remote]
formatAction (ActionWhile a b) =
formatAction a ++ ["while"] ++ formatAction b
parseSimCommand :: [String] -> Either String SimCommand
parseSimCommand ("init":name:[]) =
Right $ CommandInit (RepoName name)
parseSimCommand ("initremote":name:[]) =
Right $ CommandInitRemote (RepoName name)
parseSimCommand ("use":name:rest) =
Right $ CommandUse (RepoName name) (unwords rest)
parseSimCommand ("connect":rest) =
CommandConnect <$> parseConnections rest
parseSimCommand ("disconnect":rest) =
CommandDisconnect <$> parseConnections rest
parseSimCommand ("addtree":name:rest) =
Right $ CommandAddTree(RepoName name) (unwords rest)
parseSimCommand ("add":filename:size:repos) =
case readSize dataUnits size of
Just sz -> Right $ CommandAdd
(toRawFilePath filename)
sz
(map RepoName repos)
Nothing -> Left $ "Unable to parse file size \"" ++ size ++ "\""
parseSimCommand ("addmulti":num:suffix:minsize:maxsize:repos) =
case readSize dataUnits minsize of
Just minsz -> case readSize dataUnits maxsize of
Just maxsz -> case readMaybe num of
Just n -> Right $ CommandAddMulti
n suffix minsz maxsz
(map RepoName repos)
Nothing -> Left $ "Unable to parse number \"" ++ num ++ "\""
Nothing -> Left $ "Unable to parse file size \"" ++ maxsize ++ "\""
Nothing -> Left $ "Unable to parse file size \"" ++ minsize ++ "\""
parseSimCommand ("step":n:[]) =
case readMaybe n of
Just n' -> Right $ CommandStep n'
Nothing -> Left $ "Unable to parse step value \"" ++ n ++ "\""
parseSimCommand ("stepstable":n:[]) =
case readMaybe n of
Just n' -> Right $ CommandStepStable n'
Nothing -> Left $ "Unable to parse step value \"" ++ n ++ "\""
parseSimCommand l@("action":_) = case parseSimAction l of
Right act -> Right $ CommandAction act
Left err -> Left err
parseSimCommand ("seed":n:[]) =
case readMaybe n of
Just n' -> Right $ CommandSeed n'
Nothing -> Left $ "Unable to parse seed value \"" ++ n ++ "\""
parseSimCommand ("present":repo:file:[]) =
Right $ CommandPresent (RepoName repo) (toRawFilePath file)
parseSimCommand ("notpresent":repo:file:[]) =
Right $ CommandNotPresent (RepoName repo) (toRawFilePath file)
parseSimCommand ("numcopies":n:[]) =
case readMaybe n of
Just n' -> Right $ CommandNumCopies n'
Nothing -> Left $ "Unable to parse numcopies value \"" ++ n ++ "\""
parseSimCommand ("mincopies":n:[]) =
case readMaybe n of
Just n' -> Right $ CommandMinCopies n'
Nothing -> Left $ "Unable to parse mincopies value \"" ++ n ++ "\""
parseSimCommand ("trustlevel":repo:s:[]) =
case readTrustLevel s of
Just trustlevel -> Right $
CommandTrustLevel (RepoName repo) trustlevel
Nothing -> Left $ "Unknown trust level \"" ++ s ++ "\"."
parseSimCommand ("group":repo:group:[]) =
Right $ CommandGroup (RepoName repo) (toGroup group)
parseSimCommand ("ungroup":repo:group:[]) =
Right $ CommandUngroup (RepoName repo) (toGroup group)
parseSimCommand ("wanted":repo:expr) =
Right $ CommandWanted (RepoName repo) (unwords expr)
parseSimCommand ("required":repo:expr) =
Right $ CommandRequired (RepoName repo) (unwords expr)
parseSimCommand ("groupwanted":group:expr) =
Right $ CommandGroupWanted (toGroup group) (unwords expr)
parseSimCommand ("randomwanted":repo:terms) =
Right $ CommandRandomWanted (RepoName repo) terms
parseSimCommand ("randomrequired":repo:terms) =
Right $ CommandRandomRequired (RepoName repo) terms
parseSimCommand ("randomgroupwanted":group:terms) =
Right $ CommandRandomGroupWanted (toGroup group) terms
parseSimCommand ("maxsize":repo:size:[]) =
case readSize dataUnits size of
Just sz -> Right $ CommandMaxSize (RepoName repo) (MaxSize sz)
Nothing -> Left $ "Unable to parse maxsize \"" ++ size ++ "\""
parseSimCommand ("clusternode":nodename:repo:[]) =
Right $ CommandClusterNode (RepoName nodename) (RepoName repo)
parseSimCommand ("rebalance":onoff:[]) = case isTrueFalse onoff of
Just b -> Right $ CommandRebalance b
Nothing -> Left $ "Unable to parse rebalance value \"" ++ onoff ++ "\""
parseSimCommand ("visit":repo:cmdparams) =
Right $ CommandVisit (RepoName repo) cmdparams
parseSimCommand ws = parseError ws
parseSimAction :: [String] -> Either String SimAction
parseSimAction ("action":repo:"pull":remote:rest) =
mkAction rest $ ActionPull (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"push":remote:rest) =
mkAction rest $ ActionPush (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"sync":remote:rest) =
mkAction rest $ ActionSync (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"getwanted":remote:rest) =
mkAction rest $ ActionGetWanted (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"sendwanted":remote:rest) =
mkAction rest $ ActionSendWanted (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"dropunwantedfrom":remote:rest) =
mkAction rest $ ActionDropUnwanted (RepoName repo)
(Just (RemoteName remote))
parseSimAction ("action":repo:"dropunwanted":rest) =
mkAction rest $ ActionDropUnwanted (RepoName repo) Nothing
parseSimAction ("action":repo:"gitpush":remote:rest) =
mkAction rest $ ActionGitPush (RepoName repo) (RemoteName remote)
parseSimAction ("action":repo:"gitpull":remote:rest) =
mkAction rest $ ActionGitPull (RepoName repo) (RemoteName remote)
parseSimAction ws = parseError ws
mkAction :: [String] -> SimAction -> Either String SimAction
mkAction [] a = Right a
mkAction ("while":rest) a = case parseSimAction rest of
Right b -> Right (ActionWhile a b)
Left err -> Left err
mkAction ws _ = parseError ws
parseError :: [String] -> Either String a
parseError ws = Left $ "Unable to parse sim command: \"" ++ unwords ws ++ "\""
parseConnections :: [String] -> Either String Connections
parseConnections = go . reverse
where
go (r2:"->":r1:rest) =
chain (RepoName r1 :-> RemoteName r2) rest
go (r2:"<-":r1:rest) =
chain (RemoteName r1 :<- RepoName r2) rest
go (r2:"<->":r1:rest) =
chain (RepoName r1 :<-> RepoName r2) rest
go rest = bad rest
chain c [] = Right c
chain c ("->":r:rest) = chain (RepoName r :=> c) rest
chain c ("<-":r:rest) = chain (RemoteName r :<= c) rest
chain c ("<->":r:rest) = chain (RepoName r :<=> c) rest
chain _ rest = bad rest
bad rest = Left $ "Bad connect syntax near \"" ++ unwords rest ++ "\""
formatConnections :: Connections -> [String]
formatConnections (RepoName repo :-> RemoteName remote) =
[repo, "->", remote]
formatConnections (RemoteName remote :<- RepoName repo) =
[remote, "<-", repo]
formatConnections (RepoName repo1 :<-> RepoName repo2) =
[repo1, "<->", repo2]
formatConnections (RepoName repo :=> c) =
repo : "->" : formatConnections c
formatConnections (RemoteName remote :<= c) =
remote : "<-" : formatConnections c
formatConnections (RepoName repo :<=> c) =
repo : "<->" : formatConnections c