/// <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); }