changeset 19411:0e05efd14b39

director: Fixed backend selection when multiple tags were used. The previous algorithm was causing an uneven load for backends. This change breaks compatibility with older director servers that were using tags because of the different selection algorithm. The new director code refuses to run within a cluster with old directors if tags are used.
author Timo Sirainen <tss@iki.fi>
date Tue, 24 Nov 2015 11:15:47 +0200
parents 5168fdd5127e
children 26c565042d9d
files src/director/director-connection.c src/director/director.c src/director/director.h src/director/mail-host.c src/director/mail-host.h
diffstat 5 files changed, 137 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/src/director/director-connection.c	Mon Nov 23 19:47:08 2015 +0200
+++ b/src/director/director-connection.c	Tue Nov 24 11:15:47 2015 +0200
@@ -893,6 +893,10 @@
 		i_assert(conn->handshake_sending_hosts);
 		return TRUE;
 	}
+	if (tag[0] != '\0' && conn->minor_version < DIRECTOR_VERSION_TAGS_V2) {
+		director_cmd_error(conn, "Received a host tag from older director version with incompatible tagging support");
+		return FALSE;
+	}
 
 	host = mail_host_lookup(conn->dir->mail_hosts, &ip);
 	if (host == NULL) {
@@ -1208,6 +1212,11 @@
 				DIRECTOR_VERSION_MAJOR);
 			return -1;
 		}
+		if (conn->minor_version < DIRECTOR_VERSION_TAGS_V2 &&
+		    mail_hosts_have_tags(conn->dir->mail_hosts)) {
+			i_error("director(%s): Director version supports incompatible tags", conn->name);
+			return FALSE;
+		}
 		conn->version_received = TRUE;
 		if (conn->done_pending) {
 			if (director_connection_send_done(conn) < 0)
--- a/src/director/director.c	Mon Nov 23 19:47:08 2015 +0200
+++ b/src/director/director.c	Tue Nov 24 11:15:47 2015 +0200
@@ -536,13 +536,18 @@
 		    net_ip2addr(&orig_src->ip), orig_src->port,
 		    orig_src->last_seq,
 		    net_ip2addr(&host->ip), host->vhost_count);
-	if (dir->ring_min_version >= DIRECTOR_VERSION_TAGS) {
+	if (dir->ring_min_version >= DIRECTOR_VERSION_TAGS_V2) {
 		str_append_c(str, '\t');
 		str_append_tabescaped(str, host_tag);
 	} else if (host_tag[0] != '\0' &&
-		   dir->ring_min_version < DIRECTOR_VERSION_TAGS) {
-		i_error("Ring has directors that don't support tags - removing host %s with tag '%s'",
-			net_ip2addr(&host->ip), host_tag);
+		   dir->ring_min_version < DIRECTOR_VERSION_TAGS_V2) {
+		if (dir->ring_min_version < DIRECTOR_VERSION_TAGS) {
+			i_error("Ring has directors that don't support tags - removing host %s with tag '%s'",
+				net_ip2addr(&host->ip), host_tag);
+		} else {
+			i_error("Ring has directors that support mixed versions of tags - removing host %s with tag '%s'",
+				net_ip2addr(&host->ip), host_tag);
+		}
 		director_remove_host(dir, NULL, NULL, host);
 		return;
 	}
--- a/src/director/director.h	Mon Nov 23 19:47:08 2015 +0200
+++ b/src/director/director.h	Tue Nov 24 11:15:47 2015 +0200
@@ -6,7 +6,7 @@
 
 #define DIRECTOR_VERSION_NAME "director"
 #define DIRECTOR_VERSION_MAJOR 1
-#define DIRECTOR_VERSION_MINOR 6
+#define DIRECTOR_VERSION_MINOR 7
 
 /* weak users supported in protocol */
 #define DIRECTOR_VERSION_WEAK_USERS 1
@@ -22,6 +22,8 @@
 #define DIRECTOR_VERSION_TAGS 5
 /* up/down state is tracked */
 #define DIRECTOR_VERSION_UPDOWN 6
+/* user tag version 2 supported */
+#define DIRECTOR_VERSION_TAGS_V2 7
 
 /* Minimum time between even attempting to communicate with a director that
    failed due to a protocol error. */
--- a/src/director/mail-host.c	Mon Nov 23 19:47:08 2015 +0200
+++ b/src/director/mail-host.c	Tue Nov 24 11:15:47 2015 +0200
@@ -14,12 +14,19 @@
 	struct mail_host *host;
 };
 
-struct mail_host_list {
-	ARRAY_TYPE(mail_host) hosts;
+struct mail_tag {
+	/* "" = no tag */
+	char *name;
 	ARRAY(struct mail_vhost) vhosts;
+};
+
+struct mail_host_list {
+	ARRAY(struct mail_tag *) tags;
+	ARRAY_TYPE(mail_host) hosts;
 	unsigned int hosts_hash;
+	bool consistent_hashing;
 	bool vhosts_unsorted;
-	bool consistent_hashing;
+	bool have_vhosts;
 };
 
 static int
@@ -51,7 +58,7 @@
 		return 0;
 }
 
-static void mail_vhost_add(struct mail_host_list *list, struct mail_host *host)
+static void mail_vhost_add(struct mail_tag *tag, struct mail_host *host)
 {
 	struct mail_vhost *vhost;
 	struct md5_context md5_ctx, md5_ctx2;
@@ -74,56 +81,65 @@
 		md5_update(&md5_ctx2, num_str, strlen(num_str));
 		md5_final(&md5_ctx2, md5);
 
-		vhost = array_append_space(&list->vhosts);
+		vhost = array_append_space(&tag->vhosts);
 		vhost->host = host;
 		for (j = 0; j < sizeof(vhost->hash); j++)
 			vhost->hash = (vhost->hash << CHAR_BIT) | md5[j];
 	}
 }
 
-static void mail_hosts_sort_ring(struct mail_host_list *list)
+static void
+mail_tag_vhosts_sort_ring(struct mail_host_list *list, struct mail_tag *tag)
 {
 	struct mail_host *const *hostp;
 
 	/* rebuild vhosts */
-	array_clear(&list->vhosts);
+	array_clear(&tag->vhosts);
 	array_foreach(&list->hosts, hostp)
-		mail_vhost_add(list, *hostp);
-	array_sort(&list->vhosts, mail_vhost_cmp);
-	list->vhosts_unsorted = FALSE;
+		mail_vhost_add(tag, *hostp);
+	array_sort(&tag->vhosts, mail_vhost_cmp);
 }
 
-static void mail_hosts_sort_direct(struct mail_host_list *list)
+static void
+mail_tag_vhosts_sort_direct(struct mail_host_list *list, struct mail_tag *tag)
 {
 	struct mail_vhost *vhost;
 	struct mail_host *const *hostp;
 	unsigned int i;
 
 	/* rebuild vhosts */
-	array_clear(&list->vhosts);
+	array_clear(&tag->vhosts);
 	array_foreach(&list->hosts, hostp) {
 		if ((*hostp)->down)
 			continue;
 		for (i = 0; i < (*hostp)->vhost_count; i++) {
-			vhost = array_append_space(&list->vhosts);
+			vhost = array_append_space(&tag->vhosts);
 			vhost->host = *hostp;
 		}
 	}
-	list->vhosts_unsorted = FALSE;
 }
 
-static void mail_hosts_sort(struct mail_host_list *list)
+static void
+mail_hosts_sort(struct mail_host_list *list)
 {
 	struct mail_host *const *hostp;
+	struct mail_tag *const *tagp;
 	uint32_t num;
 
 	array_sort(&list->hosts, mail_host_cmp);
 
-	if (list->consistent_hashing)
-		mail_hosts_sort_ring(list);
-	else
-		mail_hosts_sort_direct(list);
+	list->have_vhosts = FALSE;
+	array_foreach(&list->tags, tagp) {
+		if (list->consistent_hashing)
+			mail_tag_vhosts_sort_ring(list, *tagp);
+		else
+			mail_tag_vhosts_sort_direct(list, *tagp);
+		if (array_count(&(*tagp)->vhosts) > 0)
+			list->have_vhosts = TRUE;
+	}
+	list->vhosts_unsorted = FALSE;
 
+	/* recalculate the hosts_hash */
 	list->hosts_hash = 0;
 	array_foreach(&list->hosts, hostp) {
 		num = ((*hostp)->down ? 1 : 0) ^ (*hostp)->vhost_count;
@@ -135,6 +151,40 @@
 	}
 }
 
+static struct mail_tag *
+mail_tag_find(struct mail_host_list *list, const char *tag_name)
+{
+	struct mail_tag *const *tagp;
+
+	array_foreach(&list->tags, tagp) {
+		if (strcmp((*tagp)->name, tag_name) == 0)
+			return *tagp;
+	}
+	return NULL;
+}
+
+static struct mail_tag *
+mail_tag_get(struct mail_host_list *list, const char *tag_name)
+{
+	struct mail_tag *tag;
+
+	tag = mail_tag_find(list, tag_name);
+	if (tag == NULL) {
+		tag = i_new(struct mail_tag, 1);
+		tag->name = i_strdup(tag_name);
+		i_array_init(&tag->vhosts, 16*VHOST_MULTIPLIER);
+		array_append(&list->tags, &tag, 1);
+	}
+	return tag;
+}
+
+static void mail_tag_free(struct mail_tag *tag)
+{
+	array_free(&tag->vhosts);
+	i_free(tag->name);
+	i_free(tag);
+}
+
 struct mail_host *
 mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip,
 		 const char *tag_name)
@@ -147,7 +197,7 @@
 	host->list = list;
 	host->vhost_count = VHOST_MULTIPLIER;
 	host->ip = *ip;
-	host->tag = i_strdup(tag_name);
+	host->tag = mail_tag_get(list, tag_name);
 	array_append(&list->hosts, &host, 1);
 
 	list->vhosts_unsorted = TRUE;
@@ -302,15 +352,15 @@
 
 const char *mail_host_get_tag(struct mail_host *host)
 {
-	return host->tag;
+	return host->tag->name;
 }
 
 void mail_host_set_tag(struct mail_host *host, const char *tag_name)
 {
 	i_assert(tag_name != NULL);
 
-	i_free(host->tag);
-	host->tag = i_strdup(tag_name);
+	host->tag = mail_tag_get(host->list, tag_name);
+	host->list->vhosts_unsorted = TRUE;
 }
 
 void mail_host_set_down(struct mail_host *host, bool down, time_t timestamp)
@@ -330,7 +380,6 @@
 
 static void mail_host_free(struct mail_host *host)
 {
-	i_free(host->tag);
 	i_free(host->hostname);
 	i_free(host);
 }
@@ -344,11 +393,10 @@
 	hosts = array_get(&list->hosts, &count);
 	for (i = 0; i < count; i++) {
 		if (hosts[i] == host) {
-			array_delete(&list->hosts, i, 1);
+			array_delete(&host->list->hosts, i, 1);
 			break;
 		}
 	}
-
 	mail_host_free(host);
 	list->vhosts_unsorted = TRUE;
 }
@@ -369,15 +417,13 @@
 }
 
 static struct mail_host *
-mail_host_get_by_hash_ring(struct mail_host_list *list, unsigned int hash,
-			   const char *tag_name)
+mail_host_get_by_hash_ring(struct mail_tag *tag, unsigned int hash)
 {
-	struct mail_host *host;
 	const struct mail_vhost *vhosts;
-	unsigned int i, count, idx;
+	unsigned int count, idx;
 
-	vhosts = array_get(&list->vhosts, &count);
-	array_bsearch_insert_pos(&list->vhosts, &hash,
+	vhosts = array_get(&tag->vhosts, &count);
+	array_bsearch_insert_pos(&tag->vhosts, &hash,
 				 mail_vhost_hash_cmp, &idx);
 	i_assert(idx <= count);
 	if (idx == count) {
@@ -385,46 +431,38 @@
 			return NULL;
 		idx = 0;
 	}
-
-	for (i = 0; i < count; i++) {
-		host = vhosts[(idx + i) % count].host;
-		if (strcmp(host->tag, tag_name) == 0)
-			return host;
-	}
-	return NULL;
+	return vhosts[idx % count].host;
 }
 
 static struct mail_host *
-mail_host_get_by_hash_direct(struct mail_host_list *list, unsigned int hash,
-			     const char *tag_name)
+mail_host_get_by_hash_direct(struct mail_tag *tag, unsigned int hash)
 {
-	struct mail_host *host;
 	const struct mail_vhost *vhosts;
-	unsigned int i, count;
+	unsigned int count;
 
-	vhosts = array_get(&list->vhosts, &count);
+	vhosts = array_get(&tag->vhosts, &count);
 	if (count == 0)
 		return NULL;
-
-	for (i = 0; i < count; i++) {
-		host = vhosts[(hash + i) % count].host;
-		if (strcmp(host->tag, tag_name) == 0)
-			return host;
-	}
-	return NULL;
+	return vhosts[hash % count].host;
 }
 
 struct mail_host *
 mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash,
 		      const char *tag_name)
 {
+	struct mail_tag *tag;
+
 	if (list->vhosts_unsorted)
 		mail_hosts_sort(list);
 
+	tag = mail_tag_find(list, tag_name);
+	if (tag == NULL)
+		return NULL;
+
 	if (list->consistent_hashing)
-		return mail_host_get_by_hash_ring(list, hash, tag_name);
+		return mail_host_get_by_hash_ring(tag, hash);
 	else
-		return mail_host_get_by_hash_direct(list, hash, tag_name);
+		return mail_host_get_by_hash_direct(tag, hash);
 }
 
 void mail_hosts_set_synced(struct mail_host_list *list)
@@ -448,7 +486,7 @@
 {
 	if (list->vhosts_unsorted)
 		mail_hosts_sort(list);
-	return array_count(&list->vhosts) > 0;
+	return list->have_vhosts;
 }
 
 const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list)
@@ -458,6 +496,20 @@
 	return &list->hosts;
 }
 
+bool mail_hosts_have_tags(struct mail_host_list *list)
+{
+	struct mail_tag *const *tagp;
+
+	if (list->vhosts_unsorted)
+		mail_hosts_sort(list);
+
+	array_foreach(&list->tags, tagp) {
+		if ((*tagp)->name[0] != '\0' && array_count(&(*tagp)->vhosts) > 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
 struct mail_host_list *mail_hosts_init(bool consistent_hashing)
 {
 	struct mail_host_list *list;
@@ -465,21 +517,24 @@
 	list = i_new(struct mail_host_list, 1);
 	list->consistent_hashing = consistent_hashing;
 	i_array_init(&list->hosts, 16);
-	i_array_init(&list->vhosts, 16*VHOST_MULTIPLIER);
+	i_array_init(&list->tags, 4);
 	return list;
 }
 
 void mail_hosts_deinit(struct mail_host_list **_list)
 {
 	struct mail_host_list *list = *_list;
-	struct mail_host **hostp;
+	struct mail_host *const *hostp;
+	struct mail_tag *const *tagp;
 
 	*_list = NULL;
 
-	array_foreach_modifiable(&list->hosts, hostp)
+	array_foreach(&list->hosts, hostp)
 		mail_host_free(*hostp);
+	array_foreach(&list->tags, tagp)
+		mail_tag_free(*tagp);
 	array_free(&list->hosts);
-	array_free(&list->vhosts);
+	array_free(&list->tags);
 	i_free(list);
 }
 
@@ -489,7 +544,7 @@
 
 	dest = i_new(struct mail_host, 1);
 	*dest = *src;
-	dest->tag = i_strdup(src->tag);
+	dest->tag = src->tag;
 	dest->hostname = i_strdup(src->hostname);
 	return dest;
 }
--- a/src/director/mail-host.h	Mon Nov 23 19:47:08 2015 +0200
+++ b/src/director/mail-host.h	Tue Nov 24 11:15:47 2015 +0200
@@ -17,7 +17,7 @@
 
 	struct ip_addr ip;
 	char *hostname;
-	char *tag;
+	struct mail_tag *tag;
 
 	/* host was recently changed and ring hasn't synced yet since */
 	unsigned int desynced:1;
@@ -49,6 +49,7 @@
 unsigned int mail_hosts_hash(struct mail_host_list *list);
 bool mail_hosts_have_usable(struct mail_host_list *list);
 const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list);
+bool mail_hosts_have_tags(struct mail_host_list *list);
 
 struct mail_host_list *mail_hosts_init(bool consistent_hashing);
 void mail_hosts_deinit(struct mail_host_list **list);