git.iko.soy/distributed-process
on master help sign in
Get rid of genericMain
8a42670Edsko de Vries
distributed-process-azure/demos/Conway.hs+14−16
@@ -1,4 +1,5 @@
1{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
1{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
2+import System.Environment (getArgs)
2import Data.Data (Typeable, Data)
3import Data.Data (Typeable, Data)
3import Data.Binary (Binary(get, put))
4import Data.Binary (Binary(get, put))
4import Data.Binary.Generic (getGeneric, putGeneric)
5import Data.Binary.Generic (getGeneric, putGeneric)
@@ -13,14 +14,6 @@ import Control.Distributed.Process.Closure
13 , mkClosure
14 , mkClosure
14 )
15 )
15import Control.Distributed.Process.Backend.Azure
16import Control.Distributed.Process.Backend.Azure
16− ( Backend(findVMs)
17− , ProcessPair(ProcessPair)
18− , RemoteProcess
19− , LocalProcess
20− , remoteSend
21− , localExpect
22− )
23−import Control.Distributed.Process.Backend.Azure.GenericMain (genericMain)
24
17
25data ControllerMsg =
18data ControllerMsg =
26 ControllerExit
19 ControllerExit
@@ -43,11 +36,16 @@ echo = forever $ do
43 liftIO $ putStrLn msg
36 liftIO $ putStrLn msg
44
37
45main :: IO ()
38main :: IO ()
46−main = genericMain __remoteTable callable spawnable
39+main = do
47− where
40+ args <- getArgs
48− callable :: String -> IO (ProcessPair ())
41+ case args of
42+ "onvm":args' -> onVmMain __remoteTable args'
43+ cmd:sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
44+ params <- defaultAzureParameters sid x509 pkey
45+ let params' = params { azureSshUserName = user }
46+ backend <- initializeBackend params' cloudService
47+ Just vm <- findNamedVM backend virtualMachine
48+ case cmd of
49+ "start" -> callOnVM backend vm port $
49− callable "start" = return $ ProcessPair ($(mkClosure 'conwayStart) ()) echo
50+ ProcessPair ($(mkClosure 'conwayStart) ())
50− callable _ = error "callable: unknown"
51+ echo
51−
52− spawnable :: String -> IO (RemoteProcess ())
53− spawnable _ = error "spawnable: unknown"
distributed-process-azure/demos/Echo.hs+13−17
@@ -1,20 +1,12 @@
1{-# LANGUAGE TemplateHaskell #-}
1{-# LANGUAGE TemplateHaskell #-}
2
2
3import System.IO (hFlush, stdout)
3import System.IO (hFlush, stdout)
4+import System.Environment (getArgs)
4import Control.Monad (unless, forever)
5import Control.Monad (unless, forever)
5import Control.Monad.IO.Class (liftIO)
6import Control.Monad.IO.Class (liftIO)
6import Control.Distributed.Process (Process, expect)
7import Control.Distributed.Process (Process, expect)
7import Control.Distributed.Process.Closure (remotable, mkClosure)
8import Control.Distributed.Process.Closure (remotable, mkClosure)
8import Control.Distributed.Process.Backend.Azure
9import Control.Distributed.Process.Backend.Azure
9− ( Backend
10− , ProcessPair(..)
11− , RemoteProcess
12− , LocalProcess
13− , localExpect
14− , remoteSend
15− , localSend
16− )
17−import Control.Distributed.Process.Backend.Azure.GenericMain (genericMain)
18
10
19echoRemote :: () -> Backend -> Process ()
11echoRemote :: () -> Backend -> Process ()
20echoRemote () _backend = forever $ do
12echoRemote () _backend = forever $ do
@@ -34,11 +26,15 @@ echoLocal = do
34 echoLocal
26 echoLocal
35
27
36main :: IO ()
28main :: IO ()
37−main = genericMain __remoteTable callable spawnable
29+main = do
38− where
30+ args <- getArgs
39− callable :: String -> IO (ProcessPair ())
31+ case args of
32+ "onvm":args' -> onVmMain __remoteTable args'
33+ sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
34+ params <- defaultAzureParameters sid x509 pkey
35+ let params' = params { azureSshUserName = user }
36+ backend <- initializeBackend params' cloudService
37+ Just vm <- findNamedVM backend virtualMachine
38+ callOnVM backend vm port $
40− callable "echo" = return $ ProcessPair ($(mkClosure 'echoRemote) ()) echoLocal
39+ ProcessPair ($(mkClosure 'echoRemote) ())
41− callable _ = error "callable: unknown"
40+ echoLocal
42−
43− spawnable :: String -> IO (RemoteProcess ())
44− spawnable _ = error "spawnable: unknown"
distributed-process-azure/demos/Ping.hs+19−17
@@ -1,5 +1,6 @@
1{-# LANGUAGE TemplateHaskell #-}
1{-# LANGUAGE TemplateHaskell #-}
2
2
3+import System.Environment (getArgs)
3import Data.Binary (encode, decode)
4import Data.Binary (encode, decode)
4import Control.Monad (forever)
5import Control.Monad (forever)
5import Control.Monad.IO.Class (liftIO)
6import Control.Monad.IO.Class (liftIO)
@@ -16,14 +17,6 @@ import Control.Distributed.Process
16 )
17 )
17import Control.Distributed.Process.Closure (remotable, mkClosure)
18import Control.Distributed.Process.Closure (remotable, mkClosure)
18import Control.Distributed.Process.Backend.Azure
19import Control.Distributed.Process.Backend.Azure
19− ( Backend
20− , ProcessPair(..)
21− , RemoteProcess
22− , LocalProcess
23− , localExpect
24− , remoteSend
25− )
26−import Control.Distributed.Process.Backend.Azure.GenericMain (genericMain)
27import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
20import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
28
21
29pingServer :: () -> Backend -> Process ()
22pingServer :: () -> Backend -> Process ()
@@ -59,12 +52,21 @@ pingClientLocal :: LocalProcess ()
59pingClientLocal = localExpect >>= liftIO . putStrLn
52pingClientLocal = localExpect >>= liftIO . putStrLn
60
53
61main :: IO ()
54main :: IO ()
62−main = genericMain __remoteTable callable spawnable
55+main = do
63− where
56+ args <- getArgs
64− callable :: String -> IO (ProcessPair ())
57+ case args of
58+ "onvm":args' -> onVmMain __remoteTable args'
59+ "list":sid:x509:pkey:_ -> do
60+ params <- defaultAzureParameters sid x509 pkey
61+ css <- cloudServices (azureSetup params)
62+ mapM_ print css
63+ cmd:sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
64+ params <- defaultAzureParameters sid x509 pkey
65+ let params' = params { azureSshUserName = user }
66+ backend <- initializeBackend params' cloudService
67+ Just vm <- findNamedVM backend virtualMachine
68+ case cmd of
69+ "server" -> spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
70+ "client" -> callOnVM backend vm port $
65− callable "client" = return $ ProcessPair ($(mkClosure 'pingClientRemote) ()) pingClientLocal
71+ ProcessPair ($(mkClosure 'pingClientRemote) ())
66− callable _ = error "callable: unknown"
72+ pingClientLocal
67−
68− spawnable :: String -> IO (RemoteProcess ())
69− spawnable "server" = return $ ($(mkClosure 'pingServer) ())
70− spawnable _ = error "spawnable: unknown"
distributed-process-azure/distributed-process-azure.cabal+1−2
@@ -39,8 +39,7 @@ Library
39 unix >= 2.5 && < 2.6,
39 unix >= 2.5 && < 2.6,
40 network-transport >= 0.2 && < 0.3,
40 network-transport >= 0.2 && < 0.3,
41 mtl >= 2.1 && < 2.2
41 mtl >= 2.1 && < 2.2
42− Exposed-modules: Control.Distributed.Process.Backend.Azure,
42+ Exposed-modules: Control.Distributed.Process.Backend.Azure
43− Control.Distributed.Process.Backend.Azure.GenericMain
44 Extensions: ViewPatterns,
43 Extensions: ViewPatterns,
45 RankNTypes,
44 RankNTypes,
46 ExistentialQuantification,
45 ExistentialQuantification,
distributed-process-azure/src/Control/Distributed/Process/Backend/Azure.hs+221−150
@@ -103,13 +103,13 @@
103--
103--
104-- > cabal-dev install distributed-process-azure -f build-demos
104-- > cabal-dev install distributed-process-azure -f build-demos
105--
105--
106−-- We can use any of the demos to test our setup; we will use the @ping@ demo:
106+-- We can the @cloud-haskell-azure-ping@ demo to test our setup:
107--
107--
108-- > cloud-haskell-azure-ping list \
108-- > cloud-haskell-azure-ping list \
109−-- > --subscription-id <<your subscription ID>> \
109+-- > <<your subscription ID>> \
110−-- > --certificate /path/to/credentials.x509 \
110+-- > /path/to/credentials.x509 \
111−-- > --private /path/to/credentials.private
111+-- > /path/to/credentials.private
112−--
112+--
113-- (you can find your subscription ID in the @.publishsettings@ file from the previous step).
113-- (you can find your subscription ID in the @.publishsettings@ file from the previous step).
114-- If everything went well, this will output something like
114-- If everything went well, this will output something like
115--
115--
@@ -182,63 +182,35 @@
182-- machine will echo this back; the local machine will wait for the echo, show
182-- machine will echo this back; the local machine will wait for the echo, show
183-- the echo, and repeat.
183-- the echo, and repeat.
184--
184--
185−-- Before you can try it you will first need to copy the executable to the remote server:
185+-- Before you can try it you will first need to copy the executable (for
186−--
186+-- example, using scp, although the Azure backend also provides this natively
187−-- > cloud-haskell-azure-echo install \
188−-- > --subscription-id <<your subscription ID>> \
189−-- > --certificate /path/to/credentials.x509 \
190−-- > --private /path/to/credentials.private \
191−-- > --user <<remote username>> \
192−-- > --cloud-service <<Cloud Service name>> \
193−-- > --virtual-machine <<Virtual Machine name>>
194−--
195−-- (If you leave out the @--virtual-machine@ argument the binary will be copied
196−-- to every virtual machine in the specified cloud service). Once installed,
197−-- you can run it as follows:
187+-- in Haskell). Once that's done, you can run the demo as follows:
198−--
188+--
199−-- > cloud-haskell-azure-echo run \
189+-- > cloud-haskell-azure-echo \
200−-- > --subscription-id <<your subscription ID>> \
190+-- > <<subscription ID>> \
201−-- > --certificate /path/to/credentials.x509 \
191+-- > /path/to/credentials.x509 \
202−-- > --private /path/to/credentials.private \
192+-- > /path/to/credentials.private \
203−-- > --user <<remote username>> \
193+-- > <<remote username>> \
204−-- > --cloud-service <<Cloud Service name>> \
194+-- > <<cloud service name>> \
205−-- > --virtual-machine <<Virtual Machine name>> \
195+-- > <<virtual machine name>> \
206−-- > --port 8080 \
196+-- > <<port number>>
207−-- > --closure echo
208−-- > CHDemo1: # Everything I type will be echoed back
197+-- > # Everything I type gets echoed back
209−-- > Echo: Everything I type will be echoed back
198+-- > Echo: Everything I type gets echoed back
210-- > # Until I enter a blank line
199-- > # Until I enter a blank line
211-- > Echo: Until I enter a blank line
200-- > Echo: Until I enter a blank line
212-- > #
201-- > #
213--
202--
214−-- "Control.Distributed.Process.Backend.Azure.GenericMain" provides a generic
215−-- main function that you can use to structure your code. It provides command line
216−-- arguments such as the ones we saw in section /Testing the Setup/, it
217−-- initializes the Azure backend, and it takes care of running your code on the
218−-- remote machines. You don't have to use 'genericMain' if you prefer not to,
219−-- but then it will be your own responsibility to initialize Azure and to make
220−-- sure that your executable does the right thing when it's invoked on the
221−-- remote node. In these notes we will assume that you will use 'genericMain'.
222−--
223-- The full @echo@ demo is
203-- The full @echo@ demo is
224--
204--
225-- > {-# LANGUAGE TemplateHaskell #-}
205-- > {-# LANGUAGE TemplateHaskell #-}
226-- >
206-- >
227-- > import System.IO (hFlush, stdout)
207-- > import System.IO (hFlush, stdout)
208+-- > import System.Environment (getArgs)
228-- > import Control.Monad (unless, forever)
209-- > import Control.Monad (unless, forever)
229-- > import Control.Monad.IO.Class (liftIO)
210-- > import Control.Monad.IO.Class (liftIO)
230-- > import Control.Distributed.Process (Process, expect)
211-- > import Control.Distributed.Process (Process, expect)
231-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
212-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
232-- > import Control.Distributed.Process.Backend.Azure
213-- > import Control.Distributed.Process.Backend.Azure
233−-- > ( Backend
234−-- > , ProcessPair(..)
235−-- > , RemoteProcess
236−-- > , LocalProcess
237−-- > , localExpect
238−-- > , remoteSend
239−-- > , localSend
240−-- > )
241−-- > import Control.Distributed.Process.Backend.Azure.GenericMain (genericMain)
242-- >
214-- >
243-- > echoRemote :: () -> Backend -> Process ()
215-- > echoRemote :: () -> Backend -> Process ()
244-- > echoRemote () _backend = forever $ do
216-- > echoRemote () _backend = forever $ do
@@ -258,84 +230,61 @@
258-- > echoLocal
230-- > echoLocal
259-- >
231-- >
260-- > main :: IO ()
232-- > main :: IO ()
261−-- > main = genericMain __remoteTable callable spawnable
233+-- > main = do
262−-- > where
234+-- > args <- getArgs
263−-- > callable :: String -> IO (ProcessPair ())
235+-- > case args of
236+-- > "onvm":args' -> onVmMain __remoteTable args'
237+-- > sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
238+-- > params <- defaultAzureParameters sid x509 pkey
239+-- > let params' = params { azureSshUserName = user }
240+-- > backend <- initializeBackend params' cloudService
241+-- > Just vm <- findNamedVM backend virtualMachine
242+-- > callOnVM backend vm port $
264−-- > callable "echo" = return $ ProcessPair ($(mkClosure 'echoRemote) ()) echoLocal
243+-- > ProcessPair ($(mkClosure 'echoRemote) ())
265−-- > callable _ = error "callable: unknown"
244+-- > echoLocal
266−-- >
267−-- > spawnable :: String -> IO (RemoteProcess ())
268−-- > spawnable _ = error "spawnable: unknown"
269−--
270−-- 'genericMain' expects three arguments: the first is the standard
271−-- '__remoteTable' argument familiar from
272−-- "Control.Distributed.Process.Closure"; the second and third should map
273−-- strings to process pairs (for use with 'callOnVM') or remote processes (for
274−-- use with 'spawnOnVM') respectively.
275−--
276−-- When you invoke the @echo@ demo with @--closure echo@ the 'genericMain'
277−-- function calls 'callOnVM' with the process pair consisting of 'echoRemote'
278−-- and 'echoLocal'. Hopefully the definition of these two functions is
279−-- self-explanatory.
280--
245--
281-- [Second Example: Ping]
246-- [Second Example: Ping]
282--
247--
283-- The second example differs from the @echo@ demo in that it uses both
248-- The second example differs from the @echo@ demo in that it uses both
284−-- 'callable' ('callOnVM') and 'spawnable' ('spawnOnVM'). It uses the latter to
249+-- 'callOnVM' and 'spawnOnVM'. It uses the latter to
285-- install a ping server which keeps running in the background; it uses the
250-- install a ping server which keeps running in the background; it uses the
286-- former to run a ping client which sends a request to the ping server and
251-- former to run a ping client which sends a request to the ping server and
287−-- outputs the response. As with the @echo@ server, we must first copy the
252+-- outputs the response.
288−-- executable:
253+--
289−--
254+-- As with the @echo@ demo, make sure to copy the executable to the remote server first.
290−-- > cloud-haskell-azure-ping install \
255+-- Once that is done, you can start a ping server on a virtual machine using
291−-- > --subscription-id <<your subscription ID>> \
292−-- > --certificate /path/to/credentials.x509 \
293−-- > --private /path/to/credentials.private \
294−-- > --user <<remote username>> \
295−-- > --cloud-service <<Cloud Service name>> \
296−-- > CHDemo3: Done
297−-- > CHDemo2: Done
298−-- > CHDemo1: Done
299−--
300−-- Now we can start the ping server on every virtual machine in the cloud
301−-- service (to install it to a single virtual machine only, pass the
302−-- @--virtual-machine@ argument):
303−--
256+--
304−-- > cloud-haskell-azure-ping run \
257+-- > cloud-haskell-azure-ping server \
305−-- > --subscription-id <<your subscription ID>> \
258+-- > <<subscription ID>> \
306−-- > --certificate /path/to/credentials.x509 \
259+-- > /path/to/credentials.x509 \
307−-- > --private /path/to/credentials.private \
260+-- > /path/to/credentials.private \
308−-- > --user <<remote username>> \
261+-- > <<remote username>> \
309−-- > --cloud-service <<Cloud Service name>> \
262+-- > <<cloud service name>> \
263+-- > <<virtual machine name>> \
310−-- > --port 8080
264+-- > <<port number>>
311−-- > --closure server
312−-- > --background
313−-- > CHDemo3: OK
314−-- > CHDemo2: OK
315−-- > CHDemo1: OK
316--
265--
317-- Finally, we can run the ping client:
266-- Finally, we can run the ping client:
318--
267--
319−-- > cloud-haskell-azure-ping run
268+-- > cloud-haskell-azure-ping client \
320−-- > --subscription-id <<your subscription ID>> \
269+-- > <<subscription ID>> \
321−-- > --certificate /path/to/credentials.x509 \
270+-- > /path/to/credentials.x509 \
322−-- > --private /path/to/credentials.private \
271+-- > /path/to/credentials.private \
323−-- > --user <<remote username>> \
272+-- > <<remote username>> \
324−-- > --cloud-service <<Cloud Service name>> \
273+-- > <<cloud service name>> \
325−-- > --port 8081
274+-- > <<virtual machine name>> \
326−-- > --closure client
275+-- > <<DIFFERENT port number>>
327−-- > CHDemo3: Ping server at pid://10.119.182.127:8080:0:2 ok
328−-- > CHDemo2: Ping server at pid://10.59.238.125:8080:0:2 ok
329−-- > CHDemo1: Ping server at pid://10.59.224.122:8080:0:2 ok
276+-- > Ping server at pid://10.59.224.122:8080:0:2 ok
330--
277--
331-- Note that we must pass a different port number, because the client will run
278-- Note that we must pass a different port number, because the client will run
332-- within its own Cloud Haskell instance.
279-- within its own Cloud Haskell instance.
333--
280--
334−-- The code for the ping demo is similar to the echo demo, but demonstrates
281+-- The code for the @ping@ demo is similar to the @echo@ demo, but uses both
335−-- both 'callable' and 'spawnable' and shows one way to discover nodes.
282+-- 'callOnVM' and 'spawnOnVM' and demonstrates a way to discover processes (in
283+-- this case, through a PID file).
336--
284--
337-- > {-# LANGUAGE TemplateHaskell #-}
285-- > {-# LANGUAGE TemplateHaskell #-}
338-- >
286-- >
287+-- > import System.Environment (getArgs)
339-- > import Data.Binary (encode, decode)
288-- > import Data.Binary (encode, decode)
340-- > import Control.Monad (forever)
289-- > import Control.Monad (forever)
341-- > import Control.Monad.IO.Class (liftIO)
290-- > import Control.Monad.IO.Class (liftIO)
@@ -352,14 +301,6 @@
352-- > )
301-- > )
353-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
302-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
354-- > import Control.Distributed.Process.Backend.Azure
303-- > import Control.Distributed.Process.Backend.Azure
355−-- > ( Backend
356−-- > , ProcessPair(..)
357−-- > , RemoteProcess
358−-- > , LocalProcess
359−-- > , localExpect
360−-- > , remoteSend
361−-- > )
362−-- > import Control.Distributed.Process.Backend.Azure.GenericMain (genericMain)
363-- > import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
304-- > import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
364-- >
305-- >
365-- > pingServer :: () -> Backend -> Process ()
306-- > pingServer :: () -> Backend -> Process ()
@@ -395,28 +336,34 @@
395-- > pingClientLocal = localExpect >>= liftIO . putStrLn
336-- > pingClientLocal = localExpect >>= liftIO . putStrLn
396-- >
337-- >
397-- > main :: IO ()
338-- > main :: IO ()
398−-- > main = genericMain __remoteTable callable spawnable
339+-- > main = do
399−-- > where
340+-- > args <- getArgs
400−-- > callable :: String -> IO (ProcessPair ())
341+-- > case args of
342+-- > "onvm":args' -> onVmMain __remoteTable args'
343+-- > "list":sid:x509:pkey:_ -> do
344+-- > params <- defaultAzureParameters sid x509 pkey
345+-- > css <- cloudServices (azureSetup params)
346+-- > mapM_ print css
347+-- > cmd:sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
348+-- > params <- defaultAzureParameters sid x509 pkey
349+-- > let params' = params { azureSshUserName = user }
350+-- > backend <- initializeBackend params' cloudService
351+-- > Just vm <- findNamedVM backend virtualMachine
352+-- > case cmd of
353+-- > "server" -> spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
354+-- > "client" -> callOnVM backend vm port $
401−-- > callable "client" = return $ ProcessPair ($(mkClosure 'pingClientRemote) ()) pingClientLocal
355+-- > ProcessPair ($(mkClosure 'pingClientRemote) ())
402−-- > callable _ = error "callable: unknown"
356+-- > pingClientLocal
403−-- >
404−-- > spawnable :: String -> IO (RemoteProcess ())
405−-- > spawnable "server" = return $ ($(mkClosure 'pingServer) ())
406−-- > spawnable _ = error "spawnable: unknown"
407−--
408−-- The ping server stores its PID in a file, which the ping client attempts to
409−-- read. This kind of pattern is typical, and is provided for in the high-level API.
410−--
411−-- [Using the High-Level API]
412−--
413−-- TODO: Does not yet exist.
414module Control.Distributed.Process.Backend.Azure
357module Control.Distributed.Process.Backend.Azure
415 ( -- * Initialization
358 ( -- * Initialization
416 Backend(..)
359 Backend(..)
417 , AzureParameters(..)
360 , AzureParameters(..)
418 , defaultAzureParameters
361 , defaultAzureParameters
419 , initializeBackend
362 , initializeBackend
363+ -- * Utilities
364+ , findNamedVM
365+ -- * On-VM main
366+ , onVmMain
420 -- * Re-exports from Azure Service Management
367 -- * Re-exports from Azure Service Management
421 , CloudService(..)
368 , CloudService(..)
422 , VirtualMachine(..)
369 , VirtualMachine(..)
@@ -433,10 +380,22 @@ module Control.Distributed.Process.Backend.Azure
433 , remoteThrow
380 , remoteThrow
434 ) where
381 ) where
435
382
383+import Prelude hiding (catch)
436import System.Environment (getEnv)
384import System.Environment (getEnv)
437import System.FilePath ((</>), takeFileName)
385import System.FilePath ((</>), takeFileName)
438import System.Environment.Executable (getExecutablePath)
386import System.Environment.Executable (getExecutablePath)
439−import System.IO (stdout, hFlush)
387+import System.IO
388+ ( stdout
389+ , hFlush
390+ , hSetBinaryMode
391+ , stdin
392+ , stdout
393+ , stderr
394+ , Handle
395+ , hClose
396+ )
397+import qualified System.Posix.Process as Posix (forkProcess, createSession)
398+import Data.Maybe (listToMaybe)
440import Data.Binary (Binary(get, put), encode, decode)
399import Data.Binary (Binary(get, put), encode, decode)
441import Data.Digest.Pure.MD5 (md5, MD5Digest)
400import Data.Digest.Pure.MD5 (md5, MD5Digest)
442import qualified Data.ByteString as BSS
401import qualified Data.ByteString as BSS
@@ -444,6 +403,7 @@ import qualified Data.ByteString as BSS
444 , length
403 , length
445 , concat
404 , concat
446 , hPut
405 , hPut
406+ , hGet
447 )
407 )
448import qualified Data.ByteString.Char8 as BSSC (pack)
408import qualified Data.ByteString.Char8 as BSSC (pack)
449import qualified Data.ByteString.Lazy as BSL
409import qualified Data.ByteString.Lazy as BSL
@@ -453,14 +413,23 @@ import qualified Data.ByteString.Lazy as BSL
453 , fromChunks
413 , fromChunks
454 , toChunks
414 , toChunks
455 , hPut
415 , hPut
416+ , hGet
456 )
417 )
457import qualified Data.ByteString.Lazy.Char8 as BSLC (unpack)
418import qualified Data.ByteString.Lazy.Char8 as BSLC (unpack)
458import Data.Typeable (Typeable)
419import Data.Typeable (Typeable)
420+import Data.Foldable (forM_)
459import Control.Applicative ((<$>), (<*>))
421import Control.Applicative ((<$>), (<*>))
460import Control.Monad (void, unless)
422import Control.Monad (void, unless)
461import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
423import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
462−import Control.Exception (Exception, catches, Handler(Handler), throwIO)
424+import Control.Exception
425+ ( Exception
426+ , catches
427+ , Handler(Handler)
428+ , throwIO
429+ , SomeException
430+ )
463import Control.Monad.IO.Class (MonadIO, liftIO)
431import Control.Monad.IO.Class (MonadIO, liftIO)
432+import Control.Concurrent.MVar (MVar, newEmptyMVar, readMVar, putMVar)
464
433
465-- Azure
434-- Azure
466import Network.Azure.ServiceManagement
435import Network.Azure.ServiceManagement
@@ -499,12 +468,30 @@ import qualified Network.SSH.Client.LibSSH2.Errors as SSH
499 )
468 )
500
469
501-- CH
470-- CH
502−import Control.Distributed.Process (Process, Closure)
471+import Control.Distributed.Process
472+ ( Process
473+ , Closure
474+ , RemoteTable
475+ , catch
476+ , unClosure
477+ )
503import Control.Distributed.Process.Serializable (Serializable)
478import Control.Distributed.Process.Serializable (Serializable)
504−import Control.Distributed.Process.Internal.Types
479+import qualified Control.Distributed.Process.Internal.Types as CH
480+ ( LocalNode
481+ , LocalProcess(processQueue)
482+ , Message
483+ , payloadToMessage
505− ( messageToPayload
484+ , messageToPayload
506 , createMessage
485 , createMessage
507 )
486 )
487+import Control.Distributed.Process.Node
488+ ( runProcess
489+ , forkProcess
490+ , newLocalNode
491+ , initRemoteTable
492+ )
493+import Control.Distributed.Process.Internal.CQueue (CQueue, enqueue)
494+import Network.Transport.TCP (createTransport, defaultTCPParameters)
508import Network.Transport.Internal (encodeInt32, decodeInt32, prependLength)
495import Network.Transport.Internal (encodeInt32, decodeInt32, prependLength)
509
496
510-- | Azure backend
497-- | Azure backend
@@ -609,10 +596,11 @@ apiCallOnVM :: AzureParameters
609apiCallOnVM params cloudService vm port ppair =
596apiCallOnVM params cloudService vm port ppair =
610 withSSH2 params vm $ \s -> do
597 withSSH2 params vm $ \s -> do
611 let exe = "PATH=. " ++ azureSshRemotePath params
598 let exe = "PATH=. " ++ azureSshRemotePath params
612− ++ " onvm run "
599+ ++ " onvm"
613− ++ " --host " ++ vmIpAddress vm
600+ ++ " " ++ vmIpAddress vm
614− ++ " --port " ++ port
601+ ++ " " ++ port
615− ++ " --cloud-service " ++ cloudService
602+ ++ " " ++ cloudService
603+ ++ " False"
616 ++ " 2>&1"
604 ++ " 2>&1"
617 let paramsEnc = encode params
605 let paramsEnc = encode params
618 (status, r) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
606 (status, r) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
@@ -639,11 +627,11 @@ apiSpawnOnVM params cloudService vm port proc =
639 withSSH2 params vm $ \s -> do
627 withSSH2 params vm $ \s -> do
640 -- TODO: reduce duplication with apiCallOnVM
628 -- TODO: reduce duplication with apiCallOnVM
641 let exe = "PATH=. " ++ azureSshRemotePath params
629 let exe = "PATH=. " ++ azureSshRemotePath params
642− ++ " onvm run "
630+ ++ " onvm"
643− ++ " --host " ++ vmIpAddress vm
631+ ++ " " ++ vmIpAddress vm
644− ++ " --port " ++ port
632+ ++ " " ++ port
645− ++ " --cloud-service " ++ cloudService
633+ ++ " " ++ cloudService
646− ++ " --background "
634+ ++ " True"
647 ++ " 2>&1"
635 ++ " 2>&1"
648 let paramsEnc = encode params
636 let paramsEnc = encode params
649 (status, r) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
637 (status, r) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
@@ -702,6 +690,15 @@ catchSshError s io =
702localHash :: AzureParameters -> IO MD5Digest
690localHash :: AzureParameters -> IO MD5Digest
703localHash params = md5 <$> BSL.readFile (azureSshLocalPath params)
691localHash params = md5 <$> BSL.readFile (azureSshLocalPath params)
704
692
693+--------------------------------------------------------------------------------
694+-- Utilities --
695+--------------------------------------------------------------------------------
696+
697+-- | Find a virtual machine with a particular name
698+findNamedVM :: Backend -> String -> IO (Maybe VirtualMachine)
699+findNamedVM backend vm =
700+ listToMaybe . filter ((== vm) . vmName) <$> findVMs backend
701+
705--------------------------------------------------------------------------------
702--------------------------------------------------------------------------------
706-- Local and remote processes --
703-- Local and remote processes --
707--------------------------------------------------------------------------------
704--------------------------------------------------------------------------------
@@ -735,8 +732,8 @@ localSend x = LocalProcess $ do
735 ch <- ask
732 ch <- ask
736 liftIO $ mapM_ (SSH.writeChannel ch)
733 liftIO $ mapM_ (SSH.writeChannel ch)
737 . prependLength
734 . prependLength
738− . messageToPayload
735+ . CH.messageToPayload
739− . createMessage
736+ . CH.createMessage
740 $ x
737 $ x
741
738
742-- | Wait for a message from the remote process (see 'ProcessPair').
739-- | Wait for a message from the remote process (see 'ProcessPair').
@@ -773,6 +770,80 @@ remoteSend' flags x = do
773 BSL.hPut stdout enc
770 BSL.hPut stdout enc
774 hFlush stdout
771 hFlush stdout
775
772
773+--------------------------------------------------------------------------------
774+-- On-VM main --
775+--------------------------------------------------------------------------------
776+
777+-- | Program main when run on the VM. A typical 'main' function looks like
778+--
779+-- > main :: IO ()
780+-- > main = do
781+-- > args <- getArgs
782+-- > case args of
783+-- > "onvm":args' -> onVmMain __remoteTable args'
784+-- > _ -> -- your normal main
785+onVmMain :: (RemoteTable -> RemoteTable) -> [String] -> IO ()
786+onVmMain rtable [host, port, cloudService, bg] = do
787+ hSetBinaryMode stdin True
788+ hSetBinaryMode stdout True
789+ Just procEnc <- getWithLength stdin
790+ Just paramsEnc <- getWithLength stdin
791+ backend <- initializeBackend (decode paramsEnc) cloudService
792+ let proc = decode procEnc
793+ lprocMVar <- newEmptyMVar :: IO (MVar CH.LocalProcess)
794+ if read bg
795+ then detach $ startCH proc lprocMVar backend runProcess (\_ -> return ())
796+ else do
797+ startCH proc lprocMVar backend forkProcess (liftIO . remoteThrow)
798+ lproc <- readMVar lprocMVar
799+ queueFromHandle stdin (CH.processQueue lproc)
800+ where
801+ startCH :: RemoteProcess ()
802+ -> MVar CH.LocalProcess
803+ -> Backend
804+ -> (CH.LocalNode -> Process () -> IO a)
805+ -> (SomeException -> Process ())
806+ -> IO ()
807+ startCH rproc lprocMVar backend go exceptionHandler = do
808+ mTransport <- createTransport host port defaultTCPParameters
809+ case mTransport of
810+ Left err -> remoteThrow err
811+ Right transport -> do
812+ node <- newLocalNode transport (rtable initRemoteTable)
813+ void . go node $ do
814+ ask >>= liftIO . putMVar lprocMVar
815+ proc <- unClosure rproc :: Process (Backend -> Process ())
816+ catch (proc backend) exceptionHandler
817+onVmMain _ _
818+ = error "Invalid arguments passed on onVmMain"
819+
820+-- | Read a 4-byte length @l@ and then an @l@-byte payload
821+--
822+-- Returns Nothing on EOF
823+getWithLength :: Handle -> IO (Maybe BSL.ByteString)
824+getWithLength h = do
825+ lenEnc <- BSS.hGet h 4
826+ if BSS.length lenEnc < 4
827+ then return Nothing
828+ else do
829+ let len = decodeInt32 lenEnc
830+ bs <- BSL.hGet h len
831+ if BSL.length bs < fromIntegral len
832+ then return Nothing
833+ else return (Just bs)
834+
835+queueFromHandle :: Handle -> CQueue CH.Message -> IO ()
836+queueFromHandle h q = do
837+ mPayload <- getWithLength stdin
838+ forM_ mPayload $ \payload -> do
839+ enqueue q $ CH.payloadToMessage (BSL.toChunks payload)
840+ queueFromHandle h q
841+
842+detach :: IO () -> IO ()
843+detach io = do
844+ mapM_ hClose [stdin, stdout, stderr]
845+ void . Posix.forkProcess $ void Posix.createSession >> io
846+
776--------------------------------------------------------------------------------
847--------------------------------------------------------------------------------
777-- SSH utilities --
848-- SSH utilities --
778--------------------------------------------------------------------------------
849--------------------------------------------------------------------------------
distributed-process-azure/src/Control/Distributed/Process/Backend/Azure/GenericMain.hsdeleted−407
@@ -1,407 +0,0 @@
1−-- | Generic main
2−module Control.Distributed.Process.Backend.Azure.GenericMain
3− ( genericMain
4− , ProcessPair(..)
5− ) where
6−
7−import Prelude hiding (catch)
8−import System.Exit (exitSuccess, exitFailure)
9−import System.IO
10− ( hFlush
11− , stdout
12− , stdin
13− , stderr
14− , hSetBinaryMode
15− , hClose
16− , Handle
17− )
18−import Data.Foldable (forM_)
19−import Data.Binary (decode)
20−import qualified Data.ByteString.Lazy as BSL (ByteString, hGet, toChunks, length)
21−import qualified Data.ByteString as BSS (hGet, length)
22−import Control.Monad (unless, forM, void)
23−import Control.Monad.Reader (ask)
24−import Control.Exception (SomeException)
25−import Control.Applicative ((<$>), (<*>), optional)
26−import Control.Monad.IO.Class (liftIO)
27−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, readMVar)
28−
29−-- Posix
30−import qualified System.Posix.Process as Posix (forkProcess, createSession)
31−
32−-- SSH
33−import qualified Network.SSH.Client.LibSSH2.Foreign as SSH
34− ( initialize
35− , exit
36− )
37−
38−-- Command line options
39−import Options.Applicative
40− ( Parser
41− , strOption
42− , long
43− , (&)
44− , metavar
45− , help
46− , subparser
47− , command
48− , info
49− , progDesc
50− , execParser
51− , helper
52− , fullDesc
53− , header
54− , switch
55− )
56−
57−-- CH
58−import Control.Distributed.Process
59− ( RemoteTable
60− , Process
61− , unClosure
62− , catch
63− )
64−import Control.Distributed.Process.Node
65− ( newLocalNode
66− , runProcess
67− , forkProcess
68− , initRemoteTable
69− , LocalNode
70− )
71−import Control.Distributed.Process.Internal.Types
72− ( LocalProcess(processQueue)
73− , payloadToMessage
74− , Message
75− )
76−import Control.Distributed.Process.Internal.CQueue (CQueue, enqueue)
77−
78−-- Azure
79−import Control.Distributed.Process.Backend.Azure
80− ( AzureParameters(azureSshUserName, azureSetup)
81− , defaultAzureParameters
82− , initializeBackend
83− , cloudServices
84− , VirtualMachine(vmName)
85− , Backend(findVMs, copyToVM, checkMD5, callOnVM, spawnOnVM)
86− , ProcessPair(..)
87− , RemoteProcess
88− , remoteThrow
89− )
90−
91−-- Transport
92−import Network.Transport.Internal (decodeInt32)
93−import Network.Transport.TCP (createTransport, defaultTCPParameters)
94−
95−--------------------------------------------------------------------------------
96−-- Main --
97−--------------------------------------------------------------------------------
98−
99−genericMain :: (RemoteTable -> RemoteTable) -- ^ Standard CH remote table
100− -> (String -> IO (ProcessPair ())) -- ^ Closures to support in 'run'
101− -> (String -> IO (RemoteProcess ())) -- ^ Closures to support in @run --background@
102− -> IO ()
103−genericMain remoteTable callable spawnable = do
104− _ <- SSH.initialize True
105− cmd <- execParser opts
106− case cmd of
107− List {} -> do
108− params <- azureParameters (azureOptions cmd) Nothing
109− css <- cloudServices (azureSetup params)
110− mapM_ print css
111− CopyTo {} -> do
112− params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
113− backend <- initializeBackend params (targetService (target cmd))
114− vms <- findMatchingVMs backend (targetVM (target cmd))
115− forM_ vms $ \vm -> do
116− putStr (vmName vm ++ ": ") >> hFlush stdout
117− copyToVM backend vm
118− putStrLn "Done"
119− CheckMD5 {} -> do
120− params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
121− backend <- initializeBackend params (targetService (target cmd))
122− vms <- findMatchingVMs backend (targetVM (target cmd))
123− matches <- forM vms $ \vm -> do
124− unless (status cmd) $ putStr (vmName vm ++ ": ") >> hFlush stdout
125− match <- checkMD5 backend vm
126− unless (status cmd) $ putStrLn $ if match then "OK" else "FAILED"
127− return match
128− if and matches
129− then exitSuccess
130− else exitFailure
131− RunOn {} | background cmd -> do
132− params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
133− backend <- initializeBackend params (targetService (target cmd))
134− vms <- findMatchingVMs backend (targetVM (target cmd))
135− rProc <- spawnable (closureId cmd)
136− forM_ vms $ \vm -> do
137− putStr (vmName vm ++ ": ") >> hFlush stdout
138− spawnOnVM backend vm (remotePort cmd) rProc
139− putStrLn "OK"
140− RunOn {} {- not (background cmd) -} -> do
141− params <- azureParameters (azureOptions cmd) (Just (sshOptions cmd))
142− backend <- initializeBackend params (targetService (target cmd))
143− vms <- findMatchingVMs backend (targetVM (target cmd))
144− procPair <- callable (closureId cmd)
145− forM_ vms $ \vm -> do
146− putStr (vmName vm ++ ": ") >> hFlush stdout
147− callOnVM backend vm (remotePort cmd) procPair
148− OnVmCommand (vmCmd@OnVmRun {}) ->
149− onVmRun (remoteTable initRemoteTable)
150− (onVmIP vmCmd)
151− (onVmPort vmCmd)
152− (onVmService vmCmd)
153− (onVmBackground vmCmd)
154− SSH.exit
155− where
156− opts = info (helper <*> commandParser)
157− ( fullDesc
158− & header "Cloud Haskell backend for Azure"
159− )
160−
161−findMatchingVMs :: Backend -> Maybe String -> IO [VirtualMachine]
162−findMatchingVMs backend Nothing = findVMs backend
163−findMatchingVMs backend (Just vm) = filter ((== vm) . vmName) `fmap` findVMs backend
164−
165−azureParameters :: AzureOptions -> Maybe SshOptions -> IO AzureParameters
166−azureParameters opts Nothing =
167− defaultAzureParameters (subscriptionId opts)
168− (pathToCert opts)
169− (pathToKey opts)
170−azureParameters opts (Just sshOpts) = do
171− params <- defaultAzureParameters (subscriptionId opts)
172− (pathToCert opts)
173− (pathToKey opts)
174− return params {
175− azureSshUserName = remoteUser sshOpts
176− }
177−
178−--------------------------------------------------------------------------------
179−-- Executing a closure on the VM --
180−--------------------------------------------------------------------------------
181−
182−onVmRun :: RemoteTable -> String -> String -> String -> Bool -> IO ()
183−onVmRun rtable host port cloudService bg = do
184− hSetBinaryMode stdin True
185− hSetBinaryMode stdout True
186− Just procEnc <- getWithLength stdin
187− Just paramsEnc <- getWithLength stdin
188− backend <- initializeBackend (decode paramsEnc) cloudService
189− let proc = decode procEnc
190− lprocMVar <- newEmptyMVar :: IO (MVar LocalProcess)
191− if bg
192− then detach $ startCH proc lprocMVar backend runProcess (\_ -> return ())
193− else do
194− startCH proc lprocMVar backend forkProcess (liftIO . remoteThrow)
195− lproc <- readMVar lprocMVar
196− queueFromHandle stdin (processQueue lproc)
197− where
198− startCH :: RemoteProcess ()
199− -> MVar LocalProcess
200− -> Backend
201− -> (LocalNode -> Process () -> IO a)
202− -> (SomeException -> Process ())
203− -> IO ()
204− startCH rproc lprocMVar backend go exceptionHandler = do
205− mTransport <- createTransport host port defaultTCPParameters
206− case mTransport of
207− Left err -> remoteThrow err
208− Right transport -> do
209− node <- newLocalNode transport rtable
210− void . go node $ do
211− ask >>= liftIO . putMVar lprocMVar
212− proc <- unClosure rproc :: Process (Backend -> Process ())
213− catch (proc backend) exceptionHandler
214−
215−-- | Read a 4-byte length @l@ and then an @l@-byte payload
216−--
217−-- Returns Nothing on EOF
218−getWithLength :: Handle -> IO (Maybe BSL.ByteString)
219−getWithLength h = do
220− lenEnc <- BSS.hGet h 4
221− if BSS.length lenEnc < 4
222− then return Nothing
223− else do
224− let len = decodeInt32 lenEnc
225− bs <- BSL.hGet h len
226− if BSL.length bs < fromIntegral len
227− then return Nothing
228− else return (Just bs)
229−
230−queueFromHandle :: Handle -> CQueue Message -> IO ()
231−queueFromHandle h q = do
232− mPayload <- getWithLength stdin
233− forM_ mPayload $ \payload -> do
234− enqueue q $ payloadToMessage (BSL.toChunks payload)
235− queueFromHandle h q
236−
237−detach :: IO () -> IO ()
238−detach io = do
239− mapM_ hClose [stdin, stdout, stderr]
240− void . Posix.forkProcess $ void Posix.createSession >> io
241−
242−--------------------------------------------------------------------------------
243−-- Command line options --
244−--------------------------------------------------------------------------------
245−
246−data AzureOptions = AzureOptions {
247− subscriptionId :: String
248− , pathToCert :: FilePath
249− , pathToKey :: FilePath
250− }
251− deriving Show
252−
253−data SshOptions = SshOptions {
254− remoteUser :: String
255− }
256− deriving Show
257−
258−data Target = Target {
259− targetService :: String
260− , targetVM :: Maybe String
261− }
262− deriving Show
263−
264−data Command =
265− List {
266− azureOptions :: AzureOptions
267− }
268− | CopyTo {
269− azureOptions :: AzureOptions
270− , sshOptions :: SshOptions
271− , target :: Target
272− }
273− | CheckMD5 {
274− azureOptions :: AzureOptions
275− , sshOptions :: SshOptions
276− , target :: Target
277− , status :: Bool
278− }
279− | RunOn {
280− azureOptions :: AzureOptions
281− , sshOptions :: SshOptions
282− , target :: Target
283− , remotePort :: String
284− , closureId :: String
285− , background :: Bool
286− }
287− | OnVmCommand {
288− _onVmCommand :: OnVmCommand
289− }
290− deriving Show
291−
292−data OnVmCommand =
293− OnVmRun {
294− onVmIP :: String
295− , onVmPort :: String
296− , onVmService :: String
297− , onVmBackground :: Bool
298− }
299− deriving Show
300−
301−azureOptionsParser :: Parser AzureOptions
302−azureOptionsParser = AzureOptions
303− <$> strOption ( long "subscription-id"
304− & metavar "SID"
305− & help "Azure subscription ID"
306− )
307− <*> strOption ( long "certificate"
308− & metavar "CERT"
309− & help "X509 certificate"
310− )
311− <*> strOption ( long "private"
312− & metavar "PRI"
313− & help "Private key in PKCS#1 format"
314− )
315−
316−sshOptionsParser :: Parser SshOptions
317−sshOptionsParser = SshOptions
318− <$> strOption ( long "user"
319− & metavar "USER"
320− & help "Remove SSH username"
321− )
322−
323−listParser :: Parser Command
324−listParser = List <$> azureOptionsParser
325−
326−copyToParser :: Parser Command
327−copyToParser = CopyTo
328− <$> azureOptionsParser
329− <*> sshOptionsParser
330− <*> targetParser
331−
332−targetParser :: Parser Target
333−targetParser = Target
334− <$> strOption ( long "cloud-service"
335− & metavar "CS"
336− & help "Cloud service name"
337− )
338− <*> optional (strOption ( long "virtual-machine"
339− & metavar "VM"
340− & help "Virtual machine name (all VMs if unspecified)"
341− ))
342−
343−checkMD5Parser :: Parser Command
344−checkMD5Parser = CheckMD5
345− <$> azureOptionsParser
346− <*> sshOptionsParser
347− <*> targetParser
348− <*> switch ( long "status"
349− & help "Don't output anything, status code shows success"
350− )
351−
352−commandParser :: Parser Command
353−commandParser = subparser
354− ( command "list" (info listParser
355− (progDesc "List Azure cloud services"))
356− & command "install" (info copyToParser
357− (progDesc "Install the executable"))
358− & command "md5" (info checkMD5Parser
359− (progDesc "Check if the remote and local MD5 hash match"))
360− & command "run" (info runOnParser
361− (progDesc "Run the executable"))
362− & command "onvm" (info onVmCommandParser
363− (progDesc "Commands used when running ON the vm (usually used internally only)"))
364− )
365−
366−runOnParser :: Parser Command
367−runOnParser = RunOn
368− <$> azureOptionsParser
369− <*> sshOptionsParser
370− <*> targetParser
371− <*> strOption ( long "port"
372− & metavar "PORT"
373− & help "Port number of the CH instance"
374− )
375− <*> strOption ( long "closure"
376− & metavar "PROC"
377− & help "Process to run on the CH instance"
378− )
379− <*> switch ( long "background"
380− & help "Run the process in the background"
381− )
382−
383−onVmRunParser :: Parser OnVmCommand
384−onVmRunParser = OnVmRun
385− <$> strOption ( long "host"
386− & metavar "IP"
387− & help "IP address"
388− )
389− <*> strOption ( long "port"
390− & metavar "PORT"
391− & help "port number"
392− )
393− <*> strOption ( long "cloud-service"
394− & metavar "CS"
395− & help "Cloud service name"
396− )
397− <*> switch ( long "background"
398− & help "Run the process in the background"
399− )
400−
401−onVmCommandParser :: Parser Command
402−onVmCommandParser = OnVmCommand <$> subparser
403− ( command "run" (info onVmRunParser
404− (progDesc "Run the executable"))
405− )
406−
407−