{-| Implementation of an execution environment that uses "libvirt-lxc". -}
module B9.LibVirtLXC ( runInEnvironment
, supportedImageTypes
, setDefaultConfig
) where
import Control.Applicative
import Control.Monad.IO.Class ( liftIO )
import System.Directory
import System.FilePath
import Text.Printf ( printf )
import Data.Char (toLower)
import B9.ShellScript
import B9.B9Monad
import B9.DiskImages
import B9.ExecEnv
import B9.ConfigUtils
lxcDefaultRamSize :: RamSize
lxcDefaultRamSize = RamSize 1 GB
supportedImageTypes :: [ImageType]
supportedImageTypes = [Raw]
runInEnvironment :: ExecEnv -> Script -> B9 Bool
runInEnvironment env scriptIn =
if emptyScript scriptIn
then return True
else setUp >>= execute
where
setUp = do
cfg <- configureLibVirtLXC
buildId <- getBuildId
buildBaseDir <- getBuildDir
uuid <- randomUUID
let scriptDirHost = buildDir > "init-script"
scriptDirGuest = "/" ++ buildId
domainFile = buildBaseDir > uuid' <.> domainConfig
domain = createDomain cfg env buildId uuid' scriptDirHost scriptDirGuest
uuid' = printf "%U" uuid
script = Begin [scriptIn, successMarkerCmd scriptDirGuest]
buildDir = buildBaseDir > uuid'
liftIO $ do
createDirectoryIfMissing True scriptDirHost
writeSh (scriptDirHost > initScript) script
writeFile domainFile domain
return $ Context scriptDirHost uuid domainFile cfg
successMarkerCmd scriptDirGuest =
In scriptDirGuest [Run "touch" [successMarkerFile]]
execute (Context scriptDirHost uuid domainFile cfg) = do
let virsh = virshCommand cfg
cmd $ printf "%s create '%s'" virsh domainFile
cmd $ printf "%s console %U" virsh uuid
checkSuccessMarker scriptDirHost
checkSuccessMarker scriptDirHost =
liftIO (doesFileExist $ scriptDirHost > successMarkerFile)
successMarkerFile = "SUCCESS"
virshCommand :: LibVirtLXCConfig -> String
virshCommand cfg = printf "%s%s -c %s" useSudo' virshPath' virshURI'
where
useSudo' = if useSudo cfg
then "sudo "
else ""
virshPath' = virshPath cfg
virshURI' = virshURI cfg
data Context = Context FilePath UUID FilePath LibVirtLXCConfig
data LibVirtLXCConfig = LibVirtLXCConfig { useSudo :: Bool
, virshPath :: FilePath
, emulator :: FilePath
, virshURI :: FilePath
, networkId :: Maybe String
, guestCapabilities :: [LXCGuestCapability]
} deriving (Read, Show)
-- | Available linux capabilities for lxc containers. This maps directly to the
-- capabilities defined in 'man 7 capabilities'.
data LXCGuestCapability = CAP_MKNOD
| CAP_AUDIT_CONTROL
| CAP_AUDIT_READ
| CAP_AUDIT_WRITE
| CAP_BLOCK_SUSPEND
| CAP_CHOWN
| CAP_DAC_OVERRIDE
| CAP_DAC_READ_SEARCH
| CAP_FOWNER
| CAP_FSETID
| CAP_IPC_LOCK
| CAP_IPC_OWNER
| CAP_KILL
| CAP_LEASE
| CAP_LINUX_IMMUTABLE
| CAP_MAC_ADMIN
| CAP_MAC_OVERRIDE
| CAP_NET_ADMIN
| CAP_NET_BIND_SERVICE
| CAP_NET_BROADCAST
| CAP_NET_RAW
| CAP_SETGID
| CAP_SETFCAP
| CAP_SETPCAP
| CAP_SETUID
| CAP_SYS_ADMIN
| CAP_SYS_BOOT
| CAP_SYS_CHROOT
| CAP_SYS_MODULE
| CAP_SYS_NICE
| CAP_SYS_PACCT
| CAP_SYS_PTRACE
| CAP_SYS_RAWIO
| CAP_SYS_RESOURCE
| CAP_SYS_TIME
| CAP_SYS_TTY_CONFIG
| CAP_SYSLOG
| CAP_WAKE_ALARM
deriving (Read, Show)
defaultLibVirtLXCConfig :: LibVirtLXCConfig
defaultLibVirtLXCConfig = LibVirtLXCConfig
True
"/usr/bin/virsh"
"/usr/lib/libvirt/libvirt_lxc"
"lxc:///"
Nothing
[ CAP_MKNOD
, CAP_SYS_ADMIN
, CAP_SYS_CHROOT
, CAP_SETGID
, CAP_SETUID
, CAP_NET_BIND_SERVICE
, CAP_SETPCAP
, CAP_SYS_PTRACE
, CAP_SYS_MODULE
]
cfgFileSection :: String
cfgFileSection = "libvirt-lxc"
useSudoK :: String
useSudoK = "use_sudo"
virshPathK :: String
virshPathK = "virsh_path"
emulatorK :: String
emulatorK = "emulator_path"
virshURIK :: String
virshURIK = "connection"
networkIdK :: String
networkIdK = "network"
guestCapabilitiesK :: String
guestCapabilitiesK = "guest_capabilities"
configureLibVirtLXC :: B9 LibVirtLXCConfig
configureLibVirtLXC = do
c <- readLibVirtConfig
traceL $ printf "USING LibVirtLXCConfig: %s" (show c)
return c
setDefaultConfig :: ConfigParser
setDefaultConfig = either (error . show) id eitherCp
where
eitherCp = do
let cp = emptyCP
c = defaultLibVirtLXCConfig
cp1 <- add_section cp cfgFileSection
cp2 <- setshow cp1 cfgFileSection useSudoK $ useSudo c
cp3 <- set cp2 cfgFileSection virshPathK $ virshPath c
cp4 <- set cp3 cfgFileSection emulatorK $ emulator c
cp5 <- set cp4 cfgFileSection virshURIK $ virshURI c
cp6 <- setshow cp5 cfgFileSection networkIdK $ networkId c
setshow cp6 cfgFileSection guestCapabilitiesK $ guestCapabilities c
readLibVirtConfig :: B9 LibVirtLXCConfig
readLibVirtConfig =
do
cp <- getConfigParser
let geto :: (Get_C a, Read a)
=> OptionSpec -> a -> a
geto = getOptionOr cp cfgFileSection
return $
LibVirtLXCConfig { useSudo = geto useSudoK $
useSudo defaultLibVirtLXCConfig
, virshPath = geto virshPathK $
virshPath defaultLibVirtLXCConfig
, emulator = geto emulatorK $
emulator defaultLibVirtLXCConfig
, virshURI = geto virshURIK $
virshURI defaultLibVirtLXCConfig
, networkId = geto networkIdK $
networkId defaultLibVirtLXCConfig
, guestCapabilities = geto guestCapabilitiesK $
guestCapabilities defaultLibVirtLXCConfig
}
initScript :: String
initScript = "init.sh"
domainConfig :: String
domainConfig = "domain.xml"
createDomain :: LibVirtLXCConfig
-> ExecEnv
-> String
-> String
-> FilePath
-> FilePath
-> String
createDomain cfg e buildId uuid scriptDirHost scriptDirGuest =
"\n\
\ " ++ buildId ++ "\n\
\ " ++ uuid ++ "\n\
\ " ++ memoryAmount e ++ "\n\
\ " ++ memoryAmount e ++ "\n\
\ " ++ cpuCountStr e ++ "\n\
\ \n\
\ \n\
\ "++ renderGuestCapabilityEntries cfg ++"\n\
\ \n\
\ \n\
\ \n\
\ exe\n\
\ " ++ scriptDirGuest > initScript ++ "\n\
\ \n\
\ \n\
\ destroy\n\
\ restart\n\
\ destroy\n\
\ \n\
\ " ++ emulator cfg ++ "\n"
++ unlines (libVirtNetwork (networkId cfg) ++
(fsImage <$> (envImageMounts e)) ++
(fsSharedDir <$> (envSharedDirectories e))) ++ "\n" ++
" \n\
\ \n\
\ \n\
\ \n\
\ \n\
\ \n\
\ \n\
\ \n\
\\n"
renderGuestCapabilityEntries :: LibVirtLXCConfig -> String
renderGuestCapabilityEntries = unlines . map render . guestCapabilities
where
render :: LXCGuestCapability -> String
render cap =
let capStr = toLower <$> drop (length "CAP_") (show cap)
in printf "<%s state='on'/>" capStr
osArch :: ExecEnv -> String
osArch e = case cpuArch (envResources e) of
X86_64 -> "x86_64"
I386 -> "i686"
libVirtNetwork :: Maybe String -> [String]
libVirtNetwork Nothing = []
libVirtNetwork (Just n) =
[ ""
, " "
, "" ]
fsImage :: (Image, MountPoint) -> String
fsImage (img, mnt) =
case fsTarget mnt of
Just mntXml ->
"\n " ++
fsImgDriver img ++ "\n " ++ fsImgSource img ++ "\n " ++ mntXml ++
"\n"
Nothing ->
""
where
fsImgDriver (Image _img fmt _fs) =
printf "" driver fmt'
where
(driver, fmt') =
case fmt of
Raw -> ("type='loop'", "format='raw'")
QCow2 -> ("type='nbd'", "format='qcow2'")
Vmdk -> ("type='nbd'", "format='vmdk'")
fsImgSource (Image src _fmt _fs) = ""
fsSharedDir :: SharedDirectory -> String
fsSharedDir (SharedDirectory hostDir mnt) =
case fsTarget mnt of
Just mntXml ->
"\n " ++
"" ++ "\n " ++ mntXml ++
"\n"
Nothing ->
""
fsSharedDir (SharedDirectoryRO hostDir mnt) =
case fsTarget mnt of
Just mntXml ->
"\n " ++
"" ++ "\n " ++ mntXml ++
"\n \n"
Nothing ->
""
fsSharedDir (SharedSources _) =
error "Unreachable code reached!"
fsTarget :: MountPoint -> Maybe String
fsTarget (MountPoint dir) = Just $ ""
fsTarget _ = Nothing
memoryUnit :: ExecEnv -> String
memoryUnit = toUnit . maxMemory . envResources
where
toUnit AutomaticRamSize = toUnit lxcDefaultRamSize
toUnit (RamSize _ u) =
case u of
GB -> "GiB"
MB -> "MiB"
KB -> "KiB"
B -> "B"
memoryAmount :: ExecEnv -> String
memoryAmount = show . toAmount . maxMemory . envResources
where
toAmount AutomaticRamSize = toAmount lxcDefaultRamSize
toAmount (RamSize n _) = n
cpuCountStr :: ExecEnv -> String
cpuCountStr = show . cpuCount . envResources