Revision 5ca84bdd

b/test/Makefile.am
1
TESTS = ganeti.hooks_unittest.py ganeti.utils_unittest.py
2
TESTS_ENVIRONMENT = PYTHONPATH=.:$(srcdir)
3

  
4
check_DATA = ganeti
5
ganeti:
6
	rm -f ganeti
7
	ln -s $(top_srcdir)/lib ganeti
8

  
9
EXTRA_DIST = $(TESTS) mocks.py
10
CLEANFILES = *.py[co]
11
MAINTAINERCLEANFILES = ganeti
b/test/ganeti.config_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the config module"""
23

  
24

  
25
import unittest
26
import os
27
import time
28
import tempfile
29
import os.path
30
import socket
31

  
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import config
35
from ganeti import objects
36

  
37

  
38
class TestConfigRunner(unittest.TestCase):
39
  """Testing case for HooksRunner"""
40
  def setUp(self):
41
    fd, self.cfg_file = tempfile.mkstemp()
42
    os.close(fd)
43

  
44
  def tearDown(self):
45
    try:
46
      os.unlink(self.cfg_file)
47
    except OSError:
48
      pass
49

  
50
  def _get_object(self):
51
    """Returns a instance of ConfigWriter"""
52
    cfg = config.ConfigWriter(cfg_file=self.cfg_file, offline=True)
53
    return cfg
54

  
55
  def _init_cluster(self, cfg):
56
    """Initializes the cfg object"""
57
    cfg.InitConfig(socket.gethostname(), '127.0.0.1', None, '', 'aa:00:00',
58
                   'xenvg', constants.DEFAULT_BRIDGE)
59

  
60
  def _create_instance(self):
61
    """Create and return an instance object"""
62
    inst = objects.Instance(name="test.example.com", disks=[],
63
                            disk_template=constants.DT_DISKLESS)
64
    return inst
65

  
66
  def testEmpty(self):
67
    """Test instantiate config object"""
68
    self._get_object()
69

  
70
  def testInit(self):
71
    """Test initialize the config file"""
72
    cfg = self._get_object()
73
    self._init_cluster(cfg)
74
    self.failUnlessEqual(1, len(cfg.GetNodeList()))
75
    self.failUnlessEqual(0, len(cfg.GetInstanceList()))
76

  
77
  def testUpdateCluster(self):
78
    """Test updates on the cluster object"""
79
    cfg = self._get_object()
80
    # construct a fake cluster object
81
    fake_cl = objects.Cluster()
82
    # fail if we didn't read the config
83
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_cl)
84

  
85
    self._init_cluster(cfg)
86
    cl = cfg.GetClusterInfo()
87
    # first pass, must not fail
88
    cfg.Update(cl)
89
    # second pass, also must not fail (after the config has been written)
90
    cfg.Update(cl)
91
    # but the fake_cl update should still fail
92
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl)
93

  
94
  def testUpdateNode(self):
95
    """Test updates on one node object"""
96
    cfg = self._get_object()
97
    # construct a fake node
98
    fake_node = objects.Node()
99
    # fail if we didn't read the config
100
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_node)
101

  
102
    self._init_cluster(cfg)
103
    node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
104
    # first pass, must not fail
105
    cfg.Update(node)
106
    # second pass, also must not fail (after the config has been written)
107
    cfg.Update(node)
108
    # but the fake_node update should still fail
109
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node)
110

  
111
  def testUpdateInstance(self):
112
    """Test updates on one instance object"""
113
    cfg = self._get_object()
114
    # construct a fake instance
115
    inst = self._create_instance()
116
    fake_instance = objects.Instance()
117
    # fail if we didn't read the config
118
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_instance)
119

  
120
    self._init_cluster(cfg)
121
    cfg.AddInstance(inst)
122
    instance = cfg.GetInstanceInfo(cfg.GetInstanceList()[0])
123
    # first pass, must not fail
124
    cfg.Update(instance)
125
    # second pass, also must not fail (after the config has been written)
126
    cfg.Update(instance)
127
    # but the fake_instance update should still fail
128
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance)
129

  
130

  
131
if __name__ == '__main__':
132
  unittest.main()
b/test/ganeti.hooks_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the hooks module"""
23

  
24

  
25
import unittest
26
import os
27
import time
28
import tempfile
29
import os.path
30

  
31
from ganeti import errors
32
from ganeti import opcodes
33
from ganeti import mcpu
34
from ganeti import backend
35
from ganeti import constants
36
from ganeti import cmdlib
37
from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP
38

  
39
from mocks import FakeConfig, FakeSStore
40

  
41
class FakeLU(cmdlib.LogicalUnit):
42
  HPATH = "test"
43
  def BuildHooksEnv(self):
44
    return {}, ["localhost"], ["localhost"]
45

  
46
class TestHooksRunner(unittest.TestCase):
47
  """Testing case for HooksRunner"""
48
  def setUp(self):
49
    self.torm = []
50
    self.tmpdir = tempfile.mkdtemp()
51
    self.torm.append((self.tmpdir, True))
52
    self.logdir = tempfile.mkdtemp()
53
    self.torm.append((self.logdir, True))
54
    self.hpath = "fake"
55
    self.ph_dirs = {}
56
    for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
57
      dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
58
      os.mkdir(dname)
59
      self.torm.append((dname, True))
60
      self.ph_dirs[i] = dname
61
    self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
62

  
63
  def tearDown(self):
64
    self.torm.reverse()
65
    for path, kind in self.torm:
66
      if kind:
67
        os.rmdir(path)
68
      else:
69
        os.unlink(path)
70

  
71
  def _rname(self, fname):
72
    return "/".join(fname.split("/")[-2:])
73

  
74
  def testEmpty(self):
75
    """Test no hooks"""
76
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
77
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
78

  
79
  def testSkipNonExec(self):
80
    """Test skip non-exec file"""
81
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
82
      fname = "%s/test" % self.ph_dirs[phase]
83
      f = open(fname, "w")
84
      f.close()
85
      self.torm.append((fname, False))
86
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
87
                           [(self._rname(fname), HKR_SKIP, "")])
88

  
89
  def testSkipInvalidName(self):
90
    """Test skip script with invalid name"""
91
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
92
      fname = "%s/a.off" % self.ph_dirs[phase]
93
      f = open(fname, "w")
94
      f.write("#!/bin/sh\nexit 0\n")
95
      f.close()
96
      os.chmod(fname, 0700)
97
      self.torm.append((fname, False))
98
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
99
                           [(self._rname(fname), HKR_SKIP, "")])
100

  
101
  def testSkipDir(self):
102
    """Test skip directory"""
103
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
104
      fname = "%s/testdir" % self.ph_dirs[phase]
105
      os.mkdir(fname)
106
      self.torm.append((fname, True))
107
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
108
                           [(self._rname(fname), HKR_SKIP, "")])
109

  
110
  def testSuccess(self):
111
    """Test success execution"""
112
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
113
      fname = "%s/success" % self.ph_dirs[phase]
114
      f = open(fname, "w")
115
      f.write("#!/bin/sh\nexit 0\n")
116
      f.close()
117
      self.torm.append((fname, False))
118
      os.chmod(fname, 0700)
119
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
120
                           [(self._rname(fname), HKR_SUCCESS, "")])
121

  
122
  def testSymlink(self):
123
    """Test running a symlink"""
124
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
125
      fname = "%s/success" % self.ph_dirs[phase]
126
      os.symlink("/bin/true", fname)
127
      self.torm.append((fname, False))
128
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
129
                           [(self._rname(fname), HKR_SUCCESS, "")])
130

  
131
  def testFail(self):
132
    """Test success execution"""
133
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
134
      fname = "%s/success" % self.ph_dirs[phase]
135
      f = open(fname, "w")
136
      f.write("#!/bin/sh\nexit 1\n")
137
      f.close()
138
      self.torm.append((fname, False))
139
      os.chmod(fname, 0700)
140
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
141
                           [(self._rname(fname), HKR_FAIL, "")])
142

  
143
  def testCombined(self):
144
    """Test success, failure and skip all in one test"""
145
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
146
      expect = []
147
      for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
148
                               ("10fail", 1, HKR_FAIL),
149
                               ("20inv.", 0, HKR_SKIP),
150
                               ]:
151
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
152
        f = open(fname, "w")
153
        f.write("#!/bin/sh\nexit %d\n" % ecode)
154
        f.close()
155
        self.torm.append((fname, False))
156
        os.chmod(fname, 0700)
157
        expect.append((self._rname(fname), rs, ""))
158
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
159

  
160
  def testOrdering(self):
161
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
162
      expect = []
163
      for fbase in ["10s1",
164
                    "00s0",
165
                    "10sa",
166
                    "80sc",
167
                    "60sd",
168
                    ]:
169
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
170
        os.symlink("/bin/true", fname)
171
        self.torm.append((fname, False))
172
        expect.append((self._rname(fname), HKR_SUCCESS, ""))
173
      expect.sort()
174
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
175

  
176
  def testEnv(self):
177
    """Test environment execution"""
178
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
179
      fbase = "success"
180
      fname = "%s/%s" % (self.ph_dirs[phase], fbase)
181
      os.symlink("/usr/bin/env", fname)
182
      self.torm.append((fname, False))
183
      env_snt = {"PHASE": phase}
184
      env_exp = "PHASE=%s\n" % phase
185
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
186
                           [(self._rname(fname), HKR_SUCCESS, env_exp)])
187

  
188

  
189
class TestHooksMaster(unittest.TestCase):
190
  """Testing case for HooksMaster"""
191

  
192
  def _call_false(*args):
193
    """Fake call_hooks_runner function which returns False."""
194
    return False
195

  
196
  @staticmethod
197
  def _call_nodes_false(node_list, hpath, phase, env):
198
    """Fake call_hooks_runner function.
199

  
200
    Returns:
201
      - list of False values with the same len as the node_list argument
202

  
203
    """
204
    return [False for node_name in node_list]
205

  
206
  @staticmethod
207
  def _call_script_fail(node_list, hpath, phase, env):
208
    """Fake call_hooks_runner function.
209

  
210
    Returns:
211
      - list of False values with the same len as the node_list argument
212

  
213
    """
214
    return dict([(node_name, [("unittest", constants.HKR_FAIL, "error")])
215
                 for node_name in node_list])
216

  
217
  @staticmethod
218
  def _call_script_succeed(node_list, hpath, phase, env):
219
    """Fake call_hooks_runner function.
220

  
221
    Returns:
222
      - list of False values with the same len as the node_list argument
223

  
224
    """
225
    return dict([(node_name, [("unittest", constants.HKR_SUCCESS, "ok")])
226
                 for node_name in node_list])
227

  
228
  def testTotalFalse(self):
229
    """Test complete rpc failure"""
230
    cfg = FakeConfig()
231
    sstore = FakeSStore()
232
    op = opcodes.OpCode()
233
    lu = FakeLU(None, op, cfg, sstore)
234
    hm = mcpu.HooksMaster(self._call_false, cfg, sstore, lu)
235
    self.failUnlessRaises(errors.HooksFailure,
236
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
237
    hm.RunPhase(constants.HOOKS_PHASE_POST)
238

  
239
  def testIndividualFalse(self):
240
    """Test individual rpc failure"""
241
    cfg = FakeConfig()
242
    sstore = FakeSStore()
243
    op = opcodes.OpCode()
244
    lu = FakeLU(None, op, cfg, sstore)
245
    hm = mcpu.HooksMaster(self._call_nodes_false, cfg, sstore, lu)
246
    self.failUnlessRaises(errors.HooksFailure,
247
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
248
    hm.RunPhase(constants.HOOKS_PHASE_POST)
249

  
250
  def testScriptFalse(self):
251
    """Test individual rpc failure"""
252
    cfg = FakeConfig()
253
    op = opcodes.OpCode()
254
    sstore = FakeSStore()
255
    lu = FakeLU(None, op, cfg, sstore)
256
    hm = mcpu.HooksMaster(self._call_script_fail, cfg, sstore, lu)
257
    self.failUnlessRaises(errors.HooksAbort,
258
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
259
    hm.RunPhase(constants.HOOKS_PHASE_POST)
260

  
261
  def testScriptSucceed(self):
262
    """Test individual rpc failure"""
263
    cfg = FakeConfig()
264
    op = opcodes.OpCode()
265
    sstore = FakeSStore()
266
    lu = FakeLU(None, op, cfg, sstore)
267
    hm = mcpu.HooksMaster(self._call_script_succeed, cfg, sstore, lu)
268
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
269
      hm.RunPhase(phase)
270

  
271
if __name__ == '__main__':
272
  unittest.main()
b/test/ganeti.utils_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the utils module"""
23

  
24
import unittest
25
import os
26
import time
27
import tempfile
28
import os.path
29
import md5
30

  
31
import ganeti
32
from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \
33
     RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
34
     ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
35
     ShellQuote, ShellQuoteArgs, _ParseIpOutput
36
from ganeti.errors import LockError, UnitParseError
37

  
38
class TestIsProcessAlive(unittest.TestCase):
39
  """Testing case for IsProcessAlive"""
40
  def setUp(self):
41
    # create a zombie and a (hopefully) non-existing process id
42
    self.pid_zombie = os.fork()
43
    if self.pid_zombie == 0:
44
      os._exit(0)
45
    elif self.pid_zombie < 0:
46
      raise SystemError("can't fork")
47
    self.pid_non_existing = os.fork()
48
    if self.pid_non_existing == 0:
49
      os._exit(0)
50
    elif self.pid_non_existing > 0:
51
      os.waitpid(self.pid_non_existing, 0)
52
    else:
53
      raise SystemError("can't fork")
54

  
55

  
56
  def testExists(self):
57
    mypid = os.getpid()
58
    self.assert_(IsProcessAlive(mypid),
59
                 "can't find myself running")
60

  
61
  def testZombie(self):
62
    self.assert_(not IsProcessAlive(self.pid_zombie),
63
                 "zombie not detected as zombie")
64

  
65

  
66
  def testNotExisting(self):
67
    self.assert_(not IsProcessAlive(self.pid_non_existing),
68
                 "noexisting process detected")
69

  
70

  
71
class TestLocking(unittest.TestCase):
72
  """Testing case for the Lock/Unlock functions"""
73
  def clean_lock(self, name):
74
    try:
75
      ganeti.utils.Unlock("unittest")
76
    except LockError:
77
      pass
78

  
79

  
80
  def testLock(self):
81
    self.clean_lock("unittest")
82
    self.assertEqual(None, Lock("unittest"))
83

  
84

  
85
  def testUnlock(self):
86
    self.clean_lock("unittest")
87
    ganeti.utils.Lock("unittest")
88
    self.assertEqual(None, Unlock("unittest"))
89

  
90

  
91
  def testDoubleLock(self):
92
    self.clean_lock("unittest")
93
    ganeti.utils.Lock("unittest")
94
    self.assertRaises(LockError, Lock, "unittest")
95

  
96

  
97
class TestRunCmd(unittest.TestCase):
98
  """Testing case for the RunCmd function"""
99

  
100
  def setUp(self):
101
    self.magic = time.ctime() + " ganeti test"
102

  
103
  def testOk(self):
104
    """Test successful exit code"""
105
    result = RunCmd("/bin/sh -c 'exit 0'")
106
    self.assertEqual(result.exit_code, 0)
107

  
108
  def testFail(self):
109
    """Test fail exit code"""
110
    result = RunCmd("/bin/sh -c 'exit 1'")
111
    self.assertEqual(result.exit_code, 1)
112

  
113

  
114
  def testStdout(self):
115
    """Test standard output"""
116
    cmd = 'echo -n "%s"' % self.magic
117
    result = RunCmd("/bin/sh -c '%s'" % cmd)
118
    self.assertEqual(result.stdout, self.magic)
119

  
120

  
121
  def testStderr(self):
122
    """Test standard error"""
123
    cmd = 'echo -n "%s"' % self.magic
124
    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
125
    self.assertEqual(result.stderr, self.magic)
126

  
127

  
128
  def testCombined(self):
129
    """Test combined output"""
130
    cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
131
    result = RunCmd("/bin/sh -c '%s'" % cmd)
132
    self.assertEqual(result.output, "A" + self.magic + "B" + self.magic)
133

  
134
  def testSignal(self):
135
    """Test standard error"""
136
    result = RunCmd("/bin/sh -c 'kill -15 $$'")
137
    self.assertEqual(result.signal, 15)
138

  
139
  def testListRun(self):
140
    """Test list runs"""
141
    result = RunCmd(["true"])
142
    self.assertEqual(result.signal, None)
143
    self.assertEqual(result.exit_code, 0)
144
    result = RunCmd(["/bin/sh", "-c", "exit 1"])
145
    self.assertEqual(result.signal, None)
146
    self.assertEqual(result.exit_code, 1)
147
    result = RunCmd(["echo", "-n", self.magic])
148
    self.assertEqual(result.signal, None)
149
    self.assertEqual(result.exit_code, 0)
150
    self.assertEqual(result.stdout, self.magic)
151

  
152
  def testLang(self):
153
    """Test locale environment"""
154
    old_env = os.environ.copy()
155
    try:
156
      os.environ["LANG"] = "en_US.UTF-8"
157
      os.environ["LC_ALL"] = "en_US.UTF-8"
158
      result = RunCmd(["locale"])
159
      for line in result.output.splitlines():
160
        key, value = line.split("=", 1)
161
        # Ignore these variables, they're overridden by LC_ALL
162
        if key == "LANG" or key == "LANGUAGE":
163
          continue
164
        self.failIf(value and value != "C" and value != '"C"',
165
            "Variable %s is set to the invalid value '%s'" % (key, value))
166
    finally:
167
      os.environ = old_env
168

  
169

  
170
class TestRemoveFile(unittest.TestCase):
171
  """Test case for the RemoveFile function"""
172

  
173
  def setUp(self):
174
    """Create a temp dir and file for each case"""
175
    self.tmpdir = tempfile.mkdtemp('', 'ganeti-unittest-')
176
    fd, self.tmpfile = tempfile.mkstemp('', '', self.tmpdir)
177
    os.close(fd)
178

  
179
  def tearDown(self):
180
    if os.path.exists(self.tmpfile):
181
      os.unlink(self.tmpfile)
182
    os.rmdir(self.tmpdir)
183

  
184

  
185
  def testIgnoreDirs(self):
186
    """Test that RemoveFile() ignores directories"""
187
    self.assertEqual(None, RemoveFile(self.tmpdir))
188

  
189

  
190
  def testIgnoreNotExisting(self):
191
    """Test that RemoveFile() ignores non-existing files"""
192
    RemoveFile(self.tmpfile)
193
    RemoveFile(self.tmpfile)
194

  
195

  
196
  def testRemoveFile(self):
197
    """Test that RemoveFile does remove a file"""
198
    RemoveFile(self.tmpfile)
199
    if os.path.exists(self.tmpfile):
200
      self.fail("File '%s' not removed" % self.tmpfile)
201

  
202

  
203
  def testRemoveSymlink(self):
204
    """Test that RemoveFile does remove symlinks"""
205
    symlink = self.tmpdir + "/symlink"
206
    os.symlink("no-such-file", symlink)
207
    RemoveFile(symlink)
208
    if os.path.exists(symlink):
209
      self.fail("File '%s' not removed" % symlink)
210
    os.symlink(self.tmpfile, symlink)
211
    RemoveFile(symlink)
212
    if os.path.exists(symlink):
213
      self.fail("File '%s' not removed" % symlink)
214

  
215

  
216
class TestCheckdict(unittest.TestCase):
217
  """Test case for the CheckDict function"""
218

  
219
  def testAdd(self):
220
    """Test that CheckDict adds a missing key with the correct value"""
221

  
222
    tgt = {'a':1}
223
    tmpl = {'b': 2}
224
    CheckDict(tgt, tmpl)
225
    if 'b' not in tgt or tgt['b'] != 2:
226
      self.fail("Failed to update dict")
227

  
228

  
229
  def testNoUpdate(self):
230
    """Test that CheckDict does not overwrite an existing key"""
231
    tgt = {'a':1, 'b': 3}
232
    tmpl = {'b': 2}
233
    CheckDict(tgt, tmpl)
234
    self.failUnlessEqual(tgt['b'], 3)
235

  
236

  
237
class TestMatchNameComponent(unittest.TestCase):
238
  """Test case for the MatchNameComponent function"""
239

  
240
  def testEmptyList(self):
241
    """Test that there is no match against an empty list"""
242

  
243
    self.failUnlessEqual(MatchNameComponent("", []), None)
244
    self.failUnlessEqual(MatchNameComponent("test", []), None)
245

  
246
  def testSingleMatch(self):
247
    """Test that a single match is performed correctly"""
248
    mlist = ["test1.example.com", "test2.example.com", "test3.example.com"]
249
    for key in "test2", "test2.example", "test2.example.com":
250
      self.failUnlessEqual(MatchNameComponent(key, mlist), mlist[1])
251

  
252
  def testMultipleMatches(self):
253
    """Test that a multiple match is returned as None"""
254
    mlist = ["test1.example.com", "test1.example.org", "test1.example.net"]
255
    for key in "test1", "test1.example":
256
      self.failUnlessEqual(MatchNameComponent(key, mlist), None)
257

  
258

  
259
class TestFormatUnit(unittest.TestCase):
260
  """Test case for the FormatUnit function"""
261

  
262
  def testMiB(self):
263
    self.assertEqual(FormatUnit(1), '1M')
264
    self.assertEqual(FormatUnit(100), '100M')
265
    self.assertEqual(FormatUnit(1023), '1023M')
266

  
267
  def testGiB(self):
268
    self.assertEqual(FormatUnit(1024), '1.0G')
269
    self.assertEqual(FormatUnit(1536), '1.5G')
270
    self.assertEqual(FormatUnit(17133), '16.7G')
271
    self.assertEqual(FormatUnit(1024 * 1024 - 1), '1024.0G')
272

  
273
  def testTiB(self):
274
    self.assertEqual(FormatUnit(1024 * 1024), '1.0T')
275
    self.assertEqual(FormatUnit(5120 * 1024), '5.0T')
276
    self.assertEqual(FormatUnit(29829 * 1024), '29.1T')
277

  
278

  
279
class TestParseUnit(unittest.TestCase):
280
  """Test case for the ParseUnit function"""
281

  
282
  SCALES = (('', 1),
283
            ('M', 1), ('G', 1024), ('T', 1024 * 1024),
284
            ('MB', 1), ('GB', 1024), ('TB', 1024 * 1024),
285
            ('MiB', 1), ('GiB', 1024), ('TiB', 1024 * 1024))
286

  
287
  def testRounding(self):
288
    self.assertEqual(ParseUnit('0'), 0)
289
    self.assertEqual(ParseUnit('1'), 4)
290
    self.assertEqual(ParseUnit('2'), 4)
291
    self.assertEqual(ParseUnit('3'), 4)
292

  
293
    self.assertEqual(ParseUnit('124'), 124)
294
    self.assertEqual(ParseUnit('125'), 128)
295
    self.assertEqual(ParseUnit('126'), 128)
296
    self.assertEqual(ParseUnit('127'), 128)
297
    self.assertEqual(ParseUnit('128'), 128)
298
    self.assertEqual(ParseUnit('129'), 132)
299
    self.assertEqual(ParseUnit('130'), 132)
300

  
301
  def testFloating(self):
302
    self.assertEqual(ParseUnit('0'), 0)
303
    self.assertEqual(ParseUnit('0.5'), 4)
304
    self.assertEqual(ParseUnit('1.75'), 4)
305
    self.assertEqual(ParseUnit('1.99'), 4)
306
    self.assertEqual(ParseUnit('2.00'), 4)
307
    self.assertEqual(ParseUnit('2.01'), 4)
308
    self.assertEqual(ParseUnit('3.99'), 4)
309
    self.assertEqual(ParseUnit('4.00'), 4)
310
    self.assertEqual(ParseUnit('4.01'), 8)
311
    self.assertEqual(ParseUnit('1.5G'), 1536)
312
    self.assertEqual(ParseUnit('1.8G'), 1844)
313
    self.assertEqual(ParseUnit('8.28T'), 8682212)
314

  
315
  def testSuffixes(self):
316
    for sep in ('', ' ', '   ', "\t", "\t "):
317
      for suffix, scale in TestParseUnit.SCALES:
318
        for func in (lambda x: x, str.lower, str.upper):
319
          self.assertEqual(ParseUnit('1024' + sep + func(suffix)), 1024 * scale)
320

  
321
  def testInvalidInput(self):
322
    for sep in ('-', '_', ',', 'a'):
323
      for suffix, _ in TestParseUnit.SCALES:
324
        self.assertRaises(UnitParseError, ParseUnit, '1' + sep + suffix)
325

  
326
    for suffix, _ in TestParseUnit.SCALES:
327
      self.assertRaises(UnitParseError, ParseUnit, '1,3' + suffix)
328

  
329

  
330
class TestSshKeys(unittest.TestCase):
331
  """Test case for the AddAuthorizedKey function"""
332

  
333
  KEY_A = 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a'
334
  KEY_B = ('command="/usr/bin/fooserver -t --verbose",from="1.2.3.4" '
335
           'ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b')
336

  
337
  # NOTE: The MD5 sums below were calculated after manually
338
  #       checking the output files.
339

  
340
  def writeTestFile(self):
341
    (fd, tmpname) = tempfile.mkstemp(prefix = 'ganeti-test')
342
    f = os.fdopen(fd, 'w')
343
    try:
344
      f.write(TestSshKeys.KEY_A)
345
      f.write("\n")
346
      f.write(TestSshKeys.KEY_B)
347
      f.write("\n")
348
    finally:
349
      f.close()
350

  
351
    return tmpname
352

  
353
  def testAddingNewKey(self):
354
    tmpname = self.writeTestFile()
355
    try:
356
      AddAuthorizedKey(tmpname, 'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
357

  
358
      f = open(tmpname, 'r')
359
      try:
360
        self.assertEqual(md5.new(f.read(8192)).hexdigest(),
361
                         'ccc71523108ca6e9d0343797dc3e9f16')
362
      finally:
363
        f.close()
364
    finally:
365
      os.unlink(tmpname)
366

  
367
  def testAddingAlmostButNotCompletlyTheSameKey(self):
368
    tmpname = self.writeTestFile()
369
    try:
370
      AddAuthorizedKey(tmpname,
371
          'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
372

  
373
      f = open(tmpname, 'r')
374
      try:
375
        self.assertEqual(md5.new(f.read(8192)).hexdigest(),
376
                         'f2c939d57addb5b3a6846884be896b46')
377
      finally:
378
        f.close()
379
    finally:
380
      os.unlink(tmpname)
381

  
382
  def testAddingExistingKeyWithSomeMoreSpaces(self):
383
    tmpname = self.writeTestFile()
384
    try:
385
      AddAuthorizedKey(tmpname,
386
          'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
387

  
388
      f = open(tmpname, 'r')
389
      try:
390
        self.assertEqual(md5.new(f.read(8192)).hexdigest(),
391
                         '4e612764808bd46337eb0f575415fc30')
392
      finally:
393
        f.close()
394
    finally:
395
      os.unlink(tmpname)
396

  
397
  def testRemovingExistingKeyWithSomeMoreSpaces(self):
398
    tmpname = self.writeTestFile()
399
    try:
400
      RemoveAuthorizedKey(tmpname,
401
          'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
402

  
403
      f = open(tmpname, 'r')
404
      try:
405
        self.assertEqual(md5.new(f.read(8192)).hexdigest(),
406
                         '77516d987fca07f70e30b830b3e4f2ed')
407
      finally:
408
        f.close()
409
    finally:
410
      os.unlink(tmpname)
411

  
412
  def testRemovingNonExistingKey(self):
413
    tmpname = self.writeTestFile()
414
    try:
415
      RemoveAuthorizedKey(tmpname,
416
          'ssh-dss  AAAAB3Nsdfj230xxjxJjsjwjsjdjU   root@test')
417

  
418
      f = open(tmpname, 'r')
419
      try:
420
        self.assertEqual(md5.new(f.read(8192)).hexdigest(),
421
                         '4e612764808bd46337eb0f575415fc30')
422
      finally:
423
        f.close()
424
    finally:
425
      os.unlink(tmpname)
426

  
427

  
428
class TestShellQuoting(unittest.TestCase):
429
  """Test case for shell quoting functions"""
430

  
431
  def testShellQuote(self):
432
    self.assertEqual(ShellQuote('abc'), "abc")
433
    self.assertEqual(ShellQuote('ab"c'), "'ab\"c'")
434
    self.assertEqual(ShellQuote("a'bc"), "'a'\\''bc'")
435
    self.assertEqual(ShellQuote("a b c"), "'a b c'")
436
    self.assertEqual(ShellQuote("a b\\ c"), "'a b\\ c'")
437

  
438
  def testShellQuoteArgs(self):
439
    self.assertEqual(ShellQuoteArgs(['a', 'b', 'c']), "a b c")
440
    self.assertEqual(ShellQuoteArgs(['a', 'b"', 'c']), "a 'b\"' c")
441
    self.assertEqual(ShellQuoteArgs(['a', 'b\'', 'c']), "a 'b'\\\''' c")
442

  
443

  
444
class TestIpAdressList(unittest.TestCase):
445
  """Test case for local IP addresses"""
446

  
447
  def _test(self, output, required):
448
    ips = _ParseIpOutput(output)
449

  
450
    # Sort the output, so our check below works in all cases
451
    ips.sort()
452
    required.sort()
453

  
454
    self.assertEqual(required, ips)
455

  
456
  def testSingleIpAddress(self):
457
    output = \
458
      ("3: lo    inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
459
       "5: eth0    inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n")
460
    self._test(output, ['127.0.0.1', '10.0.0.1'])
461

  
462
  def testMultipleIpAddresses(self):
463
    output = \
464
      ("3: lo    inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
465
       "5: eth0    inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n"
466
       "5: eth0    inet 1.2.3.4/8 brd 1.255.255.255 scope global eth0:test\n")
467
    self._test(output, ['127.0.0.1', '10.0.0.1', '1.2.3.4'])
468

  
469

  
470
if __name__ == '__main__':
471
  unittest.main()
b/test/mocks.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Module implementing a fake ConfigWriter"""
23

  
24
import socket
25

  
26
class FakeConfig:
27
    """Fake configuration object"""
28

  
29
    def IsCluster(self):
30
        return True
31

  
32
    def GetNodeList(self):
33
        return ["a", "b", "c"]
34

  
35
    def GetMaster(self):
36
        return socket.gethostname()
37

  
38

  
39
class FakeSStore:
40
    """Fake simplestore object"""
41

  
42
    def GetClusterName(self):
43
        return "test.cluster"
44

  
45
    def GetMasterNode(self):
46
        return socket.gethostname()
/dev/null
1
TESTS = ganeti.hooks_unittest.py ganeti.utils_unittest.py
2
TESTS_ENVIRONMENT = PYTHONPATH=.:$(srcdir)
3

  
4
check_DATA = ganeti
5
ganeti:
6
	rm -f ganeti
7
	ln -s $(top_srcdir)/lib ganeti
8

  
9
EXTRA_DIST = $(TESTS) mocks.py
10
CLEANFILES = *.py[co]
11
MAINTAINERCLEANFILES = ganeti
/dev/null
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the config module"""
23

  
24

  
25
import unittest
26
import os
27
import time
28
import tempfile
29
import os.path
30
import socket
31

  
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import config
35
from ganeti import objects
36

  
37

  
38
class TestConfigRunner(unittest.TestCase):
39
  """Testing case for HooksRunner"""
40
  def setUp(self):
41
    fd, self.cfg_file = tempfile.mkstemp()
42
    os.close(fd)
43

  
44
  def tearDown(self):
45
    try:
46
      os.unlink(self.cfg_file)
47
    except OSError:
48
      pass
49

  
50
  def _get_object(self):
51
    """Returns a instance of ConfigWriter"""
52
    cfg = config.ConfigWriter(cfg_file=self.cfg_file, offline=True)
53
    return cfg
54

  
55
  def _init_cluster(self, cfg):
56
    """Initializes the cfg object"""
57
    cfg.InitConfig(socket.gethostname(), '127.0.0.1', None, '', 'aa:00:00',
58
                   'xenvg', constants.DEFAULT_BRIDGE)
59

  
60
  def _create_instance(self):
61
    """Create and return an instance object"""
62
    inst = objects.Instance(name="test.example.com", disks=[],
63
                            disk_template=constants.DT_DISKLESS)
64
    return inst
65

  
66
  def testEmpty(self):
67
    """Test instantiate config object"""
68
    self._get_object()
69

  
70
  def testInit(self):
71
    """Test initialize the config file"""
72
    cfg = self._get_object()
73
    self._init_cluster(cfg)
74
    self.failUnlessEqual(1, len(cfg.GetNodeList()))
75
    self.failUnlessEqual(0, len(cfg.GetInstanceList()))
76

  
77
  def testUpdateCluster(self):
78
    """Test updates on the cluster object"""
79
    cfg = self._get_object()
80
    # construct a fake cluster object
81
    fake_cl = objects.Cluster()
82
    # fail if we didn't read the config
83
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_cl)
84

  
85
    self._init_cluster(cfg)
86
    cl = cfg.GetClusterInfo()
87
    # first pass, must not fail
88
    cfg.Update(cl)
89
    # second pass, also must not fail (after the config has been written)
90
    cfg.Update(cl)
91
    # but the fake_cl update should still fail
92
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl)
93

  
94
  def testUpdateNode(self):
95
    """Test updates on one node object"""
96
    cfg = self._get_object()
97
    # construct a fake node
98
    fake_node = objects.Node()
99
    # fail if we didn't read the config
100
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_node)
101

  
102
    self._init_cluster(cfg)
103
    node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
104
    # first pass, must not fail
105
    cfg.Update(node)
106
    # second pass, also must not fail (after the config has been written)
107
    cfg.Update(node)
108
    # but the fake_node update should still fail
109
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node)
110

  
111
  def testUpdateInstance(self):
112
    """Test updates on one instance object"""
113
    cfg = self._get_object()
114
    # construct a fake instance
115
    inst = self._create_instance()
116
    fake_instance = objects.Instance()
117
    # fail if we didn't read the config
118
    self.failUnlessRaises(errors.ProgrammerError, cfg.Update, fake_instance)
119

  
120
    self._init_cluster(cfg)
121
    cfg.AddInstance(inst)
122
    instance = cfg.GetInstanceInfo(cfg.GetInstanceList()[0])
123
    # first pass, must not fail
124
    cfg.Update(instance)
125
    # second pass, also must not fail (after the config has been written)
126
    cfg.Update(instance)
127
    # but the fake_instance update should still fail
128
    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance)
129

  
130

  
131
if __name__ == '__main__':
132
  unittest.main()
/dev/null
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the hooks module"""
23

  
24

  
25
import unittest
26
import os
27
import time
28
import tempfile
29
import os.path
30

  
31
from ganeti import errors
32
from ganeti import opcodes
33
from ganeti import mcpu
34
from ganeti import backend
35
from ganeti import constants
36
from ganeti import cmdlib
37
from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP
38

  
39
from mocks import FakeConfig, FakeSStore
40

  
41
class FakeLU(cmdlib.LogicalUnit):
42
  HPATH = "test"
43
  def BuildHooksEnv(self):
44
    return {}, ["localhost"], ["localhost"]
45

  
46
class TestHooksRunner(unittest.TestCase):
47
  """Testing case for HooksRunner"""
48
  def setUp(self):
49
    self.torm = []
50
    self.tmpdir = tempfile.mkdtemp()
51
    self.torm.append((self.tmpdir, True))
52
    self.logdir = tempfile.mkdtemp()
53
    self.torm.append((self.logdir, True))
54
    self.hpath = "fake"
55
    self.ph_dirs = {}
56
    for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
57
      dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
58
      os.mkdir(dname)
59
      self.torm.append((dname, True))
60
      self.ph_dirs[i] = dname
61
    self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
62

  
63
  def tearDown(self):
64
    self.torm.reverse()
65
    for path, kind in self.torm:
66
      if kind:
67
        os.rmdir(path)
68
      else:
69
        os.unlink(path)
70

  
71
  def _rname(self, fname):
72
    return "/".join(fname.split("/")[-2:])
73

  
74
  def testEmpty(self):
75
    """Test no hooks"""
76
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
77
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
78

  
79
  def testSkipNonExec(self):
80
    """Test skip non-exec file"""
81
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
82
      fname = "%s/test" % self.ph_dirs[phase]
83
      f = open(fname, "w")
84
      f.close()
85
      self.torm.append((fname, False))
86
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
87
                           [(self._rname(fname), HKR_SKIP, "")])
88

  
89
  def testSkipInvalidName(self):
90
    """Test skip script with invalid name"""
91
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
92
      fname = "%s/a.off" % self.ph_dirs[phase]
93
      f = open(fname, "w")
94
      f.write("#!/bin/sh\nexit 0\n")
95
      f.close()
96
      os.chmod(fname, 0700)
97
      self.torm.append((fname, False))
98
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
99
                           [(self._rname(fname), HKR_SKIP, "")])
100

  
101
  def testSkipDir(self):
102
    """Test skip directory"""
103
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
104
      fname = "%s/testdir" % self.ph_dirs[phase]
105
      os.mkdir(fname)
106
      self.torm.append((fname, True))
107
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
108
                           [(self._rname(fname), HKR_SKIP, "")])
109

  
110
  def testSuccess(self):
111
    """Test success execution"""
112
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
113
      fname = "%s/success" % self.ph_dirs[phase]
114
      f = open(fname, "w")
115
      f.write("#!/bin/sh\nexit 0\n")
116
      f.close()
117
      self.torm.append((fname, False))
118
      os.chmod(fname, 0700)
119
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
120
                           [(self._rname(fname), HKR_SUCCESS, "")])
121

  
122
  def testSymlink(self):
123
    """Test running a symlink"""
124
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
125
      fname = "%s/success" % self.ph_dirs[phase]
126
      os.symlink("/bin/true", fname)
127
      self.torm.append((fname, False))
128
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
129
                           [(self._rname(fname), HKR_SUCCESS, "")])
130

  
131
  def testFail(self):
132
    """Test success execution"""
133
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
134
      fname = "%s/success" % self.ph_dirs[phase]
135
      f = open(fname, "w")
136
      f.write("#!/bin/sh\nexit 1\n")
137
      f.close()
138
      self.torm.append((fname, False))
139
      os.chmod(fname, 0700)
140
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
141
                           [(self._rname(fname), HKR_FAIL, "")])
142

  
143
  def testCombined(self):
144
    """Test success, failure and skip all in one test"""
145
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
146
      expect = []
147
      for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
148
                               ("10fail", 1, HKR_FAIL),
149
                               ("20inv.", 0, HKR_SKIP),
150
                               ]:
151
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
152
        f = open(fname, "w")
153
        f.write("#!/bin/sh\nexit %d\n" % ecode)
154
        f.close()
155
        self.torm.append((fname, False))
156
        os.chmod(fname, 0700)
157
        expect.append((self._rname(fname), rs, ""))
158
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
159

  
160
  def testOrdering(self):
161
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
162
      expect = []
163
      for fbase in ["10s1",
164
                    "00s0",
165
                    "10sa",
166
                    "80sc",
167
                    "60sd",
168
                    ]:
169
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
170
        os.symlink("/bin/true", fname)
171
        self.torm.append((fname, False))
172
        expect.append((self._rname(fname), HKR_SUCCESS, ""))
173
      expect.sort()
174
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
175

  
176
  def testEnv(self):
177
    """Test environment execution"""
178
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
179
      fbase = "success"
180
      fname = "%s/%s" % (self.ph_dirs[phase], fbase)
181
      os.symlink("/usr/bin/env", fname)
182
      self.torm.append((fname, False))
183
      env_snt = {"PHASE": phase}
184
      env_exp = "PHASE=%s\n" % phase
185
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
186
                           [(self._rname(fname), HKR_SUCCESS, env_exp)])
187

  
188

  
189
class TestHooksMaster(unittest.TestCase):
190
  """Testing case for HooksMaster"""
191

  
192
  def _call_false(*args):
193
    """Fake call_hooks_runner function which returns False."""
194
    return False
195

  
196
  @staticmethod
197
  def _call_nodes_false(node_list, hpath, phase, env):
198
    """Fake call_hooks_runner function.
199

  
200
    Returns:
201
      - list of False values with the same len as the node_list argument
202

  
203
    """
204
    return [False for node_name in node_list]
205

  
206
  @staticmethod
207
  def _call_script_fail(node_list, hpath, phase, env):
208
    """Fake call_hooks_runner function.
209

  
210
    Returns:
211
      - list of False values with the same len as the node_list argument
212

  
213
    """
214
    return dict([(node_name, [("unittest", constants.HKR_FAIL, "error")])
215
                 for node_name in node_list])
216

  
217
  @staticmethod
218
  def _call_script_succeed(node_list, hpath, phase, env):
219
    """Fake call_hooks_runner function.
220

  
221
    Returns:
222
      - list of False values with the same len as the node_list argument
223

  
224
    """
225
    return dict([(node_name, [("unittest", constants.HKR_SUCCESS, "ok")])
226
                 for node_name in node_list])
227

  
228
  def testTotalFalse(self):
229
    """Test complete rpc failure"""
230
    cfg = FakeConfig()
231
    sstore = FakeSStore()
232
    op = opcodes.OpCode()
233
    lu = FakeLU(None, op, cfg, sstore)
234
    hm = mcpu.HooksMaster(self._call_false, cfg, sstore, lu)
235
    self.failUnlessRaises(errors.HooksFailure,
236
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
237
    hm.RunPhase(constants.HOOKS_PHASE_POST)
238

  
239
  def testIndividualFalse(self):
240
    """Test individual rpc failure"""
241
    cfg = FakeConfig()
242
    sstore = FakeSStore()
243
    op = opcodes.OpCode()
244
    lu = FakeLU(None, op, cfg, sstore)
245
    hm = mcpu.HooksMaster(self._call_nodes_false, cfg, sstore, lu)
246
    self.failUnlessRaises(errors.HooksFailure,
247
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
248
    hm.RunPhase(constants.HOOKS_PHASE_POST)
249

  
250
  def testScriptFalse(self):
251
    """Test individual rpc failure"""
252
    cfg = FakeConfig()
253
    op = opcodes.OpCode()
254
    sstore = FakeSStore()
255
    lu = FakeLU(None, op, cfg, sstore)
256
    hm = mcpu.HooksMaster(self._call_script_fail, cfg, sstore, lu)
257
    self.failUnlessRaises(errors.HooksAbort,
258
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
259
    hm.RunPhase(constants.HOOKS_PHASE_POST)
260

  
261
  def testScriptSucceed(self):
262
    """Test individual rpc failure"""
263
    cfg = FakeConfig()
264
    op = opcodes.OpCode()
265
    sstore = FakeSStore()
266
    lu = FakeLU(None, op, cfg, sstore)
267
    hm = mcpu.HooksMaster(self._call_script_succeed, cfg, sstore, lu)
268
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
269
      hm.RunPhase(phase)
270

  
271
if __name__ == '__main__':
272
  unittest.main()
/dev/null
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the utils module"""
23

  
24
import unittest
25
import os
26
import time
27
import tempfile
28
import os.path
29
import md5
30

  
31
import ganeti
32
from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \
33
     RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
34
     ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
35
     ShellQuote, ShellQuoteArgs, _ParseIpOutput
36
from ganeti.errors import LockError, UnitParseError
37

  
38
class TestIsProcessAlive(unittest.TestCase):
39
  """Testing case for IsProcessAlive"""
40
  def setUp(self):
41
    # create a zombie and a (hopefully) non-existing process id
42
    self.pid_zombie = os.fork()
43
    if self.pid_zombie == 0:
44
      os._exit(0)
45
    elif self.pid_zombie < 0:
46
      raise SystemError("can't fork")
47
    self.pid_non_existing = os.fork()
48
    if self.pid_non_existing == 0:
49
      os._exit(0)
50
    elif self.pid_non_existing > 0:
51
      os.waitpid(self.pid_non_existing, 0)
52
    else:
53
      raise SystemError("can't fork")
54

  
55

  
56
  def testExists(self):
57
    mypid = os.getpid()
58
    self.assert_(IsProcessAlive(mypid),
59
                 "can't find myself running")
60

  
61
  def testZombie(self):
62
    self.assert_(not IsProcessAlive(self.pid_zombie),
63
                 "zombie not detected as zombie")
64

  
65

  
66
  def testNotExisting(self):
67
    self.assert_(not IsProcessAlive(self.pid_non_existing),
68
                 "noexisting process detected")
69

  
70

  
71
class TestLocking(unittest.TestCase):
72
  """Testing case for the Lock/Unlock functions"""
73
  def clean_lock(self, name):
74
    try:
75
      ganeti.utils.Unlock("unittest")
76
    except LockError:
77
      pass
78

  
79

  
80
  def testLock(self):
81
    self.clean_lock("unittest")
82
    self.assertEqual(None, Lock("unittest"))
83

  
84

  
85
  def testUnlock(self):
86
    self.clean_lock("unittest")
87
    ganeti.utils.Lock("unittest")
88
    self.assertEqual(None, Unlock("unittest"))
89

  
90

  
91
  def testDoubleLock(self):
92
    self.clean_lock("unittest")
93
    ganeti.utils.Lock("unittest")
94
    self.assertRaises(LockError, Lock, "unittest")
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff