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  }