Skip to content

Commit cbefe82

Browse files
committed
adding tests for instances
1 parent 386352a commit cbefe82

File tree

6 files changed

+353
-0
lines changed

6 files changed

+353
-0
lines changed

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def get_requirements(lookup=None):
103103
description=DESCRIPTION,
104104
long_description=LONG_DESCRIPTION,
105105
keywords=KEYWORDS,
106+
setup_requires=["pytest-runner"],
107+
tests_require=["pytest"],
106108
install_requires = INSTALL_REQUIRES,
107109
classifiers=[
108110
'Intended Audience :: Science/Research',

spython/instance/cmd/__init__.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (C) 2018 The Board of Trustees of the Leland Stanford Junior
2+
# University.
3+
# Copyright (C) 2017-2018 Vanessa Sochat.
4+
5+
# This program is free software: you can redistribute it and/or modify it
6+
# under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# This program is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
19+
def generate_instance_commands():
20+
''' The Instance client holds the Singularity Instance command group
21+
The levels of verbosity (debug and quiet) are passed from the main
22+
client via the environment variable MESSAGELEVEL.
23+
24+
'''
25+
from spython.instance import Instance
26+
27+
from spython.main.base.logger import println
28+
from spython.main.instances import instances
29+
from spython.utils import run_command as run_cmd
30+
31+
# run_command uses run_cmd, but wraps to catch error
32+
from spython.main.base.command import ( init_command, run_command )
33+
from spython.main.base.generate import RobotNamer
34+
from .start import start
35+
from .stop import stop
36+
37+
Instance.RobotNamer = RobotNamer()
38+
Instance._init_command = init_command
39+
Instance.run_command = run_cmd
40+
Instance._run_command = run_command
41+
Instance._list = instances # list command is used to get metadata
42+
Instance._println = println
43+
Instance._start = start # intended to be called on init, not by user
44+
Instance.stop = stop
45+
46+
# Give an instance the ability to breed :)
47+
Instance.instance = Instance
48+
49+
return Instance
50+
51+
instance_group = generate_instance_commands()

spython/instance/cmd/iutils.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (C) 2018 The Board of Trustees of the Leland Stanford Junior
2+
# University.
3+
# Copyright (C) 2017-2018 Vanessa Sochat.
4+
5+
# This program is free software: you can redistribute it and/or modify it
6+
# under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# This program is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
19+
def parse_table(table_string, header, remove_rows=1):
20+
'''parse a table to json from a string, where a header is expected by default.
21+
Return a jsonified table.
22+
23+
Parameters
24+
==========
25+
table_string: the string table, ideally with a header
26+
header: header of expected table, must match dimension (number columns)
27+
remove_rows: an integer to indicate a number of rows to remove from top
28+
the default is 1 assuming we don't want the header
29+
'''
30+
rows = [x for x in table_string.split('\n') if x]
31+
rows = rows[0+remove_rows:]
32+
33+
# Parse into json dictionary
34+
parsed = []
35+
36+
for row in rows:
37+
item = {}
38+
# This assumes no white spaces in each entry, which should be the case
39+
row = [x for x in row.split(' ') if x]
40+
for e in range(len(row)):
41+
item[header[e]] = row[e]
42+
parsed.append(item)
43+
return parsed
44+
45+
46+
def get(self, name, return_json=False, quiet=False):
47+
'''get is a list for a single instance. It is assumed to be running,
48+
and we need to look up the PID, etc.
49+
'''
50+
self._check_install()
51+
cmd = self._init_command('instance.list')
52+
cmd.append(name)
53+
output = run_command(cmd, quiet=True)
54+
55+
# Success, we have instances
56+
57+
if output['return_code'] == 0:
58+
59+
# Only print the table if we are returning json
60+
if quiet is False:
61+
print(''.join(output['message']))
62+
63+
# Prepare json result from table
64+
65+
header = ['daemon_name','pid','container_image']
66+
instances = parse_table(output['message'][0], header)
67+
68+
# Does the user want instance objects instead?
69+
listing = []
70+
if return_json is False:
71+
for i in instances:
72+
new_instance = Instance(pid=i['pid'],
73+
name=i['daemon_name'],
74+
image=i['container_image'],
75+
start=False)
76+
77+
listing.append(new_instance)
78+
instances = listing
79+
80+
# Couldn't get UID
81+
82+
elif output['return_code'] == 255:
83+
bot.error("Couldn't get UID")
84+
85+
# Return code of 0
86+
else:
87+
bot.info('No instances found.')
88+
89+
# If we are given a name, return just one
90+
if name is not None and len(instances) == 1:
91+
instances = instances[0]
92+
93+
return instances
94+

spython/instance/cmd/start.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright (C) 2018 The Board of Trustees of the Leland Stanford Junior
2+
# University.
3+
# Copyright (C) 2017-2018 Vanessa Sochat.
4+
5+
# This program is free software: you can redistribute it and/or modify it
6+
# under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# This program is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
19+
from spython.logger import bot
20+
import sys
21+
22+
def start(self, image=None, name=None, sudo=False, options=[]):
23+
'''start an instance. This is done by default when an instance is created.
24+
25+
Parameters
26+
==========
27+
image: optionally, an image uri (if called as a command from Client)
28+
name: a name for the instance
29+
sudo: if the user wants to run the command with sudo
30+
options: a list of tuples, each an option to give to the start command
31+
[("--bind", "/tmp"),...]
32+
33+
USAGE:
34+
singularity [...] instance.start [...] <container path> <instance name>
35+
36+
'''
37+
from spython.utils import run_command
38+
self._check_install()
39+
40+
# If no name provided, give it an excellent one!
41+
if name is None:
42+
name = self.RobotNamer.generate()
43+
self.name = name.replace('-','_')
44+
45+
# If an image isn't provided, we have an initialized instance
46+
if image is None:
47+
48+
# Not having this means it was called as a command, without an image
49+
if not hasattr(self, "_image"):
50+
bot.error('Please provide an image, or create an Instance first.')
51+
sys.exit(1)
52+
53+
image = self._image
54+
55+
cmd = self._init_command('instance.start')
56+
57+
# Add options, if they are provided
58+
if not isinstance(options, list):
59+
options = options.split(' ')
60+
61+
# Assemble the command!
62+
cmd = cmd + options + [image, self.name]
63+
64+
# Save the options and cmd, if the user wants to see them later
65+
self.options = options
66+
self.cmd = cmd
67+
68+
output = run_command(cmd, sudo=sudo, quiet=True)
69+
70+
if output['return_code'] == 0:
71+
self._update_metadata()
72+
73+
else:
74+
message = '%s : return code %s' %(output['message'],
75+
output['return_code'])
76+
bot.error(message)
77+
78+
return self

spython/instance/cmd/stop.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (C) 2018 The Board of Trustees of the Leland Stanford Junior
2+
# University.
3+
# Copyright (C) 2017-2018 Vanessa Sochat.
4+
5+
# This program is free software: you can redistribute it and/or modify it
6+
# under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# This program is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
19+
from spython.logger import bot
20+
import sys
21+
22+
23+
def stop(self, name=None, sudo=False):
24+
'''start an instance. This is done by default when an instance is created.
25+
26+
Parameters
27+
==========
28+
image: optionally, an image uri (if called as a command from Client)
29+
name: a name for the instance
30+
sudo: if the user wants to run the command with sudo
31+
32+
USAGE:
33+
singularity [...] instance.start [...] <container path> <instance name>
34+
35+
'''
36+
from spython.utils import check_install, run_command
37+
check_install()
38+
39+
cmd = self._init_command('instance.stop')
40+
41+
# If name is provided assume referencing an instance
42+
instance_name = self.name
43+
if name is not None:
44+
instance_name = name
45+
cmd = cmd + [instance_name]
46+
47+
output = run_command(cmd, sudo=sudo, quiet=True)
48+
49+
if output['return_code'] != 0:
50+
message = '%s : return code %s' %(output['message'],
51+
output['return_code'])
52+
bot.error(message)
53+
return output['return_code']
54+
55+
return output['return_code']

spython/tests/test_instances.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/python
2+
3+
# Copyright (C) 2017-2018 Vanessa Sochat.
4+
# Copyright (C) 2018 The Board of Trustees of the Leland Stanford Junior
5+
# University.
6+
7+
# This program is free software: you can redistribute it and/or modify it
8+
# under the terms of the GNU Affero General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or (at your
10+
# option) any later version.
11+
12+
# This program is distributed in the hope that it will be useful, but WITHOUT
13+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
15+
# License for more details.
16+
17+
# You should have received a copy of the GNU Affero General Public License
18+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
from spython.utils import get_installdir
21+
from spython.logger import bot
22+
from spython.main import Client
23+
import unittest
24+
import tempfile
25+
import shutil
26+
import json
27+
import os
28+
29+
30+
print("######################################################## test_instances")
31+
32+
class TestInstances(unittest.TestCase):
33+
34+
def setUp(self):
35+
self.pwd = get_installdir()
36+
self.cli = Client
37+
self.tmpdir = tempfile.mkdtemp()
38+
39+
def tearDown(self):
40+
shutil.rmtree(self.tmpdir)
41+
42+
def test_instances(self):
43+
44+
print('Pulling testing container')
45+
image = self.cli.pull("shub://vsoch/singularity-images",
46+
pull_folder=self.tmpdir)
47+
self.assertTrue(os.path.exists(image))
48+
self.assertTrue('vsoch-singularity-images' in image)
49+
print(image)
50+
51+
print("...Case 0: No instances: objects")
52+
instances = self.cli.instances()
53+
self.assertEqual(instances, None)
54+
55+
print("...Case 1: Create instance")
56+
myinstance = self.cli.instance(image)
57+
self.assertTrue(myinstance.get_uri().startswith('instance://'))
58+
59+
print("...Case 2: List instances")
60+
instances = self.cli.instances()
61+
self.assertEqual(len(instances), 1)
62+
instances = self.cli.instances(return_json=True)
63+
self.assertEqual(len(instances), 1)
64+
self.assertTrue(isinstance(instances[0], dict))
65+
66+
print("...Case 3: Commands to instances")
67+
result = self.cli.execute(myinstance, ['echo', 'hello'])
68+
self.assertTrue('hello\n' == result)
69+
result = self.cli.run(myinstance)
70+
71+
72+
if __name__ == '__main__':
73+
unittest.main()

0 commit comments

Comments
 (0)