commit 670e954ab53c9ea1ed5dbf94822e7d345aca2c8d
Author: Thomas Thrainer <thomasth@google.com>
Date:   Thu Jul 4 17:10:35 2013 +0200

    Add queryd daemon (split from confd)
    
    queryd is added as a new daemon which handles configuration queries over
    LUXI. This functionality was removed from confd, which now only queries
    over the network.
    
    The queryd user is added to the master group such that it can access
    server.pem. The confd user no longer needs to be in the master group.
    This fixes issue 292.
    
    Signed-off-by: Thomas Thrainer <thomasth@google.com>
    Reviewed-by: Helga Velroyen <helgav@google.com>

diff --git a/.gitignore b/.gitignore
index 0279908..d681b47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,7 +124,9 @@
 /src/mon-collector
 /src/htools
 /src/hconfd
+/src/hqueryd
 /src/ganeti-confd
+/src/ganeti-queryd
 /src/ganeti-mond
 /src/rpc-test
 
diff --git a/Makefile.am b/Makefile.am
index e495f64..ec797c5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -201,6 +201,7 @@ CLEANFILES = \
 	$(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
 	$(HS_BUILT_TEST_HELPERS) \
 	src/ganeti-confd \
+	src/ganeti-queryd \
 	src/ganeti-mond \
 	.hpc/*.mix src/*.tix test/hs/*.tix \
 	doc/hs-lint.html
@@ -214,7 +215,7 @@ HS_GENERATED_FILES =
 if WANT_HTOOLS
 HS_GENERATED_FILES += $(HS_PROGS)
 if ENABLE_CONFD
-HS_GENERATED_FILES += src/hconfd src/ganeti-confd
+HS_GENERATED_FILES += src/hconfd src/ganeti-confd src/hqueryd src/ganeti-queryd
 endif
 
 if ENABLE_MOND
@@ -478,6 +479,7 @@ endif
 HS_COMPILE_PROGS= \
 	src/ganeti-mond \
 	src/hconfd \
+	src/hqueryd \
 	src/rpc-test
 
 # All Haskell non-test programs to be compiled but not automatically installed
@@ -868,7 +870,11 @@ if ENABLE_CONFD
 src/ganeti-confd: src/hconfd
 	cp -f $< $@
 
+src/ganeti-queryd: src/hqueryd
+	cp -f $< $@
+
 nodist_sbin_SCRIPTS += src/ganeti-confd
+nodist_sbin_SCRIPTS += src/ganeti-queryd
 endif
 
 if ENABLE_MOND
@@ -1532,6 +1538,8 @@ lib/_autoconf.py: Makefile | stamp-directories
 	  echo "RAPI_GROUP = '$(RAPI_GROUP)'"; \
 	  echo "CONFD_USER = '$(CONFD_USER)'"; \
 	  echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \
+	  echo "QUERYD_USER = '$(QUERYD_USER)'"; \
+	  echo "QUERYD_GROUP = '$(QUERYD_GROUP)'"; \
 	  echo "NODED_USER = '$(NODED_USER)'"; \
 	  echo "NODED_GROUP = '$(NODED_GROUP)'"; \
 	  echo "MOND_USER = '$(MOND_USER)'"; \
@@ -1613,11 +1621,13 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
 	  echo 's#@''GNTMASTERUSER@#$(MASTERD_USER)#g'; \
 	  echo 's#@''GNTRAPIUSER@#$(RAPI_USER)#g'; \
 	  echo 's#@''GNTCONFDUSER@#$(CONFD_USER)#g'; \
+	  echo 's#@''GNTQUERYDUSER@#$(QUERY_USER)#g'; \
 	  echo 's#@''GNTNODEDUSER@#$(NODED_USER)#g'; \
 	  echo 's#@''GNTMONDUSER@#$(MOND_USER)#g'; \
 	  echo 's#@''GNTRAPIGROUP@#$(RAPI_GROUP)#g'; \
 	  echo 's#@''GNTADMINGROUP@#$(ADMIN_GROUP)#g'; \
 	  echo 's#@''GNTCONFDGROUP@#$(CONFD_GROUP)#g'; \
+	  echo 's#@''GNTQUERYDGROUP@#$(QUERYD_GROUP)#g'; \
 	  echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
 	  echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
 	  echo 's#@''GNTDAEMONSGROUP@#$(DAEMONS_GROUP)#g'; \
diff --git a/configure.ac b/configure.ac
index 3d0f096..86ed937 100644
--- a/configure.ac
+++ b/configure.ac
@@ -243,16 +243,19 @@ AC_ARG_WITH([user-prefix],
   [user_masterd="${withval}masterd";
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
+   user_queryd="${withval}queryd";
    user_noded="$user_default";
    user_mond="${withval}mond"],
   [user_masterd="$user_default";
    user_rapi="$user_default";
    user_confd="$user_default";
+   user_queryd="$user_default";
    user_noded="$user_default";
    user_mond="$user_default"])
 AC_SUBST(MASTERD_USER, $user_masterd)
 AC_SUBST(RAPI_USER, $user_rapi)
 AC_SUBST(CONFD_USER, $user_confd)
+AC_SUBST(QUERYD_USER, $user_queryd)
 AC_SUBST(NODED_USER, $user_noded)
 AC_SUBST(MOND_USER, $user_mond)
 
@@ -266,6 +269,7 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="${withval}rapi";
    group_admin="${withval}admin";
    group_confd="${withval}confd";
+   group_queryd="${withval}queryd";
    group_masterd="${withval}masterd";
    group_noded="$group_default";
    group_daemons="${withval}daemons";
@@ -273,6 +277,7 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="$group_default";
    group_admin="$group_default";
    group_confd="$group_default";
+   group_queryd="$group_default";
    group_masterd="$group_default";
    group_noded="$group_default";
    group_daemons="$group_default";
@@ -280,6 +285,7 @@ AC_ARG_WITH([group-prefix],
 AC_SUBST(RAPI_GROUP, $group_rapi)
 AC_SUBST(ADMIN_GROUP, $group_admin)
 AC_SUBST(CONFD_GROUP, $group_confd)
+AC_SUBST(QUERYD_GROUP, $group_queryd)
 AC_SUBST(MASTERD_GROUP, $group_masterd)
 AC_SUBST(NODED_GROUP, $group_noded)
 AC_SUBST(DAEMONS_GROUP, $group_daemons)
@@ -289,6 +295,7 @@ AC_SUBST(MOND_GROUP, $group_mond)
 AC_MSG_NOTICE([Running ganeti-masterd as $group_masterd:$group_masterd])
 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-queryd as $user_queryd:$group_queryd])
 AC_MSG_NOTICE([Group for daemons is $group_daemons])
 AC_MSG_NOTICE([Group for clients is $group_admin])
 
diff --git a/daemons/daemon-util.in b/daemons/daemon-util.in
index 2b3d24d..2d11404 100644
--- a/daemons/daemon-util.in
+++ b/daemons/daemon-util.in
@@ -39,6 +39,7 @@ _confd_enabled() {
 
 if _confd_enabled; then
   DAEMONS+=( ganeti-confd )
+  DAEMONS+=( ganeti-queryd )
 fi
 
 _mond_enabled() {
@@ -52,6 +53,7 @@ fi
 NODED_ARGS=
 MASTERD_ARGS=
 CONFD_ARGS=
+QUERYD_ARGS=
 RAPI_ARGS=
 MOND_ARGS=
 
@@ -82,6 +84,9 @@ _daemon_usergroup() {
     confd)
       echo "@GNTCONFDUSER@:@GNTDAEMONSGROUP@"
       ;;
+    queryd)
+      echo "@GNTQUERYDUSER@:@GNTQUERYDGROUP@"
+      ;;
     rapi)
       echo "@GNTRAPIUSER@:@GNTRAPIGROUP@"
       ;;
@@ -228,7 +233,8 @@ start() {
   local usergroup=$(_daemon_usergroup $plain_name)
   local daemonexec=$(_daemon_executable $name)
 
-  if [[ "$name" == ganeti-confd ]] && ! _confd_enabled; then
+  if ( [[ "$name" == ganeti-confd ]] || [[ "$name" == ganeti-queryd ]] ) \
+      && ! _confd_enabled; then
     echo 'ganeti-confd disabled at build time' >&2
     return 1
   fi
diff --git a/lib/constants.py b/lib/constants.py
index 1eaf88c..b814ffa 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -104,6 +104,8 @@ RAPI_USER = _autoconf.RAPI_USER
 RAPI_GROUP = _autoconf.RAPI_GROUP
 CONFD_USER = _autoconf.CONFD_USER
 CONFD_GROUP = _autoconf.CONFD_GROUP
+QUERYD_USER = _autoconf.QUERYD_USER
+QUERYD_GROUP = _autoconf.QUERYD_GROUP
 NODED_USER = _autoconf.NODED_USER
 NODED_GROUP = _autoconf.NODED_GROUP
 MOND_USER = _autoconf.MOND_USER
@@ -156,6 +158,7 @@ SCP = "scp"
 
 NODED = "ganeti-noded"
 CONFD = "ganeti-confd"
+QUERYD = "ganeti-queryd"
 RAPI = "ganeti-rapi"
 MASTERD = "ganeti-masterd"
 MOND = "ganeti-mond"
@@ -163,6 +166,7 @@ MOND = "ganeti-mond"
 DAEMONS = compat.UniqueFrozenset([
   NODED,
   CONFD,
+  QUERYD,
   RAPI,
   MASTERD,
   MOND,
@@ -188,6 +192,7 @@ LAST_DRBD_PORT = 14999
 DAEMONS_LOGBASE = {
   NODED: "node-daemon",
   CONFD: "conf-daemon",
+  QUERYD: "query-daemon",
   RAPI: "rapi-daemon",
   MASTERD: "master-daemon",
   MOND: "monitoring-daemon",
diff --git a/lib/runtime.py b/lib/runtime.py
index 883b2e5..69b259f 100644
--- a/lib/runtime.py
+++ b/lib/runtime.py
@@ -75,6 +75,8 @@ class GetentResolver:
   @ivar masterd_gid: The resolved gid of the masterd group
   @ivar confd_uid: The resolved uid of the confd user
   @ivar confd_gid: The resolved gid of the confd group
+  @ivar queryd_uid: The resolved uid of the queryd user
+  @ivar queyrd_gid: The resolved gid of the queyrd group
   @ivar rapi_uid: The resolved uid of the rapi user
   @ivar rapi_gid: The resolved gid of the rapi group
   @ivar noded_uid: The resolved uid of the noded user
@@ -93,6 +95,9 @@ class GetentResolver:
     self.confd_uid = GetUid(constants.CONFD_USER, _getpwnam)
     self.confd_gid = GetGid(constants.CONFD_GROUP, _getgrnam)
 
+    self.queryd_uid = GetUid(constants.QUERYD_USER, _getpwnam)
+    self.queryd_gid = GetGid(constants.QUERYD_GROUP, _getgrnam)
+
     self.rapi_uid = GetUid(constants.RAPI_USER, _getpwnam)
     self.rapi_gid = GetGid(constants.RAPI_GROUP, _getgrnam)
 
@@ -106,6 +111,7 @@ class GetentResolver:
     self._uid2user = {
       self.masterd_uid: constants.MASTERD_USER,
       self.confd_uid: constants.CONFD_USER,
+      self.queryd_uid: constants.QUERYD_USER,
       self.rapi_uid: constants.RAPI_USER,
       self.noded_uid: constants.NODED_USER,
       }
@@ -113,6 +119,7 @@ class GetentResolver:
     self._gid2group = {
       self.masterd_gid: constants.MASTERD_GROUP,
       self.confd_gid: constants.CONFD_GROUP,
+      self.queryd_gid: constants.QUERYD_GROUP,
       self.rapi_gid: constants.RAPI_GROUP,
       self.noded_gid: constants.NODED_GROUP,
       self.daemons_gid: constants.DAEMONS_GROUP,
diff --git a/lib/tools/ensure_dirs.py b/lib/tools/ensure_dirs.py
index d8f1f8c..cebc54f 100644
--- a/lib/tools/ensure_dirs.py
+++ b/lib/tools/ensure_dirs.py
@@ -180,7 +180,7 @@ def GetPaths():
     (pathutils.MASTER_SOCKET, FILE, 0660,
      getent.masterd_uid, getent.daemons_gid, False),
     (pathutils.QUERY_SOCKET, FILE, 0660,
-     getent.confd_uid, getent.daemons_gid, False),
+     getent.queryd_uid, getent.daemons_gid, False),
     (pathutils.BDEV_CACHE_DIR, DIR, 0755,
      getent.noded_uid, getent.masterd_gid),
     (pathutils.UIDPOOL_LOCKDIR, DIR, 0750,
diff --git a/src/Ganeti/Confd/Server.hs b/src/Ganeti/Confd/Server.hs
index adde99a..3df6f42 100644
--- a/src/Ganeti/Confd/Server.hs
+++ b/src/Ganeti/Confd/Server.hs
@@ -52,7 +52,6 @@ import Ganeti.ConfigReader
 import Ganeti.Hash
 import Ganeti.Logging
 import qualified Ganeti.Constants as C
-import Ganeti.Query.Server (prepQueryD, runQueryD)
 import Ganeti.Utils
 
 -- * Types and constants definitions
@@ -250,15 +249,8 @@ listener s hmac resp = do
     else logDebug "Invalid magic code!" >> return ()
   return ()
 
--- | Extract the configuration from our IORef.
-configReader :: CRef -> ConfigReader
-configReader cref = do
-  cdata <- readIORef cref
-  return $ liftM fst cdata
-
 -- | Type alias for prepMain results
-type PrepResult = (S.Socket, (FilePath, S.Socket),
-                   IORef (Result (ConfigData, LinkIpMap)))
+type PrepResult = (S.Socket, IORef (Result (ConfigData, LinkIpMap)))
 
 -- | Check function for confd.
 checkMain :: CheckFn (S.Family, S.SockAddr)
@@ -275,20 +267,16 @@ prepMain :: PrepFn (S.Family, S.SockAddr) PrepResult
 prepMain _ (af_family, bindaddr) = do
   s <- S.socket af_family S.Datagram S.defaultProtocol
   S.bindSocket s bindaddr
-  -- prepare the queryd listener
-  query_data <- prepQueryD Nothing
   cref <- newIORef (Bad "Configuration not yet loaded")
-  return (s, query_data, cref)
+  return (s, cref)
 
 -- | Main function.
 main :: MainFn (S.Family, S.SockAddr) PrepResult
-main _ _ (s, query_data, cref) = do
+main _ _ (s, cref) = do
   let cfg_transform :: Result ConfigData -> Result (ConfigData, LinkIpMap)
       cfg_transform = liftM (\cfg -> (cfg, buildLinkIpInstnameMap cfg))
   initConfigReader cfg_transform cref
 
   hmac <- getClusterHmac
-  -- launch the queryd listener
-  _ <- forkIO $ runQueryD query_data (configReader cref)
-  -- and finally enter the responder loop
+  -- enter the responder loop
   forever $ listener s hmac (responder cref)
diff --git a/src/Ganeti/Daemon.hs b/src/Ganeti/Daemon.hs
index 73bdb16..6f9db4a 100644
--- a/src/Ganeti/Daemon.hs
+++ b/src/Ganeti/Daemon.hs
@@ -50,6 +50,7 @@ import Control.Monad
 import Data.Maybe (fromMaybe)
 import Data.Word
 import GHC.IO.Handle (hDuplicateTo)
+import Network.BSD (getHostName)
 import qualified Network.Socket as Socket
 import System.Console.GetOpt
 import System.Exit
@@ -291,6 +292,36 @@ parseAddress opts defport = do
                     (resolveAddr port saddr)
                     (ioErrorToResult $ "Invalid address " ++ saddr)
 
+-- | Environment variable to override the assumed host name of the
+-- current node.
+vClusterHostNameEnvVar :: String
+vClusterHostNameEnvVar = "GANETI_HOSTNAME"
+
+-- | Returns if the current node is the master node.
+isMaster :: IO Bool
+isMaster = do
+  let ioErrorToNothing :: IOError -> IO (Maybe String)
+      ioErrorToNothing _ = return Nothing
+  vcluster_node <- Control.Exception.catch
+                     (liftM Just (getEnv vClusterHostNameEnvVar))
+                     ioErrorToNothing
+  curNode <- case vcluster_node of
+    Just node_name -> return node_name
+    Nothing -> getHostName
+  masterNode <- Ssconf.getMasterNode Nothing
+  case masterNode of
+    Ok n -> return (curNode == n)
+    Bad _ -> return False
+
+-- | Ensures that the daemon runs on the right node (and exits
+-- gracefully if it doesnt)
+ensureNode :: GanetiDaemon -> IO ()
+ensureNode daemon = do
+  is_master <- isMaster
+  when (daemonOnlyOnMaster daemon && not is_master) $ do
+    putStrLn "Not master, exiting."
+    exitWith (ExitFailure C.exitNotmaster)
+
 -- | Run an I\/O action that might throw an I\/O error, under a
 -- handler that will simply annotate and re-throw the exception.
 describeError :: String -> Maybe Handle -> Maybe FilePath -> IO a -> IO a
@@ -337,8 +368,11 @@ genericMain :: GanetiDaemon -- ^ The daemon we're running
             -> IO ()
 genericMain daemon options check_fn prep_fn exec_fn = do
   let progname = daemonName daemon
+
   (opts, args) <- parseArgs progname options
 
+  ensureNode daemon
+
   exitUnless (null args) "This program doesn't take any arguments"
 
   unless (optNoUserChecks opts) $ do
diff --git a/src/Ganeti/Query/Server.hs b/src/Ganeti/Query/Server.hs
index ebdbba5..6d4dd3c 100644
--- a/src/Ganeti/Query/Server.hs
+++ b/src/Ganeti/Query/Server.hs
@@ -26,15 +26,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 -}
 
 module Ganeti.Query.Server
-  ( prepQueryD
-  , runQueryD
+  ( main
+  , checkMain
+  , prepMain
   ) where
 
 import Control.Applicative
 import Control.Concurrent
 import Control.Exception
+import Control.Monad (forever)
 import Data.Bits (bitSize)
-import Data.Maybe
+import Data.IORef
 import qualified Network.Socket as S
 import qualified Text.JSON as J
 import Text.JSON (showJSON, JSValue(..))
@@ -226,27 +228,37 @@ clientLoop client creader = do
     then clientLoop client creader
     else closeClient client
 
--- | Main loop: accepts clients, forks an I/O thread to handle that
--- client, and then restarts.
-mainLoop :: ConfigReader -> S.Socket -> IO ()
-mainLoop creader socket = do
+-- | Main listener loop: accepts clients, forks an I/O thread to handle
+-- that client.
+listener :: ConfigReader -> S.Socket -> IO ()
+listener creader socket = do
   client <- acceptClient socket
   _ <- forkIO $ clientLoop client creader
-  mainLoop creader socket
+  return ()
 
--- | Function that prepares the server socket.
-prepQueryD :: Maybe FilePath -> IO (FilePath, S.Socket)
-prepQueryD fpath = do
-  def_socket <- Path.defaultQuerySocket
-  let socket_path = fromMaybe def_socket fpath
+-- | Type alias for prepMain results
+type PrepResult = (FilePath, S.Socket, IORef (Result ConfigData))
+
+-- | Check function for queryd.
+checkMain :: CheckFn ()
+checkMain _ = return $ Right ()
+
+-- | Prepare function for queryd.
+prepMain :: PrepFn () PrepResult
+prepMain _ _ = do
+  socket_path <- Path.defaultQuerySocket
   cleanupSocket socket_path
   s <- describeError "binding to the Luxi socket"
          Nothing (Just socket_path) $ getServer socket_path
-  return (socket_path, s)
+  cref <- newIORef (Bad "Configuration not yet loaded")
+  return (socket_path, s, cref)
+
+-- | Main function.
+main :: MainFn () PrepResult
+main _ _ (socket_path, server, cref) = do
+  initConfigReader id cref
+  let creader = readIORef cref
 
--- | Main function that runs the query endpoint.
-runQueryD :: (FilePath, S.Socket) -> ConfigReader -> IO ()
-runQueryD (socket_path, server) creader =
   finally
-    (mainLoop creader server)
+    (forever $ listener creader server)
     (closeServer socket_path server)
diff --git a/src/Ganeti/Runtime.hs b/src/Ganeti/Runtime.hs
index effa41f..2e1a67e 100644
--- a/src/Ganeti/Runtime.hs
+++ b/src/Ganeti/Runtime.hs
@@ -29,6 +29,7 @@ module Ganeti.Runtime
   , GanetiGroup(..)
   , RuntimeEnts
   , daemonName
+  , daemonOnlyOnMaster
   , daemonUser
   , daemonGroup
   , daemonLogFile
@@ -56,6 +57,7 @@ data GanetiDaemon = GanetiMasterd
                   | GanetiNoded
                   | GanetiRapi
                   | GanetiConfd
+                  | GanetiQueryd
                   | GanetiMond
                     deriving (Show, Enum, Bounded, Eq, Ord)
 
@@ -75,14 +77,25 @@ daemonName GanetiMasterd = C.masterd
 daemonName GanetiNoded   = C.noded
 daemonName GanetiRapi    = C.rapi
 daemonName GanetiConfd   = C.confd
+daemonName GanetiQueryd  = C.queryd
 daemonName GanetiMond    = C.mond
 
+-- | Returns whether the daemon only runs on the master node.
+daemonOnlyOnMaster :: GanetiDaemon -> Bool
+daemonOnlyOnMaster GanetiMasterd = True
+daemonOnlyOnMaster GanetiNoded   = False
+daemonOnlyOnMaster GanetiRapi    = False
+daemonOnlyOnMaster GanetiConfd   = False
+daemonOnlyOnMaster GanetiQueryd  = True
+daemonOnlyOnMaster GanetiMond    = False
+
 -- | Returns the log file base for a daemon.
 daemonLogBase :: GanetiDaemon -> String
 daemonLogBase GanetiMasterd = C.daemonsLogbaseGanetiMasterd
 daemonLogBase GanetiNoded   = C.daemonsLogbaseGanetiNoded
 daemonLogBase GanetiRapi    = C.daemonsLogbaseGanetiRapi
 daemonLogBase GanetiConfd   = C.daemonsLogbaseGanetiConfd
+daemonLogBase GanetiQueryd  = C.daemonsLogbaseGanetiQueryd
 daemonLogBase GanetiMond    = C.daemonsLogbaseGanetiMond
 
 -- | Returns the configured user name for a daemon.
@@ -91,6 +104,7 @@ daemonUser GanetiMasterd = C.masterdUser
 daemonUser GanetiNoded   = C.nodedUser
 daemonUser GanetiRapi    = C.rapiUser
 daemonUser GanetiConfd   = C.confdUser
+daemonUser GanetiQueryd  = C.querydUser
 daemonUser GanetiMond    = C.mondUser
 
 -- | Returns the configured group for a daemon.
@@ -99,6 +113,7 @@ daemonGroup (DaemonGroup GanetiMasterd) = C.masterdGroup
 daemonGroup (DaemonGroup GanetiNoded)   = C.nodedGroup
 daemonGroup (DaemonGroup GanetiRapi)    = C.rapiGroup
 daemonGroup (DaemonGroup GanetiConfd)   = C.confdGroup
+daemonGroup (DaemonGroup GanetiQueryd)  = C.querydGroup
 daemonGroup (DaemonGroup GanetiMond)    = C.mondGroup
 daemonGroup (ExtraGroup  DaemonsGroup)  = C.daemonsGroup
 daemonGroup (ExtraGroup  AdminGroup)    = C.adminGroup
diff --git a/src/Ganeti/Ssconf.hs b/src/Ganeti/Ssconf.hs
index ed0dbbd..dd3a52c 100644
--- a/src/Ganeti/Ssconf.hs
+++ b/src/Ganeti/Ssconf.hs
@@ -31,6 +31,7 @@ module Ganeti.Ssconf
   , sSKeyFromRaw
   , getPrimaryIPFamily
   , getMasterCandidatesIps
+  , getMasterNode
   , keyToFilename
   , sSFilePrefix
   ) where
@@ -134,3 +135,9 @@ getMasterCandidatesIps :: Maybe FilePath -> IO (Result [String])
 getMasterCandidatesIps optPath = do
   result <- readSSConfFile optPath Nothing SSMasterCandidatesIps
   return $ liftM lines result
+
+-- | Read the name of the master node.
+getMasterNode :: Maybe FilePath -> IO (Result String)
+getMasterNode optPath = do
+  result <- readSSConfFile optPath Nothing SSMasterNode
+  return (liftM rStripSpace result)
diff --git a/src/hqueryd.hs b/src/hqueryd.hs
new file mode 100644
index 0000000..100cc14
--- /dev/null
+++ b/src/hqueryd.hs
@@ -0,0 +1,47 @@
+{-| Ganeti query daemon
+
+-}
+
+{-
+
+Copyright (C) 2009, 2011, 2012, 2013 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.Query.Server
+import Ganeti.Daemon
+import Ganeti.Runtime
+
+-- | Options list and functions.
+options :: [OptType]
+options =
+  [ oNoDaemonize
+  , oNoUserChecks
+  , oDebug
+  , oSyslogUsage
+  ]
+
+-- | Main function.
+main :: IO ()
+main =
+  genericMain GanetiQueryd options
+    Ganeti.Query.Server.checkMain
+    Ganeti.Query.Server.prepMain
+    Ganeti.Query.Server.main
diff --git a/test/hs/Test/Ganeti/Runtime.hs b/test/hs/Test/Ganeti/Runtime.hs
index faa2ac6..8e08e78 100644
--- a/test/hs/Test/Ganeti/Runtime.hs
+++ b/test/hs/Test/Ganeti/Runtime.hs
@@ -83,12 +83,14 @@ case_UsersGroups = do
               \         constants.NODED_USER,\n\
               \         constants.RAPI_USER,\n\
               \         constants.CONFD_USER,\n\
+              \         constants.QUERYD_USER,\n\
               \         constants.MOND_USER,\n\
               \        ]\n\
               \groups = [constants.MASTERD_GROUP,\n\
               \          constants.NODED_GROUP,\n\
               \          constants.RAPI_GROUP,\n\
               \          constants.CONFD_GROUP,\n\
+              \          constants.QUERYD_GROUP,\n\
               \          constants.MOND_GROUP,\n\
               \          constants.DAEMONS_GROUP,\n\
               \          constants.ADMIN_GROUP,\n\
diff --git a/test/py/daemon-util_unittest.bash b/test/py/daemon-util_unittest.bash
index 75414eb..d40a274 100755
--- a/test/py/daemon-util_unittest.bash
+++ b/test/py/daemon-util_unittest.bash
@@ -40,8 +40,8 @@ DAEMONS_LIST="noded masterd rapi"
 STOPDAEMONS_LIST="rapi masterd noded"
 
 if grep -q '^ENABLE_CONFD = True' lib/_autoconf.py; then
-  DAEMONS_LIST="$DAEMONS_LIST confd"
-  STOPDAEMONS_LIST="confd $STOPDAEMONS_LIST"
+  DAEMONS_LIST="$DAEMONS_LIST confd queryd"
+  STOPDAEMONS_LIST="queryd confd $STOPDAEMONS_LIST"
 fi
 
 if grep -q '^ENABLE_MOND = True' lib/_autoconf.py; then
diff --git a/tools/users-setup.in b/tools/users-setup.in
index f63765b..92686b4 100644
--- a/tools/users-setup.in
+++ b/tools/users-setup.in
@@ -7,17 +7,19 @@ addgroup --system @GNTADMINGROUP@
 addgroup --system @GNTMASTERUSER@
 addgroup --system @GNTRAPIUSER@
 addgroup --system @GNTCONFDUSER@
+addgroup --system @GNTQUERYDUSER@
 addgroup --system @GNTMONDUSER@
 
 #Create users
 adduser --system --ingroup @GNTMASTERDGROUP@ @GNTMASTERUSER@
 adduser --system --ingroup @GNTRAPIGROUP@ @GNTRAPIUSER@
 adduser --system --ingroup @GNTCONFDGROUP@ @GNTCONFDUSER@
+adduser --system --ingroup @GNTQUERYDGROUP@ @GNTQUERYDUSER@
 adduser --system --ingroup @GNTMONDGROUP@ @GNTMONDUSER@
 adduser --system @GNTNODEDUSER@
 
 #Add users to extra groups
-for u in @GNTMASTERUSER@ @GNTCONFDUSER@ @GNTRAPIUSER@ @GNTMONDUSER@
+for u in @GNTMASTERUSER@ @GNTCONFDUSER@ @GNTQUERYDUSER@ @GNTRAPIUSER@ @GNTMONDUSER@
 do
   adduser $u @GNTDAEMONSGROUP@
 done
@@ -29,4 +31,5 @@ done
 
 adduser @GNTMASTERUSER@ @GNTCONFDGROUP@
 adduser @GNTMONDUSER@ @GNTMASTERDGROUP@
-adduser @GNTCONFDUSER@ @GNTMASTERDGROUP@
+adduser @GNTQUERYDUSER@ @GNTMASTERDGROUP@
+adduser @GNTQUERYDUSER@ @GNTCONFDGROUP@
diff --git a/tools/vcluster-setup.in b/tools/vcluster-setup.in
index ad918bd..da766a9 100644
--- a/tools/vcluster-setup.in
+++ b/tools/vcluster-setup.in
@@ -203,6 +203,7 @@ NODED_ARGS='--no-mlock $daemon_args'
 MASTERD_ARGS=''
 RAPI_ARGS='$daemon_args'
 CONFD_ARGS='$daemon_args'
+QUERYD_ARGS=''
 
 export GANETI_ROOTDIR='$nodedir'
 export GANETI_HOSTNAME='$(node_hostname $number)'
