package be.iminds.ilabt.jfed.lowlevel.api.test;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AMv2Wrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AMv3Wrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AutomaticAggregateManagerWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.rspec_source.AdvertisementRspecSource;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.testing.base.ApiTest;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.TargetAuthority;
import be.iminds.ilabt.jfed.util.TextUtil;

import javax.inject.Inject;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.*;

/**
 * TestNodeLogin will test node login for AMv2 or AMv3 (automatic and configurable).
 * It can test a specific node, and it can test any node.
 *
 * TestAggregateManager2/3 is more meant to test API conformance
 */
public class TestNodeLogin extends ApiTest {
    protected NodeLoginTester nodeLoginTester;
    protected CommonAMTest commonAMTest;
    protected AggregateManagerWrapper amWrapper;

    private Boolean createSliceIfNotExists = true;
    private Boolean reuseSliceIfExists = false;
    private Boolean requireNodesInManifest = true;
    private Boolean disableCleanup = false;
    private Boolean useGeniEndTimeOptionForCreateSliver = true;
    private Map versionMap = null;
    /* Create the slices needed in the next tests. Uses SA, but not AM */
    protected CommonAMTest.SliceInfo slice;
    private boolean foundNodeLoginInProvision = false;

    private final AutomaticAggregateManagerWrapper.AutomaticAggregateManagerWrapperFactory automaticAggregateManagerWrapperFactory;
    private String fixedNodeUrnString;
    private String fixedNodeSliverType;
    private Boolean fixedNodeExclusive;
    private boolean needListResources;

    protected String requestRspec;
    protected String manifestRspec;
    protected String manifestRspec2;
    protected Boolean notEnoughResourcesDetected = null;

    protected int sliceExpireTimeInMinutes = 60;
//    protected int allocateExpireTimeInMinutes = 10;
    protected int provisionExpireTimeInMinutes = 25;
    protected int maxTimeToWaitForReadyInMinutes = 20;

    protected boolean forceUseExistingSliver = false;

    @Inject
    public TestNodeLogin(Logger logger, TargetAuthority testedAuthority,
                         GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider,
                         TestbedInfoSource testbedInfoSource, JFedPreferences jFedPreferences,
                         AutomaticAggregateManagerWrapper.AutomaticAggregateManagerWrapperFactory automaticAggregateManagerWrapperFactory) {
        super(logger, testedAuthority, geniUserProvider, connectionProvider, testbedInfoSource, jFedPreferences);
        this.automaticAggregateManagerWrapperFactory = automaticAggregateManagerWrapperFactory;
    }

    private static List<String> toStringList(String s) {
        List<String> res = new ArrayList<>();
        res.add(s);
        return res;
    }

    public String getTestDescription() {
        return "This tests login to a node. AMv2 or AMv3 can be used, and login to a specific node can be configured." +
                "The sliver will be deleted afterwards.";
    }

    @Override
    public List<String> getOptConfKeys() {
        List<String> res = new ArrayList<>();
        /* fixed_ssh_private_key_file and fixed_ssh_public_key_file option:
              There are 3 cases:
                  option not specified: use randomly generated key that is different each time
                  option specified, with private key file specified: use public and private key from files (public key can be read from PEM certificate)
                  option specified, with private key file specified, AND this file is the same file for both and is a user login file (key+cert file): use public and private key from file.

         */
        res.add("fixed_ssh_public_key_file"); //needed if fixed_ssh_private_key_file is specified (file containing PEM x509certificate is also allowed)
        res.add("fixed_ssh_private_key_file"); //needed if fixed_ssh_public_key_file is specified
        res.add("fixed_ssh_private_key_password"); //may be null,even if fixed_ssh_private_key_file is not (in case no password is needed)

        res.add("use_geni_end_time_option_for_createsliver_calls"); //default = true

        res.add("create_slice_if_not_exists");
        res.add("reuse_slice_if_exists");
        res.add("disable_slice_cleanup"); //no deleteSliver call
        res.add("fixed_slice_name"); //uses this slice name. If not specified, will generate a slice name automatically
        res.add("fixed_project_name"); //uses this as the project/subauthority. If not specified, will choose one if needed, or not use one otherwise.
        res.add("try_to_use_project_for_slices"); //if set and no project name set, this will try to find any project for the user, and use it to create slices.
        CommonAMTest.addOptionsForFixedRspec(res);

        res.add("require_nodes_in_manifest"); //default: true
        res.add("skip_final_manifest_check"); //don't check the manifest too much

        res.add("fixed_node_sliver_type"); //fixed node sliver_type. Note that fixed_node_urn option might overwrite this.
        res.add("fixed_node_exclusive"); //fixed node exclusive attribute value (true or false). Note that fixed_node_urn option might overwrite this.

        res.add("fixed_node_urn"); //urn of node to test login to, or "auto". If not specified, no component_id will be specified in request rspec, and AM will pick one.
        //If "auto" is specified instead of an URN, a ListResources call will be done, and a free node will be picked at random from that result.

        res.add("am_version"); //"2", "3" or  "auto" (default: "auto")

        res.add("nodelogin_deadline_ms");
        res.add("nodelogin_preferred_user");

        res.add("force_uses_existing_sliver"); //if set to true, the test will assume a sliver already exists in the specified slice, in the specified authority

        res.add("extra_wait_after_status_ready_in_seconds");

        res.add("nodelogin_force_ip_version"); //4 or 6  (default: don't force)

        res.add("nodelogin_use_external_ssh"); //boolean  (default: false)

        res.add("use_nitos_lease"); //boolean  (default: false)

        res.add("slice_expire_time_in_minutes"); //int  (default: 60)
//        res.add("allocate_expire_time_in_minutes"); //int  (default: 10)
        res.add("provision_expire_time_in_minutes"); //int  (default: 25)
        res.add("max_time_to_wait_for_ready_in_minutes"); //int  (default: 20)

        CommonAMTest.addOptionsForNodeLoginSshProxy(res);
        CommonAMTest.addOptionsForConnectionSshProxy(res);

        //speaksfor and delegation support
        CommonAMTest.addCredentialOptions(res);

        return res;
    }

    public void setUp() {
        assertNotNull(user);
        assertNotNull(user.getUserAuthorityServer());

        commonAMTest = new CommonAMTest(this, user.getUserAuthorityServer());
        String amVersion = getTestConfig().getProperty("am_version");
        if (amVersion == null || amVersion.trim().equalsIgnoreCase("auto")) {
            amWrapper = automaticAggregateManagerWrapperFactory.create(logger, testedAuthority.getServerToConnect());
            //new AutomaticAggregateManagerWrapper(logger, getGeniUserProvider(), connectionProvider, jFedPreferences, testedAuthority.getServerToConnect());
        } else {
            if (amVersion.trim().equalsIgnoreCase("2")) {
                amWrapper = new AMv2Wrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, testedAuthority.getServerToConnect());
            }
            if (amVersion.trim().equalsIgnoreCase("3")) {
                amWrapper = new AMv3Wrapper(logger, geniUserProvider, connectionProvider, jFedPreferences, testedAuthority.getServerToConnect());
            }
            if (amWrapper == null)
                throw new RuntimeException("Option \"am_version\" has unknown value (\""+amVersion+"\") should be \"auto\", \"2\" or \"3\"");
        }

        commonAMTest.processCredentialOptions(testbedInfoSource, authorityFinder); //handle speaksFor etc

        createSliceIfNotExists = TextUtil.objectToBoolean(getTestConfig().getProperty("create_slice_if_not_exists"));
        reuseSliceIfExists = TextUtil.objectToBoolean(getTestConfig().getProperty("reuse_slice_if_exists"));
        requireNodesInManifest = TextUtil.objectToBoolean(getTestConfig().getProperty("require_nodes_in_manifest"));
        disableCleanup = TextUtil.objectToBoolean(getTestConfig().getProperty("disable_slice_cleanup"));
        if (createSliceIfNotExists == null) createSliceIfNotExists = true;
        if (reuseSliceIfExists == null) reuseSliceIfExists = false;
        if (disableCleanup == null) disableCleanup = false;
        if (requireNodesInManifest == null) requireNodesInManifest = true;

        if (getTestConfig().get("use_geni_end_time_option_for_createsliver_calls") != null) {
            Object v = getTestConfig().get("use_geni_end_time_option_for_createsliver_calls");
            useGeniEndTimeOptionForCreateSliver = TextUtil.objectToBoolean(v + "");
        }


        if (getTestConfig().get("slice_expire_time_in_minutes") != null) {
            sliceExpireTimeInMinutes = Integer.parseInt(
                        TextUtil.objectToStringOrNull(
                                getTestConfig().get("slice_expire_time_in_minutes"),
                                true
                        ));
        }
//        if (getTestConfig().get("allocate_expire_time_in_minutes") != null) {
//                allocateExpireTimeInMinutes = Integer.parseInt(
//                        TextUtil.objectToStringOrNull(
//                                getTestConfig().get("allocate_expire_time_in_minutes"),
//                                true
//                        ));
//        }
        if (getTestConfig().get("provision_expire_time_in_minutes") != null) {
            provisionExpireTimeInMinutes = Integer.parseInt(
                        TextUtil.objectToStringOrNull(
                                getTestConfig().get("provision_expire_time_in_minutes"),
                                true
                        ));
        }
        if (getTestConfig().get("max_time_to_wait_for_ready_in_minutes") != null) {
            maxTimeToWaitForReadyInMinutes = Integer.parseInt(
                        TextUtil.objectToStringOrNull(
                                getTestConfig().get("max_time_to_wait_for_ready_in_minutes"),
                                true
                        ));
        }

        //OLD: wrapper now supports speaksFor etc
//        //since we use wrapper, we can not use special credential types.
//        //  (and we don't want to test these in this class anyway, use TestAggregateMAnager2/3)
//        commonAMTest.selectRegularTestCredentialType(this);

        commonAMTest.setupProxyForConnectionPool(getTestConfig());

        fixedNodeUrnString = getTestConfig().getProperty("fixed_node_urn");
        needListResources = fixedNodeUrnString != null && fixedNodeUrnString.trim().equalsIgnoreCase("auto");
        if (needListResources) {
            //needs to be filled in by listResources()
            fixedNodeUrnString = null;
        }

        fixedNodeSliverType = getTestConfig().getProperty("fixed_node_sliver_type");
        fixedNodeExclusive = TextUtil.stringToBoolean(getTestConfig().getProperty("fixed_node_exclusive"));

        //this has to be done after commonAMTest.processCredentialOptions
        if (commonAMTest.getSpeaksForConfig() != null) {
            amWrapper.setSpeaksFor(commonAMTest.getSpeaksForConfig().getSpeaksForCredentials(), commonAMTest.getSpeaksForConfig().getUser2Urn());
            //Note: this isn't raelly needed ormally, as amWrapper.setSpeaksFor should do this.
            //  But it can be done manually, and we do so here, because the user might specify other options.
            if (!commonAMTest.getSpeaksForConfig().isAutomaticSpeaksForOptionNames())
                amWrapper.setExtraOptionsForCallsWithCredential(commonAMTest.addCredentialExtraOptions(null));
        }

        String existingSliverStr = getTestConfig().getProperty("force_uses_existing_sliver");
        if (existingSliverStr != null) {
            forceUseExistingSliver = TextUtil.stringToBoolean(existingSliverStr);
            if (forceUseExistingSliver) {
                assertTrue(reuseSliceIfExists, "Can only reuse slive if slice is fixed and exists");
                String fixedSliceName = getTestConfig().getProperty("fixed_slice_name");
                assertNonEmptyString(fixedSliceName, "Can only reuse slive if slice is fixed and exists");
            }
        }
    }

    @Test(groups = {"getversion", "nonodelogin", "beforesliver"})
    public void testGetVersion() throws JFedException {
        versionMap = amWrapper.getVersion();
    }

    @Test(hardDepends = {}, softDepends = {"testGetVersion"},
            description = "Create the slices used in the next tests", groups= {"nonodelogin", "beforesliver"})
    public void createTestSlices() throws JFedException, IOException {
        String fixedProjectName = getTestConfig().getProperty("fixed_project_name"); //may be null, in that case, no sub authority is used, unless mandatory
        boolean tryToUseProject = TextUtil.objectToBoolean(getTestConfig().getProperty("try_to_use_project_for_slices"))  != Boolean.FALSE; //so default is tru
        //create slices to use in AM tests

        String fixedSliceName = getTestConfig().getProperty("fixed_slice_name");

        if (reuseSliceIfExists && fixedSliceName != null) {
            //check if exists: reuseSlice returns info if exists, null otherwise
            slice = commonAMTest.reuseSlice(fixedSliceName);

            if (slice != null)
                return;

            //else fall back to creating slice
            if (!createSliceIfNotExists)
                throw new RuntimeException("create_slice_if_not_exists is false, and slice does not exist.");
        }

        note("Will create slice that expires in "+sliceExpireTimeInMinutes+" minutes");
        if (fixedSliceName == null)
            slice = commonAMTest.createSlice("s", fixedProjectName, tryToUseProject,
                    true /*hasFixedRspecOption*/, sliceExpireTimeInMinutes);
        else
            slice = commonAMTest.createSlice(fixedSliceName, false, fixedProjectName, tryToUseProject,
                    true /*hasFixedRspecOption*/, sliceExpireTimeInMinutes);

        assertNotNull(slice.urnString, "sliceS.urnString may not be null");
        assertTrue(slice.urnString.startsWith("urn:publicid:IDN+"));
        assertNotNull(slice.credentials, "sliceS.credential may not be null");
        assertNotEmpty(slice.credentials, "sliceS.credential may not be empty");
        assertValidUrn(slice.urnString, "slice");
    }


    @Test(hardDepends = {}, softDepends = {"testGetVersion"},
            groups = {"createslivernowait", "createsliver", "nodelogin", "nonodelogin", "beforesliver"})
    public void listResources() throws JFedException, NoSuchAlgorithmException, ParseException, IOException {
        if (!needListResources) {
            note("The \"fixed_node_urn\" option is not \"auto\", so a ListResources call is not needed.");
            return;
        }
        note("The \"fixed_node_urn\" option is \"auto\", so ListResources call is needed to fill in the node URN to test.");

        List<AnyCredential> userCredentials = commonAMTest.getUserCredentialListForAM();
        String advertismentRspec = amWrapper.listResources(userCredentials, true/*available*/);
        if (advertismentRspec == null) {
            errorFatal("ListResources call did not return an advertisement RSpec");
            return;
        }

        AdvertisementRspecSource advertisementRspecSource = new AdvertisementRspecSource(advertismentRspec, ModelRspecType.BASIC);
        List<BasicStringRspec.BasicNodeInfo> basicNodeInfos = advertisementRspecSource.getBasicNodeInfo();
        if (basicNodeInfos == null) {
            errorFatal("info could not be extracted from the advertisement RSpec. Is it valid?");
            return;
        }
        for (BasicStringRspec.BasicNodeInfo basicNodeInfo : basicNodeInfos) {
            if (basicNodeInfo.getComponentId() != null) {
                if (basicNodeInfo.isAvailable() == Boolean.FALSE) {
                    warn("Found an node to use: \"" + basicNodeInfo.getComponentId() + "\", but it is marked as not available. This is a server bug, as we requested only available nodes.");
                } else {
                    fixedNodeUrnString = basicNodeInfo.getComponentId();
                    fixedNodeSliverType = basicNodeInfo.getSliverTypes().isEmpty() ? null : basicNodeInfo.getSliverTypes().iterator().next();
                    fixedNodeExclusive = basicNodeInfo.isExclusive();
                    note("Found an available node to use: \"" + fixedNodeUrnString + "\" sliver_type="+fixedNodeSliverType+" exclusive="+fixedNodeExclusive);
                    return;
                }
            }
        }

        errorFatal("Did not find a single available node in the advertisement RSpec. The createSliver test will continue, BUT NOT specify a component_id.");
    }

    protected boolean isGeniRspec = true;
    protected boolean isGeniRequestRspec() {
        return isGeniRspec;
    }
    protected boolean isGeniManifestRspec() {
        //TODO: take actual manifest into account, it's possible that there exist servers that we give a non geni request rspec, but that can return a geni v3 rspec manifest
        return isGeniRspec;
    }
    protected String getRequestRspec() throws IOException {
        String rspecArg = commonAMTest.getFixedRspecOption();
        if (rspecArg == null) {
            boolean noFixedNode = fixedNodeUrnString == null || fixedNodeUrnString.trim().isEmpty();
            GeniUrn fixedNodeUrn = noFixedNode ? null : GeniUrn.parse(fixedNodeUrnString.trim());

            if (!noFixedNode && fixedNodeUrn == null)
                warn("The setting \"fixed_node_urn\" contains an invalid urn: \""+fixedNodeUrnString+"\". It will be ignored.");

            //TODO: make sure geni 3 is advertised in GetVersion. If not, use another RSpec request format!
            String res = commonAMTest.getOneNodeRequestRSpec(testedAuthority.getServerForRspecComponentManager(), slice.urn, fixedNodeUrn, fixedNodeExclusive, fixedNodeSliverType);
            if (res == null)
                skip("testCreateSliver skipped, because no RSpec example known for type=\"" + testedAuthority.getServerForRspecComponentManager().getServerType() + "\"");
            isGeniRspec = true;
            return res;
        } else {
            isGeniRspec = commonAMTest.isGeniFixedRspec();
            return rspecArg;
        }
    }

    protected Date requestedEndTime;
    @Test(hardDepends = {"createTestSlices"}, softDepends = {"testGetVersion", "listResources"},
            groups = {"createslivernowait", "createsliver", "nodelogin", "nonodelogin"})
    public void createSliver() throws JFedException, NoSuchAlgorithmException, ParseException, IOException {
        assertNotNull(slice);

        requestRspec = getRequestRspec();
        assert requestRspec != null;
        int requestNodeCount = isGeniRequestRspec() ? new BasicStringRspec(requestRspec).getBasicNodeInfo().size() : 0;
        note("Using request with "+requestNodeCount+" nodes.");
        if (requestNodeCount < 1) {
            note("Fallback activated. Since "+requestNodeCount+" are in request RSpec, no node count will be assumed in manifest.");
            requireNodesInManifest = false;
        }

        requestedEndTime = null;
        if (useGeniEndTimeOptionForCreateSliver) {
            note("Will Provision slivers with "+provisionExpireTimeInMinutes+" minutes expire time");
            requestedEndTime = new Date(System.currentTimeMillis() + (provisionExpireTimeInMinutes * 60 * 1000L));
        } else {
            note("Will Provision slivers without explicit expire time (thus slice expire time should be used automatically by AM)");
        }

        assert nodeLoginTester == null;
        String fixedSshPublicKeyFile = getTestConfig().getProperty("fixed_ssh_public_key_file");
        String fixedSshPrivateKeyFile = getTestConfig().getProperty("fixed_ssh_private_key_file");
        String fixedSshPrivateKeyPassword = getTestConfig().getProperty("fixed_ssh_private_key_password");
        nodeLoginTester =
                new NodeLoginTester(this, fixedSshPublicKeyFile, fixedSshPrivateKeyFile, fixedSshPrivateKeyPassword);

        String nodeLoginDeadlineMsString = getTestConfig().getProperty("nodelogin_deadline_ms");
        if (nodeLoginDeadlineMsString != null)
            nodeLoginTester.setNodeLoginDeadlineMs(Long.parseLong(nodeLoginDeadlineMsString.trim()));

        String nodeloginPreferredUser = getTestConfig().getProperty("nodelogin_preferred_user");
        if (nodeloginPreferredUser != null)
            nodeLoginTester.setNodeLoginPreferredUser(nodeloginPreferredUser);

        List<String> sshKeys = new ArrayList<>();
        sshKeys.add(nodeLoginTester.getSshKeyHelper().getSshPublicKeyString());
        UserSpec userSpec = new UserSpec(user.getUserUrnString(), sshKeys);
        List<UserSpec> users = new ArrayList<>();
        users.add(userSpec);

        if (forceUseExistingSliver) {
            notEnoughResourcesDetected = false;

            return;
        }

        try {
            manifestRspec = amWrapper.createSliver(slice.urn, commonAMTest.getSliceCredentialListForAM(slice), requestRspec, users, requestedEndTime);
            notEnoughResourcesDetected = false;
        } catch (AggregateManagerWrapper.NotEnoughFreeResourcesException e) {
            warn("Not enough free resources");
            notEnoughResourcesDetected = true;
            return;
        }

        if (manifestRspec == null)
            warn("Created sliver for \"" + slice.urnString + "\" manifest=null");
        else
            note("Created sliver for \"" + slice.urnString + "\" manifest.length=" + manifestRspec.length());

        assertNotNull(manifestRspec, "Provision should return a manifest RSpec");
        if (requireNodesInManifest) {
            assertEquals(CommonAMTest.testValidGeni3ManifestRspec(this, manifestRspec), requestNodeCount,
                    "Provision did not return a valid RSpec" + manifestRspec);

            assertFalse(CommonAMTest.isEmptyRspec(this, manifestRspec),
                    "Provision returned and empty RSpec: " + manifestRspec);

            setErrorsNotFatal();
            assertTrue(CommonAMTest.sameNodesInRequestAndManifest(this, requestRspec, manifestRspec));
            setErrorsFatal();
        }
        else {
            if (isGeniManifestRspec())
                CommonAMTest.testValidGeni3ManifestRspec(this, manifestRspec); //just test, don't assume any nodecount
        }

        if (isGeniManifestRspec()) {
            assert !nodeLoginTester.hasParsed();
            foundNodeLoginInProvision = nodeLoginTester.parseSshInfoFromGeni3ManifestRspec(manifestRspec, null, null);
            if (foundNodeLoginInProvision)
                note("Successfully found node login info in reply");
            else
                note("Did not find node login info in reply");

            ManifestRspecSource manifestRspecSource = new ManifestRspecSource(manifestRspec, ModelRspecType.BASIC);
            ModelRspec modelRspec = manifestRspecSource.getModelRspec();
            if (modelRspec != null) {
                for (RspecNode node : modelRspec.getNodes()) {
                    NodeLocation location = node.getLocation();
                    if (location != null) {
                        if (location.getCountry() != null) {
                            note("NODE COUNTRY: " + location.getCountry());
                        }
                        if (location.getLatitude() != null) {
                            note("NODE LATITUDE: " + location.getLatitude());
                        }
                        if (location.getLongitude() != null) {
                            note("NODE LONGITUDE: " + location.getLongitude());
                        }
                    }
                }
            }
        }
    }

    protected final Set<GeniUrn> createSliverUrns = new HashSet<>();
    @Test(hardDepends = {"createSliver"}, softDepends = {}, groups = {"createsliver", "nodelogin", "nonodelogin"})
    public void registerSlivers() throws JFedException, ParseException {
        //Note: this sees a lot of problems as reasons to skip instead as failures

        if (requestedEndTime == null)
            skip("Something went wrong creating the slivers. Will not register them.");
        if (manifestRspec == null)
            skip("No manifest after createSliver. Will not register any slivers.");
        if (!isGeniManifestRspec())
            skip("Manifest after createSliver is no geni v3 format. Will not register any slivers.");

        try {
            ManifestRspecSource manifestRspecSource = new ManifestRspecSource(manifestRspec, ModelRspecType.BASIC);
            ModelRspec modelRspec = manifestRspecSource.getModelRspec();
            if (modelRspec != null) {
                for (RspecNode node : modelRspec.getNodes()) {
                    if (node.getSliverId() != null)
                        createSliverUrns.add(node.getSliverId());
                }
                if (!createSliverUrns.isEmpty())
                    commonAMTest.registerSlivers(slice, testedAuthority.getServerToConnect(), createSliverUrns, requestedEndTime);
                else
                    skip("No info on sliver URN's in manifest. Must skip register.");
            } else
                skip("Problem parsing manifest. Will not register any slivers.");
        } catch (Exception t) {
            skip("Exception registering slivers.", t);
        }
    }

    @Test(hardDepends = {"createSliver"}, softDepends = { "registerSlivers" }, groups = {"createsliver", "nodelogin", "nonodelogin"})
    public void waitForSliverReady() throws JFedException, ParseException {
        assertNotNull(notEnoughResourcesDetected);
        if (notEnoughResourcesDetected) skip("Test skipped because not enough free resources detected while tying to create the sliver(s)");

        //Test if the sliver ever becomes ready. We wait for maximum 20 minutes.
        assertNotNull(slice);

        note("Will wait max "+maxTimeToWaitForReadyInMinutes+" minutes for sliver to become ready");
        long now = System.currentTimeMillis();
        long deadline = now + (maxTimeToWaitForReadyInMinutes * 60 * 1000);
        while (now < deadline) {
            StatusDetails statusDetails = amWrapper.status(slice.urn, commonAMTest.getSliceCredentialListForAM(slice));

            switch (statusDetails.getGlobalStatus()) {
                case UNINITIALISED: //fall-through
                case UNALLOCATED: //fall-through
                case UNKNOWN: //fall-through
                default:
                case CHANGING: {
                    note("testCreatedSliverBecomesReady -> sliver not ready: " + statusDetails.getGlobalStatus() + ".  Trying again in 30 seconds...");
                    break;
                }
                case READY: {
                    note("testSliverBecomesReady -> sliver ready: "+statusDetails);

                    String extraWait = getTestConfig().getProperty("extra_wait_after_status_ready_in_seconds");
                    if (extraWait != null && !extraWait.trim().isEmpty() && extraWait.matches("[0-9]*")) {
                        int extraSeconds = Integer.parseInt(extraWait);
                        note("Waiting "+extraSeconds+" extra seconds before continuing. (due to extra_wait_after_status_ready_in_seconds option)");
                        try {
                            Thread.sleep(extraSeconds * 1000L);
                        } catch (InterruptedException e) {
                            warn("Extra waiting time was interrupted. This should not happen.", e);
                        }
                    }

                    return;
                }
                case FAIL: {
                    note("testSliverBecomesReady -> sliver failed: "+statusDetails);
                    return;
                }
            }

            try {
                Thread.sleep(30000 /*ms*/);
            } catch (InterruptedException e) { /* Ignore*/ }
            now = System.currentTimeMillis();
        }
        throw new RuntimeException("Sliver did not become ready within "+maxTimeToWaitForReadyInMinutes+" minutes!");
    }

    @Test(hardDepends = { "createSliver" }, softDepends = {"waitForSliverReady"}, groups = {"createsliver", "nodelogin", "nonodelogin"})
    public void describeReadySliver() throws JFedException {
        assertNotNull(notEnoughResourcesDetected);
        if (notEnoughResourcesDetected) skip("Test skipped because not enough free resources detected while tying to create the sliver(s)");
        assertNotNull(slice);

        manifestRspec2 = amWrapper.describe(slice.urn, commonAMTest.getSliceCredentialListForAM(slice));

        assertNotNull(manifestRspec2);

        setErrorsNotFatal();
        if (requestRspec != null && manifestRspec2 != null && isGeniRequestRspec() && isGeniManifestRspec())
            assertTrue(CommonAMTest.sameNodesInRequestAndManifest(this, requestRspec, manifestRspec2));
        setErrorsFatal();
        if (isGeniManifestRspec()) {
            boolean foundNodeLoginInDescribe = nodeLoginTester.parseSshInfoFromGeni3ManifestRspec(manifestRspec2, null, null);
            if (foundNodeLoginInDescribe)
                note("Successfully found node login info in Describe reply");
            else
                note("Did not find node login info in Describe reply");

            if (foundNodeLoginInProvision)
                assertTrue(foundNodeLoginInDescribe,
                        "Found node login info in the CreateSliver/Provision reply. But NO node login info was found in the Describe/ListResources call done afterwards!");

            if (TextUtil.objectToBoolean(getTestConfig().getProperty("skip_final_manifest_check")) != Boolean.TRUE)
                nodeLoginTester.checkManifestCorrectness(manifestRspec, testedAuthority);
        }
    }

    @Test(hardDepends = {"createSliver"}, softDepends = {"waitForSliverReady",
            "describeReadySliver"}, groups = {"nodelogin"})
    public void testNodeLogin() throws JFedException, IOException {
        assertNotNull(notEnoughResourcesDetected);
//        if (notEnoughResourcesDetected) skip("Test skipped because not enough free resources detected while tying to create the sliver(s)");
        if (notEnoughResourcesDetected) {
            warn("Test skipped because not enough free resources detected while tying to create the sliver(s)");
            return;
        }
        if (!isGeniManifestRspec()) {
            skip("Test skipped because manifest is not a geni version 3 RSpec (so this test does not know how to extract node login info)");
            return;
        }

        assert nodeLoginTester != null;

        commonAMTest.setupProxyForNodeLogin(getTestConfig(), nodeLoginTester, slice.urn);

        String nodeloginForceIpVersion = getTestConfig().getProperty("nodelogin_force_ip_version");
        if (nodeloginForceIpVersion != null) {
            try {
                nodeLoginTester.setNodeLoginForceIpVersion(Integer.parseInt(nodeloginForceIpVersion));
            } catch (NumberFormatException e) {
                warn("nodelogin_force_ip_version property is set to invalid value: \""+nodeloginForceIpVersion+"\". Supported: 4 or 6");
            }
        }

        Boolean nodeloginUseExternalSsh = TextUtil.objectToBoolean(getTestConfig().getProperty("nodelogin_use_external_ssh"));
        if (nodeloginUseExternalSsh == null) nodeloginUseExternalSsh = false;

        nodeLoginTester.testNodeLogin(false/*warnIfNotYetReady*/, !nodeloginUseExternalSsh); //only note, no warning
    }

    /* Test must run after tests that use sliver. Will always run, to cleanup any created sliver. */
    @Test(softDepends = {"testNodeLogin", "createSliver", "describeReadySliver", "waitForSliverReady"},
            groups = {"createsliver", "nodelogin", "nonodelogin"})
    public void testDeleteSliver() throws JFedException {
        if (disableCleanup) { note("Config specified that no cleanup is needed."); return; }
        if (slice.urnString == null) skip("Skipped because other test created nothing to delete");

        assertNotNull(slice);

        System.out.println("DeleteSliver for slice " + slice.urnString);

        amWrapper.deleteSliver(slice.urn, commonAMTest.getSliceCredentialListForAM(slice));
    }

    @Test(hardDepends = { "testDeleteSliver", "registerSlivers" },
            softDepends = {},
            groups = {"createsliver", "nodelogin", "nonodelogin"})
    public void unregisterSlivers() throws JFedException {
        if (!createSliverUrns.isEmpty())
            commonAMTest.unregisterSlivers(slice, testedAuthority.getServerToConnect(), createSliverUrns);
    }
}
