From b0caada215c3ca0bcdd40e5b9735e9f26f454b83 Mon Sep 17 00:00:00 2001 From: Botond Baranyi Date: Wed, 29 Jun 2016 14:18:33 +0200 Subject: [PATCH] implemented the Main Controller's 'reconf' command (artf468488) Change-Id: Ie84819f7e01d73b0735aaab24207117e4adef28b Signed-off-by: Botond Baranyi --- core/Communication.cc | 23 ++++++-- core/Communication.hh | 2 +- core/Message_types.hh | 12 +++- core/Runtime.hh | 8 +-- mctr2/cli/Cli.cc | 45 +++++++++++--- mctr2/cli/Cli.h | 1 + mctr2/mctr/MainController.cc | 110 +++++++++++++++++++++++++++++++---- mctr2/mctr/MainController.h | 9 ++- usrguide/userguide.doc | Bin 968192 -> 966656 bytes 9 files changed, 174 insertions(+), 36 deletions(-) diff --git a/core/Communication.cc b/core/Communication.cc index 44352b9..6c4df1c 100644 --- a/core/Communication.cc +++ b/core/Communication.cc @@ -572,7 +572,7 @@ void TTCN_Communication::process_all_messages_hc() process_error(); break; case MSG_CONFIGURE: - process_configure(msg_end); + process_configure(msg_end, false); break; case MSG_CREATE_MTC: process_create_mtc(); @@ -704,6 +704,9 @@ void TTCN_Communication::process_all_messages_tc() case MSG_EXIT_MTC: process_exit_mtc(); break; + case MSG_CONFIGURE: + process_configure(msg_end, true); + break; default: process_unsupported_message(msg_type, msg_end); } @@ -1263,20 +1266,28 @@ void TTCN_Communication::send_message(Text_Buf& text_buf) } } -void TTCN_Communication::process_configure(int msg_end) +void TTCN_Communication::process_configure(int msg_end, bool to_mtc) { switch (TTCN_Runtime::get_state()) { case TTCN_Runtime::HC_IDLE: case TTCN_Runtime::HC_ACTIVE: case TTCN_Runtime::HC_OVERLOADED: - break; + if (!to_mtc) { + break; + } + // no break + case TTCN_Runtime::MTC_IDLE: + if (to_mtc) { + break; + } + // no break default: incoming_buf.cut_message(); send_error("Message CONFIGURE arrived in invalid state."); return; } - TTCN_Runtime::set_state(TTCN_Runtime::HC_CONFIGURING); + TTCN_Runtime::set_state(to_mtc ? TTCN_Runtime::MTC_CONFIGURING : TTCN_Runtime::HC_CONFIGURING); TTCN_Logger::log_configdata(TitanLoggerApiSimple::ExecutorConfigdata_reason::received__from__mc); // take the config string directly from the buffer for efficiency reasons @@ -1311,12 +1322,12 @@ void TTCN_Communication::process_configure(int msg_end) if (success) { send_configure_ack(); - TTCN_Runtime::set_state(TTCN_Runtime::HC_ACTIVE); + TTCN_Runtime::set_state(to_mtc ? TTCN_Runtime::MTC_IDLE : TTCN_Runtime::HC_ACTIVE); TTCN_Logger::log_configdata( TitanLoggerApiSimple::ExecutorConfigdata_reason::processing__succeeded); } else { send_configure_nak(); - TTCN_Runtime::set_state(TTCN_Runtime::HC_IDLE); + TTCN_Runtime::set_state(to_mtc ? TTCN_Runtime::MTC_IDLE : TTCN_Runtime::HC_IDLE); } incoming_buf.cut_message(); diff --git a/core/Communication.hh b/core/Communication.hh index 3cf058e..1cf41e2 100644 --- a/core/Communication.hh +++ b/core/Communication.hh @@ -182,7 +182,7 @@ private: /** @name Handlers of various messages * @{ */ - static void process_configure(int msg_end); + static void process_configure(int msg_end, bool to_mtc); static void process_create_mtc(); static void process_create_ptc(); static void process_kill_process(); diff --git a/core/Message_types.hh b/core/Message_types.hh index b8873d6..cb71898 100644 --- a/core/Message_types.hh +++ b/core/Message_types.hh @@ -34,7 +34,6 @@ /* Messages from MC to HC (down) */ -#define MSG_CONFIGURE 1 #define MSG_CREATE_MTC 2 #define MSG_CREATE_PTC 3 #define MSG_KILL_PROCESS 4 @@ -42,8 +41,6 @@ /* Messages from HC to MC (up) */ -#define MSG_CONFIGURE_ACK 2 -#define MSG_CONFIGURE_NAK 3 #define MSG_CREATE_NAK 4 #define MSG_HC_READY 5 @@ -128,4 +125,13 @@ #define MSG_DEBUG_RETURN_VALUE 100 +/* Messages from MC to HC or MTC (down) */ + +#define MSG_CONFIGURE 200 + +/* Messages from HC or MTC to MC (up) */ + +#define MSG_CONFIGURE_ACK 200 +#define MSG_CONFIGURE_NAK 201 + #endif diff --git a/core/Runtime.hh b/core/Runtime.hh index 282a531..10e67fc 100644 --- a/core/Runtime.hh +++ b/core/Runtime.hh @@ -50,11 +50,11 @@ public: MTC_TERMINATING_TESTCASE, MTC_TERMINATING_EXECUTION, MTC_PAUSED, // 14-16 MTC_CREATE, MTC_START, MTC_STOP, MTC_KILL, MTC_RUNNING, MTC_ALIVE, // 17-22 MTC_DONE, MTC_KILLED, MTC_CONNECT, MTC_DISCONNECT, MTC_MAP, MTC_UNMAP, // 23-28 - MTC_EXIT, // 29 + MTC_CONFIGURING, MTC_EXIT, // 30 - PTC_INITIAL, PTC_IDLE, PTC_FUNCTION, PTC_CREATE, PTC_START, PTC_STOP, // 30-35 - PTC_KILL, PTC_RUNNING, PTC_ALIVE, PTC_DONE, PTC_KILLED, PTC_CONNECT, // 36-41 - PTC_DISCONNECT, PTC_MAP, PTC_UNMAP, PTC_STOPPED, PTC_EXIT // 42-46 + PTC_INITIAL, PTC_IDLE, PTC_FUNCTION, PTC_CREATE, PTC_START, PTC_STOP, // 31-36 + PTC_KILL, PTC_RUNNING, PTC_ALIVE, PTC_DONE, PTC_KILLED, PTC_CONNECT, // 37-42 + PTC_DISCONNECT, PTC_MAP, PTC_UNMAP, PTC_STOPPED, PTC_EXIT // 43-47 }; private: static executor_state_enum executor_state; diff --git a/mctr2/cli/Cli.cc b/mctr2/cli/Cli.cc index b4818ac..6a4886a 100644 --- a/mctr2/cli/Cli.cc +++ b/mctr2/cli/Cli.cc @@ -195,12 +195,14 @@ Cli::Cli() perror("Cli::Cli: pthread_cond_init failed."); exit(EXIT_FAILURE); } + cfg_file_name = NULL; } Cli::~Cli() { pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); + Free(cfg_file_name); } //---------------------------------------------------------------------------- @@ -217,8 +219,9 @@ int Cli::enterLoop(int argc, char *argv[]) printWelcome(); if (argc == 2) { - printf("Using configuration file: %s\n", argv[1]); - if (process_config_read_file(argv[1], &mycfg)) { + cfg_file_name = mcopystr(argv[1]); + printf("Using configuration file: %s\n", cfg_file_name); + if (process_config_read_file(cfg_file_name, &mycfg)) { puts("Error was found in the configuration file. Exiting."); cleanUp(); return EXIT_FAILURE; @@ -728,12 +731,37 @@ void Cli::infoCallback(const char *arguments) void Cli::reconfCallback(const char *arguments) { - if(*arguments == 0) { // reconf called without its optional argument - puts("Reconfiguration of MC and HCs using original configuration " - "data\n -- not supported, yet."); - } else { // reconf called with config_file argument - puts("Reconfiguration of MC and HCs using configuration file" - "specified in\ncommand line argument -- not supported, yet."); + if (!MainController::start_reconfiguring()) { + return; + } + if (*arguments != 0) { + Free(cfg_file_name); + cfg_file_name = mcopystr(arguments); + } + + printf("Using configuration file: %s\n", cfg_file_name); + if (process_config_read_file(cfg_file_name, &mycfg)) { + puts("Error was found in the configuration file. Exiting."); + cleanUp(); + puts("exit"); + exitCallback(""); + } + else { + MainController::set_kill_timer(mycfg.kill_timer); + + for (int i = 0; i < mycfg.group_list_len; ++i) { + MainController::add_host(mycfg.group_list[i].group_name, + mycfg.group_list[i].host_name); + } + + for (int i = 0; i < mycfg.component_list_len; ++i) { + MainController::assign_component(mycfg.component_list[i].host_or_group, + mycfg.component_list[i].component); + } + + if (MainController::get_state() == mctr::MC_RECONFIGURING) { + MainController::configure(mycfg.config_read_buffer); + } } } @@ -800,6 +828,7 @@ void Cli::exitCallback(const char *arguments) if (*arguments == 0) { switch (MainController::get_state()) { case mctr::MC_READY: + case mctr::MC_RECONFIGURING: MainController::exit_mtc(); waitMCState(WAIT_MTC_TERMINATED); case mctr::MC_LISTENING: diff --git a/mctr2/cli/Cli.h b/mctr2/cli/Cli.h index 190de8f..5485f0e 100644 --- a/mctr2/cli/Cli.h +++ b/mctr2/cli/Cli.h @@ -191,6 +191,7 @@ private: */ int executeListIndex; + char* cfg_file_name; config_data mycfg; }; diff --git a/mctr2/mctr/MainController.cc b/mctr2/mctr/MainController.cc index 0af6531..8ff3258 100644 --- a/mctr2/mctr/MainController.cc +++ b/mctr2/mctr/MainController.cc @@ -688,20 +688,37 @@ void MainController::configure_host(host_struct *host, boolean should_notify) host->hostname); } send_configure(host, config_str); - send_debug_setup(host); + if (mc_state != MC_RECONFIGURING) { + send_debug_setup(host); + } + } +} + +void MainController::configure_mtc() +{ + if (config_str == NULL) { + fatal_error("MainController::configure_mtc: no config file"); + } + if (mtc->tc_state != TC_IDLE) { + error("MainController::configure_mtc(): MTC is in wrong state."); + } + else { + mtc->tc_state = MTC_CONFIGURING; + send_configure_mtc(config_str); } } void MainController::check_all_hc_configured() { + bool reconf = mc_state == MC_RECONFIGURING; if (is_hc_in_state(HC_CONFIGURING) || is_hc_in_state(HC_CONFIGURING_OVERLOADED)) return; if (is_hc_in_state(HC_IDLE)) { error("There were errors during configuring HCs."); - mc_state = MC_HC_CONNECTED; + mc_state = reconf ? MC_READY : MC_HC_CONNECTED; } else if (is_hc_in_state(HC_ACTIVE) || is_hc_in_state(HC_OVERLOADED)) { notify("Configuration file was processed on all HCs."); - mc_state = MC_ACTIVE; + mc_state = reconf ? MC_READY : MC_ACTIVE; } else { error("There is no HC connection after processing the configuration " "file."); @@ -2782,6 +2799,7 @@ void MainController::handle_hc_data(host_struct *hc, boolean recv_from_socket) if (all_hc_in_state(HC_DOWN)) mc_state = MC_LISTENING; break; case MC_CONFIGURING: + case MC_RECONFIGURING: check_all_hc_configured(); break; case MC_ACTIVE: @@ -2901,6 +2919,12 @@ void MainController::handle_tc_data(component_struct *tc, case MSG_MTC_READY: process_mtc_ready(); break; + case MSG_CONFIGURE_ACK: + process_configure_ack_mtc(); + break; + case MSG_CONFIGURE_NAK: + process_configure_nak_mtc(); + break; default: error("Invalid message type (%d) was received " "from the MTC at %s [%s].", message_type, @@ -3538,6 +3562,15 @@ void MainController::send_exit_mtc() send_message(mtc->tc_fd, text_buf); } +void MainController::send_configure_mtc(const char* config_file) +{ + Text_Buf text_buf; + text_buf.push_int(MSG_CONFIGURE); + text_buf.push_string(config_file); + send_message(mtc->tc_fd, text_buf); +} + + void MainController::send_cancel_done_ptc(component_struct *tc, component component_reference) { @@ -3840,7 +3873,8 @@ void MainController::process_configure_ack(host_struct *hc) "received."); return; } - if (mc_state == MC_CONFIGURING) check_all_hc_configured(); + if (mc_state == MC_CONFIGURING || mc_state == MC_RECONFIGURING) + check_all_hc_configured(); else notify("Host %s was configured successfully.", hc->hostname); status_change(); } @@ -3857,7 +3891,8 @@ void MainController::process_configure_nak(host_struct *hc) "received."); return; } - if (mc_state == MC_CONFIGURING) check_all_hc_configured(); + if (mc_state == MC_CONFIGURING || mc_state == MC_RECONFIGURING) + check_all_hc_configured(); else notify("Processing of configuration file failed on host %s.", hc->hostname); status_change(); @@ -5766,6 +5801,26 @@ void MainController::process_mtc_ready() status_change(); } +void MainController::process_configure_ack_mtc() +{ + if (mtc->tc_state != MTC_CONFIGURING) { + send_error_str(mtc->tc_fd, "Unexpected message CONFIGURE_ACK was received."); + return; + } + mtc->tc_state = TC_IDLE; + notify("Configuration file was processed on the MTC."); +} + +void MainController::process_configure_nak_mtc() +{ + if (mtc->tc_state != MTC_CONFIGURING) { + send_error_str(mtc->tc_fd, "Unexpected message CONFIGURE_NAK was received."); + return; + } + mtc->tc_state = TC_IDLE; + notify("Processing of configuration file failed on the MTC."); +} + void MainController::process_stopped(component_struct *tc, int message_end) { switch (tc->tc_state) { @@ -6049,12 +6104,20 @@ void MainController::destroy_host_groups() void MainController::set_kill_timer(double timer_val) { lock(); - if (mc_state != MC_INACTIVE) + switch (mc_state) { + case MC_INACTIVE: + case MC_LISTENING: + case MC_HC_CONNECTED: + case MC_RECONFIGURING: + if (timer_val < 0.0) + error("MainController::set_kill_timer: setting a negative kill timer " + "value."); + else kill_timer = timer_val; + break; + default: error("MainController::set_kill_timer: called in wrong state."); - else if (timer_val < 0.0) - error("MainController::set_kill_timer: setting a negative kill timer " - "value."); - else kill_timer = timer_val; + break; + } unlock(); } @@ -6309,6 +6372,8 @@ void MainController::configure(const char *config_file) case MC_LISTENING_CONFIGURED: mc_state = MC_LISTENING_CONFIGURED; break; + case MC_RECONFIGURING: + break; default: error("MainController::configure: called in wrong state."); unlock(); @@ -6316,14 +6381,35 @@ void MainController::configure(const char *config_file) } Free(config_str); config_str = mcopystr(config_file); - if(mc_state == MC_CONFIGURING) { + if (mc_state == MC_CONFIGURING || mc_state == MC_RECONFIGURING) { notify("Downloading configuration file to all HCs."); for (int i = 0; i < n_hosts; i++) configure_host(hosts[i], FALSE); } + if (mc_state == MC_RECONFIGURING) { + notify("Downloading configuration file to the MTC."); + configure_mtc(); + } status_change(); unlock(); } +bool MainController::start_reconfiguring() +{ + switch (mc_state) { + case MC_READY: + mc_state = MC_RECONFIGURING; + return true; + case MC_LISTENING: + case MC_HC_CONNECTED: + return true; + default: + lock(); + error("MainController::start_reconfiguring: called in wrong state."); + unlock(); + return false; + } +} + void MainController::create_mtc(int host_index) { lock(); @@ -6415,7 +6501,7 @@ void MainController::create_mtc(int host_index) void MainController::exit_mtc() { lock(); - if (mc_state != MC_READY) { + if (mc_state != MC_READY && mc_state != MC_RECONFIGURING) { error("MainController::exit_mtc: called in wrong state."); unlock(); return; diff --git a/mctr2/mctr/MainController.h b/mctr2/mctr/MainController.h index 561c665..c670f01 100644 --- a/mctr2/mctr/MainController.h +++ b/mctr2/mctr/MainController.h @@ -63,7 +63,7 @@ enum mc_state_enum { MC_INACTIVE, MC_LISTENING, MC_LISTENING_CONFIGURED, MC_HC_CONNECTED, MC_CONFIGURING, MC_ACTIVE, MC_SHUTDOWN, MC_CREATING_MTC, MC_READY, MC_TERMINATING_MTC, MC_EXECUTING_CONTROL, MC_EXECUTING_TESTCASE, - MC_TERMINATING_TESTCASE, MC_PAUSED + MC_TERMINATING_TESTCASE, MC_PAUSED, MC_RECONFIGURING }; /** Data structure for unknown incoming connections (before receiving @@ -164,7 +164,7 @@ enum tc_state_enum { TC_INITIAL, TC_IDLE, TC_CREATE, TC_START, TC_STOP, TC_KILL, MTC_CONTROLPART, MTC_TESTCASE, MTC_ALL_COMPONENT_STOP, MTC_ALL_COMPONENT_KILL, MTC_TERMINATING_TESTCASE, MTC_PAUSED, PTC_FUNCTION, PTC_STARTING, PTC_STOPPED, PTC_KILLING, PTC_STOPPING_KILLING, - PTC_STALE, TC_SYSTEM }; + PTC_STALE, TC_SYSTEM, MTC_CONFIGURING }; /** Data structure for each TC */ struct component_struct { @@ -345,6 +345,7 @@ class MainController { static boolean is_hc_in_state(hc_state_enum checked_state); static boolean all_hc_in_state(hc_state_enum checked_state); static void configure_host(host_struct *host, boolean should_notify); + static void configure_mtc(); static void check_all_hc_configured(); static void add_component_to_host(host_struct *host, component_struct *comp); @@ -544,6 +545,7 @@ private: static void send_ptc_verdict(boolean continue_execution); static void send_continue(); static void send_exit_mtc(); + static void send_configure_mtc(const char *config_file); /** Messages to PTCs */ static void send_cancel_done_ptc(component_struct *tc, @@ -607,6 +609,8 @@ private: static void process_testcase_started(); static void process_testcase_finished(); static void process_mtc_ready(); + static void process_configure_ack_mtc(); + static void process_configure_nak_mtc(); /* Incoming messages from PTCs */ static void process_stopped(component_struct *tc, int message_end); @@ -629,6 +633,7 @@ public: static void shutdown_session(); static void configure(const char *config_file); + static bool start_reconfiguring(); static void create_mtc(int host_index); static void exit_mtc(); diff --git a/usrguide/userguide.doc b/usrguide/userguide.doc index a440e5d62ec66b7e9d4ddd52d11b68c603476180..430e53215fdb5c788ae9dae09b4307ef7ff58387 100644 GIT binary patch delta 16436 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