changeset 18067:a7e830b9b967

director: Added support for backend cluster "tags". This allows using a single director ring for multiple backend clusters. By default everything has an empty tag. A passdb lookup can return "director_tag" field containing the wanted tag name. If there aren't any backend servers with the wanted tag, it's treated the same as if there aren't any backend servers available (= wait for 30 secs for a backend and then return temporary failure). Tags can be added to configuration by adding @tag suffix to IPs/hosts. For example: director_mail_servers = 10.0.0.100-10.0.0.110@name1 10.0.0.120@name2 "doveadm director add" can also add tags either with @tag suffix or with -t parameter. "doveadm director status user@domain" requires giving the user's correct tag with -t parameter or the results won't be correct (empty tag's results are shown). Tags can't currently be changed for an existing host without removing it first.
author Timo Sirainen <tss@iki.fi>
date Wed, 12 Nov 2014 06:58:37 +0200
parents 26679856fbd5
children 9c2bcafcf121
files src/director/director-connection.c src/director/director-request.c src/director/director-request.h src/director/director.c src/director/director.h src/director/doveadm-connection.c src/director/login-connection.c src/director/mail-host.c src/director/mail-host.h src/director/main.c src/doveadm/doveadm-director.c
diffstat 11 files changed, 214 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/src/director/director-connection.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/director-connection.c	Wed Nov 12 06:58:37 2014 +0200
@@ -35,6 +35,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
+#include "strescape.h"
 #include "master-service.h"
 #include "mail-host.h"
 #include "director.h"
@@ -821,15 +822,18 @@
 {
 	struct mail_host *host;
 	struct ip_addr ip;
+	const char *tag = "";
 	unsigned int vhost_count;
 	bool update;
 
-	if (str_array_length(args) != 2 ||
+	if (str_array_length(args) < 2 ||
 	    net_addr2ip(args[0], &ip) < 0 ||
 	    str_to_uint(args[1], &vhost_count) < 0) {
 		director_cmd_error(conn, "Invalid parameters");
 		return FALSE;
 	}
+	if (args[2] != NULL)
+		tag = args[2];
 	if (conn->ignore_host_events) {
 		/* remote is sending hosts in a handshake, but it doesn't have
 		   a completed ring and we do. */
@@ -839,10 +843,17 @@
 
 	host = mail_host_lookup(conn->dir->mail_hosts, &ip);
 	if (host == NULL) {
-		host = mail_host_add_ip(conn->dir->mail_hosts, &ip);
+		host = mail_host_add_ip(conn->dir->mail_hosts, &ip, tag);
 		update = TRUE;
 	} else {
 		update = host->vhost_count != vhost_count;
+		if (strcmp(tag, host->tag) != 0) {
+			i_error("director(%s): Host %s changed tag from '%s' to '%s'",
+				conn->name, net_ip2addr(&host->ip),
+				host->tag, tag);
+			mail_host_set_tag(host, tag);
+			update = TRUE;
+		}
 	}
 
 	if (update) {
@@ -1541,8 +1552,13 @@
 
 	str_printfa(str, "HOST-HAND-START\t%u\n", conn->dir->ring_handshaked);
 	array_foreach(mail_hosts_get(conn->dir->mail_hosts), hostp) {
-		str_printfa(str, "HOST\t%s\t%u\n",
+		str_printfa(str, "HOST\t%s\t%u",
 			    net_ip2addr(&(*hostp)->ip), (*hostp)->vhost_count);
+		if ((*hostp)->tag[0] != '\0') {
+			str_append_c(str, '\t');
+			str_append_tabescaped(str, (*hostp)->tag);
+		}
+		str_append_c(str, '\n');
 	}
 	str_printfa(str, "HOST-HAND-END\t%u\n", conn->dir->ring_handshaked);
 }
--- a/src/director/director-request.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/director-request.c	Wed Nov 12 06:58:37 2014 +0200
@@ -36,11 +36,18 @@
 	time_t create_time;
 	unsigned int username_hash;
 	enum director_request_delay_reason delay_reason;
+	char *username_tag;
 
 	director_request_callback *callback;
 	void *context;
 };
 
+static void director_request_free(struct director_request *request)
+{
+	i_free(request->username_tag);
+	i_free(request);
+}
+
 static const char *
 director_request_get_timeout_error(struct director_request *request,
 				   struct user *user, string_t *str)
@@ -68,7 +75,10 @@
 		str_printfa(str, ", user refreshed %u secs ago",
 			    (unsigned int)(ioloop_time - user->timestamp));
 	}
-	str_printfa(str, "hash=%u)", request->username_hash);
+	str_printfa(str, ", hash=%u", request->username_hash);
+	if (request->username_tag != NULL)
+		str_printfa(str, ", tag=%s", request->username_tag);
+	str_append_c(str, ')');
 	return str_c(str);
 }
 
@@ -103,7 +113,7 @@
 		T_BEGIN {
 			request->callback(NULL, errormsg, request->context);
 		} T_END;
-		i_free(request);
+		director_request_free(request);
 	}
 
 	if (array_count(&dir->pending_requests) == 0 && dir->to_request != NULL)
@@ -111,6 +121,7 @@
 }
 
 void director_request(struct director *dir, const char *username,
+		      const char *tag,
 		      director_request_callback *callback, void *context)
 {
 	struct director_request *request;
@@ -121,6 +132,7 @@
 	request->dir = dir;
 	request->create_time = ioloop_time;
 	request->username_hash = username_hash;
+	request->username_tag = tag[0] == '\0' ? NULL : i_strdup(tag);
 	request->callback = callback;
 	request->context = context;
 
@@ -159,7 +171,8 @@
 }
 
 static bool
-director_request_existing(struct director_request *request, struct user *user)
+director_request_existing(struct director_request *request, struct user *user,
+			  const char *tag)
 {
 	struct director *dir = request->dir;
 	struct mail_host *host;
@@ -193,7 +206,7 @@
 
 	/* user is close to being expired. another director may have
 	   already expired it. */
-	host = mail_host_get_by_hash(dir->mail_hosts, user->username_hash);
+	host = mail_host_get_by_hash(dir->mail_hosts, user->username_hash, tag);
 	if (!dir->ring_synced) {
 		/* try again later once ring is synced */
 		request->delay_reason = REQUEST_DELAY_RINGNOTSYNCED;
@@ -253,6 +266,7 @@
 	struct director *dir = request->dir;
 	struct mail_host *host;
 	struct user *user;
+	const char *tag;
 
 	if (!dir->ring_handshaked) {
 		/* delay requests until ring handshaking is complete */
@@ -264,8 +278,9 @@
 	}
 
 	user = user_directory_lookup(dir->users, request->username_hash);
+	tag = request->username_tag == NULL ? "" : request->username_tag;
 	if (user != NULL) {
-		if (!director_request_existing(request, user))
+		if (!director_request_existing(request, user, tag))
 			return FALSE;
 		user_directory_refresh(dir->users, user);
 		dir_debug("request: %u refreshed timeout to %u",
@@ -280,7 +295,7 @@
 			return FALSE;
 		}
 		host = mail_host_get_by_hash(dir->mail_hosts,
-					     request->username_hash);
+					     request->username_hash, tag);
 		if (host == NULL) {
 			/* all hosts have been removed */
 			request->delay_reason = REQUEST_DELAY_NOHOSTS;
@@ -299,6 +314,6 @@
 	T_BEGIN {
 		request->callback(&user->host->ip, NULL, request->context);
 	} T_END;
-	i_free(request);
+	director_request_free(request);
 	return TRUE;
 }
--- a/src/director/director-request.h	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/director-request.h	Wed Nov 12 06:58:37 2014 +0200
@@ -9,6 +9,7 @@
 			  void *context);
 
 void director_request(struct director *dir, const char *username,
+		      const char *tag,
 		      director_request_callback *callback, void *context);
 bool director_request_continue(struct director_request *request);
 
--- a/src/director/director.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/director.c	Wed Nov 12 06:58:37 2014 +0200
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "array.h"
 #include "str.h"
+#include "strescape.h"
 #include "ipc-client.h"
 #include "user-directory.h"
 #include "mail-host.h"
@@ -503,6 +504,8 @@
 			  struct director_host *orig_src,
 			  struct mail_host *host)
 {
+	string_t *str;
+
 	/* update state in case this is the first mail host being added */
 	director_set_state_changed(dir);
 
@@ -511,10 +514,25 @@
 		orig_src->last_seq++;
 	}
 
-	director_update_send(dir, src, t_strdup_printf(
-		"HOST\t%s\t%u\t%u\t%s\t%u\n",
-		net_ip2addr(&orig_src->ip), orig_src->port, orig_src->last_seq,
-		net_ip2addr(&host->ip), host->vhost_count));
+	str = t_str_new(128);
+	str_printfa(str, "HOST\t%s\t%u\t%u\t%s\t%u",
+		    net_ip2addr(&orig_src->ip), orig_src->port,
+		    orig_src->last_seq,
+		    net_ip2addr(&host->ip), host->vhost_count);
+	if (host->tag[0] == '\0')
+		;
+	else if (dir->ring_handshaked &&
+		 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);
+		director_remove_host(dir, NULL, NULL, host);
+		return;
+	} else {
+		str_append_c(str, '\t');
+		str_append_tabescaped(str, host->tag);
+	}
+	str_append_c(str, '\n');
+	director_update_send(dir, src, str_c(str));
 	director_sync(dir);
 }
 
--- a/src/director/director.h	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/director.h	Wed Nov 12 06:58:37 2014 +0200
@@ -18,6 +18,8 @@
 #define DIRECTOR_VERSION_USER_KICK 4
 /* options supported in handshake */
 #define DIRECTOR_VERSION_OPTIONS 5
+/* user tags supported */
+#define DIRECTOR_VERSION_TAGS 5
 
 /* Minimum time between even attempting to communicate with a director that
    failed due to a protocol error. */
--- a/src/director/doveadm-connection.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/doveadm-connection.c	Wed Nov 12 06:58:37 2014 +0200
@@ -7,6 +7,7 @@
 #include "ostream.h"
 #include "array.h"
 #include "str.h"
+#include "strescape.h"
 #include "llist.h"
 #include "master-service.h"
 #include "user-directory.h"
@@ -46,9 +47,11 @@
 	string_t *str = t_str_new(1024);
 
 	array_foreach(mail_hosts_get(conn->dir->mail_hosts), hostp) {
-		str_printfa(str, "%s\t%u\t%u\n",
+		str_printfa(str, "%s\t%u\t%u\t",
 			    net_ip2addr(&(*hostp)->ip), (*hostp)->vhost_count,
 			    (*hostp)->user_count);
+		str_append_tabescaped(str, (*hostp)->tag);
+		str_append_c(str, '\n');
 	}
 	str_append_c(str, '\n');
 	o_stream_nsend(conn->output, str_data(str), str_len(str));
@@ -244,14 +247,21 @@
 doveadm_cmd_host_set(struct doveadm_connection *conn, const char *line)
 {
 	struct director *dir = conn->dir;
-	const char *const *args;
+	const char *const *args, *ip_str, *tag = "";
 	struct mail_host *host;
 	struct ip_addr ip;
 	unsigned int vhost_count = UINT_MAX;
 
 	args = t_strsplit_tab(line);
-	if (args[0] == NULL ||
-	    net_addr2ip(args[0], &ip) < 0 ||
+	ip_str = args[0];
+	if (ip_str != NULL) {
+		tag = strchr(ip_str, '@');
+		if (tag == NULL)
+			tag = "";
+		else
+			ip_str = t_strdup_until(ip_str, tag++);
+	}
+	if (ip_str == NULL || net_addr2ip(ip_str, &ip) < 0 ||
 	    (args[1] != NULL && str_to_uint(args[1], &vhost_count) < 0)) {
 		i_error("doveadm sent invalid HOST-SET parameters: %s", line);
 		return FALSE;
@@ -262,9 +272,12 @@
 	}
 	host = mail_host_lookup(dir->mail_hosts, &ip);
 	if (host == NULL)
-		host = mail_host_add_ip(dir->mail_hosts, &ip);
+		host = mail_host_add_ip(dir->mail_hosts, &ip, tag);
 	if (vhost_count != UINT_MAX)
 		mail_host_set_vhost_count(dir->mail_hosts, host, vhost_count);
+	/* NOTE: we don't supporting changing a tag for an existing host.
+	   it needs to be removed first. otherwise it would be a bit ugly to
+	   handle. */
 	director_update_host(dir, dir->self_host, NULL, host);
 
 	o_stream_nsend(conn->output, "OK\n", 3);
@@ -340,10 +353,19 @@
 {
 	struct user *user;
 	struct mail_host *host;
+	const char *username, *tag, *const *args;
 	unsigned int username_hash;
 	string_t *str = t_str_new(256);
 
-	if (str_to_uint(line, &username_hash) < 0)
+	args = t_strsplit_tab(line);
+	if (args[0] == NULL) {
+		username = "";
+		tag = "";
+	} else {
+		username = args[0];
+		tag = args[1] != NULL ? args[1] : "";
+	}
+	if (str_to_uint(username, &username_hash) < 0)
 		username_hash = user_directory_get_username_hash(conn->dir->users, line);
 
 	/* get user's current host */
@@ -357,7 +379,7 @@
 	}
 
 	/* get host if it wasn't in user directory */
-	host = mail_host_get_by_hash(conn->dir->mail_hosts, username_hash);
+	host = mail_host_get_by_hash(conn->dir->mail_hosts, username_hash, tag);
 	if (host == NULL)
 		str_append(str, "\t");
 	else
@@ -365,7 +387,7 @@
 
 	/* get host with default configuration */
 	host = mail_host_get_by_hash(conn->dir->orig_config_hosts,
-				     username_hash);
+				     username_hash, tag);
 	if (host == NULL)
 		str_append(str, "\t");
 	else
--- a/src/director/login-connection.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/login-connection.c	Wed Nov 12 06:58:37 2014 +0200
@@ -109,7 +109,7 @@
 {
 	struct login_connection *conn = context;
 	struct login_host_request *request;
-	const char *const *args, *line_params, *username = NULL;
+	const char *const *args, *line_params, *username = NULL, *tag = "";
 	bool proxy = FALSE, host = FALSE;
 
 	if (line == NULL) {
@@ -142,6 +142,8 @@
 			host = TRUE;
 		else if (strncmp(*args, "destuser=", 9) == 0)
 			username = *args + 9;
+		else if (strncmp(*args, "director_tag=", 13) == 0)
+			tag = *args + 13;
 		else if (strncmp(*args, "user=", 5) == 0) {
 			if (username == NULL)
 				username = *args + 5;
@@ -165,7 +167,7 @@
 	request->username = i_strdup(username);
 
 	conn->refcount++;
-	director_request(conn->dir, username, login_host_callback, request);
+	director_request(conn->dir, username, tag, login_host_callback, request);
 }
 
 struct login_connection *
--- a/src/director/mail-host.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/mail-host.c	Wed Nov 12 06:58:37 2014 +0200
@@ -116,20 +116,25 @@
 }
 
 struct mail_host *
-mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip)
+mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip,
+		 const char *tag)
 {
 	struct mail_host *host;
 
+	i_assert(tag != NULL);
+
 	host = i_new(struct mail_host, 1);
 	host->vhost_count = VHOST_MULTIPLIER;
 	host->ip = *ip;
+	host->tag = i_strdup(tag);
 	array_append(&list->hosts, &host, 1);
 
 	list->hosts_unsorted = TRUE;
 	return host;
 }
 
-static int mail_host_add(struct mail_host_list *list, const char *host)
+static int
+mail_host_add(struct mail_host_list *list, const char *host, const char *tag)
 {
 	struct ip_addr *ips;
 	unsigned int i, ips_count;
@@ -140,13 +145,13 @@
 	}
 
 	for (i = 0; i < ips_count; i++)
-		(void)mail_host_add_ip(list, &ips[i]);
+		(void)mail_host_add_ip(list, &ips[i], tag);
 	return 0;
 }
 
 static int
 mail_hosts_add_range(struct mail_host_list *list,
-		     struct ip_addr ip1, struct ip_addr ip2)
+		     struct ip_addr ip1, struct ip_addr ip2, const char *tag)
 {
 	uint32_t *ip1_arr, *ip2_arr;
 	uint32_t i1, i2;
@@ -201,7 +206,7 @@
 	/* create hosts from the final bits */
 	do {
 		ip1_arr[i] = ntohl(i1);
-		(void)mail_host_add_ip(list, &ip1);
+		(void)mail_host_add_ip(list, &ip1, tag);
 		i1++;
 	} while (ip1_arr[i] != ip2_arr[i]);
 	return 0;
@@ -218,21 +223,30 @@
 
 		tmp = t_strsplit_spaces(hosts_string, " ");
 		for (; *tmp != NULL; tmp++) {
-			p = strchr(*tmp, '-');
+			const char *tag, *value = *tmp;
+
+			p = strchr(value, '@');
+			if (p == NULL)
+				tag = "";
+			else {
+				value = t_strdup_until(value, p++);
+				tag = p;
+			}
+			p = strchr(value, '-');
 			if (p != NULL) {
 				/* see if this is ip1-ip2 range */
-				host1 = t_strdup_until(*tmp, p);
+				host1 = t_strdup_until(value, p);
 				host2 = p + 1;
 				if (net_addr2ip(host1, &ip1) == 0 &&
 				    net_addr2ip(host2, &ip2) == 0) {
-					if (mail_hosts_add_range(list, ip1,
-								 ip2) < 0)
+					if (mail_hosts_add_range(list, ip1, ip2,
+								 tag) < 0)
 						ret = -1;
 					continue;
 				}
 			}
 
-			if (mail_host_add(list, *tmp) < 0)
+			if (mail_host_add(list, value, tag) < 0)
 				ret = -1;
 		}
 	} T_END;
@@ -247,6 +261,14 @@
 	return ret;
 }
 
+void mail_host_set_tag(struct mail_host *host, const char *tag)
+{
+	i_assert(tag != NULL);
+
+	i_free(host->tag);
+	host->tag = i_strdup(tag);
+}
+
 void mail_host_set_vhost_count(struct mail_host_list *list,
 			       struct mail_host *host, unsigned int vhost_count)
 {
@@ -254,6 +276,12 @@
 	list->hosts_unsorted = TRUE;
 }
 
+static void mail_host_free(struct mail_host *host)
+{
+	i_free(host->tag);
+	i_free(host);
+}
+
 void mail_host_remove(struct mail_host_list *list, struct mail_host *host)
 {
 	struct mail_host *const *hosts;
@@ -267,7 +295,7 @@
 		}
 	}
 
-	i_free(host);
+	mail_host_free(host);
 	list->hosts_unsorted = TRUE;
 }
 
@@ -287,46 +315,69 @@
 }
 
 static struct mail_host *
-mail_host_get_by_hash_ring(struct mail_host_list *list, unsigned int hash)
+mail_host_get_by_hash_ring(struct mail_host_list *list, unsigned int hash,
+			   const char *tag)
 {
+	struct mail_host *host;
 	const struct mail_vhost *vhosts;
-	unsigned int count, idx;
+	unsigned int i, count, idx;
 
 	vhosts = array_get(&list->vhosts, &count);
 	array_bsearch_insert_pos(&list->vhosts, &hash,
 				 mail_vhost_hash_cmp, &idx);
+	i_assert(idx <= count);
 	if (idx == count) {
 		if (count == 0)
 			return NULL;
 		idx = 0;
 	}
 
-	return vhosts[idx].host;
+	for (i = 0; i < count; i++) {
+		host = vhosts[(idx + i) % count].host;
+		if (strcmp(host->tag, tag) == 0)
+			return host;
+	}
+	return NULL;
 }
 
 static struct mail_host *
-mail_host_get_by_hash_direct(struct mail_host_list *list, unsigned int hash)
+mail_host_get_by_hash_direct(struct mail_host_list *list, unsigned int hash,
+			     const char *tag)
 {
+	struct mail_host *host;
 	const struct mail_vhost *vhosts;
-	unsigned int count;
+	unsigned int i, count;
 
 	vhosts = array_get(&list->vhosts, &count);
 	if (count == 0)
 		return NULL;
 
-	return vhosts[hash % count].host;
+	for (i = 0; i < count; i++) {
+		host = vhosts[(hash + i) % count].host;
+		if (strcmp(host->tag, tag) == 0)
+			return host;
+	}
+	return NULL;
 }
 
 struct mail_host *
-mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash)
+mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash,
+		      const char *tag)
 {
 	if (list->hosts_unsorted)
 		mail_hosts_sort(list);
 
 	if (list->consistent_hashing)
-		return mail_host_get_by_hash_ring(list, hash);
+		return mail_host_get_by_hash_ring(list, hash, tag);
 	else
-		return mail_host_get_by_hash_direct(list, hash);
+		return mail_host_get_by_hash_direct(list, hash, tag);
+}
+
+bool mail_hosts_have_usable(struct mail_host_list *list)
+{
+	if (list->hosts_unsorted)
+		mail_hosts_sort(list);
+	return array_count(&list->vhosts) > 0;
 }
 
 const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list)
@@ -355,7 +406,7 @@
 	*_list = NULL;
 
 	array_foreach_modifiable(&list->hosts, hostp)
-		i_free(*hostp);
+		mail_host_free(*hostp);
 	array_free(&list->hosts);
 	array_free(&list->vhosts);
 	i_free(list);
@@ -367,6 +418,7 @@
 
 	dest = i_new(struct mail_host, 1);
 	*dest = *src;
+	dest->tag = i_strdup(src->tag);
 	return dest;
 }
 
--- a/src/director/mail-host.h	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/mail-host.h	Wed Nov 12 06:58:37 2014 +0200
@@ -10,23 +10,28 @@
 	unsigned int vhost_count;
 
 	struct ip_addr ip;
+	char *tag;
 };
 ARRAY_DEFINE_TYPE(mail_host, struct mail_host *);
 
 struct mail_host *
-mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip);
+mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip,
+		 const char *tag);
 struct mail_host *
 mail_host_lookup(struct mail_host_list *list, const struct ip_addr *ip);
 struct mail_host *
-mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash);
+mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash,
+		      const char *tag);
 
 int mail_hosts_parse_and_add(struct mail_host_list *list,
 			     const char *hosts_string);
+void mail_host_set_tag(struct mail_host *host, const char *tag);
 void mail_host_set_vhost_count(struct mail_host_list *list,
 			       struct mail_host *host,
 			       unsigned int vhost_count);
 void mail_host_remove(struct mail_host_list *list, struct mail_host *host);
 
+bool mail_hosts_have_usable(struct mail_host_list *list);
 const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list);
 
 struct mail_host_list *mail_hosts_init(bool consistent_hashing);
--- a/src/director/main.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/director/main.c	Wed Nov 12 06:58:37 2014 +0200
@@ -119,8 +119,7 @@
 	ARRAY(struct director_request *) new_requests;
 	bool ret;
 
-	if (!dir->ring_synced ||
-	    mail_host_get_by_hash(dir->mail_hosts, 0) == NULL)
+	if (!dir->ring_synced || !mail_hosts_have_usable(dir->mail_hosts))
 		return;
 
 	/* if there are any pending client requests, finish them now */
--- a/src/doveadm/doveadm-director.c	Wed Nov 12 06:46:45 2014 +0200
+++ b/src/doveadm/doveadm-director.c	Wed Nov 12 06:58:37 2014 +0200
@@ -21,6 +21,7 @@
 struct director_context {
 	const char *socket_path;
 	const char *users_path;
+	const char *tag;
 	struct istream *input;
 	bool explicit_socket_path;
 	bool hash_map, user_map;
@@ -111,6 +112,9 @@
 		case 'u':
 			ctx->user_map = TRUE;
 			break;
+		case 't':
+			ctx->tag = optarg;
+			break;
 		default:
 			director_cmd_help(cmd);
 		}
@@ -121,12 +125,14 @@
 }
 
 static void
-cmd_director_status_user(struct director_context *ctx, const char *user)
+cmd_director_status_user(struct director_context *ctx, char *argv[])
 {
+	const char *user = argv[0], *tag = argv[1];
 	const char *line, *const *args;
 	unsigned int expires;
 
-	director_send(ctx, t_strdup_printf("USER-LOOKUP\t%s\n", user));
+	director_send(ctx, t_strdup_printf("USER-LOOKUP\t%s\t%s\n", user,
+					   tag != NULL ? tag : ""));
 	line = i_stream_read_next_line(ctx->input);
 	if (line == NULL) {
 		i_error("Lookup failed");
@@ -158,14 +164,15 @@
 	struct director_context *ctx;
 	const char *line, *const *args;
 
-	ctx = cmd_director_init(argc, argv, "a:", cmd_director_status);
+	ctx = cmd_director_init(argc, argv, "a:t:", cmd_director_status);
 	if (argv[optind] != NULL) {
-		cmd_director_status_user(ctx, argv[optind]);
+		cmd_director_status_user(ctx, argv+optind);
 		return;
 	}
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
 	doveadm_print_header_simple("mail server ip");
+	doveadm_print_header_simple("tag");
 	doveadm_print_header("vhosts", "vhosts",
 			     DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
 	doveadm_print_header("users", "users",
@@ -177,8 +184,9 @@
 			break;
 		T_BEGIN {
 			args = t_strsplit_tab(line);
-			if (str_array_length(args) >= 3) {
+			if (str_array_length(args) >= 4) {
 				doveadm_print(args[0]);
+				doveadm_print(args[3]);
 				doveadm_print(args[1]);
 				doveadm_print(args[2]);
 			}
@@ -390,9 +398,12 @@
 	struct director_context *ctx;
 	struct ip_addr *ips;
 	unsigned int i, ips_count, vhost_count = UINT_MAX;
-	const char *host, *cmd, *line;
+	const char *host, *line;
+	string_t *cmd;
 
-	ctx = cmd_director_init(argc, argv, "a:", cmd_director_add);
+	ctx = cmd_director_init(argc, argv, "a:t:", cmd_director_add);
+	if (ctx->tag != NULL && ctx->tag[0] == '\0')
+		ctx->tag = NULL;
 	host = argv[optind++];
 	if (host == NULL)
 		director_cmd_help(cmd_director_add);
@@ -403,14 +414,22 @@
 	if (argv[optind] != NULL)
 		director_cmd_help(cmd_director_add);
 
+	if (ctx->tag == NULL) {
+		ctx->tag = strchr(host, '@');
+		if (ctx->tag != NULL)
+			host = t_strdup_until(host, ctx->tag++);
+	}
 	director_get_host(host, &ips, &ips_count);
+	cmd = t_str_new(128);
 	for (i = 0; i < ips_count; i++) {
-		cmd = vhost_count == UINT_MAX ?
-			t_strdup_printf("HOST-SET\t%s\n",
-					net_ip2addr(&ips[i])) :
-			t_strdup_printf("HOST-SET\t%s\t%u\n",
-					net_ip2addr(&ips[i]), vhost_count);
-		director_send(ctx, cmd);
+		str_truncate(cmd, 0);
+		str_printfa(cmd, "HOST-SET\t%s", net_ip2addr(&ips[i]));
+		if (ctx->tag != NULL)
+			str_printfa(cmd, "@%s", ctx->tag);
+		if (vhost_count != UINT_MAX)
+			str_printfa(cmd, "\t%u", vhost_count);
+		str_append_c(cmd, '\n');
+		director_send(ctx, str_c(cmd));
 	}
 	for (i = 0; i < ips_count; i++) {
 		line = i_stream_read_next_line(ctx->input);
@@ -752,7 +771,7 @@
 	{ cmd_director_map, "director map",
 	  "[-a <director socket path>] [-f <users file>] [-h | -u] [<host>]" },
 	{ cmd_director_add, "director add",
-	  "[-a <director socket path>] <host> [<vhost count>]" },
+	  "[-a <director socket path>] [-t <tag>] <host> [<vhost count>]" },
 	{ cmd_director_remove, "director remove",
 	  "[-a <director socket path>] <host>" },
 	{ cmd_director_move, "director move",