You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

websocket.go 5.8 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. Copyright 2021 The KubeEdge 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 gmclient
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "net/http"
  18. "net/url"
  19. "time"
  20. "github.com/gorilla/websocket"
  21. "k8s.io/klog/v2"
  22. "github.com/kubeedge/sedna/cmd/sedna-lc/app/options"
  23. "github.com/kubeedge/sedna/pkg/localcontroller/common/constants"
  24. )
  25. // wsClient defines a websocket client
  26. type wsClient struct {
  27. Options *options.LocalControllerOptions
  28. WSConnection *WSConnection
  29. SubscribeMessageMap map[string]MessageResourceHandler
  30. SendMessageChannel chan Message
  31. ReconnectChannel chan struct{}
  32. }
  33. // WSConnection defines conn
  34. type WSConnection struct {
  35. WSConn *websocket.Conn
  36. }
  37. const (
  38. // RetryCount is count of retrying to connecting to global manager
  39. RetryCount = 5
  40. // RetryConnectIntervalSeconds is interval time of retrying to connecting to global manager
  41. RetryConnectIntervalSeconds = 5
  42. // MessageChannelCacheSize is size of channel cache
  43. MessageChannelCacheSize = 100
  44. )
  45. // NewWebSocketClient creates client
  46. func NewWebSocketClient(options *options.LocalControllerOptions) ClientI {
  47. c := wsClient{
  48. Options: options,
  49. SubscribeMessageMap: make(map[string]MessageResourceHandler),
  50. SendMessageChannel: make(chan Message, MessageChannelCacheSize),
  51. }
  52. return &c
  53. }
  54. // Subscribe registers in client
  55. func (c *wsClient) Subscribe(m MessageResourceHandler) error {
  56. name := m.GetName()
  57. if c.SubscribeMessageMap[name] == nil {
  58. c.SubscribeMessageMap[name] = m
  59. } else {
  60. klog.Warningf("%s had been registered in websocket client", name)
  61. }
  62. return nil
  63. }
  64. // handleReceivedMessage handles received message
  65. func (c *wsClient) handleReceivedMessage(stop chan struct{}) {
  66. defer func() {
  67. stop <- struct{}{}
  68. }()
  69. ws := c.WSConnection.WSConn
  70. for {
  71. message := Message{}
  72. if err := ws.ReadJSON(&message); err != nil {
  73. klog.Errorf("client received message from global manager(address: %s) failed, error: %v",
  74. c.Options.GMAddr, err)
  75. return
  76. }
  77. klog.V(2).Infof("client received message header: %+v from global manager(address: %s)",
  78. message.Header, c.Options.GMAddr)
  79. klog.V(4).Infof("client received message content: %s from global manager(address: %s)",
  80. message.Content, c.Options.GMAddr)
  81. m := c.SubscribeMessageMap[message.Header.ResourceKind]
  82. if m != nil {
  83. go func() {
  84. var err error
  85. switch message.Header.Operation {
  86. case InsertOperation:
  87. err = m.Insert(&message)
  88. case DeleteOperation:
  89. err = m.Delete(&message)
  90. default:
  91. err = fmt.Errorf("unknown operation: %s", message.Header.Operation)
  92. }
  93. if err != nil {
  94. klog.Errorf("failed to handle message(%+v): %v", message.Header, err)
  95. }
  96. }()
  97. } else {
  98. klog.Errorf("%s hadn't registered in websocket client", message.Header.ResourceKind)
  99. }
  100. }
  101. }
  102. // WriteMessage saves message in a queue
  103. func (c *wsClient) WriteMessage(messageBody interface{}, messageHeader MessageHeader) error {
  104. content, err := json.Marshal(&messageBody)
  105. if err != nil {
  106. return err
  107. }
  108. message := Message{
  109. Content: content,
  110. Header: messageHeader,
  111. }
  112. c.SendMessageChannel <- message
  113. return nil
  114. }
  115. // sendMessage sends the message through the connection
  116. func (c *wsClient) sendMessage(stop chan struct{}) {
  117. defer func() {
  118. stop <- struct{}{}
  119. }()
  120. ws := c.WSConnection.WSConn
  121. for {
  122. message, ok := <-c.SendMessageChannel
  123. if !ok {
  124. return
  125. }
  126. if err := ws.WriteJSON(&message); err != nil {
  127. klog.Errorf("client sent message to global manager(address: %s) failed, error: %v",
  128. c.Options.GMAddr, err)
  129. c.SendMessageChannel <- message
  130. return
  131. }
  132. klog.V(2).Infof("client sent message header: %+v to global manager(address: %s)",
  133. message.Header, c.Options.GMAddr)
  134. klog.V(4).Infof("client sent message content: %s to global manager(address: %s)",
  135. message.Content, c.Options.GMAddr)
  136. }
  137. }
  138. // connect tries to connect remote server
  139. func (c *wsClient) connect() error {
  140. header := http.Header{}
  141. header.Add(constants.WSHeaderNodeName, c.Options.NodeName)
  142. u := url.URL{Scheme: constants.WSScheme, Host: c.Options.GMAddr, Path: "/"}
  143. klog.Infof("client starts to connect global manager(address: %s)", c.Options.GMAddr)
  144. for i := 0; i < RetryCount; i++ {
  145. wsConn, _, err := websocket.DefaultDialer.Dial(u.String(), header)
  146. if err == nil {
  147. if errW := wsConn.WriteJSON(&MessageHeader{}); errW != nil {
  148. return errW
  149. }
  150. c.WSConnection = &WSConnection{WSConn: wsConn}
  151. klog.Infof("websocket connects global manager(address: %s) successful", c.Options.GMAddr)
  152. return nil
  153. }
  154. klog.Errorf("client tries to connect global manager(address: %s) failed, error: %v",
  155. c.Options.GMAddr, err)
  156. time.Sleep(time.Duration(RetryConnectIntervalSeconds) * time.Second)
  157. }
  158. errorMsg := fmt.Errorf("max retry count reached when connecting global manager(address: %s)",
  159. c.Options.GMAddr)
  160. klog.Errorf("%v", errorMsg)
  161. return errorMsg
  162. }
  163. // Start starts websocket client
  164. func (c *wsClient) Start() error {
  165. go c.reconnect()
  166. return nil
  167. }
  168. // reconnect reconnects global manager
  169. func (c *wsClient) reconnect() {
  170. for {
  171. if err := c.connect(); err != nil {
  172. continue
  173. }
  174. ws := c.WSConnection.WSConn
  175. stop := make(chan struct{}, 2)
  176. go c.handleReceivedMessage(stop)
  177. go c.sendMessage(stop)
  178. <-stop
  179. _ = ws.Close()
  180. }
  181. }