github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/resourcemanager/fetcher/manager_test.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     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 fetcher
    18  
    19  import (
    20  	"context"
    21  	"io/ioutil"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/require"
    27  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"k8s.io/client-go/tools/record"
    30  	"k8s.io/kubernetes/pkg/kubelet/config"
    31  	"k8s.io/kubernetes/pkg/kubelet/pluginmanager"
    32  	plugincache "k8s.io/kubernetes/pkg/kubelet/pluginmanager/cache"
    33  
    34  	"github.com/kubewharf/katalyst-api/pkg/plugins/registration"
    35  	"github.com/kubewharf/katalyst-api/pkg/plugins/skeleton"
    36  	"github.com/kubewharf/katalyst-api/pkg/protocol/reporterplugin/v1alpha1"
    37  	"github.com/kubewharf/katalyst-core/pkg/agent/resourcemanager/fetcher/plugin"
    38  	"github.com/kubewharf/katalyst-core/pkg/agent/resourcemanager/reporter"
    39  	katalystconfig "github.com/kubewharf/katalyst-core/pkg/config"
    40  	"github.com/kubewharf/katalyst-core/pkg/config/agent"
    41  	"github.com/kubewharf/katalyst-core/pkg/config/agent/metaserver"
    42  	reporterconfig "github.com/kubewharf/katalyst-core/pkg/config/agent/reporter"
    43  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    44  )
    45  
    46  const (
    47  	testPluginName       = "fake-reporter-plugin-1"
    48  	testPluginNameSecond = "fake-reporter-plugin-2"
    49  	testPluginNameThird  = "fake-reporter-plugin-3"
    50  )
    51  
    52  var testGroupVersionKind = v1.GroupVersionKind{
    53  	Group:   "test-group",
    54  	Kind:    "test-kind",
    55  	Version: "test-version",
    56  }
    57  
    58  func tmpSocketDir() (socketDir string, err error) {
    59  	socketDir, err = ioutil.TempDir("", "reporter_plugin")
    60  	if err != nil {
    61  		return
    62  	}
    63  	_ = os.MkdirAll(socketDir, 0o755)
    64  	return
    65  }
    66  
    67  func generateTestConfiguration(dir string) *katalystconfig.Configuration {
    68  	return &katalystconfig.Configuration{
    69  		AgentConfiguration: &agent.AgentConfiguration{
    70  			GenericAgentConfiguration: &agent.GenericAgentConfiguration{
    71  				MetaServerConfiguration: &metaserver.MetaServerConfiguration{CheckpointManagerDir: dir},
    72  				GenericReporterConfiguration: &reporterconfig.GenericReporterConfiguration{
    73  					CollectInterval: 5 * time.Second,
    74  				},
    75  			},
    76  		},
    77  	}
    78  }
    79  
    80  func TestNewManagerImpl(t *testing.T) {
    81  	t.Parallel()
    82  
    83  	socketDir, err := tmpSocketDir()
    84  	testReporter := reporter.NewReporterManagerStub()
    85  	require.NoError(t, err)
    86  	defer os.RemoveAll(socketDir)
    87  	_, err = NewReporterPluginManager(testReporter, metrics.DummyMetrics{}, nil, generateTestConfiguration(socketDir))
    88  	require.NoError(t, err)
    89  	os.RemoveAll(socketDir)
    90  }
    91  
    92  // Tests that the device plugin manager correctly handles registration and re-registration by
    93  // making sure that after registration, devices are correctly updated and if a re-registration
    94  // happens, we will NOT delete devices; and no orphaned devices left.
    95  func TestReporterPluginReRegistration(t *testing.T) {
    96  	t.Parallel()
    97  
    98  	socketDir, err := tmpSocketDir()
    99  	require.NoError(t, err)
   100  	defer os.RemoveAll(socketDir)
   101  
   102  	content1 := []*v1alpha1.ReportContent{
   103  		{
   104  			GroupVersionKind: &testGroupVersionKind,
   105  			Field: []*v1alpha1.ReportField{
   106  				{
   107  					FieldType: v1alpha1.FieldType_Spec,
   108  					FieldName: "fieldName_a",
   109  					Value:     []byte("Value_a"),
   110  				},
   111  			},
   112  		},
   113  	}
   114  
   115  	testReporter := reporter.NewReporterManagerStub()
   116  
   117  	ctx, cancel := context.WithCancel(context.TODO())
   118  	defer cancel()
   119  	_, ch, p1 := setup(t, ctx, content1, nil, socketDir, testPluginName, testReporter)
   120  
   121  	select {
   122  	case <-ch:
   123  	case <-time.After(5 * time.Second):
   124  		t.Fatalf("timeout while waiting for manager update")
   125  	}
   126  
   127  	p1GetReportContentResponse := testReporter.GetReportContentResponse(p1.Name())
   128  	require.NotNil(t, p1GetReportContentResponse)
   129  	reporterContentsEqual(t, content1, p1GetReportContentResponse.Content)
   130  
   131  	content2 := []*v1alpha1.ReportContent{
   132  		{
   133  			GroupVersionKind: &testGroupVersionKind,
   134  			Field: []*v1alpha1.ReportField{
   135  				{
   136  					FieldType: v1alpha1.FieldType_Spec,
   137  					FieldName: "fieldName_b",
   138  					Value:     []byte("Value_b"),
   139  				},
   140  			},
   141  		},
   142  	}
   143  
   144  	p2 := setupReporterPlugin(t, content2, socketDir, testPluginNameSecond)
   145  
   146  	select {
   147  	case <-ch:
   148  	case <-time.After(5 * time.Second):
   149  		t.Fatalf("timeout while waiting for manager update")
   150  	}
   151  
   152  	p1GetReportContentResponse = testReporter.GetReportContentResponse(p1.Name())
   153  	require.NotNil(t, p1GetReportContentResponse)
   154  	reporterContentsEqual(t, content1, p1GetReportContentResponse.Content)
   155  
   156  	p2GetReportContentResponse := testReporter.GetReportContentResponse(p2.Name())
   157  	require.NotNil(t, p2GetReportContentResponse)
   158  	reporterContentsEqual(t, content2, p2GetReportContentResponse.Content)
   159  
   160  	// test the scenario that plugin de-register and graceful shut down
   161  	_ = p1.Stop()
   162  	_ = p2.Stop()
   163  }
   164  
   165  func TestHealthz(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	socketDir, err := tmpSocketDir()
   169  	require.NoError(t, err)
   170  	defer os.RemoveAll(socketDir)
   171  
   172  	content1 := []*v1alpha1.ReportContent{
   173  		{
   174  			GroupVersionKind: &testGroupVersionKind,
   175  			Field: []*v1alpha1.ReportField{
   176  				{
   177  					FieldType: v1alpha1.FieldType_Spec,
   178  					FieldName: "fieldName_a",
   179  					Value:     []byte("Value_a"),
   180  				},
   181  			},
   182  		},
   183  	}
   184  
   185  	testReporter := reporter.NewReporterManagerStub()
   186  
   187  	ctx, cancel := context.WithCancel(context.TODO())
   188  	defer cancel()
   189  	_, ch, p := setup(t, ctx, content1, nil, socketDir, testPluginNameThird, testReporter)
   190  
   191  	select {
   192  	case <-ch:
   193  	case <-time.After(6 * time.Second):
   194  		t.Fatalf("timeout while waiting for manager update")
   195  	}
   196  
   197  	_ = p.Stop()
   198  }
   199  
   200  func setup(t *testing.T, ctx context.Context, content []*v1alpha1.ReportContent, callback plugin.ListAndWatchCallback, socketDir string, pluginSocketName string, reporter reporter.Manager) (registration.AgentPluginHandler, <-chan interface{}, skeleton.GenericPlugin) {
   201  	m, updateChan := setupReporterManager(t, ctx, content, socketDir, callback, reporter)
   202  	p := setupReporterPlugin(t, content, socketDir, pluginSocketName)
   203  	return m, updateChan, p
   204  }
   205  
   206  func setupReporterManager(t *testing.T, ctx context.Context, content []*v1alpha1.ReportContent, socketDir string, callback plugin.ListAndWatchCallback, reporter reporter.Manager) (registration.AgentPluginHandler, <-chan interface{}) {
   207  	m, err := NewReporterPluginManager(reporter, metrics.DummyMetrics{}, nil, generateTestConfiguration(socketDir))
   208  	require.NoError(t, err)
   209  	updateChan := make(chan interface{})
   210  
   211  	if callback != nil {
   212  		m.callback = callback
   213  	}
   214  
   215  	originalCallback := m.callback
   216  	m.callback = func(pluginName string, response *v1alpha1.GetReportContentResponse) {
   217  		originalCallback(pluginName, response)
   218  		updateChan <- new(interface{})
   219  	}
   220  
   221  	go m.Run(ctx)
   222  
   223  	pluginManager := pluginmanager.NewPluginManager(
   224  		socketDir,
   225  		&record.FakeRecorder{},
   226  	)
   227  
   228  	pluginManager.AddHandler(m.GetHandlerType(), plugincache.PluginHandler(m))
   229  
   230  	go pluginManager.Run(config.NewSourcesReady(func(_ sets.String) bool { return true }), ctx.Done())
   231  
   232  	return m, updateChan
   233  }
   234  
   235  func setupReporterPlugin(t *testing.T, content []*v1alpha1.ReportContent, socketDir string, pluginName string) skeleton.GenericPlugin {
   236  	p, _ := skeleton.NewRegistrationPluginWrapper(
   237  		skeleton.NewReporterPluginStub(content, pluginName),
   238  		[]string{socketDir}, nil)
   239  	err := p.Start()
   240  	require.NoError(t, err)
   241  	return p
   242  }
   243  
   244  func reporterContentsEqual(t *testing.T, expected, actual []*v1alpha1.ReportContent) {
   245  	require.Equal(t, len(expected), len(actual))
   246  	for idx := range expected {
   247  		require.Equal(t, expected[idx].GroupVersionKind, actual[idx].GroupVersionKind)
   248  		require.Equal(t, len(expected[idx].Field), len(actual[idx].Field))
   249  		for fieldIdx := range expected[idx].Field {
   250  			require.Equal(t, expected[idx].Field[fieldIdx].FieldType, actual[idx].Field[fieldIdx].FieldType)
   251  			require.Equal(t, expected[idx].Field[fieldIdx].FieldName, actual[idx].Field[fieldIdx].FieldName)
   252  			require.Equal(t, expected[idx].Field[fieldIdx].Value, actual[idx].Field[fieldIdx].Value)
   253  		}
   254  	}
   255  }