commit 3af1359fbfe4b2d06b06b79767b76d15beb4cab6
Author: Jose A. Lopes <jabolopes@google.com>
Date:   Thu Jan 2 17:13:55 2014 +0100

    Add metadata daemon
    
    ... including, user, group, daemonizing code with command line
    options, integration with the Snap HTTP server, and logic.
    
    Signed-off-by: Jose A. Lopes <jabolopes@google.com>
    Reviewed-by: Michele Tartara <mtartara@google.com>

diff --git a/.gitignore b/.gitignore
index ccde486..a02b0df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -140,6 +140,7 @@
 /src/ganeti-confd
 /src/ganeti-kvmd
 /src/ganeti-luxid
+/src/ganeti-metad
 /src/ganeti-mond
 /src/rpc-test
 
diff --git a/Makefile.am b/Makefile.am
index bfda26f..f0cd933 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -595,6 +595,7 @@ endif
 # Haskell programs to be compiled by "make really-all"
 HS_COMPILE_PROGS= \
 	src/ganeti-kvmd \
+	src/ganeti-metad \
 	src/ganeti-mond \
 	src/hconfd \
 	src/hluxid \
@@ -713,6 +714,7 @@ HS_LIB_SRCS = \
 	src/Ganeti/Logging.hs \
 	src/Ganeti/Luxi.hs \
 	src/Ganeti/Monitoring/Server.hs \
+	src/Ganeti/Metad.hs \
 	src/Ganeti/Network.hs \
 	src/Ganeti/Objects.hs \
 	src/Ganeti/OpCodes.hs \
@@ -1072,6 +1074,7 @@ src/ganeti-luxid: src/hluxid
 nodist_sbin_SCRIPTS += src/ganeti-confd
 nodist_sbin_SCRIPTS += src/ganeti-luxid
 nodist_sbin_SCRIPTS += src/ganeti-kvmd
+nodist_sbin_SCRIPTS += src/ganeti-metad
 endif
 
 if ENABLE_MOND
@@ -1849,6 +1852,8 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
 	    -DADMIN_GROUP="$(ADMIN_GROUP)" \
 	    -DMASTERD_USER="$(MASTERD_USER)" \
 	    -DMASTERD_GROUP="$(MASTERD_GROUP)" \
+	    -DMETAD_USER="$(METAD_USER)" \
+	    -DMETAD_GROUP="$(METAD_GROUP)" \
 	    -DRAPI_USER="$(RAPI_USER)" \
 	    -DRAPI_GROUP="$(RAPI_GROUP)" \
 	    -DCONFD_USER="$(CONFD_USER)" \
diff --git a/configure.ac b/configure.ac
index 47df82d..4d9cde4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -225,6 +225,7 @@ AC_ARG_WITH([user-prefix],
     [ to change the default)]
   )],
   [user_masterd="${withval}masterd";
+   user_metad="${withval}metad";
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
    user_kvmd="$user_default";
@@ -232,6 +233,7 @@ AC_ARG_WITH([user-prefix],
    user_noded="$user_default";
    user_mond="$user_default"],
   [user_masterd="$user_default";
+   user_metad="$user_default";
    user_rapi="$user_default";
    user_confd="$user_default";
    user_kvmd="$user_default";
@@ -239,6 +241,7 @@ AC_ARG_WITH([user-prefix],
    user_noded="$user_default";
    user_mond="$user_default"])
 AC_SUBST(MASTERD_USER, $user_masterd)
+AC_SUBST(METAD_USER, $user_metad)
 AC_SUBST(RAPI_USER, $user_rapi)
 AC_SUBST(CONFD_USER, $user_confd)
 AC_SUBST(KVMD_USER, $user_kvmd)
@@ -259,6 +262,7 @@ AC_ARG_WITH([group-prefix],
    group_kvmd="$group_default";
    group_luxid="${withval}luxid";
    group_masterd="${withval}masterd";
+   group_metad="${withval}metad";
    group_noded="$group_default";
    group_daemons="${withval}daemons";
    group_mond="$group_default"],
@@ -268,6 +272,7 @@ AC_ARG_WITH([group-prefix],
    group_kvmd="$group_default";
    group_luxid="$group_default";
    group_masterd="$group_default";
+   group_metad="$group_default";
    group_noded="$group_default";
    group_daemons="$group_default";
    group_mond="$group_default"])
@@ -277,12 +282,14 @@ AC_SUBST(CONFD_GROUP, $group_confd)
 AC_SUBST(KVMD_GROUP, $group_kvmd)
 AC_SUBST(LUXID_GROUP, $group_luxid)
 AC_SUBST(MASTERD_GROUP, $group_masterd)
+AC_SUBST(METAD_GROUP, $group_metad)
 AC_SUBST(NODED_GROUP, $group_noded)
 AC_SUBST(DAEMONS_GROUP, $group_daemons)
 AC_SUBST(MOND_GROUP, $group_mond)
 
 # Print the config to the user
 AC_MSG_NOTICE([Running ganeti-masterd as $group_masterd:$group_masterd])
+AC_MSG_NOTICE([Running ganeti-metad as $group_metad:$group_metad])
 AC_MSG_NOTICE([Running ganeti-rapi as $user_rapi:$group_rapi])
 AC_MSG_NOTICE([Running ganeti-confd as $user_confd:$group_confd])
 AC_MSG_NOTICE([Running ganeti-luxid as $user_luxid:$group_luxid])
diff --git a/src/AutoConf.hs.in b/src/AutoConf.hs.in
index 44720aa..c108873 100644
--- a/src/AutoConf.hs.in
+++ b/src/AutoConf.hs.in
@@ -7,7 +7,7 @@ from @AutoConf.hs.in@.
 
 {-
 
-Copyright (C) 2013 Google Inc.
+Copyright (C) 2013, 2014 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -157,6 +157,12 @@ masterdUser = "MASTERD_USER"
 masterdGroup :: String
 masterdGroup = "MASTERD_GROUP"
 
+metadUser :: String
+metadUser = "METAD_USER"
+
+metadGroup :: String
+metadGroup = "METAD_GROUP"
+
 rapiUser :: String
 rapiUser = "RAPI_USER"
 
diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs
index 1a40403..9df4f1f 100644
--- a/src/Ganeti/Constants.hs
+++ b/src/Ganeti/Constants.hs
@@ -200,6 +200,12 @@ masterdUser = Runtime.daemonUser GanetiMasterd
 masterdGroup :: String
 masterdGroup = Runtime.daemonGroup (DaemonGroup GanetiMasterd)
 
+metadUser :: String
+metadUser = Runtime.daemonUser GanetiMetad
+
+metadGroup :: String
+metadGroup = Runtime.daemonGroup (DaemonGroup GanetiMetad)
+
 rapiUser :: String
 rapiUser = Runtime.daemonUser GanetiRapi
 
@@ -335,6 +341,9 @@ confd = Runtime.daemonName GanetiConfd
 masterd :: String
 masterd = Runtime.daemonName GanetiMasterd
 
+metad :: String
+metad = Runtime.daemonName GanetiMetad
+
 mond :: String
 mond = Runtime.daemonName GanetiMond
 
@@ -365,6 +374,9 @@ defaultConfdPort = 1814
 defaultMondPort :: Int
 defaultMondPort = 1815
 
+defaultMetadPort :: Int
+defaultMetadPort = 8080
+
 defaultNodedPort :: Int
 defaultNodedPort = 1811
 
@@ -373,11 +385,14 @@ defaultRapiPort = 5080
 
 daemonsPorts :: Map String (Protocol, Int)
 daemonsPorts =
-  Map.fromList [(confd, (Udp, defaultConfdPort)),
-                (mond, (Tcp, defaultMondPort)),
-                (noded, (Tcp, defaultNodedPort)),
-                (rapi, (Tcp, defaultRapiPort)),
-                (ssh, (Tcp, 22))]
+  Map.fromList
+  [ (confd, (Udp, defaultConfdPort))
+  , (metad, (Tcp, defaultMetadPort))
+  , (mond, (Tcp, defaultMondPort))
+  , (noded, (Tcp, defaultNodedPort))
+  , (rapi, (Tcp, defaultRapiPort))
+  , (ssh, (Tcp, 22))
+  ]
 
 firstDrbdPort :: Int
 firstDrbdPort = 11000
diff --git a/src/Ganeti/Metad.hs b/src/Ganeti/Metad.hs
new file mode 100644
index 0000000..535d9a0
--- /dev/null
+++ b/src/Ganeti/Metad.hs
@@ -0,0 +1,94 @@
+{-| Metadata daemon
+
+-}
+
+{-
+
+Copyright (C) 2014 Google Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Ganeti.Metad (start) where
+
+import Control.Applicative
+import Control.Monad.IO.Class (liftIO)
+import qualified Data.ByteString.Char8 as ByteString (pack, unpack)
+import Snap.Core
+import Snap.Http.Server
+
+import Ganeti.Daemon
+import qualified Ganeti.Constants as Constants
+import qualified Ganeti.Logging as Logging
+import Ganeti.Runtime (GanetiDaemon(..), ExtraLogReason(..))
+import qualified Ganeti.Runtime as Runtime
+
+type MetaM = Snap ()
+
+error404 :: MetaM
+error404 = do
+  modifyResponse . setResponseStatus 404 $ ByteString.pack "Not found"
+  writeBS $ ByteString.pack "Resource not found"
+
+handleMetadata :: Method -> String -> String -> String -> MetaM
+handleMetadata GET  "ganeti" "latest" "meta_data.json" =
+  liftIO $ Logging.logInfo "ganeti metadata"
+handleMetadata GET  "ganeti" "latest" "os/parameters.json" =
+  liftIO $ Logging.logInfo "ganeti OS parameters"
+handleMetadata GET  "ganeti" "latest" "read" =
+  liftIO $ Logging.logInfo "ganeti READ"
+handleMetadata POST "ganeti" "latest" "write" =
+  liftIO $ Logging.logInfo "ganeti WRITE"
+handleMetadata _ _ _ _ = error404
+
+routeMetadata :: MetaM
+routeMetadata =
+  route [ (providerRoute1, dispatchMetadata)
+        , (providerRoute2, dispatchMetadata)
+        ] <|> dispatchMetadata
+  where provider = "provider"
+        version  = "version"
+
+        providerRoute1 = ByteString.pack $ ':':provider ++ "/" ++ ':':version
+        providerRoute2 = ByteString.pack $ ':':version
+
+        getParamString :: String -> Snap String
+        getParamString =
+          fmap (maybe "" ByteString.unpack) . getParam . ByteString.pack
+
+        dispatchMetadata =
+          do m <- rqMethod <$> getRequest
+             p <- getParamString provider
+             v <- getParamString version
+             r <- ByteString.unpack . rqPathInfo <$> getRequest
+             handleMetadata m p v r
+
+defaultHttpConf :: DaemonOptions -> FilePath -> FilePath -> Config Snap ()
+defaultHttpConf opts accessLog errorLog =
+  maybe id (setBind . ByteString.pack) (optBindAddress opts) .
+  setAccessLog (ConfigFileLog accessLog) .
+  setCompression False .
+  setErrorLog (ConfigFileLog errorLog) .
+  setPort (maybe Constants.defaultMetadPort fromIntegral (optPort opts)) .
+  setVerbose False $
+  emptyConfig
+
+start :: DaemonOptions -> IO ()
+start opts = do
+  accessLog <- Runtime.daemonsExtraLogFile GanetiMetad AccessLog
+  errorLog <- Runtime.daemonsExtraLogFile GanetiMetad ErrorLog
+  httpServe (defaultHttpConf opts accessLog errorLog) routeMetadata
diff --git a/src/Ganeti/Runtime.hs b/src/Ganeti/Runtime.hs
index 7a299a2..eddcee7 100644
--- a/src/Ganeti/Runtime.hs
+++ b/src/Ganeti/Runtime.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2011, 2012, 2013 Google Inc.
+Copyright (C) 2011, 2012, 2013, 2014 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -60,6 +60,7 @@ import Ganeti.BasicTypes
 import AutoConf
 
 data GanetiDaemon = GanetiMasterd
+                  | GanetiMetad
                   | GanetiNoded
                   | GanetiRapi
                   | GanetiConfd
@@ -81,6 +82,7 @@ type RuntimeEnts = (M.Map GanetiDaemon UserID, M.Map GanetiGroup GroupID)
 -- | Returns the daemon name for a given daemon.
 daemonName :: GanetiDaemon -> String
 daemonName GanetiMasterd = "ganeti-masterd"
+daemonName GanetiMetad   = "ganeti-metad"
 daemonName GanetiNoded   = "ganeti-noded"
 daemonName GanetiRapi    = "ganeti-rapi"
 daemonName GanetiConfd   = "ganeti-confd"
@@ -91,6 +93,7 @@ daemonName GanetiMond    = "ganeti-mond"
 -- | Returns whether the daemon only runs on the master node.
 daemonOnlyOnMaster :: GanetiDaemon -> Bool
 daemonOnlyOnMaster GanetiMasterd = True
+daemonOnlyOnMaster GanetiMetad   = False
 daemonOnlyOnMaster GanetiNoded   = False
 daemonOnlyOnMaster GanetiRapi    = False
 daemonOnlyOnMaster GanetiConfd   = False
@@ -101,6 +104,7 @@ daemonOnlyOnMaster GanetiMond    = False
 -- | Returns the log file base for a daemon.
 daemonLogBase :: GanetiDaemon -> String
 daemonLogBase GanetiMasterd = "master-daemon"
+daemonLogBase GanetiMetad   = "meta-daemon"
 daemonLogBase GanetiNoded   = "node-daemon"
 daemonLogBase GanetiRapi    = "rapi-daemon"
 daemonLogBase GanetiConfd   = "conf-daemon"
@@ -111,6 +115,7 @@ daemonLogBase GanetiMond    = "monitoring-daemon"
 -- | Returns the configured user name for a daemon.
 daemonUser :: GanetiDaemon -> String
 daemonUser GanetiMasterd = AutoConf.masterdUser
+daemonUser GanetiMetad   = AutoConf.metadUser
 daemonUser GanetiNoded   = AutoConf.nodedUser
 daemonUser GanetiRapi    = AutoConf.rapiUser
 daemonUser GanetiConfd   = AutoConf.confdUser
@@ -121,6 +126,7 @@ daemonUser GanetiMond    = AutoConf.mondUser
 -- | Returns the configured group for a daemon.
 daemonGroup :: GanetiGroup -> String
 daemonGroup (DaemonGroup GanetiMasterd) = AutoConf.masterdGroup
+daemonGroup (DaemonGroup GanetiMetad)   = AutoConf.metadGroup
 daemonGroup (DaemonGroup GanetiNoded)   = AutoConf.nodedGroup
 daemonGroup (DaemonGroup GanetiRapi)    = AutoConf.rapiGroup
 daemonGroup (DaemonGroup GanetiConfd)   = AutoConf.confdGroup
diff --git a/src/ganeti-metad.hs b/src/ganeti-metad.hs
new file mode 100644
index 0000000..28529b6
--- /dev/null
+++ b/src/ganeti-metad.hs
@@ -0,0 +1,48 @@
+{-| Metadata daemon.
+
+-}
+
+{-
+
+Copyright (C) 2014 Google Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Main (main) where
+
+import qualified Ganeti.Constants as Constants
+import Ganeti.Daemon (OptType)
+import qualified Ganeti.Daemon as Daemon
+import qualified Ganeti.Metad as Metad
+import qualified Ganeti.Runtime as Runtime
+
+options :: [OptType]
+options =
+  [ Daemon.oBindAddress
+  , Daemon.oDebug
+  , Daemon.oNoDaemonize
+  , Daemon.oNoUserChecks
+  , Daemon.oPort Constants.defaultMetadPort
+  ]
+
+main :: IO ()
+main =
+  Daemon.genericMain Runtime.GanetiMetad options
+    (\_ -> return . Right $ ())
+    (\_ _ -> return ())
+    (\opts _ _ -> Metad.start opts)
diff --git a/test/hs/Test/Ganeti/Runtime.hs b/test/hs/Test/Ganeti/Runtime.hs
index 605859f..f75fc42 100644
--- a/test/hs/Test/Ganeti/Runtime.hs
+++ b/test/hs/Test/Ganeti/Runtime.hs
@@ -85,6 +85,7 @@ case_UsersGroups = do
               \         constants.CONFD_USER,\n\
               \         constants.KVMD_USER,\n\
               \         constants.LUXID_USER,\n\
+              \         constants.METAD_USER,\n\
               \         constants.MOND_USER,\n\
               \        ]\n\
               \groups = [constants.MASTERD_GROUP,\n\
@@ -93,6 +94,7 @@ case_UsersGroups = do
               \          constants.CONFD_GROUP,\n\
               \          constants.KVMD_GROUP,\n\
               \          constants.LUXID_GROUP,\n\
+              \          constants.METAD_GROUP,\n\
               \          constants.MOND_GROUP,\n\
               \          constants.DAEMONS_GROUP,\n\
               \          constants.ADMIN_GROUP,\n\
