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 }