3939#include "clusterer.h"
4040#include "sync.h"
4141#include "sharing_tags.h"
42+ #include "clusterer_ctrl.h"
4243#include "clusterer_evi.h"
4344
4445int ping_interval = DEFAULT_PING_INTERVAL ;
@@ -50,6 +51,105 @@ int current_id = -1;
5051int db_mode = 1 ;
5152int clusterer_enable_rerouting = 1 ;
5253
54+ int * _current_id_shm = NULL ;
55+ int use_controller = 0 ;
56+
57+ int cl_ctr_stub_ids [64 ];
58+ int cl_ctr_stub_count = 0 ;
59+
60+ /* extract "<name>=<int>" from a "key=value, key=value" cluster_options string.
61+ * Returns 0 and sets *out on success, 1 if the key is absent, -1 if malformed. */
62+ static int cl_ctr_opt_int (str * descr , str * name , int * out )
63+ {
64+ char * p , * pe ;
65+ str aux ;
66+
67+ p = str_strstr (descr , name );
68+ if (!p )
69+ return 1 ;
70+ p += name -> len ;
71+ p = q_memchr (p , '=' , descr -> s + descr -> len - p );
72+ if (!p ) {
73+ LM_ERR ("clusterer: expected '=' after '%.*s' in cluster_options\n" ,
74+ name -> len , name -> s );
75+ return -1 ;
76+ }
77+ p ++ ;
78+ pe = q_memchr (p , ',' , descr -> s + descr -> len - p );
79+ aux .s = p ;
80+ aux .len = pe ? pe - p : descr -> s + descr -> len - p ;
81+ str_trim_spaces_lr (aux );
82+ if (aux .len == 0 || str2int (& aux , (unsigned int * )out )) {
83+ LM_ERR ("clusterer: bad value for '%.*s' in cluster_options\n" ,
84+ name -> len , name -> s );
85+ return -1 ;
86+ }
87+ return 0 ;
88+ }
89+
90+ /* "cluster_id=N, use_controller=0|1" (default 0 = native); only use_controller=1
91+ * registers the cluster, whose identity and peers the controller then drives */
92+ static int cl_ctr_add_cluster_options (modparam_t type , void * val )
93+ {
94+ static str cid_prop = str_init ("cluster_id" );
95+ static str uc_prop = str_init ("use_controller" );
96+ str descr ;
97+ int cid = -1 , uc = 0 , rc , i ;
98+
99+ if (!val || !* (char * )val ) {
100+ LM_ERR ("clusterer: empty 'cluster_options'\n" );
101+ return -1 ;
102+ }
103+ descr .s = (char * )val ;
104+ descr .len = strlen (descr .s );
105+
106+ rc = cl_ctr_opt_int (& descr , & cid_prop , & cid );
107+ if (rc < 0 )
108+ return -1 ;
109+ if (rc == 1 || cid < 1 ) {
110+ LM_ERR ("clusterer: 'cluster_options' requires cluster_id >= 1, "
111+ "e.g. \"cluster_id=1, use_controller=1\"\n" );
112+ return -1 ;
113+ }
114+
115+ rc = cl_ctr_opt_int (& descr , & uc_prop , & uc ); /* absent (rc==1) -> uc stays 0 */
116+ if (rc < 0 )
117+ return -1 ;
118+ if (uc != 0 && uc != 1 ) {
119+ LM_ERR ("clusterer: use_controller in 'cluster_options' must be 0 or 1 "
120+ "(cluster_id %d)\n" , cid );
121+ return -1 ;
122+ }
123+
124+ if (uc == 0 )
125+ return 0 ; /* native (the default): nothing to register */
126+
127+ for (i = 0 ; i < cl_ctr_stub_count ; i ++ )
128+ if (cl_ctr_stub_ids [i ] == cid ) {
129+ LM_ERR ("clusterer: cluster_options declares cluster_id %d as "
130+ "controller-managed more than once\n" , cid );
131+ return -1 ;
132+ }
133+ if (cl_ctr_stub_count >= (int )(sizeof cl_ctr_stub_ids / sizeof cl_ctr_stub_ids [0 ])) {
134+ LM_ERR ("clusterer: too many controller-managed clusters\n" );
135+ return -1 ;
136+ }
137+ cl_ctr_stub_ids [cl_ctr_stub_count ++ ] = cid ;
138+ use_controller = 1 ;
139+ return 0 ;
140+ }
141+
142+ /* retired interim modparams (never released): kept only to fail with a
143+ * migration hint instead of "unknown parameter" */
144+ static int cl_ctr_retired_param (modparam_t type , void * val )
145+ {
146+ LM_ERR ("clusterer: the 'use_controller'/'cluster_id' modparams have been "
147+ "replaced; declare each controller-managed cluster with "
148+ "modparam(\"clusterer\", \"cluster_options\", "
149+ "\"cluster_id=N, use_controller=1\")\n" );
150+ return -1 ;
151+ }
152+
53153str clusterer_db_url = {NULL , 0 };
54154
55155extern db_con_t * db_hdl ;
@@ -106,6 +206,7 @@ int cmd_check_addr(struct sip_msg *msg, int *cluster_id, str *ip_str,
106206 */
107207
108208static const cmd_export_t cmds [] = {
209+ {"load_clusterer_ctrl_binds" , (cmd_function )load_clusterer_ctrl_binds , {{0 ,0 ,0 }}, 0 },
109210 {"load_clusterer" , (cmd_function )load_clusterer , {{0 ,0 ,0 }}, 0 },
110211 {"cluster_broadcast_req" , (cmd_function )cmd_broadcast_req , {
111212 {CMD_PARAM_INT ,0 ,0 },
@@ -158,6 +259,9 @@ static const param_export_t params[] = {
158259 {"flags_col" , STR_PARAM , & flags_col .s },
159260 {"description_col" , STR_PARAM , & description_col .s },
160261 {"db_mode" , INT_PARAM , & db_mode },
262+ {"cluster_options" , STR_PARAM |USE_FUNC_PARAM , (void * )cl_ctr_add_cluster_options },
263+ {"use_controller" , INT_PARAM |USE_FUNC_PARAM , (void * )cl_ctr_retired_param },
264+ {"cluster_id" , INT_PARAM |USE_FUNC_PARAM , (void * )cl_ctr_retired_param },
161265 {"neighbor_node_info" , STR_PARAM |USE_FUNC_PARAM ,
162266 (void * )& provision_neighbor },
163267 {"my_node_info" , STR_PARAM |USE_FUNC_PARAM ,
@@ -389,11 +493,18 @@ static int mod_init(void)
389493 flags_col .len = strlen (flags_col .s );
390494 description_col .len = strlen (description_col .s );
391495
496+ /* no DB URL means no DB-native clusters: db_mode 0, so controller-only and
497+ * static (my_node_info) setups need no explicit db_mode=0 */
498+ if (!clusterer_db_url .s && !db_default_url )
499+ db_mode = 0 ;
500+
392501 /* only allow the DB URL to be skipped in "P2P discovery" mode */
393502 init_db_url (clusterer_db_url , db_mode == 0 );
394503
395- if (current_id < 1 ) {
396- LM_CRIT ("Invalid 'my_node_id' parameter\n" );
504+ /* controller clusters get their id at runtime: my_node_id is required only
505+ * for native (DB or static) clusters */
506+ if (current_id < 1 && (db_mode != 0 || (cluster_list && * cluster_list != NULL ))) {
507+ LM_CRIT ("Invalid 'my_node_id' parameter (required for DB or static native clusters)\n" );
397508 return -1 ;
398509 }
399510 if (ping_interval <= 0 ) {
@@ -421,6 +532,17 @@ static int mod_init(void)
421532 LM_CRIT ("Failed to init lock\n" );
422533 return -1 ;
423534 }
535+ {
536+ int * _shm_id = shm_malloc (sizeof (int ));
537+ if (!_shm_id ) {
538+ LM_CRIT ("No shm memory for GET_CURRENT_ID\n" );
539+ return -1 ;
540+ }
541+ /* my_node_id for native clusters; -1 (matches no node) only on a
542+ * controller-only node, whose id comes at runtime */
543+ * _shm_id = (current_id >= 1 ) ? current_id : -1 ;
544+ _current_id_shm = _shm_id ;
545+ }
424546
425547 /* if statistics are disabled, prevent their registration to core */
426548 if (clusterer_enable_stats == 0 )
@@ -445,12 +567,14 @@ static int mod_init(void)
445567
446568 if (cl -> current_node -> node_id != current_id ) {
447569 LM_ERR ("Bad 'my_node_id' parameter, value: %d different than"
448- " the node_id property in the 'my_node_info' parameter\n" , current_id );
570+ " the node_id property in the 'my_node_info' parameter\n" , GET_CURRENT_ID );
449571 goto error ;
450572 }
451573 }
452574 }
453575
576+ /* DB-native clusters load even with the controller active (hybrid); the DB
577+ * never holds controller clusters */
454578 if (db_mode ) {
455579 /* bind to the mysql module */
456580 if (db_bind_mod (& clusterer_db_url , & dr_dbf )) {
@@ -471,11 +595,51 @@ static int mod_init(void)
471595 LM_ERR ("Failed to load info from DB\n" );
472596 goto error ;
473597 }
474-
475598 dr_dbf .close (db_hdl );
476599 db_hdl = NULL ;
477600 }
478601
602+ /* a stub per controller-managed cluster_id, so tm/dialog cl_register_cap()
603+ * works before the controller sets identity; a native overlap fails here */
604+ if (use_controller ) {
605+ int _ci ;
606+ for (_ci = 0 ; _ci < cl_ctr_stub_count ; _ci ++ ) {
607+ cluster_info_t * _ex ;
608+ for (_ex = * cluster_list ; _ex ; _ex = _ex -> next )
609+ if (_ex -> cluster_id == cl_ctr_stub_ids [_ci ]) {
610+ LM_ERR ("clusterer: cluster_id %d is registered as "
611+ "controller-managed (cluster_options use_controller=1) but is "
612+ "already defined via native config "
613+ "(my_node_info/neighbor_node_info/DB) or listed "
614+ "twice - a cluster_id must be unique and either "
615+ "controller-managed or native, not both\n" ,
616+ cl_ctr_stub_ids [_ci ]);
617+ return -1 ;
618+ }
619+
620+ cluster_info_t * _cl = shm_malloc (sizeof * _cl );
621+ if (!_cl ) {
622+ LM_ERR ("clusterer: no shm for cluster %d stub\n" ,
623+ cl_ctr_stub_ids [_ci ]);
624+ return -1 ;
625+ }
626+ memset (_cl , 0 , sizeof * _cl );
627+ _cl -> cluster_id = cl_ctr_stub_ids [_ci ];
628+ _cl -> controller_managed = 1 ;
629+ _cl -> lock = lock_alloc ();
630+ if (!_cl -> lock || lock_init (_cl -> lock ) == NULL ) {
631+ LM_ERR ("clusterer: lock_alloc failed for cluster %d\n" ,
632+ cl_ctr_stub_ids [_ci ]);
633+ shm_free (_cl );
634+ return -1 ;
635+ }
636+ _cl -> next = * cluster_list ;
637+ * cluster_list = _cl ;
638+ LM_INFO ("clusterer: pre-created controller-managed cluster %d stub\n" ,
639+ cl_ctr_stub_ids [_ci ]);
640+ }
641+ }
642+
479643 /* register timer */
480644 heartbeats_timer_interval = gcd (ping_interval * 1000 , ping_timeout );
481645 heartbeats_timer_interval = gcd (heartbeats_timer_interval , node_timeout * 1000 );
@@ -536,7 +700,8 @@ static int mod_init(void)
536700 /* check if the cluster IDs in the the sharing tag list are valid */
537701 shtag_init_list ();
538702 shtag_init_reporting ();
539- shtag_validate_list ();
703+ if (!use_controller )
704+ shtag_validate_list ();
540705
541706 return 0 ;
542707error :
@@ -559,6 +724,16 @@ static int child_init(int rank)
559724 return -1 ;
560725 }
561726 }
727+
728+ /* rank 1 runs once, after every mod_init: a clusterer_controller would
729+ * have bound the controller API by now */
730+ if (rank == 1 && use_controller && !clusterer_ctrl_bound )
731+ LM_ERR ("clusterer: cluster_options declares controller-managed cluster(s) "
732+ "but no clusterer_controller module registered the controller API - "
733+ "they will never obtain a node identity or form. Load the "
734+ "clusterer_controller module, or drop use_controller from "
735+ "cluster_options.\n" );
736+
562737 return 0 ;
563738}
564739
@@ -893,7 +1068,7 @@ static mi_response_t *clusterer_list_topology(const mi_params_t *params,
8931068 if (!node_item )
8941069 goto error ;
8951070
896- if (add_mi_number (node_item , MI_SSTR ("node_id" ), current_id ) < 0 )
1071+ if (add_mi_number (node_item , MI_SSTR ("node_id" ), GET_CURRENT_ID ) < 0 )
8971072 goto error ;
8981073
8991074 neigh_arr = add_mi_array (node_item , MI_SSTR ("Neighbours" ));
@@ -925,7 +1100,7 @@ static mi_response_t *clusterer_list_topology(const mi_params_t *params,
9251100 }
9261101
9271102 if (n_info -> link_state == LS_UP )
928- if (add_mi_number (neigh_arr , 0 ,0 , current_id ) < 0 ) {
1103+ if (add_mi_number (neigh_arr , 0 ,0 , GET_CURRENT_ID ) < 0 ) {
9291104 lock_release (n_info -> lock );
9301105 goto error ;
9311106 }
@@ -1054,7 +1229,7 @@ static mi_response_t *cluster_send_mi(const mi_params_t *params,
10541229 return init_mi_param_error ();
10551230 if (node_id < 1 )
10561231 return init_mi_error (400 , MI_SSTR ("Bad value for 'destination'" ));
1057- if (node_id == current_id )
1232+ if (node_id == GET_CURRENT_ID )
10581233 return init_mi_error (400 , MI_SSTR ("Local node specified as destination" ));
10591234
10601235 if (get_mi_string_param (params , "cmd_name" , & cmd_name .s , & cmd_name .len ) < 0 )
@@ -1250,7 +1425,7 @@ static inline void generate_msg_tag(pv_value_t *tag_val, int cluster_id)
12501425 memcpy (tag_val -> rs .s , tmp , len );
12511426 tag_val -> rs .s [len ] = '-' ;
12521427 tag_val -> rs .len = len + 1 ;
1253- tmp = int2str (current_id , & len );
1428+ tmp = int2str (GET_CURRENT_ID , & len );
12541429 memcpy (tag_val -> rs .s + tag_val -> rs .len , tmp , len );
12551430 tag_val -> rs .s [tag_val -> rs .len + len ] = '-' ;
12561431 tag_val -> rs .len += len + 1 ;
0 commit comments