on master help sign in
verify-migration: ensure that graph is linear

Also verifies the it is fully connected (i.e., no isolate vertices)

commit47285408d096bc9ee038c5b80b05f3b29fb301d0 authorSridhar Ratnakumar <srid@srid.ca> dateMon 25 Jun 2018, 12:52 (8 years ago) parent45b57a4
5 files changed+46−6
lib/command/src/Obelisk/Command.hs+1−1 lib/command/src/Obelisk/Command/Upgrade.hs+14−4 lib/migration/src/Obelisk/Migration.hs+31−1 migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-handoff migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-upgrade
lib/command/src/Obelisk/Command.hs+1−1
@@ -403,7 +403,7 @@ ob = \case
403403 ObInternal_CreateMigration ->
404404 createMigrationEdgeFromHEAD "."
405405 ObInternal_VerifyMigration ->
406− verifyGraph "."
406+ verifyMigration "."
407407 ObInternal_Migrate fromHash ->
408408 migrateObelisk "." fromHash
409409 ObInternal_CLIDemo -> cliDemo
lib/command/src/Obelisk/Command/Upgrade.hs+14−4
@@ -8,7 +8,7 @@
88module Obelisk.Command.Upgrade where
99
1010import Control.Monad (forM, unless, void)
11−import Control.Monad.Catch (onException)
11+import Control.Monad.Catch (onException, try)
1212import Control.Monad.IO.Class (liftIO)
1313import Data.Maybe (catMaybes)
1414import Data.Monoid (Any (..), getAny)
@@ -183,12 +183,22 @@ computeVertexHash :: MonadObelisk m => FilePath -> m Hash
183183computeVertexHash = getDirectoryHash [migrationDirName]
184184
185185-- | Verify the integrity of the migration graph in relation to the Git repo.
186−verifyGraph :: MonadObelisk m => FilePath -> m ()
187−verifyGraph obDir = do
188− upgradeGraph <- withSpinner "Reading graph" $
186+verifyMigration :: MonadObelisk m => FilePath -> m ()
187+verifyMigration obDir = do
188+ upgradeGraph <- withSpinner "Reading migration graph" $
189189 getMigrationGraph @Text obDir MigrationGraph_ObeliskUpgrade
190190 withSpinner "Checking graph integrity" $
191191 ensureGraphIntegrity upgradeGraph
192+ withSpinner "Checking graph is linear" $
193+ -- We don't support merge commits (i.e., diamonds in a graph) yet.
194+ -- Until we do, ensure that the graph is a linear list.
195+ try (ensureGraphLinearity upgradeGraph) >>= \case
196+ Left (NonLinearGraph_MultipleAdjacents vertex) ->
197+ failWith $ "Graph is not linear; branches at vertex: " <> tshow vertex
198+ Left (NonLinearGraph_NotConnected isolatedVertices) ->
199+ failWith $ "Graph is partly linear, but with isolated vertices: " <> tshow isolatedVertices
200+ Right () ->
201+ pure ()
192202 liftIO (doesDirectoryExist $ obDir </> ".git") >>= \case
193203 True ->
194204 withSpinner "Checking existence of HEAD vertex" $
lib/migration/src/Obelisk/Migration.hs+31−1
@@ -13,6 +13,7 @@ import Data.Map (Map)
1313import qualified Data.Map as Map
1414import Data.Maybe (catMaybes)
1515import Data.Semigroup ((<>))
16+import Data.Set (Set)
1617import qualified Data.Set as Set
1718import Data.Text (Text)
1819import qualified Data.Text as T
@@ -33,6 +34,8 @@ data Migration action = Migration
3334-- A path is a list of edges
3435type Path = [(Hash, Hash)]
3536
37+data GraphInternalError = GraphInternalError Text
38+ deriving Show
3639
3740data CyclicGraphError = CyclicGraphError
3841 deriving Show
@@ -49,11 +52,18 @@ data NonEquivalentPaths = NonEquivalentPaths Hash Hash
4952data MissingEdgeInActionMap = MissingEdgeInActionMap (Hash, Hash)
5053 deriving Show
5154
55+data NonLinearGraph
56+ = NonLinearGraph_MultipleAdjacents Hash
57+ | NonLinearGraph_NotConnected (Set Hash)
58+ deriving Show
59+
60+instance Exception GraphInternalError
5261instance Exception CyclicGraphError
5362instance Exception MultipleTerminalVertices
5463instance Exception NoTerminalVertex
5564instance Exception NonEquivalentPaths
5665instance Exception MissingEdgeInActionMap
66+instance Exception NonLinearGraph
5767
5868class Action action where
5969 parseEdgeMeta :: String -> action
@@ -62,6 +72,7 @@ instance Action Text where
6272 parseEdgeMeta = T.pack
6373
6474-- | Get the directed-acylic graph of migration
75+-- TODO: Consider calling getDag only when reading the graph for efficiency.
6576getDag :: (MonadThrow m, Ord a) => AdjacencyMap a -> m (AdjacencyMap a)
6677getDag g = case topSort g of
6778 Just _ -> pure g
@@ -89,7 +100,7 @@ readGraph root name = doesDirectoryExist root >>= \case
89100 False -> do
90101 return Nothing
91102
92−-- | Ensures that the migration graph is correct
103+-- | Ensure that the migration graph is correct
93104--
94105-- In a correct migration graph: for all pairs of nodes, all paths between
95106-- those nodes have the same migration value
@@ -122,6 +133,25 @@ ensureGraphIntegrity m = do
122133 -- further above).
123134 visitFrom mempty mempty firstVertex
124135
136+-- | Ensure that the graph is a fully connected linear list
137+ensureGraphLinearity :: MonadThrow m => Migration action -> m ()
138+ensureGraphLinearity m = do
139+ graph <- fmap adjacencyMap $ getDag $ _migration_graph m
140+ firstVertex <- getFirst $ _migration_graph m
141+ let
142+ visitFrom start = case Map.lookup start graph of
143+ Nothing -> throwM $ GraphInternalError $ "Vertex not found: " <> start
144+ Just adjs -> case (Set.toList adjs) of
145+ [] -> [start]
146+ [adj] -> start : visitFrom adj
147+ _ -> throwM $ NonLinearGraph_MultipleAdjacents start
148+ visitedVertices = Set.fromList $ visitFrom firstVertex
149+ allVertices = vertexSet $ _migration_graph m
150+ unvisitedVertices = Set.difference allVertices visitedVertices
151+ if Set.null unvisitedVertices
152+ then pure ()
153+ else throwM $ NonLinearGraph_NotConnected unvisitedVertices
154+
125155-- | Find the concatenated actions between two vertices
126156--
127157-- All paths between the vertices must have the same mappend'ed action;
migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-handoffnew file

No changes to the content.

migration/80cb6b08443d55bb512e4a35b4768b72-59742705e53838ae13066a2239a72040/obelisk-upgradenew file

No changes to the content.