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
