示例#1
0
int main()
{
	//Stel onderstaande poorten in op Output en laad allemaal enen in
	DDRA=0xFF;
	DDRB=0xFF;
	DDRC=0xFF;
	PORTA=0xFF;
	PORTB=0xFF;
	PORTC=0xFF;

	//Stel de timer in die een interrupt genereert bij en overflow
	TCCR0=0x05;
	TIMSK=0x01;
	
	//Stel odnerstaande poorten in op Input en laad allemaal enen in
	DDRE=0x00;
	PINE=0xFF;

	serial.init();

	// Aanmaken van de verschillende autolichtobjecten
	AutoLicht azl(0xFE, 0xFD, 0xFB, ADRESPORTB);
	AutoLicht azr(0xF7, 0xEF, 0xDF, ADRESPORTB);
	AutoLicht ahl(0xFE, 0xFD, 0xFB, ADRESPORTC);
	AutoLicht ahr(0xF7, 0xEF, 0xDF, ADRESPORTC);

	// Aanmaken van de verschillende voetgangerlichtobjecten
	VoetgangerLicht vhr(0xFE, 0xFD, ADRESPORTA);
	VoetgangerLicht vz(0xBF, 0x7F, ADRESPORTB);
	VoetgangerLicht vhl(0xBF, 0x7F, ADRESPORTC);

	List<VoetgangerLicht*> l1, l2, l3;
	List<Scenario*> s;

	//Lijst l1 vullen
	l1.push_back(&azl);
	l1.push_back(&azr);

	//Lijst l2 vullen
	l2.push_back(&ahl);
	l2.push_back(&ahr);

	//Lijst l3 vullen
	l3.push_back(&vhr);
	l3.push_back(&vz);
	l3.push_back(&vhl);
	
	//Scenario's definieren
	Scenario s1(&l1, &variabelebeheerder);
	Scenario s2(&l2, &variabelebeheerder);
	Scenario s3(&l3, &variabelebeheerder);
	s.push_back(&s1);
	s.push_back(&s2);
	s.push_back(&s3);

	variabelebeheerder.zetAantalScenarios(3);

	//Scenario's toekennen aan sensoren
	svz.kenScenarioToe(&s3);
	svhr.kenScenarioToe(&s3);
	svhl.kenScenarioToe(&s3);
	sahr.kenScenarioToe(&s2);
	sahl.kenScenarioToe(&s2);
	sazl.kenScenarioToe(&s1);
	sazr.kenScenarioToe(&s1);

	VerkeersRegelaar vr(&s, &wachtrijbeheerder, &variabelebeheerder);
	
	sei(); //Zet interrupts aan

	while(1) {
		vr.kiesFunctie();
	}
}
std::pair<bool /*success*/, std::string /* message */>
    ivy_engine::startup(
            const std::string& output_folder_root,
            const std::string& test_name,
            const std::string& ivyscript_filename,
            const std::string& hosts_string,
            const std::string& select_available_test_LUNs)
{
    std::string api_log_entry;
    {
        std::ostringstream o;
        o << "ivy engine API startup("
            << "output_folder_root = "   << output_folder_root
            << ", test_name = "          << test_name
            << ", ivyscript_filename = " << ivyscript_filename
            << ", test_hosts = "         << hosts_string
            << ", select = "             << select_available_test_LUNs
            << ")" << std::endl;
        std::cout << o.str();
        api_log_entry = o.str();
    }

    outputFolderRoot = output_folder_root;
    testName = test_name;

    struct stat struct_stat;

    if( stat(outputFolderRoot.c_str(),&struct_stat))
    {
        std::ostringstream o;
        o << "<Error> directory \"" << outputFolderRoot << "\" does not exist." << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    if(!S_ISDIR(struct_stat.st_mode))
    {
        std::ostringstream o;
        o << "<Error> \"" << outputFolderRoot << "\" is not a directory." << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    testFolder=outputFolderRoot + std::string("/") + testName;

    if(0==stat(testFolder.c_str(),&struct_stat))  // output folder already exists
    {
        if(!S_ISDIR(struct_stat.st_mode))
        {
            std::ostringstream o;
            o << "<Error> Output folder for this test run \"" << testFolder << "\" already exists but is not a directory." << std::endl;
            std::cout << o.str();
            return std::make_pair(false,o.str());
        }
        // output folder already exists and is a directory, so we delete it to make a fresh one.
        if (0 == system((std::string("rm -rf ")+testFolder).c_str()))   // ugly but easy.
        {
            std::ostringstream o;
            o << "      Deleted pre-existing folder \"" << testFolder << "\"." << std::endl;
            std::cout << o.str();

        }
        else
        {
            std::ostringstream o;
            o << "<Error> Failed trying to delete previously existing version of test run output folder \"" << testFolder << "\"." << std::endl;
            std::cout << o.str();
            return std::make_pair(false,o.str());
        }

    }

    if (mkdir(testFolder.c_str(),S_IRWXU | S_IRWXG | S_IRWXO))
    {
        std::ostringstream o;
        o << "<Error> Failed trying to create output folder \"" << testFolder << "\" << errno = " << errno << " " << strerror(errno) << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    std::cout << "      Created test run output folder \"" << testFolder << "\"." << std::endl;

    if (mkdir((testFolder+std::string("/logs")).c_str(),S_IRWXU | S_IRWXG | S_IRWXO))
    {
        std::ostringstream o;
        o << "<Error> - Failed trying to create logs subfolder in output folder \"" << testFolder << "\" << errno = " << errno << " " << strerror(errno) << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    masterlogfile = testFolder + std::string("/logs/log.ivymaster.txt");
    ivy_engine_logfile = testFolder + std::string("/logs/ivy_engine_API_calls.txt");


    log(masterlogfile,      api_log_entry);
    log(ivy_engine_logfile, api_log_entry);

    {
        std::ostringstream o;
        o << "ivy version " << ivy_version << " build date " << IVYBUILDDATE << " starting." << std::endl << std::endl;
        log(masterlogfile,o.str());
    }

    if (!routine_logging)
    {
        std::ostringstream o;
        o << "For logging of routine (non-error) events, and to record the conversation with each ivydriver/ivy_cmddev, use the ivy -log command line option, like \"ivy -log a.ivyscript\".\n\n";
        log(masterlogfile,o.str());
    }

    if (ivyscript_filename.size() > 0)
    {
        std::string copyivyscriptcmd = std::string("cp -p ") + ivyscript_filename + std::string(" ") +
                                       testFolder + std::string("/") + testName + std::string(".ivyscript");
        if (0!=system(copyivyscriptcmd.c_str()))   // now getting lazy, but purist maintainers could write C++ code to do this.
        {
            std::ostringstream o;
            o << "<Error> Failed trying to copy input ivyscript to output folder: \"" << copyivyscriptcmd << "\"." << std::endl;
            log(masterlogfile,o.str());
            std::cout << o.str();
            return std::make_pair(false,o.str());
        }
    }

    std::pair<bool,std::string> retval = rollups.initialize();
    if (!retval.first)
    {
        std::ostringstream o;
        o << "<Error> Internal programming error - failed initializing rollups in ivymaster.cpp saying: " << retval.second << std::endl;
        log(masterlogfile,o.str());
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    test_start_time.setToNow();

    initialize_io_time_clip_levels();

    availableControllers[toLower(std::string("measure"))] = &the_dfc;

    auto rv = random_steady_template.setParameter("iosequencer=random_steady");
    if ( ! rv.first)
    {
        std::ostringstream o;
        o << "<Error> dreaded internal programming error - ivymaster startup - failed trying to set the default random steady I/O generator template - " << rv.second << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    rv = random_independent_template.setParameter("iosequencer=random_independent");
    if ( !rv.first )
    {
        std::ostringstream o;
        o << "<Error> dreaded internal programming error - ivymaster startup - failed trying to set the default random independent I/O generator template - " << rv.second << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    rv = sequential_template.setParameter("iosequencer=sequential");
    if ( !rv.first )
    {
        std::ostringstream o;
        o << "<Error> dreaded internal programming error - ivymaster startup - failed trying to set the default sequential I/O generator template - " << rv.second << std::endl;
        std::cout << o.str();
        return std::make_pair(false,o.str());
    }

    hosts_list test_hosts;
    if (!test_hosts.compile(hosts_string))
    {
        std::ostringstream o;

        o << "<Error> failed parsing list of test hosts \"" << hosts_string << "\" - " << test_hosts.message;
        return std::make_pair(false,o.str());
    }

    for (auto& s: test_hosts.hosts) { hosts.push_back(s);}

    write_clear_script();  // This writes a shell script to run the clear_hung_ivy_threads executable on all involved hosts - handy if you are a developer and you mess things up

    if (hosts.size() == 0)
    {
        std::ostringstream o;

        o << "<Error> ivy engine startup failed - No hosts specified." << std::endl;
        return std::make_pair(false,o.str());
    }

    JSON_select select;

    if (!select.compile_JSON_select(select_available_test_LUNs))
    {
        std::ostringstream o;
        o << "<Error> ivy engine startup failure - invalid select expression for available test LUNs \"" << select_available_test_LUNs << "\" - error message follows: " << select.error_message << std::endl;
        log(masterlogfile,o.str());
        std::cout << o.str();
        kill_subthreads_and_exit();
    }

    { std::ostringstream o; o << "select " << select << std::endl; std::cout << o.str(); log(masterlogfile,o.str()); }

    if
    (
        (!select.contains_attribute("serial_number"))
        &&
        (!select.contains_attribute("vendor"))
    )
    {
        std::ostringstream o;

        o << "<Error> ivy engine startup failure -  select for available test LUNs \"" << select_available_test_LUNs << "\" - must specify either \"serial_number\" or \"vendor\"." << std::endl;
        return std::make_pair(false,o.str());
    }

    for ( auto& host : hosts )
    {
        if (routine_logging) { std::cout << "Starting thread for " << host << std::endl; }
        log( masterlogfile, std::string("Starting thread for ") + host + std::string("\n") );

        pipe_driver_subthread* p_pipe_driver_subthread = new pipe_driver_subthread
            (
                host,
                outputFolderRoot,
                testName,
                testFolder+std::string("/logs")
            );
        host_subthread_pointers[host] = p_pipe_driver_subthread;
        threads.push_back(std::thread(invokeThread,p_pipe_driver_subthread));
    }
    ofstream ahl(testFolder+std::string("/all_discovered_LUNs.txt"));
    ofstream ahl_csv(testFolder+std::string("/all_discovered_LUNs.csv"));

    {
        std::ostringstream o;
        o << std::endl << "Note:" << std::endl
            << "Sometimes the ssh command to start up ivydriver on a test host can take a long time when waiting for DNS timeouts.  "
            << "This can be speeded up by editing /etc/nsswitch.conf / resolv.conf to use /etc/hosts first, or options for the sshd daemon can be edited; search for \"ssh login timeout\"." << std::endl << std::endl;
        std::cout << o.str();
        log(masterlogfile,o.str());
    }

    bool first_host {true};

    for (auto&  pear : host_subthread_pointers)
    {
        {
            std::unique_lock<std::mutex> u_lk(pear.second->master_slave_lk);

            std::chrono::system_clock::time_point leftnow {std::chrono::system_clock::now()};
            if
            (
                pear.second->master_slave_cv.wait_until
                (
                    u_lk,
                    leftnow + std::chrono::seconds(ivy_ssh_timeout),
                    [&pear]() { return pear.second->startupComplete || pear.second->dead; }
                )
            )
            {
                if (pear.second->startupSuccessful)
                {
                    std::ostringstream o;
                    o << "pipe_driver_subthread successful fireup for host " << pear.first << std::endl;
                    std::cout << o.str();
                    log(masterlogfile,o.str());
                }
                else
                {
                    std::ostringstream o;
                    o << "Aborting - unsuccessful startup for ivydriver on host " << pear.first << ".  Reason: \"" << pear.second->commandErrorMessage << "\"." << std::endl;
                    o << std::endl << "Usually if try to run ivy again, it will work the next time.  Don\'t know why this happens." << std::endl;
                    o << std::endl << "Run the \"clear_hung_ivy_threads.sh\" script to clear any hung ivy / ivydriver / ivy_cmddev threads on the ivy master host and all test hosts." << std::endl;
                    std::cout << o.str();
                    log(masterlogfile,o.str());
                    u_lk.unlock();
                    kill_subthreads_and_exit(); // doesn't return
                }

            }
            else
            {
                std::ostringstream o;
                o << "Aborting - timeout waiting " << timeout_seconds << " seconds for pipe_driver_subthread fireup for host " << pear.first << std::endl;
                std::cout << o.str();
                log(masterlogfile,o.str());
                u_lk.unlock();
                kill_subthreads_and_exit(); // doesn't return
            }
        }


        {ostringstream o; o << "Subthread for \"" << pear.first << "\" posted startupComplete." << std::endl; std::cout  << o.str(); log(masterlogfile,o.str());}

        for (auto& pLUN : pear.second->thisHostLUNs.LUNpointers)
        {
            allDiscoveredLUNs.LUNpointers.push_back(pLUN);
            ahl << pLUN->toString() << std::endl;
        }
//*debug*/{ostringstream o; o << "Loop for \"" << pear.first << "\" about to copy on sample LUN." << std::endl; std::cout  << o.str(); log(masterlogfile,o.str());}

//*debug*/{ostringstream o; o << "Loop for \"" << pear.first << "\" HostSampleLUN=" << pear.second->HostSampleLUN.toString() << std::endl; std::cout  << o.str(); log(masterlogfile,o.str());}

        TheSampleLUN.copyOntoMe(&pear.second->HostSampleLUN);

        if (first_host) {ahl_csv << pear.second->lun_csv_header;}
        ahl_csv << pear.second->lun_csv_body.str();

        first_host = false;
    }
    ahl.close();
    ahl_csv.close();
    allThreadsSentTheLUNsTheySee=true;
    /*debug*/
    if (routine_logging){
        ostringstream o;
        o <<"allDiscoveredLUNs contains:"<<std::endl<< allDiscoveredLUNs.toString() <<std::endl;
        //std::cout<<o.str();
        log(masterlogfile,o.str());
    }
    else
    {
        ostringstream o;
        o <<"Discovered " << allDiscoveredLUNs.LUNpointers.size() << " LUNs on the test hosts." << std::endl;
        //std::cout<<o.str();
        log(masterlogfile,o.str());
    }

    if (0 == allDiscoveredLUNs.LUNpointers.size())
    {
        std::ostringstream o;
        o << ivyscript_line_number << ": \"" << ivyscript_line << "\"" << std::endl;
        o << "No LUNs were found on any of the hosts." << std::endl;
        std::cout << o.str();
        log (masterlogfile,o.str());
        ivyscriptIfstream.close();
        kill_subthreads_and_exit();
    }


    allDiscoveredLUNs.split_out_command_devices_into(commandDeviceLUNs);

    /*debug*/
    {
        ostringstream o;
        o <<"commandDeviceLUNs contains:"<<std::endl<< commandDeviceLUNs.toString() <<std::endl;
        //std::cout<<o.str();
        log(masterlogfile,o.str());
    }

    for (LUN* pLUN : allDiscoveredLUNs.LUNpointers)
    {
        if (select.matches(pLUN))
        {
            if (     pLUN->attribute_value_matches("ldev",      "FF:FF"         )
                &&   pLUN->attribute_value_matches("ldev_type", ""              )
                && ( pLUN->attribute_value_matches("product",   "DISK-SUBSYSTEM") || pLUN->attribute_value_matches("product","OPEN-V") ) )
            {
                ; // Ignore "phantom LUNs" that have been deleted on the subsystem but for which a /dev/sdxxx entry still exists.
            }
            else if ( pLUN->attribute_value_matches("ldev",      "FF:FF")
                &&    pLUN->attribute_value_matches("ldev_type", "phantom LUN")  )
            {
                ; // Ignore "phantom LUNs" that have been deleted on the subsystem but for which a /dev/sdxxx entry still exists.
            }
            else
            {
                availableTestLUNs.LUNpointers.push_back(pLUN);
            }
        }
    }


    if ( 0 == availableTestLUNs.LUNpointers.size() )
    {
        std::ostringstream o;
        o << "No LUNs matched [hosts] statement [select] clause." << std::endl;
        //p_Select->display("",o);

        std::cout << o.str();
        log(masterlogfile,o.str());
        kill_subthreads_and_exit();
    }

    for (auto& pLUN : availableTestLUNs.LUNpointers) pLUN -> createNicknames();

    // Now we create the Subsystem objects

    for (auto& pLUN : availableTestLUNs.LUNpointers)
    {
        std::string serial_number = pLUN->attribute_value(std::string("serial_number"));
        trim(serial_number);

        Subsystem* pSubsystem;

        auto subsystemIt = subsystems.find(serial_number);
        if (subsystems.end() == subsystemIt)
        {
            std::string product = pLUN->attribute_value("product");
            trim(product);
            if (0 == product.compare(std::string("OPEN-V")))
            {
                pSubsystem = new Hitachi_RAID_subsystem(serial_number, pLUN);
            }
            else
            {
                pSubsystem = new Subsystem(serial_number);
            }
            subsystems[serial_number] = pSubsystem;
        }
        else
        {
            pSubsystem = (*subsystemIt).second;
        }
        pSubsystem->add(pLUN);
    }

    // For each subsystem that the available test LUNs map to,
    // for the first available real-time interface,
    // start up the appropriate subthread to drive that interface.

    // Which means - for each Hitachi RAID subsystem, use the first command device
    // in commandDeviceLUNs that leads to that subsystem and start an
    // pipe_driver_subthread, which fires up the ivy_cmddev executable remotely via ssh
    // communicating via stdin/stdout pipes the same way we communicate with remote ivydriver instances.

    for (auto& pear : subsystems)
    {
        Subsystem* pSubsystem = pear.second;

        if (0 == std::string("Hitachi RAID").compare(pSubsystem->type()))
        {
            Hitachi_RAID_subsystem* pRAIDsubsystem {(Hitachi_RAID_subsystem*) pSubsystem};
            bool have_cmd_dev_this_subsystem = false;

            if (m_s.use_command_device) for (auto& pL : commandDeviceLUNs.LUNpointers)
                // for each command device LUN as candidate to match as first command device for this subsystem.
            {
                if (0 == pSubsystem->serial_number.compare(pL->attribute_value("serial_number")))
                    // we have found the first command device for this subsystem
                {
                    // fire up command device thread

                    std::string cmd_dev_description;

                    {
                        std::ostringstream o;
                        o << "command device for " << pL->attribute_value("Hitachi_product") << " " << pL->attribute_value("HDS_product")
                            <<  " serial number " << pSubsystem->serial_number
                            << " on host = " << pL->attribute_value("ivyscript_hostname")
                            << ", subsystem port = " << pL->attribute_value("port")
                            << ", LDEV = " << pL->attribute_value("LDEV")
                            << std::endl;
                        cmd_dev_description = o.str();
                    }

                    std::string cmddevLDEV = pL->attribute_value("LDEV");
                    if (cmddevLDEV.length() >= 5 && ':' == cmddevLDEV[2]) cmddevLDEV.erase(2,1);

                    std::ostringstream o;
                    o << "Connecting to " << cmd_dev_description << std::endl;
                    std::cout << o.str();
                    log(masterlogfile,o.str());

                    pipe_driver_subthread* p_pipe_driver_subthread = new pipe_driver_subthread(

                        pL->attribute_value("ivyscript_hostname")
                        , outputFolderRoot
                        , testName,
                        testFolder+std::string("/logs")
                    );

                    p_pipe_driver_subthread->pCmdDevLUN = pL;  // Seeing this set tells the subthread it's running a command device.

                    p_pipe_driver_subthread->p_Hitachi_RAID_subsystem = pRAIDsubsystem;

                    pRAIDsubsystem->command_device_description = cmd_dev_description;

                    pRAIDsubsystem->pRMLIBthread=p_pipe_driver_subthread;

                    command_device_subthread_pointers[pSubsystem->serial_number] = p_pipe_driver_subthread;

                    ivymaster_RMLIB_threads.push_back(std::thread(invokeThread,p_pipe_driver_subthread));
                    // Note: I wasn't sure, since std::thread is not copyable but is moveable, how to handle
                    //       the case when the command device doesn't start up correctly.  So in the interest of
                    //       time, I left any unsuccessful-startup command device threads in moribund state
                    //       in ivymaster_RMLIB_threads to be joined when ivy shuts down.

                    // wait with timeout for thread status to show "startupComplete", i.e. waiting for command;

                    {
                        std::unique_lock<std::mutex> u_lk(p_pipe_driver_subthread->master_slave_lk);
                        std::chrono::system_clock::time_point leftnow {std::chrono::system_clock::now()};

                        if
                        (
                            p_pipe_driver_subthread->master_slave_cv.wait_until
                            (
                                u_lk,
                                leftnow + std::chrono::seconds(ivy_ssh_timeout),
                                [&p_pipe_driver_subthread](){return p_pipe_driver_subthread->startupComplete || p_pipe_driver_subthread->dead;}
                        )
                    )
                        {
                            if (p_pipe_driver_subthread->startupSuccessful)
                            {
                                std::ostringstream o;
                                o << "ivy_cmddev pipe_driver_subthread successful fireup for subsystem " << pSubsystem->serial_number << std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                haveCmdDev = true;
                                have_cmd_dev_this_subsystem = true;
                            }
                            else
                            {
                                std::ostringstream o;
                                o   << "Unsuccessful startup for command device = " << cmd_dev_description << std::endl
                                    << "Reason - " <<  p_pipe_driver_subthread->commandErrorMessage << std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                pRAIDsubsystem->command_device_description.clear();
                                pRAIDsubsystem->pRMLIBthread=nullptr;
                            }
                        }
                        else
                        {
                            std::ostringstream o;
                            o   << "Timeout waiting " << timeout_seconds << " seconds for for startup of command device = " << cmd_dev_description << std::endl;
                            std::cout << o.str();
                            log(masterlogfile,o.str());
                            pRAIDsubsystem->command_device_description.clear();
                            pRAIDsubsystem->pRMLIBthread=nullptr;
                        }
                    }

                    if (!have_cmd_dev_this_subsystem) continue;  // This command device did not start up OK - try the next one if we have another to the same subsystem.
                                                  // Maybe there is a command device perhaps on a different host that does have RMLIB installed,
                                                  // does have a valid license key installed, and does have the ivy_cmddev executable.


                    // Command device reports startup complete

                    // gather config

                    ivytime config_gather_start;
                    config_gather_start.setToNow();
                    std::chrono::system_clock::time_point
                    start_getconfig_time_point { std::chrono::system_clock::now() };

                    {
                        std::unique_lock<std::mutex> u_lk(p_pipe_driver_subthread->master_slave_lk);
                        p_pipe_driver_subthread->commandString = std::string("get config");
                        p_pipe_driver_subthread->command=true;
                        p_pipe_driver_subthread->commandComplete=false;
                        p_pipe_driver_subthread->commandSuccess=false;
                        p_pipe_driver_subthread->commandErrorMessage.clear();

                        {
                            std::ostringstream o;
                            o << "Posted \"get config\" to thread for subsystem serial " << pRAIDsubsystem->serial_number << " managed on host " << p_pipe_driver_subthread->ivyscript_hostname << '.' << std::endl;
                            log(masterlogfile,o.str());
                        }
                    }
                    p_pipe_driver_subthread->master_slave_cv.notify_all();

                    {
                        std::unique_lock<std::mutex> u_lk(p_pipe_driver_subthread->master_slave_lk);

                        if
                        (
                            p_pipe_driver_subthread->master_slave_cv.wait_until
                            (
                                u_lk,
                                start_getconfig_time_point + std::chrono::seconds(get_config_timeout_seconds /* see ivydefines.h */),
                                [&p_pipe_driver_subthread](){ return p_pipe_driver_subthread->commandComplete || p_pipe_driver_subthread->dead; }
                            )
                        )
                        {
                            if (!p_pipe_driver_subthread->commandSuccess)
                            {
                                std::ostringstream o;
                                o << "\"get config\" to thread for subsystem serial " << pRAIDsubsystem->serial_number
                                  << " managed on host " << p_pipe_driver_subthread->ivyscript_hostname << " failed.  Aborting." << std::endl;
                                log(masterlogfile,o.str());
                                kill_subthreads_and_exit();
                            }

                            ivytime config_gather_complete;
                            config_gather_complete.setToNow();

                            pRAIDsubsystem->config_gather_time  = config_gather_complete - config_gather_start;

                            {
                                std::ostringstream o;
                                o << "\"get config\" reported complete with duration " << pRAIDsubsystem->config_gather_time.format_as_duration_HMMSSns()
                                  << " by thread for subsystem serial " << pRAIDsubsystem->serial_number << " managed on host " << p_pipe_driver_subthread->ivyscript_hostname << '.' << std::endl;
                                log(masterlogfile,o.str());
                            }
                        }
                        else // timeout
                        {
                            std::ostringstream o;
                            o << "Aborting - timeout waiting " << get_config_timeout_seconds
                              << " seconds for pipe_driver_subthread for subsystem " << pRAIDsubsystem->serial_number
                              << " managed on host " << p_pipe_driver_subthread->ivyscript_hostname
                              << " to complete a \"get config\"." <<  std::endl;
                            std::cout << o.str();
                            log(masterlogfile,o.str());
                            u_lk.unlock();
                            kill_subthreads_and_exit();
                        }
                    }

                    // post process after gather to augment available test LUN attributes with LDEV attributes from the config gather

                    for (auto pLUN : availableTestLUNs.LUNpointers)
                    {
                        if ( 0 == std::string("Hitachi RAID").compare(pSubsystem->type())  &&  0 == pSubsystem->serial_number.compare(pLUN->attribute_value("serial_number")))
                        {

                            // for each test LUN, if we are processing a Hitachi RAID subsystem with a given serial number and the test LUN serial_number matches the subsystem
                            // 1) if CLPR is non-empty add the <CLPR,serial_number> pair to cooldown_WP_watch_set
                            // 2) copy the LDEV's RMLIB attributes to the test LUN.
                            // put a suffix "_RMLIB" if the test LUN already has the attribute name (from the SCSI Inquiry LUN lister tool" showluns.sh").

                            if (0 < pLUN->attribute_value("CLPR").length())
                            {
                                cooldown_WP_watch_set.insert(std::make_pair(pLUN->attribute_value("CLPR"),pSubsystem));
                            }


                            // Copy the LDEV attributes obtained from a command device to the corresponding available test LUN,
                            // appending "_RMLIB" upon attribute name collisions.

                            // NOTE - in the LUN object all map keys are first translated to lower case upon insertion.
                            //        It is advised to use the LUN's own functions to do lookups, attribute value matching, etc.
                            //        as then you won't need to worry about case sensitivity of attribute names.

                            // NOTE2 - The data returned by ivy_cmddev for a "get config" command has attribute names like LDEV
                            //         in upper case and the LDEV attribute values like 00:FF are provided in upper case.


                            if (!pLUN->contains_attribute_name(std::string("ldev")))
                            {
                                std::ostringstream o;
                                o << "Aborting - available test LUN matched on Hitachi RAID subsystem serial number " << pSubsystem->serial_number
                                  << ", but the LUN did have the \"ldev\" attribute." <<  std::endl
                                  << "  LUN =" << pLUN->toString() <<  std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                kill_subthreads_and_exit();
                            }

                            std::string LUN_ldev = pLUN->attribute_value(std::string("ldev"));

                            if (0==LUN_ldev.size())
                            {
                                std::ostringstream o;
                                o << "Aborting - available test LUN matched on Hitachi RAID subsystem serial number " << pSubsystem->serial_number
                                  << ", but the LUN's \"ldev\" attribute value was the null string." <<  std::endl
                                  << "  LUN =" << pLUN->toString() <<  std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                kill_subthreads_and_exit();
                            }

                            auto subsystemLDEVit = pRAIDsubsystem->configGatherData.data.find(std::string("LDEV"));
                            if (pRAIDsubsystem->configGatherData.data.end() == subsystemLDEVit)
                            {
                                std::ostringstream o;
                                o << "Aborting - available test LUN matched on Hitachi RAID subsystem serial number " << pSubsystem->serial_number
                                  << ", but the subsystem configuration data from a command device didn\'t have \"LDEV\" element type." <<  std::endl
                                  << "  LUN =" << pLUN->toString() <<  std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                kill_subthreads_and_exit();
                            }

                            std::string cmddev_LDEV = toUpper(LUN_ldev);

                            auto subsystemLDEVinstanceIt = (*subsystemLDEVit).second.find(cmddev_LDEV);
                            if ((*subsystemLDEVit).second.end() == subsystemLDEVinstanceIt)
                            {
                                std::ostringstream o;
                                o << "Aborting - available test LUN matched on Hitachi RAID subsystem serial number " << pSubsystem->serial_number
                                  << ", but the subsystem didn\'t have the \"LDEV\" instance \"" << cmddev_LDEV << "\"." <<  std::endl
                                  << "  LUN =" << pLUN->toString() <<  std::endl;
                                std::cout << o.str();
                                log(masterlogfile,o.str());
                                kill_subthreads_and_exit();
                            }

                            for (auto& pear : (*subsystemLDEVinstanceIt).second)
                            {

                                // To reduce clutter, upon an attribute name collision, meaning that both the SCSI Inquiry-based LUN Lister tool like showluns.sh
                                // and the RMLIB API-based command device data returned an attribute name that when translated to lower case was the same,
                                // then if the attribute values from either side are the same when translated to lower case and with leading/trailing
                                // blanks removed, we don't alter the existing attribute value, and the RMLIB API value is discarded.

                                // If RMLIB returned a substantially different value for the same metric, then we create a new attribute name
                                // by appending "_rmlib" and store the RMLIB value there.

                                // pear is std::map<std::string,metric_value>

                                std::string attribute_name = toLower(pear.first);
                                std::string attribute_value = pear.second.string_value();

                                std::string trimmed_attribute_value = toLower(attribute_value);
                                trim(trimmed_attribute_value);

                                if (pLUN->contains_attribute_name(attribute_name))
                                {
                                    std::string trimmed_LUN_value = toLower(pLUN->attribute_value(attribute_name));
                                    trim(trimmed_LUN_value);
                                    if (0 != trimmed_attribute_value.compare(trimmed_LUN_value))
                                    {
                                        attribute_name += std::string("_rmlib");
                                        pLUN->set_attribute(attribute_name, attribute_value);
                                    }
                                }
                                else
                                {
                                    pLUN->set_attribute(attribute_name, attribute_value);
                                }

                            }
                        }
                    }

                    // end of augmenting available test LUN attributes with config info from the command device

                    break;  // out of loop over command device LUNs as candidates to be first command device for this subsystem


                } // if we found the first command device for this subsystem


            } // for each command device LUN as candidate to match as first command device for this subsystem.

            // back to Hitachi RAID subsystem type

            if (!have_cmd_dev_this_subsystem)
            {
                std::ostringstream o;
                o << "No command device found for subsystem " << pSubsystem->serial_number << std::endl;
                std::cout << o.str();
                log(masterlogfile,o.str());
            }
            else
            {
                pRAIDsubsystem->configGatherData.print_csv_file_set
                (
                    testFolder,
                    pRAIDsubsystem->serial_number+std::string(".config")
                );
            }

        } // end for Hitachi RAID subsystem

    } // end for each subsystem

    for (auto& pLUN : availableTestLUNs.LUNpointers)
    {
        TheSampleLUN.copyOntoMe(pLUN);  // add nicknames, and command device LDEV attributes to TheSampleLUN.
    }


    for (auto& pLUN : availableTestLUNs.LUNpointers) // populate test config thumbnail data structures
    {
        available_LUNs_thumbnail.add(pLUN);
    }

    {
        std::ostringstream o;
        o << available_LUNs_thumbnail;

        bool saw_command_device {false};

        for (auto& pear : subsystems)
        {
            if (0 == std::string("Hitachi RAID").compare(pear.second->type()))
            {
                Hitachi_RAID_subsystem* pRAID = (Hitachi_RAID_subsystem*) pear.second;
                if (pRAID->command_device_description.size() > 0)
                {
                    saw_command_device = true;
                    o << pRAID->command_device_description;
                }
            }
        }

        if (saw_command_device) o << std::endl;

        log(masterlogfile,o.str());
        std::cout << o.str();

        ofstream ofs(testFolder+std::string("/available_test_config.txt"));
        ofs << o.str();
        ofs.close();

    }

    if (routine_logging)
    {
        ostringstream o;
        o << "After adding subsystem config attributes, availableTestLUNs contains:" << std::endl << availableTestLUNs.toString() << std::endl;
        // std::cout << o.str();
        log(masterlogfile,o.str());
    }
    else
    {
        ostringstream o;
        o <<"availableTestLUNs contains " << availableTestLUNs.LUNpointers.size() << " LUNs." << std::endl;
        //std::cout<<o.str();
        log(masterlogfile,o.str());
    }


    ofstream atlcsv(testFolder+std::string("/available_test_LUNs.csv"));
    availableTestLUNs.print_csv_file(atlcsv);
    atlcsv.close();

    if (cooldown_WP_watch_set.size() == 0)

    {
        std::ostringstream o;
        o << "No command devices for RAID_subsystem LDEVs, so cooldown_by_wp settings will not have any effect." << std::endl;
        log(masterlogfile,o.str());
        std::cout << o.str();
    }
    else
    {
        std::ostringstream o;
        o << "Cooldown_WP_watch_set contains:";
        for (auto& pear : cooldown_WP_watch_set) o << " < " << pear.first << ", " << pear.second->serial_number << " >";
        o << std::endl << std::endl;
        log(masterlogfile,o.str());
        std::cout << o.str();
    }

    haveHosts = true;



    return std::make_pair(true,std::string("hello, whirled!"));
}