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 }