From d78970bac4e297aaf0fbd337fa68e98507dac8e3 Mon Sep 17 00:00:00 2001
From: Michele Tartara <mtartara@google.com>
Date: Tue, 8 Jan 2013 12:45:08 +0000
Subject: [PATCH] Support instance-minor pairing in the DRBD collector

This commits enables the DRBD data collector to use the Confd client to
gather information about the pairing between DRBD minors and instances.

For testing purposes, the DRBD data collector now requires either zero
or two parameters: one is the DRBD file, one is the pairings file.
When no parameter is passed, the collector is in "production mode" and takes
the data from /proc/drbd and from the Confd client.

The shell tests of mon-collector are updated accordingly.

Signed-off-by: Michele Tartara <mtartara@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 Makefile.am                                  |  1 +
 man/mon-collector.rst                        | 17 ++++--
 src/Ganeti/DataCollectors/CLI.hs             | 24 +++++++++
 src/Ganeti/DataCollectors/Drbd.hs            | 54 ++++++++++++++++----
 test/data/instance-minor-pairing.txt         |  2 +
 test/hs/shelltests/htools-mon-collector.test | 19 +++----
 6 files changed, 92 insertions(+), 25 deletions(-)
 create mode 100644 test/data/instance-minor-pairing.txt

diff --git a/Makefile.am b/Makefile.am
index 553b4e707..38f0e460a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -914,6 +914,7 @@ TEST_FILES = \
 	test/data/bdev-drbd-net-ip6.txt \
 	test/data/cert1.pem \
 	test/data/cert2.pem \
+	test/data/instance-minor-pairing.txt \
 	test/data/ip-addr-show-dummy0.txt \
 	test/data/ip-addr-show-lo-ipv4.txt \
 	test/data/ip-addr-show-lo-ipv6.txt \
diff --git a/man/mon-collector.rst b/man/mon-collector.rst
index 6260e2ea6..aea881200 100644
--- a/man/mon-collector.rst
+++ b/man/mon-collector.rst
@@ -33,10 +33,21 @@ COLLECTORS
 DRBD
 ~~~~
 
-| drbd [*status-file*]
+| drbd [ [ **-s** | **\--drbd-status** ] = *status-file* ] [ [ **-p** | **\--drbd-pairing**] = *pairing-file* ]
 
 Collects the information about the version and status of the DRBD kernel
 module, and of the disks it is managing.
 
-If *status-file* is specified, the status will be read from that file.
-Otherwise, the collector will read it from /proc/drbd.
+If *status-file* and *pairing-file* are specified, the status and the
+instance-minor paring information will be read from those files.
+Otherwise, the collector will read them, respectively, from /proc/drbd
+and from the Confd server.
+
+The options that can be passed to the DRBD collector are as follows:
+
+-s *status-file*, \--drbd-status=*status-file*
+  Read the DRBD status from the specified file instead of /proc/drbd.
+
+-p *pairing-file*, \--drbd-pairing=*pairing-file*
+  Read the information about the pairing between instances and DRBD minors
+  from the specified file instead of asking the Confd servers for them.
diff --git a/src/Ganeti/DataCollectors/CLI.hs b/src/Ganeti/DataCollectors/CLI.hs
index b1289b3bc..c79307807 100644
--- a/src/Ganeti/DataCollectors/CLI.hs
+++ b/src/Ganeti/DataCollectors/CLI.hs
@@ -34,9 +34,14 @@ module Ganeti.DataCollectors.CLI
   , oShowHelp
   , oShowVer
   , oShowComp
+  , oDrbdPairing
+  , oDrbdStatus
   , genericOptions
   ) where
 
+import System.Console.GetOpt
+
+import Ganeti.BasicTypes
 import Ganeti.Common as Common
 
 -- * Data types
@@ -46,6 +51,10 @@ data Options = Options
   { optShowHelp    :: Bool           -- ^ Just show the help
   , optShowComp    :: Bool           -- ^ Just show the completion info
   , optShowVer     :: Bool           -- ^ Just show the program version
+  , optDrbdStatus  :: Maybe FilePath -- ^ Path to the file containing DRBD
+                                     -- status information
+  , optDrbdPairing :: Maybe FilePath -- ^ Path to the file containing pairings
+                                     -- between instances and DRBD minors
   } deriving Show
 
 -- | Default values for the command line options.
@@ -54,6 +63,8 @@ defaultOptions  = Options
   { optShowHelp    = False
   , optShowComp    = False
   , optShowVer     = False
+  , optDrbdStatus  = Nothing
+  , optDrbdPairing = Nothing
   }
 
 -- | Abbreviation for the option type.
@@ -68,6 +79,19 @@ instance StandardOptions Options where
   requestComp o = o { optShowComp = True }
 
 -- * Command line options
+oDrbdPairing :: OptType
+oDrbdPairing =
+  ( Option "p" ["drbd-pairing"]
+      (ReqArg (\ f o -> Ok o { optDrbdPairing = Just f}) "FILE")
+      "the FILE containing pairings between instances and DRBD minors",
+    OptComplFile)
+
+oDrbdStatus :: OptType
+oDrbdStatus =
+  ( Option "s" ["drbd-status"]
+      (ReqArg (\ f o -> Ok o { optDrbdStatus = Just f }) "FILE")
+      "the DRBD status FILE",
+    OptComplFile)
 
 -- | Generic options.
 genericOptions :: [GenericOptType Options]
diff --git a/src/Ganeti/DataCollectors/Drbd.hs b/src/Ganeti/DataCollectors/Drbd.hs
index 8a9babcf1..0a3b6a317 100644
--- a/src/Ganeti/DataCollectors/Drbd.hs
+++ b/src/Ganeti/DataCollectors/Drbd.hs
@@ -31,14 +31,20 @@ module Ganeti.DataCollectors.Drbd
 
 
 import qualified Control.Exception as E
+import Control.Monad
 import Data.Attoparsec.Text.Lazy as A
+import Data.Maybe
 import Data.Text.Lazy (pack, unpack)
-import Text.JSON
+import Network.BSD (getHostName)
+import qualified Text.JSON as J
 
 import qualified Ganeti.BasicTypes as BT
 import qualified Ganeti.Constants as C
 import Ganeti.Block.Drbd.Parser(drbdStatusParser)
+import Ganeti.Block.Drbd.Types(DrbdInstMinor)
 import Ganeti.Common
+import Ganeti.Confd.Client
+import Ganeti.Confd.Types
 import Ganeti.DataCollectors.CLI
 import Ganeti.Utils
 
@@ -56,29 +62,55 @@ defaultCharNum :: Int
 defaultCharNum = 80*20
 
 options :: IO [OptType]
-options = return []
+options =
+  return
+    [ oDrbdStatus
+    , oDrbdPairing
+    ]
 
 -- | The list of arguments supported by the program.
 arguments :: [ArgCompletion]
-arguments = [ArgCompletion OptComplFile 0 (Just 1)]
+arguments = [ArgCompletion OptComplFile 0 (Just 0)]
 
 -- * Command line options
 
+-- | Get information about the pairing of DRBD minors and Ganeti instances
+-- on the current node. The information is taken from the Confd client
+-- or, if a filename is specified, from a JSON encoded file (for testing
+-- purposes).
+getPairingInfo :: Maybe String -> IO (BT.Result [DrbdInstMinor])
+getPairingInfo Nothing = do
+  curNode <- getHostName
+  client <- getConfdClient
+  reply <- query client ReqNodeDrbd $ PlainQuery curNode
+  return $
+    case fmap (J.readJSONs . confdReplyAnswer) reply of
+      Just (J.Ok instMinor) -> BT.Ok instMinor
+      Just (J.Error msg) -> BT.Bad msg
+      Nothing -> BT.Bad "No answer from the Confd server"
+getPairingInfo (Just filename) = do
+  content <- readFile filename
+  return $
+    case J.decode content of
+      J.Ok instMinor -> BT.Ok instMinor
+      J.Error msg -> BT.Bad msg
+
 -- | Main function.
 main :: Options -> [String] -> IO ()
-main _ args = do
-  proc_drbd <- case args of
-                 [ ] -> return defaultFile
-                 [x] -> return x
-                 _   -> exitErr $ "This program takes only one argument," ++
-                                  " got '" ++ unwords args ++ "'"
+main opts args = do
+  let proc_drbd = fromMaybe defaultFile $ optDrbdStatus opts
+      instMinor = optDrbdPairing opts
+  unless (null args) . exitErr $ "This program takes exactly zero" ++
+                                  " arguments, got '" ++ unwords args ++ "'"
   contents <-
     ((E.try $ readFile proc_drbd) :: IO (Either IOError String)) >>=
       exitIfBad "reading from file" . either (BT.Bad . show) BT.Ok
+  pairingResult <- getPairingInfo instMinor
+  pairing <- exitIfBad "Can't get pairing info" pairingResult
   output <-
-    case A.parse (drbdStatusParser []) $ pack contents of
+    case A.parse (drbdStatusParser pairing) $ pack contents of
       A.Fail unparsedText contexts errorMessage -> exitErr $
         show (Prelude.take defaultCharNum $ unpack unparsedText) ++ "\n"
           ++ show contexts ++ "\n" ++ errorMessage
-      A.Done _ drbdStatus -> return $ encode drbdStatus
+      A.Done _ drbdStatus -> return $ J.encode drbdStatus
   putStrLn output
diff --git a/test/data/instance-minor-pairing.txt b/test/data/instance-minor-pairing.txt
new file mode 100644
index 000000000..0ffea3353
--- /dev/null
+++ b/test/data/instance-minor-pairing.txt
@@ -0,0 +1,2 @@
+[["machine1.example.com",0,"instance1.example.com","disk/0","secondary",
+"machine2.example.com"]]
diff --git a/test/hs/shelltests/htools-mon-collector.test b/test/hs/shelltests/htools-mon-collector.test
index e0ab7a309..f9bf1c99c 100644
--- a/test/hs/shelltests/htools-mon-collector.test
+++ b/test/hs/shelltests/htools-mon-collector.test
@@ -23,30 +23,27 @@
 >>>= 0
 
 # 3. Test that the drbd collector fails parsing /dev/null
-./test/hs/hpc-mon-collector drbd /dev/null
->>>2
-Error: ""
-[]
-Failed reading: versionInfo
+./test/hs/hpc-mon-collector drbd --drbd-status=/dev/null --drbd-pairing=/dev/null
+>>>2/Malformed JSON/
 >>>= !0
 
 # 4. Test that a non-existent file is correctly reported
-./test/hs/hpc-mon-collector drbd /dev/no-such-file
+./test/hs/hpc-mon-collector drbd --drbd-status=/dev/no-such-file --drbd-pairing=/dev/no-such-file
 >>>2/Error: reading from file: .* does not exist/
 >>>= !0
 
-# 5. Test that multiple files are rejected
-./test/hs/hpc-mon-collector drbd /dev/null /dev/null
->>>2/takes only one argument/
+# 5. Test that arguments are rejected
+./test/hs/hpc-mon-collector drbd /dev/null
+>>>2/takes exactly zero arguments/
 >>>= !0
 
 # 6. Test that a standard test file is parsed correctly
-./test/hs/hpc-mon-collector drbd $PYTESTDATA_DIR/proc_drbd83.txt
+./test/hs/hpc-mon-collector drbd --drbd-status=$PYTESTDATA_DIR/proc_drbd83.txt --drbd-pairing=$PYTESTDATA_DIR/instance-minor-pairing.txt
 >>>=0
 
 # 7. Test that the drbd collector fails parsing /dev/zero, but is not
 # stuck forever printing \NUL chars
-./test/hs/hpc-mon-collector drbd /dev/zero
+./test/hs/hpc-mon-collector drbd --drbd-status=/dev/zero --drbd-pairing=$PYTESTDATA_DIR/instance-minor-pairing.txt
 >>>2
 Error: "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"
 []
-- 
GitLab