go.ligato.io/vpp-agent/v3@v3.5.0/plugins/configurator/configurator.go (about)

     1  //  Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package configurator
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"reflect"
    21  	"runtime/trace"
    22  	"strconv"
    23  	"time"
    24  
    25  	"go.ligato.io/cn-infra/v2/logging"
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/metadata"
    29  	"google.golang.org/grpc/status"
    30  	"google.golang.org/protobuf/proto"
    31  
    32  	"go.ligato.io/vpp-agent/v3/pkg/models"
    33  	"go.ligato.io/vpp-agent/v3/pkg/util"
    34  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    35  	"go.ligato.io/vpp-agent/v3/plugins/orchestrator"
    36  	"go.ligato.io/vpp-agent/v3/plugins/orchestrator/contextdecorator"
    37  	pb "go.ligato.io/vpp-agent/v3/proto/ligato/configurator"
    38  	"go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
    39  	"go.ligato.io/vpp-agent/v3/proto/ligato/linux"
    40  	"go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    41  	"go.ligato.io/vpp-agent/v3/proto/ligato/vpp"
    42  )
    43  
    44  const (
    45  	waitDoneCheckPendingPeriod = time.Millisecond * 10
    46  )
    47  
    48  // configuratorServer implements DataSyncer service.
    49  type configuratorServer struct {
    50  	pb.UnimplementedConfiguratorServiceServer
    51  
    52  	dumpService
    53  	notifyService
    54  
    55  	log      logging.Logger
    56  	dispatch orchestrator.Dispatcher
    57  }
    58  
    59  func (svc *configuratorServer) Dump(ctx context.Context, req *pb.DumpRequest) (*pb.DumpResponse, error) {
    60  	return svc.dumpService.Dump(ctx, req)
    61  }
    62  
    63  func (svc *configuratorServer) Notify(from *pb.NotifyRequest, server pb.ConfiguratorService_NotifyServer) error {
    64  	return svc.notifyService.Notify(from, server)
    65  }
    66  
    67  // Get retrieves actual configuration data.
    68  func (svc *configuratorServer) Get(context.Context, *pb.GetRequest) (*pb.GetResponse, error) {
    69  	defer trackOperation("Get")()
    70  
    71  	config := newConfig()
    72  
    73  	util.PlaceProtos(svc.dispatch.ListData(),
    74  		config.LinuxConfig,
    75  		config.VppConfig,
    76  		config.NetallocConfig,
    77  	)
    78  
    79  	return &pb.GetResponse{Config: config}, nil
    80  }
    81  
    82  // Update adds configuration data present in data request to the VPP/Linux
    83  func (svc *configuratorServer) Update(ctx context.Context, req *pb.UpdateRequest) (*pb.UpdateResponse, error) {
    84  	ctx, task := trace.NewTask(ctx, "grpc.Update")
    85  	defer task.End()
    86  	trace.Logf(ctx, "updateData", "%+v", req)
    87  
    88  	defer trackOperation("Update")()
    89  
    90  	protos := util.ExtractProtos(
    91  		req.GetUpdate().GetVppConfig(),
    92  		req.GetUpdate().GetLinuxConfig(),
    93  		req.GetUpdate().GetNetallocConfig(),
    94  	)
    95  
    96  	var kvPairs []orchestrator.KeyVal
    97  	for _, p := range protos {
    98  		key, err := models.GetKey(p)
    99  		if err != nil {
   100  			svc.log.WithFields(map[string]interface{}{
   101  				"message": proto.MessageName(p),
   102  				"type":    reflect.TypeOf(p).Elem().Name(),
   103  			}).Debug("models.GetKey error: %s", err)
   104  			return nil, status.Error(codes.InvalidArgument, err.Error())
   105  		}
   106  		kvPairs = append(kvPairs, orchestrator.KeyVal{
   107  			Key: key,
   108  			Val: p,
   109  		})
   110  	}
   111  
   112  	if req.FullResync {
   113  		ctx = kvs.WithResync(ctx, kvs.FullResync, true)
   114  	}
   115  
   116  	md, hasMeta := metadata.FromIncomingContext(ctx)
   117  	if hasMeta && len(md["datasrc"]) == 1 {
   118  		ctx = contextdecorator.DataSrcContext(ctx, md["datasrc"][0])
   119  	} else {
   120  		ctx = contextdecorator.DataSrcContext(ctx, "grpc")
   121  	}
   122  	results, err := svc.dispatch.PushData(ctx, kvPairs, nil)
   123  
   124  	header := map[string]string{}
   125  	if seqNum := svc.extractTxnSeqNum(results); seqNum >= 0 {
   126  		header["seqnum"] = fmt.Sprint(seqNum)
   127  	}
   128  	if err := grpc.SetHeader(ctx, metadata.New(header)); err != nil {
   129  		logging.Warnf("sending grpc header failed: %v", err)
   130  	}
   131  	if err != nil {
   132  		st := status.New(codes.FailedPrecondition, err.Error())
   133  		return nil, st.Err()
   134  	}
   135  
   136  	if req.WaitDone {
   137  		waitStart := time.Now()
   138  		var pendingKeys []string
   139  		for _, res := range results {
   140  			if res.Status.GetState() == kvscheduler.ValueState_PENDING {
   141  				pendingKeys = append(pendingKeys, res.Key)
   142  			}
   143  		}
   144  		if len(pendingKeys) > 0 {
   145  			svc.log.Infof("waiting for %d pending keys", len(pendingKeys))
   146  			for len(pendingKeys) > 0 {
   147  				select {
   148  				case <-time.After(waitDoneCheckPendingPeriod):
   149  					pendingKeys = svc.listPending(pendingKeys)
   150  				case <-ctx.Done():
   151  					svc.log.Warnf("update returning before %d pending keys are done: %v", len(pendingKeys), ctx.Err())
   152  					return nil, ctx.Err()
   153  				}
   154  			}
   155  		} else {
   156  			svc.log.Debugf("no pendings keys to wait for")
   157  		}
   158  		svc.log.Infof("finished waiting for done (took %v)", time.Since(waitStart))
   159  	}
   160  
   161  	svc.log.Debugf("config update finished with %d results", len(results))
   162  
   163  	return &pb.UpdateResponse{}, nil
   164  }
   165  
   166  // Delete removes configuration data present in data request from the VPP/linux
   167  func (svc *configuratorServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*pb.DeleteResponse, error) {
   168  	defer trackOperation("Delete")()
   169  
   170  	protos := util.ExtractProtos(
   171  		req.GetDelete().GetVppConfig(),
   172  		req.GetDelete().GetLinuxConfig(),
   173  		req.GetDelete().GetNetallocConfig(),
   174  	)
   175  
   176  	var kvPairs []orchestrator.KeyVal
   177  	for _, p := range protos {
   178  		key, err := models.GetKey(p)
   179  		if err != nil {
   180  			svc.log.WithFields(map[string]interface{}{
   181  				"message": proto.MessageName(p),
   182  				"type":    reflect.TypeOf(p).Elem().Name(),
   183  			}).Debug("models.GetKey error: %s", err)
   184  			return nil, status.Error(codes.InvalidArgument, err.Error())
   185  		}
   186  		kvPairs = append(kvPairs, orchestrator.KeyVal{
   187  			Key: key,
   188  			Val: nil, // delete
   189  		})
   190  	}
   191  
   192  	md, hasMeta := metadata.FromIncomingContext(ctx)
   193  	if hasMeta && len(md["datasrc"]) == 1 {
   194  		ctx = contextdecorator.DataSrcContext(ctx, md["datasrc"][0])
   195  	} else {
   196  		ctx = contextdecorator.DataSrcContext(ctx, "grpc")
   197  	}
   198  	results, err := svc.dispatch.PushData(ctx, kvPairs, nil)
   199  
   200  	header := map[string]string{}
   201  	if seqNum := svc.extractTxnSeqNum(results); seqNum >= 0 {
   202  		header["seqnum"] = fmt.Sprint(seqNum)
   203  	}
   204  	if err := grpc.SendHeader(ctx, metadata.New(header)); err != nil {
   205  		logging.Warnf("sending grpc header failed: %v", err)
   206  	}
   207  	if err != nil {
   208  		st := status.New(codes.FailedPrecondition, err.Error())
   209  		return nil, st.Err()
   210  	}
   211  
   212  	if req.WaitDone {
   213  		waitStart := time.Now()
   214  		var pendingKeys []string
   215  		for _, res := range results {
   216  			if res.Status.GetState() == kvscheduler.ValueState_PENDING {
   217  				pendingKeys = append(pendingKeys, res.Key)
   218  			}
   219  		}
   220  		if len(pendingKeys) > 0 {
   221  			svc.log.Infof("waiting for %d pending keys", len(pendingKeys))
   222  			for len(pendingKeys) > 0 {
   223  				select {
   224  				case <-time.After(waitDoneCheckPendingPeriod):
   225  					pendingKeys = svc.listPending(pendingKeys)
   226  				case <-ctx.Done():
   227  					svc.log.Warnf("update returning before %d pending keys are done: %v", len(pendingKeys), ctx.Err())
   228  					return nil, ctx.Err()
   229  				}
   230  			}
   231  		} else {
   232  			svc.log.Debugf("no pendings keys to wait for")
   233  		}
   234  		svc.log.Infof("finished waiting for done (took %v)", time.Since(waitStart))
   235  	}
   236  
   237  	svc.log.Debugf("config delete finished with %d results", len(results))
   238  
   239  	return &pb.DeleteResponse{}, nil
   240  }
   241  
   242  func (svc *configuratorServer) listPending(keys []string) []string {
   243  	var pending []string
   244  	for _, key := range keys {
   245  		st, err := svc.dispatch.GetStatus(key)
   246  		if err != nil {
   247  			svc.log.Debugf("dispatch.GetStatus for key %q error: %v", key, err)
   248  			continue
   249  		}
   250  		if st.GetState() == kvscheduler.ValueState_PENDING {
   251  			pending = append(pending, key)
   252  		}
   253  	}
   254  	return pending
   255  }
   256  
   257  func (svc *configuratorServer) extractTxnSeqNum(results []orchestrator.Result) int {
   258  	seqNum := -1
   259  	for _, result := range results {
   260  		if result.Key == "seqnum" {
   261  			str := result.Status.Details[0]
   262  			if n, err := strconv.Atoi(str); err == nil {
   263  				seqNum = n
   264  			} else {
   265  				svc.log.Debugf("invalid seqnum in result: %q", str)
   266  			}
   267  		}
   268  	}
   269  	return seqNum
   270  }
   271  
   272  func newConfig() *pb.Config {
   273  	return &pb.Config{
   274  		LinuxConfig:    &linux.ConfigData{},
   275  		VppConfig:      &vpp.ConfigData{},
   276  		NetallocConfig: &netalloc.ConfigData{},
   277  	}
   278  }