hnix-store/hnix-store-remote/tests-io/NixDaemon.hs

303 lines
9.4 KiB
Haskell
Raw Normal View History

2023-11-17 22:11:18 +03:00
{-# LANGUAGE OverloadedStrings #-}
module NixDaemon where
import Data.Text (Text)
2023-11-22 16:53:03 +03:00
import Data.Either (isRight, isLeft)
import Data.Bool (bool)
import Control.Monad (forM_, void)
2023-11-22 16:53:03 +03:00
import Control.Monad.IO.Class (liftIO)
import qualified System.Environment
2023-11-22 16:53:03 +03:00
import Control.Exception (bracket)
import Control.Concurrent (threadDelay)
import qualified Data.ByteString.Char8 as BSC
import qualified Data.Either
2023-11-22 16:53:03 +03:00
import qualified Data.HashSet as HS
import qualified Data.Map.Strict as M
import qualified Data.Text
import qualified Data.Text.Encoding
2023-11-22 16:53:03 +03:00
import System.Directory
import System.IO.Temp
import qualified System.Process as P
import System.Posix.User as U
import System.Linux.Namespaces as NS
import Test.Hspec (Spec, describe, context)
import qualified Test.Hspec as Hspec
import Test.Hspec.Expectations.Lifted
import System.FilePath
import System.Nix.Build
import System.Nix.StorePath
import System.Nix.StorePath.Metadata
import System.Nix.Store.Remote
2023-11-30 17:21:16 +03:00
import System.Nix.Store.Remote.MonadStore (mapStoreConfig)
2023-11-22 16:53:03 +03:00
import Crypto.Hash (SHA256)
import System.Nix.Nar (dumpPath)
createProcessEnv :: FilePath -> String -> [String] -> IO P.ProcessHandle
createProcessEnv fp proc args = do
mPath <- System.Environment.lookupEnv "PATH"
(_, _, _, ph) <-
P.createProcess (P.proc proc args)
2021-08-06 15:38:17 +03:00
{ P.cwd = Just fp
, P.env = Just $ mockedEnv mPath fp
}
pure ph
mockedEnv :: Maybe String -> FilePath -> [(String, FilePath)]
mockedEnv mEnvPath fp =
[ ("NIX_STORE_DIR" , fp </> "store")
, ("NIX_LOCALSTATE_DIR", fp </> "var")
, ("NIX_LOG_DIR" , fp </> "var" </> "log")
, ("NIX_STATE_DIR" , fp </> "var" </> "nix")
, ("NIX_CONF_DIR" , fp </> "etc")
, ("HOME" , fp </> "home")
-- , ("NIX_REMOTE", "daemon")
2021-08-06 19:18:38 +03:00
] <> foldMap (\x -> [("PATH", x)]) mEnvPath
waitSocket :: FilePath -> Int -> IO ()
2021-01-14 16:15:47 +03:00
waitSocket _ 0 = fail "No socket"
waitSocket fp x = do
ex <- doesFileExist fp
bool
(threadDelay 100000 >> waitSocket fp (x - 1))
(pure ())
ex
2021-01-14 16:15:47 +03:00
writeConf :: FilePath -> IO ()
writeConf fp =
writeFile fp $ unlines
[ "build-users-group = "
, "trusted-users = root"
, "allowed-users = *"
, "fsync-metadata = false"
]
{-
- we run in user namespace as root but groups are failed
- => build-users-group has to be empty but we still
- get an error (maybe older nix-daemon)
-
uid=0(root) gid=65534(nobody) groups=65534(nobody)
drwxr-xr-x 3 0 65534 60 Nov 29 05:53 store
accepted connection from pid 22959, user root (trusted)
error: changing ownership of path '/run/user/1000/test-nix-store-06b0d249e5616122/store': Invalid argument
-}
startDaemon
:: FilePath
-> IO (P.ProcessHandle, MonadStore a -> IO (Either RemoteStoreError a, [Logger]))
startDaemon fp = do
writeConf (fp </> "etc" </> "nix.conf")
p <- createProcessEnv fp "nix-daemon" []
waitSocket sockFp 30
2023-06-16 05:28:29 +03:00
pure (p, runStoreOpts sockFp (StoreDir $ BSC.pack $ fp </> "store"))
where
sockFp = fp </> "var/nix/daemon-socket/socket"
2021-01-14 16:15:47 +03:00
enterNamespaces :: IO ()
enterNamespaces = do
uid <- getEffectiveUserID
gid <- getEffectiveGroupID
unshare [User, Network, Mount]
-- fmap our (parent) uid to root
writeUserMappings Nothing [UserMapping 0 uid 1]
-- fmap our (parent) gid to root group
writeGroupMappings Nothing [GroupMapping 0 gid 1] True
2021-01-14 16:15:47 +03:00
withNixDaemon
:: ((MonadStore a -> IO (Either RemoteStoreError a, [Logger])) -> IO a) -> IO a
withNixDaemon action =
withSystemTempDirectory "test-nix-store" $ \path -> do
mapM_ (createDirectory . snd)
(filter ((/= "NIX_REMOTE") . fst) $ mockedEnv Nothing path)
ini <- createProcessEnv path "nix-store" ["--init"]
2021-01-14 16:15:47 +03:00
void $ P.waitForProcess ini
writeFile (path </> "dummy") "Hello World"
setCurrentDirectory path
bracket (startDaemon path)
(P.terminateProcess . fst)
2021-08-06 15:38:17 +03:00
(action . snd)
2021-01-14 16:15:47 +03:00
checks :: (Show a, Show b) => IO (a, b) -> (a -> Bool) -> IO ()
checks action check = action >>= (`Hspec.shouldSatisfy` (check . fst))
2021-01-14 16:15:47 +03:00
it
:: (Show a, Show b, Monad m)
=> String
-> m c
-> (a -> Bool)
-> Hspec.SpecWith (m () -> IO (a, b))
it name action check =
Hspec.it name $ \run -> run (void $ action) `checks` check
2021-01-14 16:15:47 +03:00
itRights
:: (Show a, Show b, Show c, Monad m)
=> String
-> m d
-> Hspec.SpecWith (m () -> IO (Either a b, c))
itRights name action = it name action isRight
2021-01-14 16:15:47 +03:00
itLefts
:: (Show a, Show b, Show c, Monad m)
=> String
-> m d
-> Hspec.SpecWith (m () -> IO (Either a b, c))
itLefts name action = it name action isLeft
2021-01-14 16:15:47 +03:00
withPath :: (StorePath -> MonadStore a) -> MonadStore a
withPath action = do
path <- addTextToStore "hnix-store" "test" mempty RepairMode_DontRepair
action path
-- | dummy path, adds <tmp>/dummpy with "Hello World" contents
2021-01-14 16:15:47 +03:00
dummy :: MonadStore StorePath
dummy = do
let name = Data.Either.fromRight (error "impossible") $ makeStorePathName "dummy"
addToStore @SHA256 name (dumpPath "dummy") FileIngestionMethod_Flat RepairMode_DontRepair
invalidPath :: StorePath
invalidPath =
let name = Data.Either.fromRight (error "impossible") $ makeStorePathName "invalid"
in unsafeMakeStorePath (mkStorePathHashPart @SHA256 "invalid") name
2021-01-14 16:15:47 +03:00
withBuilder :: (StorePath -> MonadStore a) -> MonadStore a
withBuilder action = do
path <- addTextToStore "builder" builderSh mempty RepairMode_DontRepair
action path
2021-01-14 16:15:47 +03:00
builderSh :: Text
builderSh = "declare -xpexport > $out"
spec_protocol :: Spec
spec_protocol = Hspec.around withNixDaemon $
describe "store" $ do
context "syncWithGC" $
itRights "syncs with garbage collector" syncWithGC
context "verifyStore" $ do
itRights "check=False repair=False" $
verifyStore
CheckMode_DontCheck
RepairMode_DontRepair
`shouldReturn` False
itRights "check=True repair=False" $
verifyStore
CheckMode_DoCheck
RepairMode_DontRepair
`shouldReturn` False
--privileged
itRights "check=True repair=True" $
verifyStore
CheckMode_DoCheck
RepairMode_DoRepair
`shouldReturn` False
context "addTextToStore" $
2021-08-06 19:18:38 +03:00
itRights "adds text to store" $ withPath pure
context "isValidPathUncached" $ do
itRights "validates path" $ withPath $ \path -> do
2021-08-06 15:38:17 +03:00
liftIO $ print path
isValidPathUncached path `shouldReturn` True
itLefts "fails on invalid path"
$ mapStoreConfig
(\sc -> sc { storeConfig_dir = StoreDir "/asdf" })
$ isValidPathUncached invalidPath
context "queryAllValidPaths" $ do
2021-08-06 15:38:17 +03:00
itRights "empty query" queryAllValidPaths
itRights "non-empty query" $ withPath $ \path ->
2021-08-06 15:38:17 +03:00
queryAllValidPaths `shouldReturn` HS.fromList [path]
context "queryPathInfoUncached" $
itRights "queries path info" $ withPath $ \path -> do
meta <- queryPathInfoUncached path
references meta `shouldSatisfy` HS.null
context "ensurePath" $
2021-08-06 15:38:17 +03:00
itRights "simple ensure" $ withPath ensurePath
context "addTempRoot" $
2021-08-06 15:38:17 +03:00
itRights "simple addition" $ withPath addTempRoot
context "addIndirectRoot" $
2021-08-06 15:38:17 +03:00
itRights "simple addition" $ withPath addIndirectRoot
context "buildPaths" $ do
itRights "build Normal" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet BuildMode_Normal
itRights "build Check" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet BuildMode_Check
itLefts "build Repair" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet BuildMode_Repair
context "roots" $ context "findRoots" $ do
2021-08-06 15:38:17 +03:00
itRights "empty roots" (findRoots `shouldReturn` M.empty)
2021-01-14 16:15:47 +03:00
itRights "path added as a temp root" $ withPath $ \_ -> do
roots <- findRoots
roots `shouldSatisfy` ((== 1) . M.size)
2021-08-06 15:38:17 +03:00
context "optimiseStore" $ itRights "optimises" optimiseStore
context "queryMissing" $
itRights "queries" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
queryMissing pathSet
`shouldReturn`
Missing
{ missingWillBuild = mempty
, missingWillSubstitute = mempty
, missingUnknownPaths = mempty
, missingDownloadSize = 0
, missingNarSize = 0
}
context "addToStore" $
itRights "adds file to store" $ do
fp <- liftIO $ writeSystemTempFile "addition" "lal"
let name = Data.Either.fromRight (error "impossible") $ makeStorePathName "tmp-addition"
res <- addToStore @SHA256 name (dumpPath fp) FileIngestionMethod_Flat RepairMode_DontRepair
liftIO $ print res
context "with dummy" $ do
itRights "adds dummy" dummy
itRights "valid dummy" $ do
path <- dummy
2021-08-06 15:38:17 +03:00
liftIO $ print path
isValidPathUncached path `shouldReturn` True
context "deleteSpecific" $
itRights "delete a path from the store" $ withPath $ \path -> do
-- clear temp gc roots so the delete works. restarting the nix daemon should also do this...
storeDir <- getStoreDir
let tempRootsDir = Data.Text.unpack $ mconcat [ Data.Text.Encoding.decodeUtf8 (unStoreDir storeDir), "/../var/nix/temproots/" ]
tempRootList <- liftIO $ listDirectory tempRootsDir
liftIO $ forM_ tempRootList $ \entry -> do
removeFile $ mconcat [ tempRootsDir, "/", entry ]
GCResult{..} <- deleteSpecific (HS.fromList [path])
gcResult_deletedPaths `shouldBe` HS.fromList [path]
gcResult_bytesFreed `shouldBe` 4