/// <summary>
        /// Create node under given path with given data and settings.
        /// </summary>
        /// <param name="path">The path to create node.</param>
        /// <param name="data">the content of the node.</param>
        /// <param name="acl">The access control list.</param>
        /// <param name="type">the znode type.</param>
        /// <returns>The node path if created.</returns>
        public async Task <string> CreateAsync(string path, byte[] data, IEnumerable <ZookeeperZnodeAcl> acl, ZookeeperZnodeType type)
        {
            await this.EnsureConnectedToZookeeperClusterAsync().ConfigureAwait(false);

            string result;

            try
            {
                var zacls = new List <ACL>();
                if (acl == null || !acl.Any())
                {
                    var ids = ZookeeperZnodeAclIdentity.WorldIdentity();
                    zacls.Add(new ACL((int)ZookeeperZnodeAclRights.All, new ZKId(ids.Schema, ids.Identity)));
                }
                else
                {
                    foreach (var ac in acl)
                    {
                        if (ac.Identity == null)
                        {
                            ac.Identity = ZookeeperZnodeAclIdentity.WorldIdentity();
                        }

                        zacls.Add(new ACL((int)ac.AclRights, new ZKId(ac.Identity.Schema, ac.Identity.Identity)));
                    }
                }

                var zmode = CreateMode.Persistent;
                if (type == ZookeeperZnodeType.Persistent)
                {
                    zmode = CreateMode.Persistent;
                }
                else if (type == ZookeeperZnodeType.PersistentSequential)
                {
                    zmode = CreateMode.PersistentSequential;
                }
                else if (type == ZookeeperZnodeType.Ephemeral)
                {
                    zmode = CreateMode.Ephemeral;
                }
                else if (type == ZookeeperZnodeType.EphemeralSequential)
                {
                    zmode = CreateMode.EphemeralSequential;
                }

                result = await this.createOrDeleteRequestor.ExecuteAsync(() => Task.Run(() => this.zookeeper.Create(path, data, zacls, zmode))).ConfigureAwait(false);
            }
            catch (KeeperException.NodeExistsException)
            {
                result = path;
            }
            catch (KeeperException.SessionExpiredException)
            {
                result = null;
            }

            return(result);
        }
        /// <summary>
        /// Delete a topic by topic name.
        /// </summary>
        /// <param name="topic">the name of the topic.</param>
        /// <returns>Task holds status of deletion. True indicates succeeded. False indicates failed.</returns>
        public async Task <bool> DeleteKafkaTopicAsync(string topic)
        {
            var existing = await this.GetKafkaTopicAsync(topic).ConfigureAwait(false);

            if (existing == null)
            {
                return(false);
            }

            // note
            // when delete a topic from kafka, it actually create a empty node under /admin/delete_topics/{topic}
            // not deleting the node under /brokers/topics/{topic}, let the kafka controller to handle the real
            // topic removing.

            var acl = new ZookeeperZnodeAcl()
            {
                AclRights = ZookeeperZnodeAclRights.All,
                Identity  = ZookeeperZnodeAclIdentity.WorldIdentity()
            };

            var path = await this.ZookeeperClient.CreateAsync("/admin/delete_topics/" + topic, Encoding.UTF8.GetBytes(string.Empty), new List <ZookeeperZnodeAcl>() { acl }, ZookeeperZnodeType.Persistent).ConfigureAwait(false);

            return(!string.IsNullOrWhiteSpace(path));
        }
        /// <summary>
        /// Create a topic with creation settings.
        /// </summary>
        /// <param name="topicConfiguration">The topic creation configuration.</param>
        /// <returns>the path to the topic. null is return when unable to create it.</returns>
        public async Task <string> CreateKafkaTopicAsync(KafkaTopicConfiguration topicConfiguration)
        {
            if (topicConfiguration == null || !this.IsTopicNameValid(topicConfiguration.TopicName) || topicConfiguration.NumOfPartitions <= 0 || topicConfiguration.NumOfReplicas <= 0)
            {
                return(null);
            }

            //// refer to https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/admin/AdminUtils.scala
            //// the method createTopic

            // 1 figure how many brokers are available
            var brokers = await this.ListKakfaBrokersAsync().ConfigureAwait(false);

            if (brokers == null)
            {
                return(null);
            }

            // note:
            // when actual number of active brokers is less than the expected number of brokers, technically you still can
            // create topic. for example: we expected 5 brokers, but actually there are only 3, when you create below topic:
            //  { "name" :"test", "partitions": "3", "replicas":"3"}
            //  as far as the number of replicas is not bigger than the number of active brokers, you can still assign replicas
            //  onto different brokers.
            // the only problem with this scenario is the partitions will not put on the another brokers when they are alive.
            // then need manually move some partitions to the addition brokers to get better balance.
            // so to make things simple, we just don't do it when this happen.
            var expected = this.GetExpectedKafkaBrokers();

            if (expected == null || brokers.Count() != expected.Count())
            {
                return(null);
            }

            var topics = await this.ListKafkaTopicsAsync().ConfigureAwait(false);

            if (topics != null)
            {
                var existing = topics.FirstOrDefault(t => t.Name == topicConfiguration.TopicName);
                if (existing != null)
                {
                    return("/brokers/topics/" + topicConfiguration.TopicName);
                }
            }

            // Note : number of replicas can not be greater than number of active brokers,
            // because replicas of same topic can not be on the same broker

            // 2 get partitions assignment info onto those brokers with round-robin fashion.
            var assignments = this.AssignPartitionsAndReplicasToBrokers(topicConfiguration.NumOfPartitions, topicConfiguration.NumOfReplicas, brokers);

            // 3 convert partition assignment into json format for zookeeper node, and create zookeepr node
            // the format of json is : {"version":1,"partitions":{"0":[],...}}
            var serializer = new ZookeeperDataSerializer();
            var content    = serializer.SerializeKafkaTopicInfo(new KafkaTopicInfo()
            {
                Name = topicConfiguration.TopicName, Version = 1, Partitions = assignments.ToArray()
            });

            // 4 create zookpper node
            // create node at path 'brokers/topics/{name}', with content above
            var acl = new ZookeeperZnodeAcl()
            {
                AclRights = ZookeeperZnodeAclRights.All,
                Identity  = ZookeeperZnodeAclIdentity.WorldIdentity()
            };

            var path = await this.ZookeeperClient.CreateAsync("/brokers/topics/" + topicConfiguration.TopicName, content, new List <ZookeeperZnodeAcl>() { acl }, ZookeeperZnodeType.Persistent).ConfigureAwait(false);

            return(path);
        }