dubbo.apache.org/dubbo-go/v3@v3.1.1/metadata/report/nacos/report.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 nacos 19 20 import ( 21 "encoding/json" 22 "net/url" 23 "strings" 24 ) 25 26 import ( 27 gxset "github.com/dubbogo/gost/container/set" 28 nacosClient "github.com/dubbogo/gost/database/kv/nacos" 29 "github.com/dubbogo/gost/log/logger" 30 31 "github.com/nacos-group/nacos-sdk-go/v2/vo" 32 33 perrors "github.com/pkg/errors" 34 ) 35 36 import ( 37 "dubbo.apache.org/dubbo-go/v3/common" 38 "dubbo.apache.org/dubbo-go/v3/common/constant" 39 "dubbo.apache.org/dubbo-go/v3/common/extension" 40 "dubbo.apache.org/dubbo-go/v3/metadata/identifier" 41 "dubbo.apache.org/dubbo-go/v3/metadata/mapping/metadata" 42 "dubbo.apache.org/dubbo-go/v3/metadata/report" 43 "dubbo.apache.org/dubbo-go/v3/metadata/report/factory" 44 "dubbo.apache.org/dubbo-go/v3/registry" 45 "dubbo.apache.org/dubbo-go/v3/remoting/nacos" 46 ) 47 48 const ( 49 // the number is a little big tricky 50 // it will be used in query which looks up all keys with the target group 51 // now, one key represents one application 52 // so only a group has more than 9999 applications will failed 53 maxKeysNum = 9999 54 ) 55 56 func init() { 57 mf := &nacosMetadataReportFactory{} 58 extension.SetMetadataReportFactory("nacos", func() factory.MetadataReportFactory { 59 return mf 60 }) 61 } 62 63 // nacosMetadataReport is the implementation 64 // of MetadataReport based on nacos. 65 type nacosMetadataReport struct { 66 client *nacosClient.NacosConfigClient 67 group string 68 } 69 70 // GetAppMetadata get metadata info from nacos 71 func (n *nacosMetadataReport) GetAppMetadata(metadataIdentifier *identifier.SubscriberMetadataIdentifier) (*common.MetadataInfo, error) { 72 // compatible with java impl first 73 data, err := n.getConfig(vo.ConfigParam{ 74 DataId: metadataIdentifier.Application, 75 Group: metadataIdentifier.Revision, 76 }) 77 if err != nil { 78 return nil, err 79 } 80 if data == "" { 81 // compatible with dubbo-go 3.1.x before 82 data, err = n.getConfig(vo.ConfigParam{ 83 DataId: metadataIdentifier.GetIdentifierKey(), 84 Group: n.group, 85 }) 86 if err != nil { 87 return nil, err 88 } 89 } 90 var metadataInfo common.MetadataInfo 91 err = json.Unmarshal([]byte(data), &metadataInfo) 92 if err != nil { 93 return nil, err 94 } 95 return &metadataInfo, nil 96 } 97 98 // PublishAppMetadata publish metadata info to nacos 99 func (n *nacosMetadataReport) PublishAppMetadata(metadataIdentifier *identifier.SubscriberMetadataIdentifier, info *common.MetadataInfo) error { 100 data, err := json.Marshal(info) 101 if err != nil { 102 return err 103 } 104 // compatible with java impl 105 err = n.storeMetadata(vo.ConfigParam{ 106 DataId: metadataIdentifier.Application, 107 Group: metadataIdentifier.Revision, 108 Content: string(data), 109 }) 110 if err != nil { 111 return err 112 } 113 // compatible with dubbo-go 3.1.x before 114 return n.storeMetadata(vo.ConfigParam{ 115 DataId: metadataIdentifier.GetIdentifierKey(), 116 Group: n.group, 117 Content: string(data), 118 }) 119 } 120 121 // StoreProviderMetadata stores the metadata. 122 func (n *nacosMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { 123 return n.storeMetadata(vo.ConfigParam{ 124 DataId: providerIdentifier.GetIdentifierKey(), 125 Group: n.group, 126 Content: serviceDefinitions, 127 }) 128 } 129 130 // StoreConsumerMetadata stores the metadata. 131 func (n *nacosMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { 132 return n.storeMetadata(vo.ConfigParam{ 133 DataId: consumerMetadataIdentifier.GetIdentifierKey(), 134 Group: n.group, 135 Content: serviceParameterString, 136 }) 137 } 138 139 // SaveServiceMetadata saves the metadata. 140 func (n *nacosMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { 141 return n.storeMetadata(vo.ConfigParam{ 142 DataId: metadataIdentifier.GetIdentifierKey(), 143 Group: n.group, 144 Content: url.String(), 145 }) 146 } 147 148 // RemoveServiceMetadata removes the metadata. 149 func (n *nacosMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { 150 return n.deleteMetadata(vo.ConfigParam{ 151 DataId: metadataIdentifier.GetIdentifierKey(), 152 Group: n.group, 153 }) 154 } 155 156 // GetExportedURLs gets the urls. 157 func (n *nacosMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) { 158 return n.getConfigAsArray(vo.ConfigParam{ 159 DataId: metadataIdentifier.GetIdentifierKey(), 160 Group: n.group, 161 }) 162 } 163 164 // SaveSubscribedData saves the urls. 165 func (n *nacosMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error { 166 return n.storeMetadata(vo.ConfigParam{ 167 DataId: subscriberMetadataIdentifier.GetIdentifierKey(), 168 Content: urls, 169 }) 170 } 171 172 // GetSubscribedURLs gets the urls. 173 func (n *nacosMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { 174 return n.getConfigAsArray(vo.ConfigParam{ 175 DataId: subscriberMetadataIdentifier.GetIdentifierKey(), 176 }) 177 } 178 179 // GetServiceDefinition gets the service definition. 180 func (n *nacosMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) { 181 return n.getConfig(vo.ConfigParam{ 182 DataId: metadataIdentifier.GetIdentifierKey(), 183 Group: n.group, 184 }) 185 } 186 187 // storeMetadata will publish the metadata to Nacos 188 // if failed or error is not nil, error will be returned 189 func (n *nacosMetadataReport) storeMetadata(param vo.ConfigParam) error { 190 res, err := n.client.Client().PublishConfig(param) 191 if err != nil { 192 return perrors.WithMessage(err, "Could not publish the metadata") 193 } 194 if !res { 195 return perrors.New("Publish the metadata failed.") 196 } 197 return nil 198 } 199 200 // deleteMetadata will delete the metadata 201 func (n *nacosMetadataReport) deleteMetadata(param vo.ConfigParam) error { 202 res, err := n.client.Client().DeleteConfig(param) 203 if err != nil { 204 return perrors.WithMessage(err, "Could not delete the metadata") 205 } 206 if !res { 207 return perrors.New("Deleting the metadata failed.") 208 } 209 return nil 210 } 211 212 // getConfigAsArray will read the config and then convert it as an one-element array 213 // error or config not found, an empty list will be returned. 214 func (n *nacosMetadataReport) getConfigAsArray(param vo.ConfigParam) ([]string, error) { 215 res := make([]string, 0, 1) 216 217 cfg, err := n.getConfig(param) 218 if err != nil || len(cfg) == 0 { 219 return res, err 220 } 221 222 decodeCfg, err := url.QueryUnescape(cfg) 223 if err != nil { 224 logger.Errorf("The config is invalid: %s", cfg) 225 return res, err 226 } 227 228 res = append(res, decodeCfg) 229 return res, nil 230 } 231 232 // getConfig will read the config 233 func (n *nacosMetadataReport) getConfig(param vo.ConfigParam) (string, error) { 234 cfg, err := n.client.Client().GetConfig(param) 235 if err != nil { 236 logger.Errorf("Finding the configuration failed: %v", param) 237 return "", err 238 } 239 return cfg, nil 240 } 241 242 func (n *nacosMetadataReport) addListener(key string, group string, notify registry.MappingListener) error { 243 return n.client.Client().ListenConfig(vo.ConfigParam{ 244 DataId: key, 245 Group: group, 246 OnChange: func(namespace, group, dataId, data string) { 247 go callback(notify, dataId, data) 248 }, 249 }) 250 } 251 252 func callback(notify registry.MappingListener, dataId, data string) { 253 appNames := strings.Split(data, constant.CommaSeparator) 254 set := gxset.NewSet() 255 for _, app := range appNames { 256 set.Add(app) 257 } 258 if err := notify.OnEvent(registry.NewServiceMappingChangedEvent(dataId, set)); err != nil { 259 logger.Errorf("serviceMapping callback err: %s", err.Error()) 260 } 261 } 262 263 func (n *nacosMetadataReport) removeServiceMappingListener(key string, group string) error { 264 return n.client.Client().CancelListenConfig(vo.ConfigParam{ 265 DataId: key, 266 Group: group, 267 }) 268 } 269 270 // RegisterServiceAppMapping map the specified Dubbo service interface to current Dubbo app name 271 func (n *nacosMetadataReport) RegisterServiceAppMapping(key string, group string, value string) error { 272 oldVal, _ := n.getConfig(vo.ConfigParam{ 273 DataId: key, 274 Group: group, 275 }) 276 if oldVal != "" { 277 oldApps := strings.Split(oldVal, constant.CommaSeparator) 278 if len(oldApps) > 0 { 279 for _, app := range oldApps { 280 if app == value { 281 return nil 282 } 283 } 284 } 285 value = oldVal + constant.CommaSeparator + value 286 } 287 return n.storeMetadata(vo.ConfigParam{ 288 DataId: key, 289 Group: group, 290 Content: value, 291 }) 292 } 293 294 // GetServiceAppMapping get the app names from the specified Dubbo service interface 295 func (n *nacosMetadataReport) GetServiceAppMapping(key string, group string, listener registry.MappingListener) (*gxset.HashSet, error) { 296 // add service mapping listener 297 if listener != nil { 298 if err := n.addListener(key, group, listener); err != nil { 299 logger.Errorf("add serviceMapping listener err: %s", err.Error()) 300 } 301 } 302 v, err := n.getConfig(vo.ConfigParam{ 303 DataId: key, 304 Group: group, 305 }) 306 if err != nil { 307 return nil, err 308 } 309 if v == "" { 310 return nil, perrors.New("There is no service app mapping data.") 311 } 312 appNames := strings.Split(v, constant.CommaSeparator) 313 set := gxset.NewSet() 314 for _, e := range appNames { 315 set.Add(e) 316 } 317 return set, nil 318 } 319 320 // GetConfigKeysByGroup will return all keys with the group 321 func (n *nacosMetadataReport) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) { 322 group = n.resolvedGroup(group) 323 page, err := n.client.Client().SearchConfig(vo.SearchConfigParam{ 324 Search: "accurate", 325 Group: group, 326 PageNo: 1, 327 // actually it's impossible for user to create 9999 application under one group 328 PageSize: maxKeysNum, 329 }) 330 331 result := gxset.NewSet() 332 if err != nil { 333 return result, perrors.WithMessage(err, "can not find the configClient config") 334 } 335 for _, itm := range page.PageItems { 336 result.Add(itm.DataId) 337 } 338 return result, nil 339 } 340 341 // resolvedGroup will regular the group. Now, it will replace the '/' with '-'. 342 // '/' is a special character for nacos 343 func (n *nacosMetadataReport) resolvedGroup(group string) string { 344 if len(group) <= 0 { 345 group = metadata.DefaultGroup 346 return group 347 } 348 return strings.ReplaceAll(group, "/", "-") 349 } 350 351 // RemoveServiceAppMappingListener remove the serviceMapping listener from metadata center 352 func (n *nacosMetadataReport) RemoveServiceAppMappingListener(key string, group string) error { 353 return n.removeServiceMappingListener(key, group) 354 } 355 356 type nacosMetadataReportFactory struct{} 357 358 // nolint 359 func (n *nacosMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport { 360 url.SetParam(constant.NacosNamespaceID, url.GetParam(constant.MetadataReportNamespaceKey, "")) 361 url.SetParam(constant.TimeoutKey, url.GetParam(constant.TimeoutKey, constant.DefaultRegTimeout)) 362 group := url.GetParam(constant.MetadataReportGroupKey, constant.ServiceDiscoveryDefaultGroup) 363 url.SetParam(constant.NacosGroupKey, group) 364 url.SetParam(constant.NacosUsername, url.Username) 365 url.SetParam(constant.NacosPassword, url.Password) 366 client, err := nacos.NewNacosConfigClientByUrl(url) 367 if err != nil { 368 logger.Errorf("Could not create nacos metadata report. URL: %s", url.String()) 369 return nil 370 } 371 return &nacosMetadataReport{client: client, group: group} 372 }