go.ligato.io/vpp-agent/v3@v3.5.0/client/local_client.go (about)

     1  //  Copyright (c) 2018 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 client
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/sirupsen/logrus"
    23  	"go.ligato.io/cn-infra/v2/datasync/kvdbsync/local"
    24  	"go.ligato.io/cn-infra/v2/datasync/syncbase"
    25  	"go.ligato.io/cn-infra/v2/db/keyval"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.ligato.io/vpp-agent/v3/pkg/models"
    29  	"go.ligato.io/vpp-agent/v3/pkg/util"
    30  	"go.ligato.io/vpp-agent/v3/plugins/orchestrator"
    31  	"go.ligato.io/vpp-agent/v3/plugins/orchestrator/contextdecorator"
    32  	"go.ligato.io/vpp-agent/v3/proto/ligato/generic"
    33  )
    34  
    35  // LocalClient is global client for direct local access.
    36  // Updates and resyncs of this client use local.DefaultRegistry for propagating data to orchestrator.Dispatcher
    37  // (going through watcher.Aggregator together with other data sources). However, data retrieval uses
    38  // orchestrator.Dispatcher directly.
    39  var LocalClient = NewClient(&txnFactory{local.DefaultRegistry}, &orchestrator.DefaultPlugin)
    40  
    41  type client struct {
    42  	txnFactory ProtoTxnFactory
    43  	dispatcher orchestrator.Dispatcher
    44  }
    45  
    46  // NewClient returns new instance that uses given registry for data propagation and dispatcher for data retrieval.
    47  func NewClient(factory ProtoTxnFactory, dispatcher orchestrator.Dispatcher) ConfigClient {
    48  	return &client{
    49  		txnFactory: factory,
    50  		dispatcher: dispatcher,
    51  	}
    52  }
    53  
    54  func (c *client) KnownModels(class string) ([]*ModelInfo, error) {
    55  	var modules []*ModelInfo
    56  	for _, model := range models.RegisteredModels() {
    57  		if class == "" || model.Spec().Class == class {
    58  			modules = append(modules, &models.ModelInfo{
    59  				ModelDetail:       model.ModelDetail(),
    60  				MessageDescriptor: model.NewInstance().ProtoReflect().Descriptor(),
    61  			})
    62  		}
    63  	}
    64  	return modules, nil
    65  }
    66  
    67  func (c *client) ResyncConfig(items ...proto.Message) error {
    68  	txn := c.txnFactory.NewTxn(true)
    69  
    70  	for _, item := range items {
    71  		key, err := models.GetKey(item)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		txn.Put(key, item)
    76  	}
    77  
    78  	ctx := context.Background()
    79  	ctx = contextdecorator.DataSrcContext(ctx, "localclient")
    80  	return txn.Commit(ctx)
    81  }
    82  
    83  func (c *client) GetFilteredConfig(filter Filter, dsts ...interface{}) error {
    84  	if filter.Ids != nil && filter.Labels != nil {
    85  		return fmt.Errorf("both fields of the filter are not nil!")
    86  	}
    87  	protos := c.dispatcher.ListData()
    88  	for key, data := range protos {
    89  		item, err := models.MarshalItem(data)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		labels := c.dispatcher.ListLabels(key)
    94  		if !orchestrator.HasCorrectLabels(filter.Labels, labels) ||
    95  			!orchestrator.ContainsItemID(filter.Ids, item.Id) {
    96  			delete(protos, key)
    97  		}
    98  	}
    99  	protoDsts := extractProtoMessages(dsts)
   100  	if len(dsts) == len(protoDsts) { // all dsts are proto messages
   101  		// TODO the clearIgnoreLayerCount function argument should be a option of generic.Client
   102  		//  (the value 1 generates from dynamic config the same json/yaml output as the hardcoded
   103  		//  configurator.Config and therefore serves for backward compatibility)
   104  		util.PlaceProtosIntoProtos(protoMapToList(protos), 1, protoDsts...)
   105  	} else {
   106  		util.PlaceProtos(protos, dsts...)
   107  	}
   108  	return nil
   109  }
   110  
   111  func (c *client) GetConfig(dsts ...interface{}) error {
   112  	return c.GetFilteredConfig(Filter{}, dsts...)
   113  }
   114  
   115  func (c *client) GetItems(ctx context.Context) ([]*ConfigItem, error) {
   116  	var configItems []*ConfigItem
   117  	for key, data := range c.dispatcher.ListData() {
   118  		labels := c.dispatcher.ListLabels(key)
   119  		item, err := models.MarshalItem(data)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		var itemStatus *generic.ItemStatus
   124  		status, err := c.dispatcher.GetStatus(key)
   125  		if err != nil {
   126  			logrus.Warnf("GetStatus failed: %v", err)
   127  		} else {
   128  			var msg string
   129  			if details := status.GetDetails(); len(details) > 0 {
   130  				msg = strings.Join(status.GetDetails(), ", ")
   131  			} else {
   132  				msg = status.GetError()
   133  			}
   134  			itemStatus = &generic.ItemStatus{
   135  				Status:  status.GetState().String(),
   136  				Message: msg,
   137  			}
   138  		}
   139  		configItems = append(configItems, &ConfigItem{
   140  			Item:   item,
   141  			Status: itemStatus,
   142  			Labels: labels,
   143  		})
   144  	}
   145  	return configItems, nil
   146  }
   147  
   148  func (c *client) UpdateItems(ctx context.Context, items []UpdateItem, resync bool) ([]*UpdateResult, error) {
   149  	// TODO: use grpc client to update items with labels
   150  	return nil, nil
   151  }
   152  
   153  func (c *client) DeleteItems(ctx context.Context, items []UpdateItem) ([]*UpdateResult, error) {
   154  	// TODO: use grpc client to delete items with labels
   155  	return nil, nil
   156  }
   157  
   158  func (c *client) DumpState() ([]*generic.StateItem, error) {
   159  	// TODO: use dispatcher to dump state
   160  	return nil, nil
   161  }
   162  
   163  func (c *client) ChangeRequest() ChangeRequest {
   164  	return &changeRequest{txn: c.txnFactory.NewTxn(false)}
   165  }
   166  
   167  type changeRequest struct {
   168  	txn keyval.ProtoTxn
   169  	err error
   170  }
   171  
   172  func (r *changeRequest) Update(items ...proto.Message) ChangeRequest {
   173  	if r.err != nil {
   174  		return r
   175  	}
   176  	for _, item := range items {
   177  		key, err := models.GetKey(item)
   178  		if err != nil {
   179  			r.err = err
   180  			return r
   181  		}
   182  		r.txn.Put(key, item)
   183  	}
   184  	return r
   185  }
   186  
   187  func (r *changeRequest) Delete(items ...proto.Message) ChangeRequest {
   188  	if r.err != nil {
   189  		return r
   190  	}
   191  	for _, item := range items {
   192  		key, err := models.GetKey(item)
   193  		if err != nil {
   194  			r.err = err
   195  			return r
   196  		}
   197  		r.txn.Delete(key)
   198  	}
   199  	return r
   200  }
   201  
   202  func (r *changeRequest) Send(ctx context.Context) error {
   203  	if r.err != nil {
   204  		return r.err
   205  	}
   206  	_, withDataSrc := contextdecorator.DataSrcFromContext(ctx)
   207  	if !withDataSrc {
   208  		ctx = contextdecorator.DataSrcContext(ctx, "localclient")
   209  	}
   210  	return r.txn.Commit(ctx)
   211  }
   212  
   213  // ProtoTxnFactory defines interface for keyval transaction provider.
   214  type ProtoTxnFactory interface {
   215  	NewTxn(resync bool) keyval.ProtoTxn
   216  }
   217  
   218  type txnFactory struct {
   219  	registry *syncbase.Registry
   220  }
   221  
   222  func (p *txnFactory) NewTxn(resync bool) keyval.ProtoTxn {
   223  	if resync {
   224  		return local.NewProtoTxn(p.registry.PropagateResync)
   225  	}
   226  	return local.NewProtoTxn(p.registry.PropagateChanges)
   227  }
   228  
   229  func extractProtoMessages(dsts []interface{}) []proto.Message {
   230  	msgs := make([]proto.Message, 0)
   231  	for _, dst := range dsts {
   232  		msg, ok := dst.(proto.Message)
   233  		if ok {
   234  			msgs = append(msgs, msg)
   235  		} else {
   236  			logrus.Debugf("at least one of the %d items is not proto message, but: %#v", len(dsts), dst)
   237  			break
   238  		}
   239  	}
   240  	return msgs
   241  }
   242  
   243  func protoMapToList(protoMap map[string]proto.Message) []proto.Message {
   244  	result := make([]proto.Message, 0, len(protoMap))
   245  	for _, msg := range protoMap {
   246  		result = append(result, msg)
   247  	}
   248  	return result
   249  }