github.com/confluentinc/confluent-kafka-go@v1.9.2/kafka/adminapi_test.go (about)

     1  /**
     2   * Copyright 2018 Confluent Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package kafka
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  )
    25  
    26  // TestAdminAPIWithDefaultValue tests CreateTopics with default
    27  // NumPartitions and ReplicationFactor values
    28  func TestAdminAPIWithDefaultValue(t *testing.T) {
    29  	if !testconfRead() {
    30  		t.Skipf("Missing testconf.json")
    31  	}
    32  
    33  	topic := "testWithDefaultValue"
    34  
    35  	conf := ConfigMap{"bootstrap.servers": testconf.Brokers}
    36  	if err := conf.updateFromTestconf(); err != nil {
    37  		t.Fatalf("Failed to update test configuration: %v\n", err)
    38  	}
    39  
    40  	expDuration, err := time.ParseDuration("30s")
    41  	if err != nil {
    42  		t.Fatalf("Failed to Parse Duration: %s", err)
    43  	}
    44  
    45  	adminClient, err := NewAdminClient(&conf)
    46  	if err != nil {
    47  		t.Fatalf("Failed to create AdminClient %v", err)
    48  	}
    49  
    50  	ctx, cancel := context.WithTimeout(context.Background(), expDuration)
    51  	defer cancel()
    52  	res, err := adminClient.CreateTopics(
    53  		ctx,
    54  		[]TopicSpecification{
    55  			{
    56  				Topic:             topic,
    57  				NumPartitions:     -1,
    58  				ReplicationFactor: -1,
    59  			},
    60  		})
    61  	if err != nil {
    62  		adminClient.Close()
    63  		t.Fatalf("Failed to create topics %v\n", err)
    64  	}
    65  	t.Logf("Succeed to create topic %v\n", res)
    66  
    67  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
    68  	defer cancel()
    69  	res, err = adminClient.DeleteTopics(ctx, []string{topic})
    70  	if err != nil {
    71  		adminClient.Close()
    72  		t.Fatalf("Failed to delete topic %v, err: %v", topic, err)
    73  	}
    74  	t.Logf("Succeed to delete topic %v\n", res)
    75  
    76  	adminClient.Close()
    77  }
    78  
    79  func testAdminAPIsCreateACLs(what string, a *AdminClient, t *testing.T) {
    80  	var res []CreateACLResult
    81  	var err error
    82  	var ctx context.Context
    83  	var cancel context.CancelFunc
    84  	var expDuration time.Duration
    85  	var expDurationLonger time.Duration
    86  	var expError string
    87  	var invalidTests []ACLBindings
    88  
    89  	checkFail := func(res []CreateACLResult, err error) {
    90  		if res != nil || err == nil {
    91  			t.Fatalf("Expected CreateACLs to fail, but got result: %v, err: %v", res, err)
    92  		}
    93  	}
    94  
    95  	testACLBindings := ACLBindings{
    96  		{
    97  			Type:                ResourceTopic,
    98  			Name:                "mytopic",
    99  			ResourcePatternType: ResourcePatternTypeLiteral,
   100  			Principal:           "User:myuser",
   101  			Host:                "*",
   102  			Operation:           ACLOperationAll,
   103  			PermissionType:      ACLPermissionTypeAllow,
   104  		},
   105  	}
   106  
   107  	copyACLBindings := func() ACLBindings {
   108  		return append(ACLBindings{}, testACLBindings...)
   109  	}
   110  
   111  	t.Logf("AdminClient API - ACLs testing on %s: %s", a, what)
   112  	expDuration, err = time.ParseDuration("0.1s")
   113  	if err != nil {
   114  		t.Fatalf("%s", err)
   115  	}
   116  
   117  	// nil aclBindings
   118  	res, err = a.CreateACLs(ctx, nil)
   119  	checkFail(res, err)
   120  	expError = "Expected non-nil slice of ACLBinding structs"
   121  	if err.Error() != expError {
   122  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   123  	}
   124  
   125  	// empty aclBindings
   126  	res, err = a.CreateACLs(ctx, ACLBindings{})
   127  	checkFail(res, err)
   128  	expError = "Expected non-empty slice of ACLBinding structs"
   129  	if err.Error() != expError {
   130  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   131  	}
   132  
   133  	// Correct input, fail with timeout
   134  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   135  	defer cancel()
   136  
   137  	res, err = a.CreateACLs(ctx, testACLBindings)
   138  	checkFail(res, err)
   139  	if ctx.Err() != context.DeadlineExceeded {
   140  		t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   141  	}
   142  
   143  	// request timeout comes before context deadline
   144  	expDurationLonger, err = time.ParseDuration("0.2s")
   145  	if err != nil {
   146  		t.Fatalf("%s", err)
   147  	}
   148  
   149  	ctx, cancel = context.WithTimeout(context.Background(), expDurationLonger)
   150  	defer cancel()
   151  
   152  	res, err = a.CreateACLs(ctx, testACLBindings, SetAdminRequestTimeout(expDuration))
   153  	checkFail(res, err)
   154  	expError = "Failed while waiting for controller: Local: Timed out"
   155  	if err.Error() != expError {
   156  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   157  	}
   158  
   159  	// Invalid ACL bindings
   160  	invalidTests = []ACLBindings{copyACLBindings(), copyACLBindings()}
   161  	invalidTests[0][0].Type = ResourceUnknown
   162  	invalidTests[1][0].Type = ResourceAny
   163  	expError = ": Invalid resource type"
   164  	for _, invalidACLBindings := range invalidTests {
   165  		res, err = a.CreateACLs(ctx, invalidACLBindings)
   166  		checkFail(res, err)
   167  		if !strings.HasSuffix(err.Error(), expError) {
   168  			t.Fatalf("Expected an error ending with \"%s\", received: \"%s\"", expError, err.Error())
   169  		}
   170  	}
   171  
   172  	suffixes := []string{
   173  		": Invalid resource pattern type",
   174  		": Invalid resource pattern type",
   175  		": Invalid resource pattern type",
   176  		": Invalid operation",
   177  		": Invalid operation",
   178  		": Invalid permission type",
   179  		": Invalid permission type",
   180  		": Invalid resource name",
   181  		": Invalid principal",
   182  		": Invalid host",
   183  	}
   184  	nInvalidTests := len(suffixes)
   185  	invalidTests = make([]ACLBindings, nInvalidTests)
   186  	for i := 0; i < nInvalidTests; i++ {
   187  		invalidTests[i] = copyACLBindings()
   188  	}
   189  	invalidTests[0][0].ResourcePatternType = ResourcePatternTypeUnknown
   190  	invalidTests[1][0].ResourcePatternType = ResourcePatternTypeMatch
   191  	invalidTests[2][0].ResourcePatternType = ResourcePatternTypeAny
   192  	invalidTests[3][0].Operation = ACLOperationUnknown
   193  	invalidTests[4][0].Operation = ACLOperationAny
   194  	invalidTests[5][0].PermissionType = ACLPermissionTypeUnknown
   195  	invalidTests[6][0].PermissionType = ACLPermissionTypeAny
   196  	invalidTests[7][0].Name = ""
   197  	invalidTests[8][0].Principal = ""
   198  	invalidTests[9][0].Host = ""
   199  
   200  	for i, invalidACLBindings := range invalidTests {
   201  		res, err = a.CreateACLs(ctx, invalidACLBindings)
   202  		checkFail(res, err)
   203  		if !strings.HasSuffix(err.Error(), suffixes[i]) {
   204  			t.Fatalf("Expected an error ending with \"%s\", received: \"%s\"", suffixes[i], err.Error())
   205  		}
   206  	}
   207  }
   208  
   209  func testAdminAPIsDescribeACLs(what string, a *AdminClient, t *testing.T) {
   210  	var res *DescribeACLsResult
   211  	var err error
   212  	var ctx context.Context
   213  	var cancel context.CancelFunc
   214  	var expDuration time.Duration
   215  	var expDurationLonger time.Duration
   216  	var expError string
   217  
   218  	checkFail := func(res *DescribeACLsResult, err error) {
   219  		if res != nil || err == nil {
   220  			t.Fatalf("Expected DescribeACLs to fail, but got result: %v, err: %v", res, err)
   221  		}
   222  	}
   223  
   224  	aclBindingsFilter := ACLBindingFilter{
   225  		Type:                ResourceTopic,
   226  		ResourcePatternType: ResourcePatternTypeLiteral,
   227  		Operation:           ACLOperationAll,
   228  		PermissionType:      ACLPermissionTypeAllow,
   229  	}
   230  
   231  	t.Logf("AdminClient API - ACLs testing on %s: %s", a, what)
   232  	expDuration, err = time.ParseDuration("0.1s")
   233  	if err != nil {
   234  		t.Fatalf("%s", err)
   235  	}
   236  
   237  	// Correct input, fail with timeout
   238  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   239  	defer cancel()
   240  
   241  	res, err = a.DescribeACLs(ctx, aclBindingsFilter)
   242  	checkFail(res, err)
   243  	if ctx.Err() != context.DeadlineExceeded {
   244  		t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   245  	}
   246  
   247  	// request timeout comes before context deadline
   248  	expDurationLonger, err = time.ParseDuration("0.2s")
   249  	if err != nil {
   250  		t.Fatalf("%s", err)
   251  	}
   252  
   253  	ctx, cancel = context.WithTimeout(context.Background(), expDurationLonger)
   254  	defer cancel()
   255  
   256  	res, err = a.DescribeACLs(ctx, aclBindingsFilter, SetAdminRequestTimeout(expDuration))
   257  	checkFail(res, err)
   258  	expError = "Failed while waiting for controller: Local: Timed out"
   259  	if err.Error() != expError {
   260  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   261  	}
   262  
   263  	// Invalid ACL binding filters
   264  	suffixes := []string{
   265  		": Invalid resource pattern type",
   266  		": Invalid operation",
   267  		": Invalid permission type",
   268  	}
   269  	nInvalidTests := len(suffixes)
   270  	invalidTests := make(ACLBindingFilters, nInvalidTests)
   271  	for i := 0; i < nInvalidTests; i++ {
   272  		invalidTests[i] = aclBindingsFilter
   273  	}
   274  	invalidTests[0].ResourcePatternType = ResourcePatternTypeUnknown
   275  	invalidTests[1].Operation = ACLOperationUnknown
   276  	invalidTests[2].PermissionType = ACLPermissionTypeUnknown
   277  
   278  	for i, invalidACLBindingFilter := range invalidTests {
   279  		res, err = a.DescribeACLs(ctx, invalidACLBindingFilter)
   280  		checkFail(res, err)
   281  		if !strings.HasSuffix(err.Error(), suffixes[i]) {
   282  			t.Fatalf("Expected an error ending with \"%s\", received: \"%s\"", suffixes[i], err.Error())
   283  		}
   284  	}
   285  
   286  	// ACL binding filters are valid with empty strings,
   287  	// matching any value
   288  	validTests := [3]ACLBindingFilter{}
   289  	for i := 0; i < len(validTests); i++ {
   290  		validTests[i] = aclBindingsFilter
   291  	}
   292  	validTests[0].Name = ""
   293  	validTests[1].Principal = ""
   294  	validTests[2].Host = ""
   295  
   296  	for _, validACLBindingFilter := range validTests {
   297  		res, err = a.DescribeACLs(ctx, validACLBindingFilter)
   298  		checkFail(res, err)
   299  		if ctx.Err() != context.DeadlineExceeded {
   300  			t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   301  		}
   302  	}
   303  }
   304  
   305  func testAdminAPIsDeleteACLs(what string, a *AdminClient, t *testing.T) {
   306  	var res []DeleteACLsResult
   307  	var err error
   308  	var ctx context.Context
   309  	var cancel context.CancelFunc
   310  	var expDuration time.Duration
   311  	var expDurationLonger time.Duration
   312  	var expError string
   313  
   314  	checkFail := func(res []DeleteACLsResult, err error) {
   315  		if res != nil || err == nil {
   316  			t.Fatalf("Expected DeleteACL to fail, but got result: %v, err: %v", res, err)
   317  		}
   318  	}
   319  
   320  	aclBindingsFilters := ACLBindingFilters{
   321  		{
   322  			Type:                ResourceTopic,
   323  			ResourcePatternType: ResourcePatternTypeLiteral,
   324  			Operation:           ACLOperationAll,
   325  			PermissionType:      ACLPermissionTypeAllow,
   326  		},
   327  	}
   328  
   329  	copyACLBindingFilters := func() ACLBindingFilters {
   330  		return append(ACLBindingFilters{}, aclBindingsFilters...)
   331  	}
   332  
   333  	t.Logf("AdminClient API - ACLs testing on %s: %s", a, what)
   334  	expDuration, err = time.ParseDuration("0.1s")
   335  	if err != nil {
   336  		t.Fatalf("%s", err)
   337  	}
   338  
   339  	// nil aclBindingFilters
   340  	res, err = a.DeleteACLs(ctx, nil)
   341  	checkFail(res, err)
   342  	expError = "Expected non-nil slice of ACLBindingFilter structs"
   343  	if err.Error() != expError {
   344  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   345  	}
   346  
   347  	// empty aclBindingFilters
   348  	res, err = a.DeleteACLs(ctx, ACLBindingFilters{})
   349  	checkFail(res, err)
   350  	expError = "Expected non-empty slice of ACLBindingFilter structs"
   351  	if err.Error() != expError {
   352  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   353  	}
   354  
   355  	// Correct input, fail with timeout
   356  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   357  	defer cancel()
   358  
   359  	res, err = a.DeleteACLs(ctx, aclBindingsFilters)
   360  	checkFail(res, err)
   361  	if ctx.Err() != context.DeadlineExceeded {
   362  		t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   363  	}
   364  
   365  	// request timeout comes before context deadline
   366  	expDurationLonger, err = time.ParseDuration("0.2s")
   367  	if err != nil {
   368  		t.Fatalf("%s", err)
   369  	}
   370  
   371  	ctx, cancel = context.WithTimeout(context.Background(), expDurationLonger)
   372  	defer cancel()
   373  
   374  	res, err = a.DeleteACLs(ctx, aclBindingsFilters, SetAdminRequestTimeout(expDuration))
   375  	checkFail(res, err)
   376  	expError = "Failed while waiting for controller: Local: Timed out"
   377  	if err.Error() != expError {
   378  		t.Fatalf("Expected error \"%s\", received: \"%v\"", expError, err.Error())
   379  	}
   380  
   381  	// Invalid ACL binding filters
   382  	suffixes := []string{
   383  		": Invalid resource pattern type",
   384  		": Invalid operation",
   385  		": Invalid permission type",
   386  	}
   387  	nInvalidTests := len(suffixes)
   388  	invalidTests := make([]ACLBindingFilters, nInvalidTests)
   389  	for i := 0; i < nInvalidTests; i++ {
   390  		invalidTests[i] = copyACLBindingFilters()
   391  	}
   392  	invalidTests[0][0].ResourcePatternType = ResourcePatternTypeUnknown
   393  	invalidTests[1][0].Operation = ACLOperationUnknown
   394  	invalidTests[2][0].PermissionType = ACLPermissionTypeUnknown
   395  
   396  	for i, invalidACLBindingFilters := range invalidTests {
   397  		res, err = a.DeleteACLs(ctx, invalidACLBindingFilters)
   398  		checkFail(res, err)
   399  		if !strings.HasSuffix(err.Error(), suffixes[i]) {
   400  			t.Fatalf("Expected an error ending with \"%s\", received: \"%s\"", suffixes[i], err.Error())
   401  		}
   402  	}
   403  
   404  	// ACL binding filters are valid with empty strings,
   405  	// matching any value
   406  	validTests := [3]ACLBindingFilters{}
   407  	for i := 0; i < len(validTests); i++ {
   408  		validTests[i] = copyACLBindingFilters()
   409  	}
   410  	validTests[0][0].Name = ""
   411  	validTests[1][0].Principal = ""
   412  	validTests[2][0].Host = ""
   413  
   414  	for _, validACLBindingFilters := range validTests {
   415  		res, err = a.DeleteACLs(ctx, validACLBindingFilters)
   416  		checkFail(res, err)
   417  		if ctx.Err() != context.DeadlineExceeded {
   418  			t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   419  		}
   420  	}
   421  }
   422  
   423  func testAdminAPIs(what string, a *AdminClient, t *testing.T) {
   424  	t.Logf("AdminClient API testing on %s: %s", a, what)
   425  
   426  	expDuration, err := time.ParseDuration("0.1s")
   427  	if err != nil {
   428  		t.Fatalf("%s", err)
   429  	}
   430  
   431  	confStrings := map[string]string{
   432  		"some.topic.config":  "unchecked",
   433  		"these.are.verified": "on the broker",
   434  		"and.this.is":        "just",
   435  		"a":                  "unit test"}
   436  
   437  	// Correct input, fail with timeout
   438  	ctx, cancel := context.WithTimeout(context.Background(), expDuration)
   439  	defer cancel()
   440  	res, err := a.CreateTopics(
   441  		ctx,
   442  		[]TopicSpecification{
   443  			{
   444  				Topic:             "mytopic",
   445  				NumPartitions:     7,
   446  				ReplicationFactor: 3,
   447  			},
   448  			{
   449  				Topic:         "mytopic2",
   450  				NumPartitions: 2,
   451  				ReplicaAssignment: [][]int32{
   452  					[]int32{1, 2, 3},
   453  					[]int32{3, 2, 1},
   454  				},
   455  			},
   456  			{
   457  				Topic:             "mytopic3",
   458  				NumPartitions:     10000,
   459  				ReplicationFactor: 90,
   460  				Config:            confStrings,
   461  			},
   462  		})
   463  	if res != nil || err == nil {
   464  		t.Fatalf("Expected CreateTopics to fail, but got result: %v, err: %v", res, err)
   465  	}
   466  	if ctx.Err() != context.DeadlineExceeded {
   467  		t.Fatalf("Expected DeadlineExceeded, not %v, %v", ctx.Err(), err)
   468  	}
   469  
   470  	// Incorrect input, fail with ErrInvalidArg
   471  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   472  	defer cancel()
   473  	res, err = a.CreateTopics(
   474  		ctx,
   475  		[]TopicSpecification{
   476  			{
   477  				// Must not specify both ReplicationFactor and ReplicaAssignment
   478  				Topic:             "mytopic",
   479  				NumPartitions:     2,
   480  				ReplicationFactor: 3,
   481  				ReplicaAssignment: [][]int32{
   482  					[]int32{1, 2, 3},
   483  					[]int32{3, 2, 1},
   484  				},
   485  			},
   486  		})
   487  	if res != nil || err == nil {
   488  		t.Fatalf("Expected CreateTopics to fail, but got result: %v, err: %v", res, err)
   489  	}
   490  	if ctx.Err() != nil {
   491  		t.Fatalf("Did not expect context to fail: %v", ctx.Err())
   492  	}
   493  	if err.(Error).Code() != ErrInvalidArg {
   494  		t.Fatalf("Expected ErrInvalidArg, not %v", err)
   495  	}
   496  
   497  	// Incorrect input, fail with ErrInvalidArg
   498  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   499  	defer cancel()
   500  	res, err = a.CreateTopics(
   501  		ctx,
   502  		[]TopicSpecification{
   503  			{
   504  				// ReplicaAssignment must be same length as Numpartitions
   505  				Topic:         "mytopic",
   506  				NumPartitions: 7,
   507  				ReplicaAssignment: [][]int32{
   508  					[]int32{1, 2, 3},
   509  					[]int32{3, 2, 1},
   510  				},
   511  			},
   512  		})
   513  	if res != nil || err == nil {
   514  		t.Fatalf("Expected CreateTopics to fail, but got result: %v, err: %v", res, err)
   515  	}
   516  	if ctx.Err() != nil {
   517  		t.Fatalf("Did not expect context to fail: %v", ctx.Err())
   518  	}
   519  	if err.(Error).Code() != ErrInvalidArg {
   520  		t.Fatalf("Expected ErrInvalidArg, not %v", err)
   521  	}
   522  
   523  	// Correct input, using options
   524  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   525  	defer cancel()
   526  	res, err = a.CreateTopics(
   527  		ctx,
   528  		[]TopicSpecification{
   529  			{
   530  				Topic:         "mytopic4",
   531  				NumPartitions: 9,
   532  				ReplicaAssignment: [][]int32{
   533  					[]int32{1},
   534  					[]int32{2},
   535  					[]int32{3},
   536  					[]int32{4},
   537  					[]int32{1},
   538  					[]int32{2},
   539  					[]int32{3},
   540  					[]int32{4},
   541  					[]int32{1},
   542  				},
   543  				Config: map[string]string{
   544  					"some.topic.config":  "unchecked",
   545  					"these.are.verified": "on the broker",
   546  					"and.this.is":        "just",
   547  					"a":                  "unit test",
   548  				},
   549  			},
   550  		},
   551  		SetAdminValidateOnly(false))
   552  	if res != nil || err == nil {
   553  		t.Fatalf("Expected CreateTopics to fail, but got result: %v, err: %v", res, err)
   554  	}
   555  	if ctx.Err() != context.DeadlineExceeded {
   556  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   557  	}
   558  
   559  	//
   560  	// Remaining APIs
   561  	// Timeout code is identical for all APIs, no need to test
   562  	// them for each API.
   563  	//
   564  
   565  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   566  	defer cancel()
   567  	res, err = a.CreatePartitions(
   568  		ctx,
   569  		[]PartitionsSpecification{
   570  			{
   571  				Topic:      "topic",
   572  				IncreaseTo: 19,
   573  				ReplicaAssignment: [][]int32{
   574  					[]int32{3234522},
   575  					[]int32{99999},
   576  				},
   577  			},
   578  			{
   579  				Topic:      "topic2",
   580  				IncreaseTo: 2,
   581  				ReplicaAssignment: [][]int32{
   582  					[]int32{99999},
   583  				},
   584  			},
   585  		})
   586  	if res != nil || err == nil {
   587  		t.Fatalf("Expected CreatePartitions to fail, but got result: %v, err: %v", res, err)
   588  	}
   589  	if ctx.Err() != context.DeadlineExceeded {
   590  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   591  	}
   592  
   593  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   594  	defer cancel()
   595  	res, err = a.DeleteTopics(
   596  		ctx,
   597  		[]string{"topic1", "topic2"})
   598  	if res != nil || err == nil {
   599  		t.Fatalf("Expected DeleteTopics to fail, but got result: %v, err: %v", res, err)
   600  	}
   601  	if ctx.Err() != context.DeadlineExceeded {
   602  		t.Fatalf("Expected DeadlineExceeded, not %v for error %v", ctx.Err(), err)
   603  	}
   604  
   605  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   606  	defer cancel()
   607  	cres, err := a.AlterConfigs(
   608  		ctx,
   609  		[]ConfigResource{{Type: ResourceTopic, Name: "topic"}})
   610  	if cres != nil || err == nil {
   611  		t.Fatalf("Expected AlterConfigs to fail, but got result: %v, err: %v", cres, err)
   612  	}
   613  	if ctx.Err() != context.DeadlineExceeded {
   614  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   615  	}
   616  
   617  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   618  	defer cancel()
   619  	cres, err = a.DescribeConfigs(
   620  		ctx,
   621  		[]ConfigResource{{Type: ResourceTopic, Name: "topic"}})
   622  	if cres != nil || err == nil {
   623  		t.Fatalf("Expected DescribeConfigs to fail, but got result: %v, err: %v", cres, err)
   624  	}
   625  	if ctx.Err() != context.DeadlineExceeded {
   626  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   627  	}
   628  
   629  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   630  	defer cancel()
   631  	clusterID, err := a.ClusterID(ctx)
   632  	if err == nil {
   633  		t.Fatalf("Expected ClusterID to fail, but got result: %v", clusterID)
   634  	}
   635  	if ctx.Err() != context.DeadlineExceeded || err != context.DeadlineExceeded {
   636  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   637  	}
   638  
   639  	ctx, cancel = context.WithTimeout(context.Background(), expDuration)
   640  	defer cancel()
   641  	controllerID, err := a.ControllerID(ctx)
   642  	if err == nil {
   643  		t.Fatalf("Expected ControllerID to fail, but got result: %v", controllerID)
   644  	}
   645  	if ctx.Err() != context.DeadlineExceeded || err != context.DeadlineExceeded {
   646  		t.Fatalf("Expected DeadlineExceeded, not %v", ctx.Err())
   647  	}
   648  
   649  	testAdminAPIsCreateACLs(what, a, t)
   650  	testAdminAPIsDescribeACLs(what, a, t)
   651  	testAdminAPIsDeleteACLs(what, a, t)
   652  }
   653  
   654  // TestAdminAPIs dry-tests most Admin APIs, no broker is needed.
   655  func TestAdminAPIs(t *testing.T) {
   656  
   657  	a, err := NewAdminClient(&ConfigMap{})
   658  	if err != nil {
   659  		t.Fatalf("%s", err)
   660  	}
   661  
   662  	testAdminAPIs("Non-derived, no config", a, t)
   663  	a.Close()
   664  
   665  	a, err = NewAdminClient(&ConfigMap{"retries": 1234})
   666  	if err != nil {
   667  		t.Fatalf("%s", err)
   668  	}
   669  
   670  	testAdminAPIs("Non-derived, config", a, t)
   671  	a.Close()
   672  
   673  	// Test derived clients
   674  	c, err := NewConsumer(&ConfigMap{"group.id": "test"})
   675  	if err != nil {
   676  		t.Fatalf("%s", err)
   677  	}
   678  	defer c.Close()
   679  
   680  	a, err = NewAdminClientFromConsumer(c)
   681  	if err != nil {
   682  		t.Fatalf("%s", err)
   683  	}
   684  
   685  	if !strings.Contains(a.String(), c.String()) {
   686  		t.Fatalf("Expected derived client %s to have similar name to parent %s", a, c)
   687  	}
   688  
   689  	testAdminAPIs("Derived from consumer", a, t)
   690  	a.Close()
   691  
   692  	a, err = NewAdminClientFromConsumer(c)
   693  	if err != nil {
   694  		t.Fatalf("%s", err)
   695  	}
   696  
   697  	if !strings.Contains(a.String(), c.String()) {
   698  		t.Fatalf("Expected derived client %s to have similar name to parent %s", a, c)
   699  	}
   700  
   701  	testAdminAPIs("Derived from same consumer", a, t)
   702  	a.Close()
   703  
   704  	p, err := NewProducer(&ConfigMap{})
   705  	if err != nil {
   706  		t.Fatalf("%s", err)
   707  	}
   708  	defer p.Close()
   709  
   710  	a, err = NewAdminClientFromProducer(p)
   711  	if err != nil {
   712  		t.Fatalf("%s", err)
   713  	}
   714  
   715  	if !strings.Contains(a.String(), p.String()) {
   716  		t.Fatalf("Expected derived client %s to have similar name to parent %s", a, p)
   717  	}
   718  
   719  	testAdminAPIs("Derived from Producer", a, t)
   720  	a.Close()
   721  
   722  	a, err = NewAdminClientFromProducer(p)
   723  	if err != nil {
   724  		t.Fatalf("%s", err)
   725  	}
   726  
   727  	if !strings.Contains(a.String(), p.String()) {
   728  		t.Fatalf("Expected derived client %s to have similar name to parent %s", a, p)
   729  	}
   730  
   731  	testAdminAPIs("Derived from same Producer", a, t)
   732  	a.Close()
   733  }