go.ligato.io/vpp-agent/v3@v3.5.0/cmd/agentctl/commands/import.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 commands 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "os" 22 "path" 23 "strings" 24 "time" 25 26 "github.com/sirupsen/logrus" 27 "github.com/spf13/cobra" 28 "go.ligato.io/cn-infra/v2/logging" 29 "go.ligato.io/cn-infra/v2/servicelabel" 30 "google.golang.org/protobuf/encoding/protojson" 31 "google.golang.org/protobuf/proto" 32 33 agentcli "go.ligato.io/vpp-agent/v3/cmd/agentctl/cli" 34 "go.ligato.io/vpp-agent/v3/pkg/models" 35 ) 36 37 const ( 38 defaultTimeout = time.Second * 30 39 defaultTxOps = 128 40 ) 41 42 func NewImportCommand(cli agentcli.Cli) *cobra.Command { 43 var opts ImportOptions 44 cmd := &cobra.Command{ 45 Use: "import FILE", 46 Args: cobra.ExactArgs(1), 47 Short: "Import config data from file", 48 Long: `Import config data from file into Etcd or via gRPC. 49 FILE FORMAT 50 Contents of the import file must contain single key-value pair per line: 51 52 <key1> <value1> 53 <key2> <value2> 54 ... 55 <keyN> <valueN> 56 57 NOTE: Empty lines and lines starting with '#' are ignored. 58 59 Sample file: 60 config/vpp/v2/interfaces/loop1 {"name":"loop1","type":"SOFTWARE_LOOPBACK"} 61 config/vpp/l2/v2/bridge-domain/bd1 {"name":"bd1"} 62 63 KEY FORMAT 64 Keys can be defined in two ways: 65 66 - Full - /vnf-agent/vpp1/config/vpp/v2/interfaces/iface1 67 - Short - config/vpp/v2/interfaces/iface1 68 69 When using short keys, import will use configured microservice label (e.g. --service-label flag).`, 70 Example: ` 71 # Import data into Etcd 72 {{.CommandPath}} input.txt 73 74 # Import data directly into agent via gRPC 75 {{.CommandPath}} --grpc input.txt 76 `, 77 RunE: func(cmd *cobra.Command, args []string) error { 78 opts.InputFile = args[0] 79 return RunImport(cli, opts) 80 }, 81 } 82 flags := cmd.Flags() 83 flags.UintVar(&opts.TxOps, "txops", defaultTxOps, "Number of ops per transaction") 84 flags.DurationVarP(&opts.Timeout, "time", "t", defaultTimeout, "Timeout to wait for server response") 85 flags.BoolVar(&opts.ViaGrpc, "grpc", false, "Import config directly to agent via gRPC") 86 return cmd 87 } 88 89 type ImportOptions struct { 90 InputFile string 91 TxOps uint 92 Timeout time.Duration 93 ViaGrpc bool 94 } 95 96 func RunImport(cli agentcli.Cli, opts ImportOptions) error { 97 keyVals, err := parseImportFile(opts.InputFile) 98 if err != nil { 99 return fmt.Errorf("parsing import data failed: %v", err) 100 } 101 fmt.Printf("importing %d key-value pairs\n", len(keyVals)) 102 103 if opts.ViaGrpc { 104 // Set up a connection to the server. 105 c, err := cli.Client().GenericClient() 106 if err != nil { 107 return err 108 } 109 req := c.ChangeRequest() 110 for _, keyVal := range keyVals { 111 fmt.Printf(" - %s\n", keyVal.Key) 112 req.Update(keyVal.Val) 113 } 114 fmt.Printf("sending via gRPC\n") 115 116 ctx, cancel := context.WithTimeout(context.Background(), opts.Timeout) 117 defer cancel() 118 119 if err := req.Send(ctx); err != nil { 120 return fmt.Errorf("send failed: %v", err) 121 } 122 } else { 123 c, err := cli.Client().KVDBClient() 124 if err != nil { 125 return fmt.Errorf("KVDB error: %v", err) 126 } 127 db := c.ProtoBroker() 128 var txn = db.NewTxn() 129 ops := 0 130 for i := 0; i < len(keyVals); i++ { 131 keyVal := keyVals[i] 132 key, err := c.CompleteFullKey(keyVal.Key) 133 if err != nil { 134 return fmt.Errorf("key processing failed: %v", err) 135 } 136 fmt.Printf(" - %s\n", key) 137 txn.Put(key, keyVal.Val) 138 ops++ 139 140 if ops == int(opts.TxOps) || i+1 == len(keyVals) { 141 fmt.Printf("commiting tx with %d ops\n", ops) 142 ctx, cancel := context.WithTimeout(context.Background(), opts.Timeout) 143 err = txn.Commit(ctx) 144 cancel() 145 if err != nil { 146 return fmt.Errorf("commit failed: %v", err) 147 } 148 ops = 0 149 txn = db.NewTxn() 150 } 151 } 152 } 153 154 logging.Debug("OK") 155 return nil 156 } 157 158 type keyVal struct { 159 Key string 160 Val proto.Message 161 } 162 163 func parseImportFile(importFile string) (keyVals []keyVal, err error) { 164 b, err := os.ReadFile(importFile) 165 if err != nil { 166 return nil, fmt.Errorf("reading input file failed: %v", err) 167 } 168 // parse lines 169 lines := bytes.Split(b, []byte("\n")) 170 for _, l := range lines { 171 line := bytes.TrimSpace(l) 172 if bytes.HasPrefix(line, []byte("#")) { 173 continue 174 } 175 parts := bytes.SplitN(line, []byte(" "), 2) 176 if len(parts) < 2 { 177 continue 178 } 179 key := string(parts[0]) 180 data := string(parts[1]) 181 if key == "" || data == "" { 182 continue 183 } 184 logrus.Debugf("parse line: %s %s\n", key, data) 185 186 // key = completeFullKey(key) 187 val, err := unmarshalKeyVal(key, data) 188 if err != nil { 189 return nil, fmt.Errorf("decoding value failed: %v", err) 190 } 191 logrus.Debugf("KEY: %s - %v\n", key, val) 192 keyVals = append(keyVals, keyVal{key, val}) 193 } 194 return 195 } 196 197 func unmarshalKeyVal(fullKey string, data string) (proto.Message, error) { 198 key := stripAgentPrefix(fullKey) 199 200 model, err := models.GetModelForKey(key) 201 if err != nil { 202 return nil, err 203 } 204 valueType := protoMessageType(model.ProtoName()) 205 if valueType == nil { 206 return nil, fmt.Errorf("unknown proto message defined for: %s", model.ProtoName()) 207 } 208 value := valueType.New().Interface() 209 if err = protojson.Unmarshal([]byte(data), value); err != nil { 210 return nil, err 211 } 212 return value, nil 213 } 214 215 func stripAgentPrefix(key string) string { 216 if !strings.HasPrefix(key, servicelabel.GetAllAgentsPrefix()) { 217 return key 218 } 219 keyParts := strings.Split(key, "/") 220 if len(keyParts) < 4 || keyParts[0] != "" { 221 return path.Join(keyParts[2:]...) 222 } 223 return path.Join(keyParts[3:]...) 224 }