def test_create_conflict(): manager = ResourceManager() manager.inventory = { "already-exists": { "name": "already-exists", "type": "shell" } } with pytest.raises(ResourceAlreadyExistsError): manager.create("already-exists", "type-doesnt-matter")
def test_create_missing_required(): with pytest.raises(ResourceError) as exc: # SSH requires host. with patch("reproman.interface.create.get_manager", return_value=ResourceManager()): create("somessh", "ssh", []) assert "host" in str(exc.value)
def test_create_start_stop(tmpdir): tmpdir = str(tmpdir) inventory_file = op.join(tmpdir, "inventory.yml") rm = ResourceManager(inventory_file) # Simple smoke test. We can't easily test the effects of start/stop with # shell because those the start and stop methods are noops. with patch("reproman.interface.create.get_manager", return_value=rm): main(["create", "-t", "shell", "testshell"]) with open(inventory_file) as ifh: inventory = yaml.safe_load(ifh) assert inventory["testshell"]["status"] == "available" with patch("reproman.interface.start.get_manager", return_value=rm): main(["start", "testshell"]) with patch("reproman.interface.stop.get_manager", return_value=rm): main(["stop", "testshell"]) with patch("reproman.interface.delete.get_manager", return_value=rm): main(["delete", "--skip-confirmation", "testshell"]) with open(inventory_file) as ifh: inventory = yaml.safe_load(ifh) assert "testshell" not in inventory
def test_create_includes_config(tmpdir): tmpdir = str(tmpdir) manager = ResourceManager(op.join(tmpdir, "inventory.yml")) # We load items from the config. config_file = op.join(tmpdir, "reproman.cfg") with open(config_file, "w") as cfh: cfh.write("[ssh]\nhost = myhost\n") config = ConfigManager(filenames=[config_file], load_default=False) with patch.object(manager, "config_manager", config): with patch.object(manager, "factory") as factory: manager.create("myssh", "ssh") factory.assert_called_with({ "host": "myhost", "name": "myssh", "type": "ssh" })
def test_resource_manager_save(tmpdir): inventory = op.join(str(tmpdir), "subdir", "inventory.yml") manager = ResourceManager(inventory) manager.inventory = { "plain": { "name": "plain", "type": "foo-type", "id": "foo_id" }, "with-secret": { "name": "with-secret", "type": "bar-type", "secret_access_key": "SECRET", "id": "bar_id" }, "null-id": { "name": "null-id", "id": None, "type": "baz-type" } } manager.save_inventory() assert op.exists(inventory) with open(inventory) as fh: content = fh.read() assert "plain" in content assert "with-secret" in content assert "SECRET" not in content assert "null-id" not in content # Reload that inventory, add another item, and save again. manager_reborn = ResourceManager(inventory) manager_reborn.inventory["added"] = { "name": "added", "type": "added-type", "id": "added-id" } manager_reborn.save_inventory() with open(inventory) as fh: content_reread = fh.read() for line in content: assert line in content_reread assert "added" in content_reread
def __call__(backends=None): backends = backends or ResourceManager._discover_types() for backend, cls in get_resource_classes(backends): param_doc = "\n".join([ " {}: {}".format(p, pdoc) for p, pdoc in sorted(get_resource_backends(cls).items()) ]) if param_doc: out = "Backend parameters for '{}'\n{}".format( backend, param_doc) else: out = "No backend parameters for '{}'".format(backend) print(out)
def get_resource_classes(names=None): for name in names or ResourceManager._discover_types(): try: module = import_module('reproman.resource.{}'.format(name)) except ImportError as exc: import difflib known = ResourceManager._discover_types() suggestions = difflib.get_close_matches(name, known) lgr.warning( "Failed to import resource %s: %s. %s: %s", name, exc_str(exc), "Similar backends" if suggestions else "Known backends", ', '.join(suggestions or known)) continue class_name = ''.join([token.capitalize() for token in name.split('_')]) cls = getattr(module, class_name) if issubclass(cls, Resource): yield name, cls else: lgr.debug( "Skipping %s.%s because it is not a Resource. " "Consider moving away", module, class_name)
def test_delete_interface(): """ Test deleting a resource. """ with patch('docker.Client') as client, \ patch('reproman.resource.ResourceManager._save'), \ patch('reproman.resource.ResourceManager._get_inventory') as get_inventory, \ swallow_logs(new_level=logging.DEBUG) as log: client.return_value = MagicMock( containers=lambda all: [{ 'Id': '326b0fdfbf838', 'Names': ['/my-resource'], 'State': 'running' }]) get_inventory.return_value = { "my-resource": { "status": "running", "engine_url": "tcp://127.0.0.1:2375", "type": "docker-container", "name": "my-resource", "id": "326b0fdfbf838" } } args = ['delete', '--skip-confirmation', 'my-resource'] with patch("reproman.interface.delete.get_manager", return_value=ResourceManager()): main(args) calls = [ call(base_url='tcp://127.0.0.1:2375'), call().remove_container( { 'State': 'running', 'Id': '326b0fdfbf838', 'Names': ['/my-resource'] }, force=True) ] client.assert_has_calls(calls, any_order=True) assert_in('Deleted the environment my-resource', log.lines)
def test_create_interface(): """ Test creating an environment """ with patch('docker.Client') as client, \ patch('reproman.resource.ResourceManager.save_inventory'), \ patch('reproman.resource.ResourceManager._get_inventory'), \ swallow_logs(new_level=logging.DEBUG) as log: client.return_value = MagicMock( containers=lambda all: [], pull=lambda repository, stream: [ b'{ "status" : "status 1", "progress" : "progress 1" }', b'{ "status" : "status 2", "progress" : "progress 2" }' ], create_container=lambda name, image, stdin_open, tty, command: { 'Id': '18b31b30e3a5' } ) args = ['create', '--resource-type', 'docker-container', '--backend', 'engine_url=tcp://127.0.0.1:2376', '--', 'my-test-resource' ] with patch("reproman.interface.create.get_manager", return_value=ResourceManager()): main(args) calls = [ call(base_url='tcp://127.0.0.1:2376'), call().start(container='18b31b30e3a5') ] client.assert_has_calls(calls, any_order=True) assert_in("status 1 progress 1", log.lines) assert_in("status 2 progress 2", log.lines) assert_in("Created the environment my-test-resource", log.lines)
def test_login_interface(): """ Test logging into an environment """ with patch('docker.Client') as client, \ patch('reproman.resource.ResourceManager._get_inventory') as get_inventory, \ patch('dockerpty.start'), \ swallow_logs(new_level=logging.DEBUG) as log: client.return_value = MagicMock( containers=lambda all: [{ 'Id': '18b31b30e3a5', 'Names': ['/my-test-resource'], 'State': 'running' }], ) get_inventory.return_value = { "my-test-resource": { "status": "running", "engine_url": "tcp://127.0.0.1:2375", "type": "docker-container", "name": "my-test-resource", "id": "18b31b30e3a5" } } args = ['login', 'my-test-resource'] with patch("reproman.interface.login.get_manager", return_value=ResourceManager()): main(args) assert client.call_count == 1 calls = [call(base_url='tcp://127.0.0.1:2375')] client.assert_has_calls(calls, any_order=True) assert_in("Opening TTY connection to docker container.", log.lines)
def test_get_resources_empty_resref(): with pytest.raises(ValueError): ResourceManager().get_resource("")
def fixture(tmpdir_factory): path = str(tmpdir_factory.mktemp("rmanager").join("inventory")) manager = ResourceManager(path) for name, kwargs in resources.items(): manager.create(name, **kwargs) return manager
def test_resource_manager_factory_missing_type(): with pytest.raises(MissingConfigError): ResourceManager.factory({})
def test_get_resources(): manager = ResourceManager() manager.inventory = { "myshell": { "name": "myshell", "type": "shell", "id": "0-myshell-id" }, "ambig-id0": { "name": "ambig-id0", "type": "shell", "id": "ambig-id" }, "ambig-id1": { "name": "ambig-id1", "type": "shell", "id": "ambig-id" }, "id-name-same": { "name": "id-name-same", "type": "shell", "id": "0-uniq-id" }, "same-id": { "name": "same-id", "type": "shell", "id": "id-name-same" }, "00": { "name": "00", "type": "shell", "id": "00s-id" }, "partial-is-other": { "name": "partial-is-other", "type": "shell", "id": "00-rest-of-id" } } with pytest.raises(ResourceError): manager.get_resource("not there") resource_uniq = manager.get_resource("myshell") assert resource_uniq.name == "myshell" # We can get the same resource by ID. assert manager.get_resource(resource_uniq.id).name == resource_uniq.name # ... or by a unique partial prefix match on ID. assert manager.get_resource("0-m").name == resource_uniq.name with pytest.raises(MultipleResourceMatches): manager.get_resource("ambig-id") # We get an exception if both if there is an name-ID collision... with pytest.raises(MultipleResourceMatches): manager.get_resource("id-name-same") # ... but we can disambiguate with resref_type. assert manager.get_resource("id-name-same", "name").id == "0-uniq-id" assert manager.get_resource("id-name-same", "id").id == "id-name-same" # Ambiguous prefix match on ID: with pytest.raises(MultipleResourceMatches): manager.get_resource("0-") # When a name matches the partial ID match, we prefer the name. assert manager.get_resource("00").id == "00s-id" # We could do partial match on ID if we specify resref type, though. assert manager.get_resource("00-r", "id").id == "00-rest-of-id" # ... but it still must be unique. with pytest.raises(MultipleResourceMatches): assert manager.get_resource("00", "id")
def test_resource_manager_inventory_undefined(): with pytest.raises(MissingConfigError): ResourceManager("")
def test_resource_manager_empty_init(tmpdir): inventory = op.join(str(tmpdir), "inventory.yml") manager = ResourceManager(inventory) assert not manager.inventory
def test_resource_manager_factory_unkown(): with pytest.raises(ResourceError): ResourceManager.factory({"type": "not really a type"})