dubbo.apache.org/dubbo-go/v3@v3.1.1/config_center/zookeeper/impl.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package zookeeper
    19  
    20  import (
    21  	"encoding/base64"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  )
    26  
    27  import (
    28  	"github.com/dubbogo/go-zookeeper/zk"
    29  
    30  	gxset "github.com/dubbogo/gost/container/set"
    31  	gxzookeeper "github.com/dubbogo/gost/database/kv/zk"
    32  	"github.com/dubbogo/gost/log/logger"
    33  
    34  	perrors "github.com/pkg/errors"
    35  )
    36  
    37  import (
    38  	"dubbo.apache.org/dubbo-go/v3/common"
    39  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    40  	"dubbo.apache.org/dubbo-go/v3/config"
    41  	"dubbo.apache.org/dubbo-go/v3/config_center"
    42  	"dubbo.apache.org/dubbo-go/v3/config_center/parser"
    43  	"dubbo.apache.org/dubbo-go/v3/remoting/zookeeper"
    44  )
    45  
    46  const (
    47  	pathSeparator = "/"
    48  )
    49  
    50  type zookeeperDynamicConfiguration struct {
    51  	config_center.BaseDynamicConfiguration
    52  	url      *common.URL
    53  	rootPath string
    54  	wg       sync.WaitGroup
    55  	cltLock  sync.Mutex
    56  	done     chan struct{}
    57  	client   *gxzookeeper.ZookeeperClient
    58  
    59  	// listenerLock  sync.Mutex
    60  	listener      *zookeeper.ZkEventListener
    61  	cacheListener *CacheListener
    62  	parser        parser.ConfigurationParser
    63  
    64  	base64Enabled bool
    65  }
    66  
    67  func newZookeeperDynamicConfiguration(url *common.URL) (*zookeeperDynamicConfiguration, error) {
    68  	c := &zookeeperDynamicConfiguration{
    69  		url: url,
    70  		// TODO adapt config center config
    71  		rootPath: "/dubbo/config",
    72  	}
    73  	logger.Infof("[Zookeeper ConfigCenter] New Zookeeper ConfigCenter with Configuration: %+v, url = %+v", c, c.GetURL())
    74  	if v, ok := config.GetRootConfig().ConfigCenter.Params["base64"]; ok {
    75  		base64Enabled, err := strconv.ParseBool(v)
    76  		if err != nil {
    77  			panic("value of base64 must be bool, error=" + err.Error())
    78  		}
    79  		c.base64Enabled = base64Enabled
    80  	}
    81  
    82  	err := zookeeper.ValidateZookeeperClient(c, url.Location)
    83  	if err != nil {
    84  		logger.Errorf("zookeeper client start error ,error message is %v", err)
    85  		return nil, err
    86  	}
    87  	err = c.client.Create(c.rootPath)
    88  	if err != nil && err != zk.ErrNodeExists {
    89  		return nil, err
    90  	}
    91  
    92  	// Before handle client restart, we need to ensure that the zk dynamic configuration successfully start and create the configuration directory
    93  	c.wg.Add(1)
    94  	go zookeeper.HandleClientRestart(c)
    95  
    96  	// Start listener
    97  	c.listener = zookeeper.NewZkEventListener(c.client)
    98  	c.cacheListener = NewCacheListener(c.rootPath, c.listener)
    99  	c.listener.ListenConfigurationEvent(c.rootPath, c.cacheListener)
   100  	return c, nil
   101  }
   102  
   103  // AddListener add listener for key
   104  // TODO this method should has a parameter 'group', and it does not now, so we should concat group and key with '/' manually
   105  func (c *zookeeperDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, options ...config_center.Option) {
   106  	key = strings.Join([]string{c.GetURL().GetParam(constant.ConfigNamespaceKey, config_center.DefaultGroup), key}, "/")
   107  	qualifiedKey := buildPath(c.rootPath, key)
   108  	c.cacheListener.AddListener(qualifiedKey, listener)
   109  }
   110  
   111  // buildPath build path and format
   112  func buildPath(rootPath, subPath string) string {
   113  	path := strings.TrimRight(rootPath+pathSeparator+subPath, pathSeparator)
   114  	if !strings.HasPrefix(path, pathSeparator) {
   115  		path = pathSeparator + path
   116  	}
   117  	path = strings.ReplaceAll(path, "//", "/")
   118  	return path
   119  }
   120  
   121  func (c *zookeeperDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener, opions ...config_center.Option) {
   122  	c.cacheListener.RemoveListener(key, listener)
   123  }
   124  
   125  func (c *zookeeperDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) {
   126  	tmpOpts := &config_center.Options{}
   127  	for _, opt := range opts {
   128  		opt(tmpOpts)
   129  	}
   130  	/**
   131  	 * when group is not null, we are getting startup configs from Config Center, for example:
   132  	 * group=dubbo, key=dubbo.properties
   133  	 */
   134  	if len(tmpOpts.Group) != 0 {
   135  		key = tmpOpts.Group + "/" + key
   136  	} else {
   137  		key = c.GetURL().GetParam(constant.ConfigNamespaceKey, config_center.DefaultGroup) + "/" + key
   138  	}
   139  	content, _, err := c.client.GetContent(c.rootPath + "/" + key)
   140  	if err != nil {
   141  		return "", perrors.WithStack(err)
   142  	}
   143  	if !c.base64Enabled {
   144  		return string(content), nil
   145  	}
   146  
   147  	decoded, err := base64.StdEncoding.DecodeString(string(content))
   148  	if err != nil {
   149  		return "", perrors.WithStack(err)
   150  	}
   151  	return string(decoded), nil
   152  }
   153  
   154  // GetInternalProperty For zookeeper, getConfig and getConfigs have the same meaning.
   155  func (c *zookeeperDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) {
   156  	return c.GetProperties(key, opts...)
   157  }
   158  
   159  // PublishConfig will put the value into Zk with specific path
   160  func (c *zookeeperDynamicConfiguration) PublishConfig(key string, group string, value string) error {
   161  	path := c.getPath(key, group)
   162  	valueBytes := []byte(value)
   163  	if c.base64Enabled {
   164  		valueBytes = []byte(base64.StdEncoding.EncodeToString(valueBytes))
   165  	}
   166  	// FIXME this method need to be fixed, because it will recursively
   167  	// create every node in the path with given value which we may not expected.
   168  	err := c.client.CreateWithValue(path, valueBytes)
   169  	if err != nil {
   170  		// try update value if node already exists
   171  		if perrors.Is(err, zk.ErrNodeExists) {
   172  			_, stat, _ := c.client.GetContent(path)
   173  			_, setErr := c.client.SetContent(path, valueBytes, stat.Version)
   174  			if setErr != nil {
   175  				return perrors.WithStack(setErr)
   176  			}
   177  			return nil
   178  		}
   179  		return perrors.WithStack(err)
   180  	}
   181  	return nil
   182  }
   183  
   184  // RemoveConfig will remove the config with the (key, group) pair
   185  func (c *zookeeperDynamicConfiguration) RemoveConfig(key string, group string) error {
   186  	path := c.getPath(key, group)
   187  	err := c.client.Delete(path)
   188  	if err != nil {
   189  		return perrors.WithStack(err)
   190  	}
   191  	return nil
   192  }
   193  
   194  // GetConfigKeysByGroup will return all keys with the group
   195  func (c *zookeeperDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
   196  	path := c.getPath("", group)
   197  	result, err := c.client.GetChildren(path)
   198  	if err != nil {
   199  		return nil, perrors.WithStack(err)
   200  	}
   201  
   202  	if len(result) == 0 {
   203  		return nil, perrors.New("could not find keys with group: " + group)
   204  	}
   205  	set := gxset.NewSet()
   206  	for _, e := range result {
   207  		set.Add(e)
   208  	}
   209  	return set, nil
   210  }
   211  
   212  func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
   213  	return c.GetProperties(key, opts...)
   214  }
   215  
   216  func (c *zookeeperDynamicConfiguration) Parser() parser.ConfigurationParser {
   217  	return c.parser
   218  }
   219  
   220  func (c *zookeeperDynamicConfiguration) SetParser(p parser.ConfigurationParser) {
   221  	c.parser = p
   222  }
   223  
   224  func (c *zookeeperDynamicConfiguration) ZkClient() *gxzookeeper.ZookeeperClient {
   225  	return c.client
   226  }
   227  
   228  func (c *zookeeperDynamicConfiguration) SetZkClient(client *gxzookeeper.ZookeeperClient) {
   229  	c.client = client
   230  }
   231  
   232  func (c *zookeeperDynamicConfiguration) ZkClientLock() *sync.Mutex {
   233  	return &c.cltLock
   234  }
   235  
   236  func (c *zookeeperDynamicConfiguration) WaitGroup() *sync.WaitGroup {
   237  	return &c.wg
   238  }
   239  
   240  func (c *zookeeperDynamicConfiguration) Done() chan struct{} {
   241  	return c.done
   242  }
   243  
   244  func (c *zookeeperDynamicConfiguration) GetURL() *common.URL {
   245  	return c.url
   246  }
   247  
   248  func (c *zookeeperDynamicConfiguration) Destroy() {
   249  	if c.listener != nil {
   250  		c.listener.Close()
   251  	}
   252  	close(c.done)
   253  	c.wg.Wait()
   254  	c.closeConfigs()
   255  }
   256  
   257  func (c *zookeeperDynamicConfiguration) IsAvailable() bool {
   258  	select {
   259  	case <-c.done:
   260  		return false
   261  	default:
   262  		return true
   263  	}
   264  }
   265  
   266  func (c *zookeeperDynamicConfiguration) closeConfigs() {
   267  	logger.Infof("begin to close provider zk client")
   268  	c.cltLock.Lock()
   269  	defer c.cltLock.Unlock()
   270  	c.client.Close()
   271  	c.client = nil
   272  }
   273  
   274  func (c *zookeeperDynamicConfiguration) RestartCallBack() bool {
   275  	return true
   276  }
   277  
   278  func (c *zookeeperDynamicConfiguration) getPath(key string, group string) string {
   279  	if len(key) == 0 {
   280  		return c.buildPath(group)
   281  	}
   282  	return c.buildPath(group) + pathSeparator + key
   283  }
   284  
   285  func (c *zookeeperDynamicConfiguration) buildPath(group string) string {
   286  	if len(group) == 0 {
   287  		group = config_center.DefaultGroup
   288  	}
   289  	return c.rootPath + pathSeparator + group
   290  }