diff options
-rw-r--r-- | erebos-tester.cabal | 1 | ||||
-rw-r--r-- | src/Network.hs | 83 | ||||
-rw-r--r-- | src/Network/Ip.hs | 6 | ||||
-rw-r--r-- | src/Run.hs | 28 | ||||
-rw-r--r-- | src/Run/Monad.hs | 3 |
5 files changed, 62 insertions, 59 deletions
diff --git a/erebos-tester.cabal b/erebos-tester.cabal index 0078481..135af97 100644 --- a/erebos-tester.cabal +++ b/erebos-tester.cabal @@ -62,6 +62,7 @@ executable erebos-tester-core MultiParamTypeClasses OverloadedStrings RankNTypes + RecordWildCards ScopedTypeVariables TupleSections TypeApplications diff --git a/src/Network.hs b/src/Network.hs index 247ef02..50dc7a3 100644 --- a/src/Network.hs +++ b/src/Network.hs @@ -59,6 +59,7 @@ data Network = Network { netPrefix :: IpPrefix , netNetns :: NetworkNamespace , netBridge :: Link Bridge + , netUpstream :: Maybe (Link VEth) , netNodes :: TVar [Node] , netSubnets :: TVar [(Word8, Network)] , netDir :: FilePath @@ -71,6 +72,7 @@ data Node = Node { nodeIp :: IpAddress , nodeName :: NodeName , nodeNetns :: NetworkNamespace + , nodeUpstream :: Link VEth , nodeNetwork :: Network , nodeDir :: FilePath } @@ -99,12 +101,12 @@ instance HasNetns Node where getNetns = nodeNetns instance ExprType Network where textExprType _ = T.pack "network" textExprValue n = "s:" <> textNetworkName (netPrefix n) - emptyVarValue = Network (IpPrefix []) undefined undefined undefined undefined undefined + emptyVarValue = Network (IpPrefix []) undefined undefined undefined undefined undefined undefined instance ExprType Node where textExprType _ = T.pack "node" textExprValue n = T.pack "n:" <> textNodeName (nodeName n) - emptyVarValue = Node (IpAddress (IpPrefix []) 0) (NodeName T.empty 0) undefined undefined undefined + emptyVarValue = Node (IpAddress (IpPrefix []) 0) (NodeName T.empty 0) undefined undefined undefined undefined recordMembers = map (first T.pack) [ ("ip", RecordSelector $ textIpAddress . nodeIp) @@ -126,27 +128,25 @@ delInternet _ = liftIO $ do callCommand $ "ip -all netns delete" newSubnet :: MonadIO m => Network -> Maybe VarName -> m Network -newSubnet net vname = do - (sub, idx) <- atomicallyWithIO $ do - idx <- lift $ nextPrefix (netPrefix net) . map fst <$> readTVar (netSubnets net) - sub <- newNetwork - (ipSubnet idx (netPrefix net)) - (netDir net </> maybe (T.unpack $ textNetnsName $ getNetns net) (("sub_"++) . unpackVarName) vname) - lift $ modifyTVar (netSubnets net) ((idx, sub) :) - return (sub, idx) +newSubnet net vname = atomicallyWithIO $ do + idx <- lift $ nextPrefix (netPrefix net) . map fst <$> readTVar (netSubnets net) + sub <- newNetwork + (ipSubnet idx (netPrefix net)) + (netDir net </> maybe (T.unpack $ textNetnsName $ getNetns net) (("sub_"++) . unpackVarName) vname) + lift $ modifyTVar (netSubnets net) ((idx, sub) :) let lan = lanSubnet $ netPrefix sub lanIp = IpAddress lan bridge = lanIp 1 router = lanIp 254 - liftIO $ do - (vethNet, vethSub) <- addVEth (net, "veth_s" <> T.pack (show idx)) (sub, "veth0") - addAddress vethNet router - setMaster vethSub (netBridge sub) -- this end needs to go up first, otherwise it - linkUp vethSub -- sometimes gets stuck with NO-CARRIER for a while. - linkUp vethNet + (vethNet, vethSub) <- addVEth (net, "veth_s" <> T.pack (show idx)) (sub, "veth0") + addAddress vethNet router + setMaster vethSub (netBridge sub) -- this end needs to go up first, otherwise it + linkUp vethSub -- sometimes gets stuck with NO-CARRIER for a while. + linkUp vethNet + postpone $ do -- If the new subnet can be split further, routing rule for the whole prefix is needed when (allowsSubnets (netPrefix sub)) $ callOn net $ "ip route add " <> textIpNetwork (netPrefix sub) @@ -154,7 +154,7 @@ newSubnet net vname = do <> " dev " <> linkName vethNet <> " src " <> textIpAddress router callOn sub $ "ip route add default via " <> textIpAddress router <> " dev br0 src " <> textIpAddress bridge - return sub + return sub { netUpstream = Just vethSub } newNetwork :: IpPrefix -> FilePath -> WriterT [IO ()] STM Network newNetwork prefix dir = do @@ -171,41 +171,38 @@ newNetwork prefix dir = do <$> pure prefix <*> pure netns <*> pure bridge + <*> pure Nothing <*> lift (newTVar []) <*> lift (newTVar []) <*> pure dir newNode :: MonadIO m => Network -> VarName -> m Node -newNode net vname = liftIO $ do - let lan = lanSubnet $ netPrefix net +newNode nodeNetwork vname = atomicallyWithIO $ do + let lan = lanSubnet $ netPrefix nodeNetwork lanIp = IpAddress lan - (node, idx) <- atomicallyWithIO $ do - nodes <- lift $ readTVar (netNodes net) - let nname = nextNodeName vname $ map nodeName nodes - netns <- addNetworkNamespace $ textNetnsName (getNetns net) <> ":" <> textNodeName nname - let idx = fromIntegral $ 2 + length nodes - node = Node { nodeName = nname - , nodeNetns = netns - , nodeIp = lanIp idx - , nodeNetwork = net - , nodeDir = netDir net </> ("node_" ++ unpackNodeName nname) - } - lift $ writeTVar (netNodes net) (node : nodes) - return (node, idx) - - let dir = nodeDir node - exists <- doesPathExist dir - when exists $ ioError $ userError $ dir ++ " exists" - createDirectoryIfMissing True dir - - (vethNet, vethNode) <- addVEth (net, "veth" <> T.pack (show idx)) (node, "veth0") - setMaster vethNet $ netBridge net + nodes <- lift $ readTVar (netNodes nodeNetwork) + let nodeName = nextNodeName vname $ map Network.nodeName nodes + idx = fromIntegral $ 2 + length nodes + nodeIp = lanIp idx + nodeDir = netDir nodeNetwork </> ("node_" ++ unpackNodeName nodeName) + nodeNetns <- addNetworkNamespace $ textNetnsName (getNetns nodeNetwork) <> ":" <> textNodeName nodeName + (vethNet, nodeUpstream) <- addVEth (nodeNetwork, "veth" <> T.pack (show idx)) (nodeNetns, "veth0") + + postpone $ do + exists <- doesPathExist nodeDir + when exists $ ioError $ userError $ nodeDir ++ " exists" + createDirectoryIfMissing True nodeDir + + let node = Node {..} + lift $ writeTVar (netNodes nodeNetwork) (node : nodes) + + setMaster vethNet $ netBridge nodeNetwork linkUp vethNet - addAddress vethNode $ nodeIp node - linkUp $ vethNode + addAddress nodeUpstream $ nodeIp + linkUp $ nodeUpstream linkUp $ loopback node - callOn node $ "ip route add default via " <> textIpAddress (lanIp 1) <> " dev veth0 src " <> textIpAddress (nodeIp node) + postpone $ callOn node $ "ip route add default via " <> textIpAddress (lanIp 1) <> " dev veth0 src " <> textIpAddress nodeIp return node diff --git a/src/Network/Ip.hs b/src/Network/Ip.hs index 13fc284..e3d95cb 100644 --- a/src/Network/Ip.hs +++ b/src/Network/Ip.hs @@ -130,10 +130,10 @@ addAddress link addr@(IpAddress prefix _) = do let bcast = IpAddress prefix 255 postpone $ callOn link $ "ip addr add " <> textIpAddressCidr addr <> " broadcast " <> textIpAddress bcast <> " dev \"" <> linkName link <> "\"" -setMaster :: (MonadPIO m, MonadFail m) => Link a -> Link Bridge -> m () -setMaster link bridge = do +setMaster :: MonadPIO m => Link a -> Link Bridge -> m () +setMaster link bridge = postpone $ do when (getNetns link /= getNetns bridge) $ fail "link and bridge in different network namespaces" - postpone $ callOn link $ "ip link set dev \"" <> linkName link <> "\" master \"" <> linkName bridge <> "\"" + callOn link $ "ip link set dev \"" <> linkName link <> "\" master \"" <> linkName bridge <> "\"" linkUp :: MonadPIO m => Link a -> m () linkUp link = do @@ -154,15 +154,17 @@ evalSteps = mapM_ $ \case DisconnectNode node inner -> do n <- eval node - withDisconnectedUp n $ evalSteps inner + withDisconnectedUp (nodeUpstream n) $ evalSteps inner DisconnectNodes net inner -> do n <- eval net - withDisconnectedBridge n $ evalSteps inner + withDisconnectedBridge (netBridge n) $ evalSteps inner DisconnectUpstream net inner -> do n <- eval net - withDisconnectedUp n $ evalSteps inner + case netUpstream n of + Just link -> withDisconnectedUp link $ evalSteps inner + Nothing -> evalSteps inner PacketLoss loss node inner -> do l <- eval loss @@ -205,30 +207,30 @@ withNode netexpr tvname inner = do node <- newNode net (either fromTypedVarName fromTypedVarName tvname) either (flip withVar node . fromTypedVarName) (const id) tvname $ inner node -withDisconnectedUp :: HasNetns n => n -> TestRun a -> TestRun a -withDisconnectedUp n inner = do - let netns = getNetns n +withDisconnectedUp :: Link VEth -> TestRun a -> TestRun a +withDisconnectedUp link inner = do + let netns = getNetns link disconnected <- asks $ S.member netns . tsDisconnectedUp . snd if disconnected then inner else do local (fmap $ \s -> s { tsDisconnectedUp = S.insert netns $ tsDisconnectedUp s }) $ do - liftIO $ callOn n $ "ip link set veth0 down" + linkDown link x <- inner - liftIO $ callOn n $ "ip link set veth0 up" + linkUp link return x -withDisconnectedBridge :: HasNetns n => n -> TestRun a -> TestRun a -withDisconnectedBridge n inner = do - let netns = getNetns n +withDisconnectedBridge :: Link Bridge -> TestRun a -> TestRun a +withDisconnectedBridge bridge inner = do + let netns = getNetns bridge disconnected <- asks $ S.member netns . tsDisconnectedBridge . snd if disconnected then inner else do local (fmap $ \s -> s { tsDisconnectedBridge = S.insert netns $ tsDisconnectedBridge s }) $ do - liftIO $ callOn n $ "ip link set br0 down" + linkDown bridge x <- inner - liftIO $ callOn n $ "ip link set br0 up" + linkUp bridge return x withNodePacketLoss :: Node -> Scientific -> TestRun a -> TestRun a diff --git a/src/Run/Monad.hs b/src/Run/Monad.hs index 1036749..037f585 100644 --- a/src/Run/Monad.hs +++ b/src/Run/Monad.hs @@ -97,6 +97,9 @@ instance MonadEval TestRun where instance MonadOutput TestRun where getOutput = asks $ teOutput . fst +instance MonadPIO TestRun where + postpone = liftIO + finally :: MonadError e m => m a -> m b -> m a finally act handler = do |