go.ligato.io/vpp-agent/v3@v3.5.0/plugins/orchestrator/dispatcher.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 orchestrator
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"runtime/trace"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	"go.ligato.io/cn-infra/v2/logging"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.ligato.io/vpp-agent/v3/pkg/models"
    29  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    30  	"go.ligato.io/vpp-agent/v3/plugins/orchestrator/contextdecorator"
    31  	"go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
    32  )
    33  
    34  // KeyVal associates value with its key.
    35  type KeyVal struct {
    36  	Key string
    37  	Val proto.Message
    38  }
    39  
    40  // KVPairs represents key-value pairs.
    41  type KVPairs map[string]proto.Message
    42  
    43  // Label is string key-value pair associated with configuration item.
    44  // Label key format guidelines: label key should be a lower-case alphanumeric string
    45  // which may contain periods and hyphens (but it should not contain consecutive
    46  // periods/hyphens and it should not start with period/hyphen). Labels for configuration
    47  // items should be prefixed with the reverse DNS notation of a domain they originate from
    48  // (with domain owner's permission) for example: com.example.foo-bar-label.
    49  // The io.ligato.* and ligato.* prefixes are reserved by vpp-agent for internal use.
    50  type Labels map[string]string
    51  
    52  type Status = kvscheduler.ValueStatus
    53  
    54  type Result struct {
    55  	Key    string
    56  	Status *Status
    57  }
    58  
    59  type Dispatcher interface {
    60  	ListData() KVPairs
    61  	PushData(context.Context, []KeyVal, map[string]Labels) ([]Result, error)
    62  	GetStatus(key string) (*Status, error)
    63  	ListState() (KVPairs, error)
    64  	ListLabels(key string) Labels
    65  }
    66  
    67  type dispatcher struct {
    68  	log logging.Logger
    69  	kvs kvs.KVScheduler
    70  	mu  sync.Mutex
    71  	db  Store
    72  }
    73  
    74  // ListData retrieves actual data.
    75  func (p *dispatcher) ListData() KVPairs {
    76  	p.mu.Lock()
    77  	defer p.mu.Unlock()
    78  
    79  	return p.db.ListAll()
    80  }
    81  
    82  func (p *dispatcher) GetStatus(key string) (*Status, error) {
    83  	s := p.kvs.GetValueStatus(key)
    84  	status := s.GetValue()
    85  	if status == nil {
    86  		return nil, errors.Errorf("status for key %q not found", key)
    87  	}
    88  	return status, nil
    89  }
    90  
    91  // PushData updates actual data.
    92  func (p *dispatcher) PushData(ctx context.Context, kvPairs []KeyVal, keyLabels map[string]Labels) (results []Result, err error) {
    93  	trace.Logf(ctx, "pushData", "%d KV pairs", len(kvPairs))
    94  
    95  	// check key-value pairs for uniqness and validate key
    96  	uniq := make(map[string]proto.Message)
    97  	for _, kv := range kvPairs {
    98  		if kv.Val != nil {
    99  			// check if given key matches the key generated from value
   100  			if k := models.Key(kv.Val); k != kv.Key {
   101  				return nil, errors.Errorf("given key %q does not match with key generated from value: %q (value: %#v)", kv.Key, k, kv.Val)
   102  			}
   103  		}
   104  		// check if key is unique
   105  		if oldVal, ok := uniq[kv.Key]; ok {
   106  			return nil, errors.Errorf("found multiple key-value pairs with same key: %q (value 1: %#v, value 2: %#v)", kv.Key, kv.Val, oldVal)
   107  		}
   108  		uniq[kv.Key] = kv.Val
   109  	}
   110  
   111  	p.mu.Lock()
   112  	defer p.mu.Unlock()
   113  
   114  	pr := trace.StartRegion(ctx, "prepare kv data")
   115  
   116  	dataSrc, ok := contextdecorator.DataSrcFromContext(ctx)
   117  	if !ok {
   118  		dataSrc = "global"
   119  	}
   120  
   121  	p.log.Debugf("Push data with %d KV pairs (source: %s)", len(kvPairs), dataSrc)
   122  
   123  	txn := p.kvs.StartNBTransaction()
   124  
   125  	if typ, _ := kvs.IsResync(ctx); typ == kvs.FullResync {
   126  		trace.Log(ctx, "resyncType", typ.String())
   127  		p.db.Reset(dataSrc)
   128  		for _, kv := range kvPairs {
   129  			if kv.Val == nil {
   130  				p.log.Debugf(" - PUT: %q (skipped nil value for resync)", kv.Key)
   131  				continue
   132  			}
   133  			p.log.Debugf(" - PUT: %q ", kv.Key)
   134  			p.db.Update(dataSrc, kv.Key, kv.Val)
   135  			p.db.ResetLabels(kv.Key)
   136  			for lkey, lval := range keyLabels[kv.Key] {
   137  				p.db.AddLabel(kv.Key, lkey, lval)
   138  			}
   139  		}
   140  		allPairs := p.db.ListAll()
   141  		p.log.Debugf("will resync %d pairs", len(allPairs))
   142  		for k, v := range allPairs {
   143  			txn.SetValue(k, v)
   144  		}
   145  	} else {
   146  		for _, kv := range kvPairs {
   147  			if kv.Val == nil {
   148  				p.log.Debugf(" - DELETE: %q", kv.Key)
   149  				txn.SetValue(kv.Key, nil)
   150  				p.db.Delete(dataSrc, kv.Key)
   151  				for lkey := range keyLabels[kv.Key] {
   152  					p.db.DeleteLabel(kv.Key, lkey)
   153  				}
   154  			} else {
   155  				p.log.Debugf(" - UPDATE: %q ", kv.Key)
   156  				txn.SetValue(kv.Key, kv.Val)
   157  				p.db.Update(dataSrc, kv.Key, kv.Val)
   158  				p.db.ResetLabels(kv.Key)
   159  				for lkey, lval := range keyLabels[kv.Key] {
   160  					p.db.AddLabel(kv.Key, lkey, lval)
   161  				}
   162  			}
   163  		}
   164  	}
   165  
   166  	pr.End()
   167  
   168  	t := time.Now()
   169  
   170  	seqID, err := txn.Commit(ctx)
   171  	p.kvs.TransactionBarrier()
   172  	results = append(results, Result{
   173  		Key: "seqnum",
   174  		Status: &Status{
   175  			Details: []string{fmt.Sprint(seqID)},
   176  		},
   177  	})
   178  	for key := range uniq {
   179  		s := p.kvs.GetValueStatus(key)
   180  		results = append(results, Result{
   181  			Key:    key,
   182  			Status: s.GetValue(),
   183  		})
   184  	}
   185  	if err != nil {
   186  		if txErr, ok := err.(*kvs.TransactionError); ok && len(txErr.GetKVErrors()) > 0 {
   187  			kvErrs := txErr.GetKVErrors()
   188  			var errInfo = ""
   189  			for i, kvErr := range kvErrs {
   190  				errInfo += fmt.Sprintf(" - %3d. error (%s) %s - %v\n", i+1, kvErr.TxnOperation, kvErr.Key, kvErr.Error)
   191  			}
   192  			p.log.Errorf("Transaction #%d finished with %d errors\n%s", seqID, len(kvErrs), errInfo)
   193  		} else {
   194  			p.log.Errorf("Transaction failed: %v", err)
   195  			return nil, err
   196  		}
   197  		return results, err
   198  	} else {
   199  		took := time.Since(t)
   200  		p.log.Infof("Transaction #%d successful! (took %v)", seqID, took.Round(time.Microsecond*100))
   201  	}
   202  
   203  	return results, nil
   204  }
   205  
   206  // ListState retrieves running state.
   207  func (p *dispatcher) ListState() (KVPairs, error) {
   208  	p.mu.Lock()
   209  	defer p.mu.Unlock()
   210  
   211  	pairs := KVPairs{}
   212  	for _, prefix := range p.kvs.GetRegisteredNBKeyPrefixes() {
   213  		data, err := p.kvs.DumpValuesByKeyPrefix(prefix, kvs.CachedView)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		for _, d := range data {
   218  			// status := p.kvs.GetValueStatus(d.Key)
   219  			pairs[d.Key] = d.Value
   220  		}
   221  	}
   222  
   223  	return pairs, nil
   224  }
   225  
   226  func (p *dispatcher) ListLabels(key string) Labels {
   227  	p.mu.Lock()
   228  	defer p.mu.Unlock()
   229  
   230  	return p.db.ListLabels(key)
   231  }