On Tue, May 14, 2013 at 6:30 PM, Helga Velroyen <[email protected]> wrote:

> This adds functionality to retrieve disk space information
> for file storage. It calls the 'df' tool and parses its
> output to extract the total and free amount of disk space
> on the disk where the given path is located.
> The code is not integrated yet, but thoroughly unit-tested.
>
> Signed-off-by: Helga Velroyen <[email protected]>
> ---
>  Makefile.am                                    |  4 +-
>  lib/storage/filestorage.py                     | 78 +++++++++++++++++++++
>  test/py/ganeti.storage.filestorage_unittest.py | 96
> ++++++++++++++++++++++++++
>  3 files changed, 177 insertions(+), 1 deletion(-)
>  create mode 100644 lib/storage/filestorage.py
>  create mode 100755 test/py/ganeti.storage.filestorage_unittest.py
>
> diff --git a/Makefile.am b/Makefile.am
> index 5a88d54..e05a336 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -322,7 +322,8 @@ storage_PYTHON = \
>         lib/storage/container.py \
>         lib/storage/drbd.py \
>         lib/storage/drbd_info.py \
> -       lib/storage/drbd_cmdgen.py
> +       lib/storage/drbd_cmdgen.py \
> +       lib/storage/filestorage.py
>
>  rapi_PYTHON = \
>         lib/rapi/__init__.py \
> @@ -1191,6 +1192,7 @@ python_tests = \
>         test/py/ganeti.server.rapi_unittest.py \
>         test/py/ganeti.ssconf_unittest.py \
>         test/py/ganeti.ssh_unittest.py \
> +       test/py/ganeti.storage.filestorage_unittest.py \
>         test/py/ganeti.tools.burnin_unittest.py \
>         test/py/ganeti.tools.ensure_dirs_unittest.py \
>         test/py/ganeti.tools.node_daemon_setup_unittest.py \
> diff --git a/lib/storage/filestorage.py b/lib/storage/filestorage.py
> new file mode 100644
> index 0000000..47152c8
> --- /dev/null
> +++ b/lib/storage/filestorage.py
> @@ -0,0 +1,78 @@
> +#
> +#
> +
> +# Copyright (C) 2011, 2012 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.
> +
> +
> +"""File storage functions.
> +
> +"""
> +
> +from ganeti import errors
> +from ganeti import utils
> +
> +DF_M_UNIT = 'M'
> +DF_MIN_NUM_COLS = 4
> +DF_NUM_LINES = 2
> +
> +
> +def _ParseDfResult(dfresult):
> +  """Parses the output of the call of the 'df' tool.
> +
> +     @type dfresult: string
> +     @param dfresult: output of the 'df' call
> +     @return: tuple (size, free) of the total and free disk space in
> MebiBytes
>

According to the styleguide there should be an empty line (same for other
comments below).


> +  """
> +  df_lines = dfresult.splitlines()
> +  if len(df_lines) != DF_NUM_LINES:
> +    raise errors.CommandError("'df' output has wrong number of lines: %s"
> %
> +                              len(df_lines))
> +  df_values = df_lines[1].strip().split()
> +  if len(df_values) < DF_MIN_NUM_COLS:
> +    raise errors.CommandError("'df' output does not have enough columns:
> %s" %
> +                              len(df_values))
> +  size_str = df_values[1]
> +  if size_str[-1] != DF_M_UNIT:
> +    raise errors.CommandError("'df': 'size' not given in Mebibytes.")
> +  free_str = df_values[3]
> +  if free_str[-1] != DF_M_UNIT:
> +    raise errors.CommandError("'df': 'free' not given in Mebibytes.")
> +  size = int(size_str[:-1])
> +  free = int(free_str[:-1])
> +  return (size, free)
> +
> +
> +def GetSpaceInfo(path, _parsefn=_ParseDfResult):
> +  """Retrieves the free and total space of the device where the file is
> +     located.
> +
> +     @type path: string
> +     @param path: Path of the file whose embracing device's capacity is
> +       reported.
> +     @type: _parse_fn: function
> +     @param: _parse_fn: Function that parses the output of the 'df'
> command;
> +       given as parameter to make this code more testable.
> +     @return: a JSON dictionary containing 'vg_size' and 'vg_free'
> +  """
> +  cmd = ['df', '-BM', path]
> +  result = utils.RunCmd(cmd)
> +  if result.failed:
> +    raise errors.CommandError("Failed to run 'df' command: %s - %s" %
> +                              (result.fail_reason, result.output))
> +  (size, free) = _parsefn(result.stdout)
> +  return '{"vg_size": %s, "vg_free": %s}' % (size, free)
> diff --git a/test/py/ganeti.storage.filestorage_unittest.py b/test/py/
> ganeti.storage.filestorage_unittest.py
> new file mode 100755
> index 0000000..90d5c26
> --- /dev/null
> +++ b/test/py/ganeti.storage.filestorage_unittest.py
> @@ -0,0 +1,96 @@
> +#!/usr/bin/python
> +#
> +
> +# Copyright (C) 2006, 2007, 2010, 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.
> +
> +
> +"""Script for unittesting the ganeti.storage.file module"""
> +
> +
> +import unittest
> +
> +from ganeti import errors
> +from ganeti.storage import filestorage
> +
> +import testutils
> +
> +
> +class TestFileStorageSpaceInfo(unittest.TestCase):
> +
> +  def testSpaceInfoPathInvalid(self):
> +    """Tests that an error is raised when the given file is not existing.
> +
> +    """
> +    self.assertRaises(errors.CommandError, filestorage.GetSpaceInfo,
> +                      "/path/does/not/exist/")
> +
> +  def testSpaceInfoPathValid(self):
> +    """Tests that the 'df' command is run if the file is valid.
> +
> +    """
> +    info = filestorage.GetSpaceInfo("/")
> +
> +  def testParseDfOutputValidInput(self):
> +    """Tests that parsing of the out put of 'df' works correctly.
> +
> +    """
> +    valid_df_output = \
> +      "Filesystem             1M-blocks   Used Available Use% Mounted
> on\n" \
> +      "/dev/mapper/sysvg-root   161002M 58421M    94403M  39% /"
> +    expected_size = 161002
> +    expected_free = 94403
> +
> +    (size, free) = filestorage._ParseDfResult(valid_df_output)
> +    self.assertEqual(expected_size, size,
> +                     "Calculation of total size is incorrect.")
> +    self.assertEqual(expected_free, free,
> +                     "Calculation of free space is incorrect.")
> +
> +
> +  def testParseDfOutputInvalidInput(self):
> +    """Tests that parsing of the out put of 'df' works correctly.
> +
> +    """
> +    invalid_output_header_missing = \
> +      "/dev/mapper/sysvg-root   161002M 58421M    94403M  39% /"
> +    invalid_output_dataline_missing = \
> +      "Filesystem             1M-blocks   Used Available Use% Mounted
> on\n"
> +    invalid_output_wrong_num_columns = \
> +      "Filesystem             1M-blocks Available\n" \
> +      "/dev/mapper/sysvg-root   161002M    94403M"
> +    invalid_output_units_wrong = \
> +      "Filesystem             1M-blocks   Used Available Use% Mounted
> on\n" \
> +      "/dev/mapper/sysvg-root   161002G 58421G    94403G  39% /"
> +    invalid_output_units_missing = \
> +      "Filesystem             1M-blocks   Used Available Use% Mounted
> on\n" \
> +      "/dev/mapper/sysvg-root    161002  58421     94403  39% /"
> +    invalid_outputs = [invalid_output_header_missing,
> +                       invalid_output_dataline_missing,
> +                       invalid_output_wrong_num_columns,
> +                       invalid_output_units_wrong,
> +                       invalid_output_units_missing]
> +
> +    for output in invalid_outputs:
> +      self.assertRaises(errors.CommandError, filestorage._ParseDfResult,
> output)
> +
> +
> +if __name__ == "__main__":
> +  testutils.GanetiTestProgram()
> +
> +if __name__ == "__main__":
> +  testutils.GanetiTestProgram()
> --
> 1.8.2.1
>
>
Maybe I don't understand correctly, but why are we parsing the output of
`df` here? Couldn't we simply call os.statvfs ([0])?

Thanks,
Thomas

[0] http://docs.python.org/2/library/os.html#os.statvfs



-- 
Thomas Thrainer | Software Engineer | [email protected] |

Google Germany GmbH
Dienerstr. 12
80331 München

Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Geschäftsführer: Graham Law, Katherine Stephens

Reply via email to