blob: 8a4108eaf8a7741e57c421699c768f53fa3c17fe [file] [log] [blame]
Scott Baker6cf525a2019-05-09 12:25:08 -07001/*
2 * Portions copyright 2019-present Open Networking Foundation
3 * Original copyright 2019-present Ciena Corporation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package commands
18
19import (
20 "context"
21 "errors"
22 "fmt"
23 "github.com/fullstorydev/grpcurl"
Scott Baker5201c0b2019-05-15 15:35:56 -070024 "github.com/golang/protobuf/proto"
25 "github.com/golang/protobuf/protoc-gen-go/descriptor"
26 "github.com/jhump/protoreflect/desc"
Scott Baker6cf525a2019-05-09 12:25:08 -070027 "github.com/jhump/protoreflect/dynamic"
28 "google.golang.org/grpc"
Scott Baker5201c0b2019-05-15 15:35:56 -070029 "io"
30 "strconv"
Scott Baker6cf525a2019-05-09 12:25:08 -070031 "strings"
32 "time"
33)
34
Scott Baker5201c0b2019-05-15 15:35:56 -070035// Flags for calling the *WithRetry methods
Scott Baker6cf525a2019-05-09 12:25:08 -070036const GM_QUIET = 1
37const GM_UNTIL_FOUND = 2
38const GM_UNTIL_ENACTED = 4
39const GM_UNTIL_STATUS = 8
40
Scott Baker5201c0b2019-05-15 15:35:56 -070041type QueryEventHandler struct {
42 RpcEventHandler
43 Elements map[string]string
44 Model *desc.MessageDescriptor
45 Kind string
46 EOF bool
47}
48
49// Separate the operator from the query value.
50// For example,
51// "==foo" --> "EQUAL", "foo"
52func DecodeOperator(query string) (string, string, bool, error) {
53 if strings.HasPrefix(query, "!=") {
54 return query[2:], "EQUAL", true, nil
55 } else if strings.HasPrefix(query, "==") {
56 return "", "", false, errors.New("Operator == is now allowed. Suggest using = instead.")
57 } else if strings.HasPrefix(query, "=") {
58 return query[1:], "EQUAL", false, nil
59 } else if strings.HasPrefix(query, ">=") {
60 return query[2:], "GREATER_THAN_OR_EQUAL", false, nil
61 } else if strings.HasPrefix(query, ">") {
62 return query[1:], "GREATER_THAN", false, nil
63 } else if strings.HasPrefix(query, "<=") {
64 return query[2:], "LESS_THAN_OR_EQUAL", false, nil
65 } else if strings.HasPrefix(query, "<") {
66 return query[1:], "LESS_THAN", false, nil
67 } else {
68 return query, "EQUAL", false, nil
69 }
70}
71
72// Generate the parameters for Query messages.
73func (h *QueryEventHandler) GetParams(msg proto.Message) error {
74 dmsg, err := dynamic.AsDynamicMessage(msg)
75 if err != nil {
76 return err
77 }
78
79 //fmt.Printf("MessageName: %s\n", dmsg.XXX_MessageName())
80
81 if h.EOF {
82 return io.EOF
83 }
84
85 // Get the MessageType for the `elements` field
86 md := dmsg.GetMessageDescriptor()
87 elements_fld := md.FindFieldByName("elements")
88 elements_mt := elements_fld.GetMessageType()
89
90 for field_name, element := range h.Elements {
91 value, operator, invert, err := DecodeOperator(element)
92 if err != nil {
93 return err
94 }
95
96 nm := dynamic.NewMessage(elements_mt)
97
98 field_type := h.Model.FindFieldByName(field_name).GetType()
99 if field_type == descriptor.FieldDescriptorProto_TYPE_INT32 {
100 i, _ := strconv.ParseInt(value, 10, 32)
101 nm.SetFieldByName("iValue", int32(i))
102 } else if field_type == descriptor.FieldDescriptorProto_TYPE_UINT32 {
103 i, _ := strconv.ParseInt(value, 10, 32)
104 nm.SetFieldByName("iValue", uint32(i))
105 } else {
106 nm.SetFieldByName("sValue", value)
107 }
108
109 nm.SetFieldByName("name", field_name)
110 nm.SetFieldByName("invert", invert)
111 SetEnumValue(nm, "operator", operator)
112 dmsg.AddRepeatedFieldByName("elements", nm)
113 }
114
115 SetEnumValue(dmsg, "kind", h.Kind)
116
117 h.EOF = true
118
119 return nil
120}
121
122// Take a string list of queries and turns it into a map of queries
123func QueryStringsToMap(query_args []string) (map[string]string, error) {
124 queries := make(map[string]string)
125 for _, query_str := range query_args {
126 query_str := strings.Trim(query_str, " ")
127 operator_pos := -1
128 for i, ch := range query_str {
129 if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') {
130 operator_pos = i
131 break
132 }
133 }
134 if operator_pos == -1 {
135 return nil, fmt.Errorf("Illegal query string %s", query_str)
136 }
137 queries[query_str[:operator_pos]] = query_str[operator_pos:]
138 }
139 return queries, nil
140}
141
142// Take a string of comma-separated queries and turn it into a map of queries
143func CommaSeparatedQueryToMap(query_str string) (map[string]string, error) {
144 if query_str == "" {
145 return nil, nil
146 }
147
148 query_strings := strings.Split(query_str, ",")
149 return QueryStringsToMap(query_strings)
150}
151
Scott Baker6cf525a2019-05-09 12:25:08 -0700152// Return a list of all available model names
153func GetModelNames(source grpcurl.DescriptorSource) (map[string]bool, error) {
154 models := make(map[string]bool)
155 methods, err := grpcurl.ListMethods(source, "xos.xos")
156
157 if err != nil {
158 return nil, err
159 }
160
161 for _, method := range methods {
162 if strings.HasPrefix(method, "xos.xos.Get") {
163 models[method[11:]] = true
164 }
165 }
166
167 return models, nil
168}
169
170// Check to see if a model name is valid
171func CheckModelName(source grpcurl.DescriptorSource, name string) error {
172 models, err := GetModelNames(source)
173 if err != nil {
174 return err
175 }
176 _, present := models[name]
177 if !present {
178 return errors.New("Model " + name + " does not exist. Use `cordctl models available` to get a list of available models")
179 }
180 return nil
181}
182
183// Create a model in XOS given a map of fields
184func CreateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
185 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
186 defer cancel()
187
188 headers := GenerateHeaders()
189
190 h := &RpcEventHandler{
191 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
192 }
193 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Create"+modelName, headers, h, h.GetParams)
194 if err != nil {
195 return err
196 } else if h.Status != nil && h.Status.Err() != nil {
197 return h.Status.Err()
198 }
199
200 resp, err := dynamic.AsDynamicMessage(h.Response)
201 if err != nil {
202 return err
203 }
204
205 fields["id"] = resp.GetFieldByName("id").(int32)
206
207 if resp.HasFieldName("uuid") {
208 fields["uuid"] = resp.GetFieldByName("uuid").(string)
209 }
210
211 return nil
212}
213
214// Get a model from XOS given its ID
215func GetModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
216 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
217 defer cancel()
218
219 headers := GenerateHeaders()
220
221 h := &RpcEventHandler{
222 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
223 }
224 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Get"+modelName, headers, h, h.GetParams)
225 if err != nil {
226 return nil, err
227 }
228
229 if h.Status != nil && h.Status.Err() != nil {
230 return nil, h.Status.Err()
231 }
232
233 d, err := dynamic.AsDynamicMessage(h.Response)
234 if err != nil {
235 return nil, err
236 }
237
238 return d, nil
239}
240
241// Get a model, but retry under a variety of circumstances
242func GetModelWithRetry(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
243 quiet := (flags & GM_QUIET) != 0
244 until_found := (flags & GM_UNTIL_FOUND) != 0
245 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
246 until_status := (flags & GM_UNTIL_STATUS) != 0
247
248 for {
249 var err error
250
251 if conn == nil {
252 conn, err = NewConnection()
253 if err != nil {
254 return nil, nil, err
255 }
256 }
257
258 model, err := GetModel(conn, descriptor, modelName, id)
259 if err != nil {
260 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
261 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
262 if !quiet {
263 fmt.Print(".")
264 }
265 time.Sleep(100 * time.Millisecond)
266 conn.Close()
267 conn = nil
268 continue
269 }
270
271 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
272 if !quiet {
273 fmt.Print("x")
274 }
275 time.Sleep(100 * time.Millisecond)
276 continue
277 }
278 return nil, nil, err
279 }
280
281 if until_enacted && !IsEnacted(model) {
282 if !quiet {
283 fmt.Print("o")
284 }
285 time.Sleep(100 * time.Millisecond)
286 continue
287 }
288
289 if until_status && model.GetFieldByName("status") == nil {
290 if !quiet {
291 fmt.Print("O")
292 }
293 time.Sleep(100 * time.Millisecond)
294 continue
295 }
296
297 return conn, model, nil
298 }
299}
300
Scott Baker5201c0b2019-05-15 15:35:56 -0700301func ItemsToDynamicMessageList(items interface{}) []*dynamic.Message {
302 result := make([]*dynamic.Message, len(items.([]interface{})))
303 for i, item := range items.([]interface{}) {
304 result[i] = item.(*dynamic.Message)
305 }
306 return result
307}
308
309// List all objects of a given model
310func ListModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
Scott Baker6cf525a2019-05-09 12:25:08 -0700311 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
312 defer cancel()
313
314 headers := GenerateHeaders()
315
Scott Baker6cf525a2019-05-09 12:25:08 -0700316 h := &RpcEventHandler{}
317 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.List"+modelName, headers, h, h.GetParams)
318 if err != nil {
319 return nil, err
320 }
321
322 if h.Status != nil && h.Status.Err() != nil {
323 return nil, h.Status.Err()
324 }
325
326 d, err := dynamic.AsDynamicMessage(h.Response)
327 if err != nil {
328 return nil, err
329 }
330
331 items, err := d.TryGetFieldByName("items")
332 if err != nil {
333 return nil, err
334 }
335
Scott Baker5201c0b2019-05-15 15:35:56 -0700336 return ItemsToDynamicMessageList(items), nil
337}
Scott Baker6cf525a2019-05-09 12:25:08 -0700338
Scott Baker5201c0b2019-05-15 15:35:56 -0700339// Filter models based on field values
340// queries is a map of <field_name> to <operator><query>
341// For example,
342// map[string]string{"name": "==mysite"}
343func FilterModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
344 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
345 defer cancel()
Scott Baker6cf525a2019-05-09 12:25:08 -0700346
Scott Baker5201c0b2019-05-15 15:35:56 -0700347 headers := GenerateHeaders()
348
349 model_descriptor, err := descriptor.FindSymbol("xos." + modelName)
350 if err != nil {
351 return nil, err
352 }
353 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
354 if !ok {
355 return nil, errors.New("Failed to convert model to a messagedescriptor")
Scott Baker6cf525a2019-05-09 12:25:08 -0700356 }
357
Scott Baker5201c0b2019-05-15 15:35:56 -0700358 h := &QueryEventHandler{
359 RpcEventHandler: RpcEventHandler{
360 Fields: map[string]map[string]interface{}{"xos.Query": map[string]interface{}{"kind": 0}},
361 },
362 Elements: queries,
363 Model: model_md,
364 Kind: "DEFAULT",
365 }
366 err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Filter"+modelName, headers, h, h.GetParams)
367 if err != nil {
368 return nil, err
369 }
370
371 if h.Status != nil && h.Status.Err() != nil {
372 return nil, h.Status.Err()
373 }
374
375 d, err := dynamic.AsDynamicMessage(h.Response)
376 if err != nil {
377 return nil, err
378 }
379
380 items, err := d.TryGetFieldByName("items")
381 if err != nil {
382 return nil, err
383 }
384
385 return ItemsToDynamicMessageList(items), nil
386}
387
388// Get a model from XOS given a fieldName/fieldValue
389func FindModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
390 models, err := FilterModels(conn, descriptor, modelName, queries)
391 if err != nil {
392 return nil, err
393 }
394
395 if len(models) == 0 {
396 return nil, errors.New("rpc error: code = NotFound")
397 }
398
399 return models[0], nil
Scott Baker6cf525a2019-05-09 12:25:08 -0700400}
401
402// Find a model, but retry under a variety of circumstances
Scott Baker5201c0b2019-05-15 15:35:56 -0700403func FindModelWithRetry(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
Scott Baker6cf525a2019-05-09 12:25:08 -0700404 quiet := (flags & GM_QUIET) != 0
405 until_found := (flags & GM_UNTIL_FOUND) != 0
406 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
407 until_status := (flags & GM_UNTIL_STATUS) != 0
408
409 for {
410 var err error
411
412 if conn == nil {
413 conn, err = NewConnection()
414 if err != nil {
415 return nil, nil, err
416 }
417 }
418
Scott Baker5201c0b2019-05-15 15:35:56 -0700419 model, err := FindModel(conn, descriptor, modelName, queries)
Scott Baker6cf525a2019-05-09 12:25:08 -0700420 if err != nil {
421 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
422 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
423 if !quiet {
424 fmt.Print(".")
425 }
426 time.Sleep(100 * time.Millisecond)
427 conn.Close()
428 conn = nil
429 continue
430 }
431
432 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
433 if !quiet {
434 fmt.Print("x")
435 }
436 time.Sleep(100 * time.Millisecond)
437 continue
438 }
439 return nil, nil, err
440 }
441
442 if until_enacted && !IsEnacted(model) {
443 if !quiet {
444 fmt.Print("o")
445 }
446 time.Sleep(100 * time.Millisecond)
447 continue
448 }
449
450 if until_status && model.GetFieldByName("status") == nil {
451 if !quiet {
452 fmt.Print("O")
453 }
454 time.Sleep(100 * time.Millisecond)
455 continue
456 }
457
458 return conn, model, nil
459 }
460}
461
Scott Baker5201c0b2019-05-15 15:35:56 -0700462// Takes a *dynamic.Message and turns it into a map of fields to interfaces
463// TODO: Might be more useful to convert the values to strings and ints
Scott Baker6cf525a2019-05-09 12:25:08 -0700464func MessageToMap(d *dynamic.Message) map[string]interface{} {
465 fields := make(map[string]interface{})
466 for _, field_desc := range d.GetKnownFields() {
467 field_name := field_desc.GetName()
468 fields[field_name] = d.GetFieldByName(field_name)
469 }
470 return fields
471}
472
Scott Baker5201c0b2019-05-15 15:35:56 -0700473// Returns True if a message has been enacted
Scott Baker6cf525a2019-05-09 12:25:08 -0700474func IsEnacted(d *dynamic.Message) bool {
475 enacted := d.GetFieldByName("enacted").(float64)
476 updated := d.GetFieldByName("updated").(float64)
477
478 return (enacted >= updated)
479}