...

Source file src/github.com/linkerd/linkerd2/pkg/profiles/proto.go

Documentation: github.com/linkerd/linkerd2/pkg/profiles

     1  package profiles
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/emicklei/proto"
    10  	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  )
    13  
    14  // RenderProto reads a protobuf definition file and renders the corresponding
    15  // ServiceProfile to a buffer, given a namespace, service, and control plane
    16  // namespace.
    17  func RenderProto(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {
    18  	input, err := readFile(fileName)
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	parser := proto.NewParser(input)
    24  
    25  	return protoToServiceProfile(parser, namespace, name, clusterDomain)
    26  }
    27  
    28  func protoToServiceProfile(parser *proto.Parser, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {
    29  	definition, err := parser.Parse()
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	routes := make([]*sp.RouteSpec, 0)
    35  	pkg := ""
    36  
    37  	handle := func(visitee proto.Visitee) {
    38  		switch typed := visitee.(type) {
    39  		case *proto.Package:
    40  			pkg = typed.Name
    41  		case *proto.RPC:
    42  			if service, ok := typed.Parent.(*proto.Service); ok {
    43  				var path string
    44  				switch pkg {
    45  				case "":
    46  					path = fmt.Sprintf("/%s/%s", service.Name, typed.Name)
    47  				default:
    48  					path = fmt.Sprintf("/%s.%s/%s", pkg, service.Name, typed.Name)
    49  				}
    50  
    51  				route := &sp.RouteSpec{
    52  					Name: typed.Name,
    53  					Condition: &sp.RequestMatch{
    54  						Method:    http.MethodPost,
    55  						PathRegex: regexp.QuoteMeta(path),
    56  					},
    57  					IsRetryable: isMethodRetryable(typed),
    58  				}
    59  				routes = append(routes, route)
    60  			}
    61  		}
    62  	}
    63  
    64  	proto.Walk(definition, handle)
    65  
    66  	return &sp.ServiceProfile{
    67  		ObjectMeta: metav1.ObjectMeta{
    68  			Name:      fmt.Sprintf("%s.%s.svc.%s", name, namespace, clusterDomain),
    69  			Namespace: namespace,
    70  		},
    71  		TypeMeta: ServiceProfileMeta,
    72  		Spec: sp.ServiceProfileSpec{
    73  			Routes: routes,
    74  		},
    75  	}, nil
    76  }
    77  
    78  func isMethodRetryable(rpc *proto.RPC) bool {
    79  	for _, e := range rpc.Elements {
    80  		option, ok := e.(*proto.Option)
    81  		if !ok {
    82  			// Not an option
    83  			continue
    84  		}
    85  
    86  		// method options can be wrapped in parentheses so we trim them away
    87  		if strings.Trim(option.Name, "()") != "idempotency_level" {
    88  			// Not an idempotency option
    89  			continue
    90  		}
    91  
    92  		return option.Constant.Source == "IDEMPOTENT" ||
    93  			option.Constant.Source == "NO_SIDE_EFFECTS"
    94  	}
    95  
    96  	return false
    97  }
    98  

View as plain text