325 lines
7.4 KiB
Haskell
325 lines
7.4 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module P2Prc ( runP2Prc )
|
|
where
|
|
|
|
|
|
import System.Exit ( ExitCode(ExitFailure) )
|
|
|
|
import System.Process
|
|
( readProcessWithExitCode
|
|
, proc
|
|
, createProcess
|
|
, terminateProcess
|
|
, ProcessHandle
|
|
)
|
|
|
|
import Control.Concurrent ( threadDelay )
|
|
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as LBC8
|
|
|
|
import Data.Aeson
|
|
|
|
|
|
import Environment ( cleanEnvironment )
|
|
import Error (IOEitherError, Error(..), assignError)
|
|
import JSON
|
|
|
|
|
|
-- URGENT TASKS
|
|
--
|
|
-- TODO: setup the project as a haskell library
|
|
-- TODO: splitting code on different files and directories
|
|
-- TODO: lock cabal index
|
|
-- TODO: add Haddock documentation
|
|
--
|
|
-- TODO: P2PRC setup
|
|
-- p2prc runtime packaging
|
|
-- Perhaps create internal script to run P2PRC from nix flake
|
|
-- "nix flake run ..."
|
|
-- simplify packaging
|
|
-- check version P2PRC: only run if version if above a certain value
|
|
-- setup p2prc command
|
|
-- check if p2prc command is available in environment first
|
|
-- otherwise check folder above
|
|
--
|
|
-- TODO: publish haskell library
|
|
--
|
|
-- TODO: add use case examples (extra-source_files)
|
|
|
|
|
|
|
|
-- Module: API
|
|
|
|
runP2Prc :: IO ()
|
|
runP2Prc = do
|
|
|
|
|
|
-- important but not urgent yet
|
|
--
|
|
-- TODO: Change Standard Library
|
|
-- TODO: add GDTA syntax to data types
|
|
-- TODO: need monad transformers to refactor the code
|
|
--
|
|
-- TODO: parse IO arguments;
|
|
-- TODO: create DSL from the standard input
|
|
--
|
|
-- TODO: setup nix flake package
|
|
-- TODO: add quickcheck testing (quickchecking-dynamic)
|
|
|
|
|
|
|
|
eitherP2prcAPI <- getP2prcAPI
|
|
|
|
case eitherP2prcAPI of
|
|
(Right p2prcAPI) -> do
|
|
let
|
|
( MkP2prAPI
|
|
{ startServer = startServer
|
|
, execInitConfig = execInitConfig
|
|
, execListServers = execListServers
|
|
, execMapPort = execMapPort
|
|
}
|
|
) = p2prcAPI
|
|
|
|
|
|
configValue <- execInitConfig
|
|
|
|
-- TODO: get name of host server from config json
|
|
|
|
print configValue
|
|
putStrLn "\n\n\n"
|
|
|
|
eitherStartProcessHandle <- startServer
|
|
|
|
|
|
case eitherStartProcessHandle of
|
|
(Right startProcessHandle) -> do
|
|
|
|
let sleepNSecs i = threadDelay (i * 1000000)
|
|
|
|
sleepNSecs 5
|
|
|
|
outputStr <- execListServers
|
|
print outputStr
|
|
|
|
mapPortOut <- execMapPort $ MkMapPortRequest 3333 "domain"
|
|
|
|
|
|
case mapPortOut of
|
|
(Right v) -> print v
|
|
(Left e) -> print e
|
|
|
|
|
|
-- TODO: work on looping function
|
|
--
|
|
-- Loop (Run replica of haskell program on different $NODES)
|
|
-- - Start server
|
|
-- - wait 4 seconds
|
|
-- - Identify new node running p2prc with SSH external port exposed
|
|
-- - SSH into machine
|
|
-- - Use simple File transfer to setup files
|
|
-- - Run server
|
|
-- - Use remote machine p2prc cmd to map a port using --mp
|
|
-- - Return back the exposed public IP and port number back to stdout
|
|
|
|
|
|
terminateProcess startProcessHandle
|
|
|
|
(Left err) -> print err
|
|
|
|
(Left err) -> print err
|
|
|
|
|
|
|
|
data P2prAPI = MkP2prAPI
|
|
{ startServer :: IOEitherError ProcessHandle
|
|
, execInitConfig :: IOEitherError P2prcConfig
|
|
, execListServers :: IOEitherError IPAdressTable
|
|
, execMapPort :: MapPortRequest -> IOEitherError MapPortResponse
|
|
}
|
|
|
|
|
|
data MapPortRequest
|
|
= MkMapPortRequest Int String
|
|
|
|
|
|
getP2prcAPI :: IOEitherError P2prAPI
|
|
getP2prcAPI = do
|
|
|
|
cleanEnvironment
|
|
|
|
eitherP2prcCmd <- getP2PrcCmd
|
|
|
|
pure $ case eitherP2prcCmd of
|
|
(Right p2prcCmd) -> let
|
|
|
|
execProcP2PrcParser ::
|
|
FromJSON a =>
|
|
[CLIOpt] -> StdInput -> IOEitherError a
|
|
execProcP2PrcParser = eitherExecProcessParser p2prcCmd
|
|
-- TODO: GHC question, why does it scope down instead staying generic
|
|
|
|
execProcP2Prc = eitherExecProcess p2prcCmd
|
|
|
|
in do
|
|
|
|
Right $ MkP2prAPI
|
|
{ startServer = spawnProcP2Prc p2prcCmd [ MkOptAtomic "--s" ]
|
|
|
|
, execListServers =
|
|
execProcP2PrcParser [ MkOptAtomic "--ls" ] MkEmptyStdInput
|
|
|
|
, execMapPort =
|
|
\ (MkMapPortRequest portNumber _) ->
|
|
execProcP2PrcParser
|
|
[ MkOptTuple
|
|
( "--mp"
|
|
, show portNumber
|
|
)
|
|
-- , MkOptTuple -- TODO: add domain parameter
|
|
-- ( "--dm"
|
|
-- , domainName
|
|
-- )
|
|
]
|
|
MkEmptyStdInput
|
|
|
|
, execInitConfig = do
|
|
|
|
confInitRes <- execProcP2Prc [ MkOptAtomic "--dc" ] MkEmptyStdInput
|
|
|
|
case confInitRes of
|
|
(Right _) -> do
|
|
|
|
-- TODO: get config file name dynamically
|
|
|
|
-- TODO: change values before loading file
|
|
let fname = "/home/xecarlox/Desktop/p2p-rendering-computation/haskell/config.json" :: FilePath
|
|
|
|
|
|
-- TODO: read config check if file exists
|
|
configContent <- readFile fname
|
|
|
|
pure $ eitherErrDecode configContent
|
|
|
|
(Left err) -> pure $ Left err
|
|
|
|
}
|
|
|
|
(Left err) -> Left err
|
|
|
|
|
|
|
|
-- Module: CLI
|
|
|
|
data StdInput
|
|
= MkEmptyStdInput
|
|
| MkStdInputVal String
|
|
|
|
|
|
instance Show StdInput where
|
|
show MkEmptyStdInput = ""
|
|
show (MkStdInputVal v) = v
|
|
|
|
|
|
data CLIOpt
|
|
= MkEmptyOpts
|
|
| MkOptAtomic String
|
|
| MkOptTuple (String, String)
|
|
|
|
type CLIOptsInput = [String]
|
|
type CLICmd = String
|
|
|
|
|
|
eitherExecProcessParser ::
|
|
FromJSON a =>
|
|
CLICmd -> [CLIOpt] -> StdInput -> IOEitherError a
|
|
eitherExecProcessParser p2prcCmd opts stdInput =
|
|
do
|
|
val <- eitherExecProcess p2prcCmd opts stdInput
|
|
|
|
pure $ case val of
|
|
(Right v) -> eitherErrDecode v
|
|
(Left e) -> Left e
|
|
|
|
|
|
eitherErrDecode ::
|
|
FromJSON a =>
|
|
String -> Either Error a
|
|
eitherErrDecode = eitherErrorDecode . eitherDecode . LBC8.pack
|
|
|
|
|
|
eitherExecProcess :: CLICmd -> [CLIOpt] -> StdInput -> IOEitherError String
|
|
eitherExecProcess cmd opts input =
|
|
do
|
|
(code, out, err) <-
|
|
readProcessWithExitCode
|
|
cmd
|
|
(optsToCLI opts)
|
|
(show input)
|
|
|
|
pure $ case code of
|
|
ExitFailure i -> Left $ MkSystemError i cmd err
|
|
_ -> Right out
|
|
|
|
|
|
optsToCLI :: [CLIOpt] -> CLIOptsInput
|
|
optsToCLI = concatMap _optToCLI
|
|
where
|
|
|
|
_optToCLI :: CLIOpt -> CLIOptsInput
|
|
_optToCLI MkEmptyOpts = []
|
|
_optToCLI (MkOptAtomic o) = [o]
|
|
_optToCLI (MkOptTuple (o, v)) = [o, v]
|
|
|
|
|
|
spawnProcP2Prc :: CLICmd -> [CLIOpt] -> IOEitherError ProcessHandle
|
|
spawnProcP2Prc cmd opts =
|
|
do
|
|
let prc = proc cmd $ optsToCLI opts
|
|
|
|
creationResult <- createProcess prc
|
|
|
|
let (_, _, _, ph) = creationResult
|
|
|
|
case creationResult of
|
|
(_, _, Just _, _) -> do
|
|
|
|
terminateProcess ph
|
|
|
|
pure $ Left $ MkErrorSpawningProcess cmd
|
|
|
|
_-> pure $ Right ph
|
|
|
|
|
|
eitherErrorDecode :: Either String a -> Either Error a
|
|
eitherErrorDecode esa =
|
|
case esa of
|
|
(Left s) -> Left $ assignError s
|
|
(Right v) -> Right v
|
|
|
|
|
|
getP2PrcCmd :: IOEitherError String
|
|
getP2PrcCmd = do
|
|
|
|
-- assumes the program is ran inside the haskell module in p2prc's repo
|
|
-- assumes that last path segment is "haskell" and that p2prc binary's name is "p2p-rendering-computation"
|
|
|
|
let trimString = T.unpack . T.strip . T.pack
|
|
|
|
eitherErrPwd <- eitherExecProcess "pwd" [MkEmptyOpts] MkEmptyStdInput
|
|
|
|
case eitherErrPwd of
|
|
|
|
(Right pwdOut) ->
|
|
eitherExecProcess
|
|
"sed"
|
|
[ MkOptAtomic "s/haskell/p2p-rendering-computation/" ]
|
|
$ MkStdInputVal
|
|
$ trimString pwdOut
|
|
|
|
err -> pure err
|