dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/resource/unmarshal.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  /*
    19   *
    20   * Copyright 2021 gRPC authors.
    21   *
    22   */
    23  
    24  // Package resource contains functions to proto xds updates (unmarshal from
    25  // proto), and types for the resource updates.
    26  package resource
    27  
    28  import (
    29  	"errors"
    30  	"fmt"
    31  	"strings"
    32  	"time"
    33  )
    34  
    35  import (
    36  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    37  
    38  	"google.golang.org/protobuf/types/known/anypb"
    39  )
    40  
    41  // UnmarshalOptions wraps the input parameters for `UnmarshalXxx` functions.
    42  type UnmarshalOptions struct {
    43  	// Version is the version of the received response.
    44  	Version string
    45  	// Resources are the xDS resources resources in the received response.
    46  	Resources []*anypb.Any
    47  	// Logger is the prefix logger to be used during unmarshaling.
    48  	Logger dubbogoLogger.Logger
    49  	// UpdateValidator is a post unmarshal validation check provided by the
    50  	// upper layer.
    51  	UpdateValidator UpdateValidatorFunc
    52  }
    53  
    54  // processAllResources unmarshals and validates the resources, populates the
    55  // provided ret (a map), and returns metadata and error.
    56  //
    57  // After this function, the ret map will be populated with both valid and
    58  // invalid updates. Invalid resources will have an entry with the key as the
    59  // resource name, value as an empty update.
    60  //
    61  // The type of the resource is determined by the type of ret. E.g.
    62  // map[string]ListenerUpdate means this is for LDS.
    63  func processAllResources(opts *UnmarshalOptions, ret interface{}) (UpdateMetadata, error) {
    64  	timestamp := time.Now()
    65  	md := UpdateMetadata{
    66  		Version:   opts.Version,
    67  		Timestamp: timestamp,
    68  	}
    69  	var topLevelErrors []error
    70  	perResourceErrors := make(map[string]error)
    71  
    72  	for _, r := range opts.Resources {
    73  		switch ret2 := ret.(type) {
    74  		case map[string]ListenerUpdateErrTuple:
    75  			name, update, err := unmarshalListenerResource(r, opts.UpdateValidator, opts.Logger)
    76  			name = ParseName(name).String()
    77  			if err == nil {
    78  				ret2[name] = ListenerUpdateErrTuple{Update: update}
    79  				continue
    80  			}
    81  			if name == "" {
    82  				topLevelErrors = append(topLevelErrors, err)
    83  				continue
    84  			}
    85  			perResourceErrors[name] = err
    86  			// Add place holder in the map so we know this resource name was in
    87  			// the response.
    88  			ret2[name] = ListenerUpdateErrTuple{Err: err}
    89  		case map[string]RouteConfigUpdateErrTuple:
    90  			name, update, err := unmarshalRouteConfigResource(r, opts.Logger)
    91  			name = ParseName(name).String()
    92  			if err == nil {
    93  				ret2[name] = RouteConfigUpdateErrTuple{Update: update}
    94  				continue
    95  			}
    96  			if name == "" {
    97  				topLevelErrors = append(topLevelErrors, err)
    98  				continue
    99  			}
   100  			perResourceErrors[name] = err
   101  			// Add place holder in the map so we know this resource name was in
   102  			// the response.
   103  			ret2[name] = RouteConfigUpdateErrTuple{Err: err}
   104  		case map[string]ClusterUpdateErrTuple:
   105  			name, update, err := unmarshalClusterResource(r, opts.UpdateValidator, opts.Logger)
   106  			name = ParseName(name).String()
   107  			if err == nil {
   108  				ret2[name] = ClusterUpdateErrTuple{Update: update}
   109  				continue
   110  			}
   111  			if name == "" {
   112  				topLevelErrors = append(topLevelErrors, err)
   113  				continue
   114  			}
   115  			perResourceErrors[name] = err
   116  			// Add place holder in the map so we know this resource name was in
   117  			// the response.
   118  			ret2[name] = ClusterUpdateErrTuple{Err: err}
   119  		case map[string]EndpointsUpdateErrTuple:
   120  			name, update, err := unmarshalEndpointsResource(r, opts.Logger)
   121  			name = ParseName(name).String()
   122  			if err == nil {
   123  				ret2[name] = EndpointsUpdateErrTuple{Update: update}
   124  				continue
   125  			}
   126  			if name == "" {
   127  				topLevelErrors = append(topLevelErrors, err)
   128  				continue
   129  			}
   130  			perResourceErrors[name] = err
   131  			// Add place holder in the map so we know this resource name was in
   132  			// the response.
   133  			ret2[name] = EndpointsUpdateErrTuple{Err: err}
   134  		}
   135  	}
   136  
   137  	if len(topLevelErrors) == 0 && len(perResourceErrors) == 0 {
   138  		md.Status = ServiceStatusACKed
   139  		return md, nil
   140  	}
   141  
   142  	var typeStr string
   143  	switch ret.(type) {
   144  	case map[string]ListenerUpdate:
   145  		typeStr = "LDS"
   146  	case map[string]RouteConfigUpdate:
   147  		typeStr = "RDS"
   148  	case map[string]ClusterUpdate:
   149  		typeStr = "CDS"
   150  	case map[string]EndpointsUpdate:
   151  		typeStr = "EDS"
   152  	}
   153  
   154  	md.Status = ServiceStatusNACKed
   155  	errRet := combineErrors(typeStr, topLevelErrors, perResourceErrors)
   156  	md.ErrState = &UpdateErrorMetadata{
   157  		Version:   opts.Version,
   158  		Err:       errRet,
   159  		Timestamp: timestamp,
   160  	}
   161  	return md, errRet
   162  }
   163  
   164  func combineErrors(rType string, topLevelErrors []error, perResourceErrors map[string]error) error {
   165  	var errStrB strings.Builder
   166  	errStrB.WriteString(fmt.Sprintf("error parsing %q response: ", rType))
   167  	if len(topLevelErrors) > 0 {
   168  		errStrB.WriteString("top level errors: ")
   169  		for i, err := range topLevelErrors {
   170  			if i != 0 {
   171  				errStrB.WriteString(";\n")
   172  			}
   173  			errStrB.WriteString(err.Error())
   174  		}
   175  	}
   176  	if len(perResourceErrors) > 0 {
   177  		var i int
   178  		for name, err := range perResourceErrors {
   179  			if i != 0 {
   180  				errStrB.WriteString(";\n")
   181  			}
   182  			i++
   183  			errStrB.WriteString(fmt.Sprintf("resource %q: %v", name, err.Error()))
   184  		}
   185  	}
   186  	return errors.New(errStrB.String())
   187  }