operation_generator.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package operationexecutor implements interfaces that enable execution of
  14. // register and unregister operations with a
  15. // goroutinemap so that more than one operation is never triggered
  16. // on the same plugin.
  17. package operationexecutor
  18. import (
  19. "context"
  20. "fmt"
  21. "net"
  22. "time"
  23. "k8s.io/klog"
  24. "github.com/pkg/errors"
  25. "google.golang.org/grpc"
  26. "k8s.io/client-go/tools/record"
  27. registerapi "k8s.io/kubelet/pkg/apis/pluginregistration/v1"
  28. "k8s.io/kubernetes/pkg/kubelet/pluginmanager/cache"
  29. )
  30. const (
  31. dialTimeoutDuration = 10 * time.Second
  32. notifyTimeoutDuration = 5 * time.Second
  33. )
  34. var _ OperationGenerator = &operationGenerator{}
  35. type operationGenerator struct {
  36. // recorder is used to record events in the API server
  37. recorder record.EventRecorder
  38. }
  39. // NewOperationGenerator is returns instance of operationGenerator
  40. func NewOperationGenerator(recorder record.EventRecorder) OperationGenerator {
  41. return &operationGenerator{
  42. recorder: recorder,
  43. }
  44. }
  45. // OperationGenerator interface that extracts out the functions from operation_executor to make it dependency injectable
  46. type OperationGenerator interface {
  47. // Generates the RegisterPlugin function needed to perform the registration of a plugin
  48. GenerateRegisterPluginFunc(
  49. socketPath string,
  50. timestamp time.Time,
  51. pluginHandlers map[string]cache.PluginHandler,
  52. actualStateOfWorldUpdater ActualStateOfWorldUpdater) func() error
  53. // Generates the UnregisterPlugin function needed to perform the unregistration of a plugin
  54. GenerateUnregisterPluginFunc(
  55. socketPath string,
  56. pluginHandlers map[string]cache.PluginHandler,
  57. actualStateOfWorldUpdater ActualStateOfWorldUpdater) func() error
  58. }
  59. func (og *operationGenerator) GenerateRegisterPluginFunc(
  60. socketPath string,
  61. timestamp time.Time,
  62. pluginHandlers map[string]cache.PluginHandler,
  63. actualStateOfWorldUpdater ActualStateOfWorldUpdater) func() error {
  64. registerPluginFunc := func() error {
  65. client, conn, err := dial(socketPath, dialTimeoutDuration)
  66. if err != nil {
  67. return fmt.Errorf("RegisterPlugin error -- dial failed at socket %s, err: %v", socketPath, err)
  68. }
  69. defer conn.Close()
  70. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  71. defer cancel()
  72. infoResp, err := client.GetInfo(ctx, &registerapi.InfoRequest{})
  73. if err != nil {
  74. return fmt.Errorf("RegisterPlugin error -- failed to get plugin info using RPC GetInfo at socket %s, err: %v", socketPath, err)
  75. }
  76. handler, ok := pluginHandlers[infoResp.Type]
  77. if !ok {
  78. if err := og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath)); err != nil {
  79. return fmt.Errorf("RegisterPlugin error -- failed to send error at socket %s, err: %v", socketPath, err)
  80. }
  81. return fmt.Errorf("RegisterPlugin error -- no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath)
  82. }
  83. if infoResp.Endpoint == "" {
  84. infoResp.Endpoint = socketPath
  85. }
  86. if err := handler.ValidatePlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil {
  87. if err = og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- plugin validation failed with err: %v", err)); err != nil {
  88. return fmt.Errorf("RegisterPlugin error -- failed to send error at socket %s, err: %v", socketPath, err)
  89. }
  90. return fmt.Errorf("RegisterPlugin error -- pluginHandler.ValidatePluginFunc failed")
  91. }
  92. // We add the plugin to the actual state of world cache before calling a plugin consumer's Register handle
  93. // so that if we receive a delete event during Register Plugin, we can process it as a DeRegister call.
  94. err = actualStateOfWorldUpdater.AddPlugin(cache.PluginInfo{
  95. SocketPath: socketPath,
  96. Timestamp: timestamp,
  97. })
  98. if err != nil {
  99. klog.Errorf("RegisterPlugin error -- failed to add plugin at socket %s, err: %v", socketPath, err)
  100. }
  101. if err := handler.RegisterPlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil {
  102. return og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- plugin registration failed with err: %v", err))
  103. }
  104. // Notify is called after register to guarantee that even if notify throws an error Register will always be called after validate
  105. if err := og.notifyPlugin(client, true, ""); err != nil {
  106. return fmt.Errorf("RegisterPlugin error -- failed to send registration status at socket %s, err: %v", socketPath, err)
  107. }
  108. return nil
  109. }
  110. return registerPluginFunc
  111. }
  112. func (og *operationGenerator) GenerateUnregisterPluginFunc(
  113. socketPath string,
  114. pluginHandlers map[string]cache.PluginHandler,
  115. actualStateOfWorldUpdater ActualStateOfWorldUpdater) func() error {
  116. unregisterPluginFunc := func() error {
  117. client, conn, err := dial(socketPath, dialTimeoutDuration)
  118. if err != nil {
  119. return fmt.Errorf("UnregisterPlugin error -- dial failed at socket %s, err: %v", socketPath, err)
  120. }
  121. defer conn.Close()
  122. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  123. defer cancel()
  124. infoResp, err := client.GetInfo(ctx, &registerapi.InfoRequest{})
  125. if err != nil {
  126. return fmt.Errorf("UnregisterPlugin error -- failed to get plugin info using RPC GetInfo at socket %s, err: %v", socketPath, err)
  127. }
  128. handler, ok := pluginHandlers[infoResp.Type]
  129. if !ok {
  130. return fmt.Errorf("UnregisterPlugin error -- no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath)
  131. }
  132. // We remove the plugin to the actual state of world cache before calling a plugin consumer's Unregister handle
  133. // so that if we receive a register event during Register Plugin, we can process it as a Register call.
  134. actualStateOfWorldUpdater.RemovePlugin(socketPath)
  135. handler.DeRegisterPlugin(infoResp.Name)
  136. return nil
  137. }
  138. return unregisterPluginFunc
  139. }
  140. func (og *operationGenerator) notifyPlugin(client registerapi.RegistrationClient, registered bool, errStr string) error {
  141. ctx, cancel := context.WithTimeout(context.Background(), notifyTimeoutDuration)
  142. defer cancel()
  143. status := &registerapi.RegistrationStatus{
  144. PluginRegistered: registered,
  145. Error: errStr,
  146. }
  147. if _, err := client.NotifyRegistrationStatus(ctx, status); err != nil {
  148. return errors.Wrap(err, errStr)
  149. }
  150. if errStr != "" {
  151. return errors.New(errStr)
  152. }
  153. return nil
  154. }
  155. // Dial establishes the gRPC communication with the picked up plugin socket. https://godoc.org/google.golang.org/grpc#Dial
  156. func dial(unixSocketPath string, timeout time.Duration) (registerapi.RegistrationClient, *grpc.ClientConn, error) {
  157. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  158. defer cancel()
  159. c, err := grpc.DialContext(ctx, unixSocketPath, grpc.WithInsecure(), grpc.WithBlock(),
  160. grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
  161. return (&net.Dialer{}).DialContext(ctx, "unix", addr)
  162. }),
  163. )
  164. if err != nil {
  165. return nil, nil, fmt.Errorf("failed to dial socket %s, err: %v", unixSocketPath, err)
  166. }
  167. return registerapi.NewRegistrationClient(c), c, nil
  168. }