blob: a817ea5ff01d263539d681c2473d3183addea2b4 [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, "!=") {
Scott Baker5281d002019-05-16 10:45:26 -070054 return strings.TrimSpace(query[2:]), "EQUAL", true, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070055 } else if strings.HasPrefix(query, "==") {
56 return "", "", false, errors.New("Operator == is now allowed. Suggest using = instead.")
57 } else if strings.HasPrefix(query, "=") {
Scott Baker5281d002019-05-16 10:45:26 -070058 return strings.TrimSpace(query[1:]), "EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070059 } else if strings.HasPrefix(query, ">=") {
Scott Baker5281d002019-05-16 10:45:26 -070060 return strings.TrimSpace(query[2:]), "GREATER_THAN_OR_EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070061 } else if strings.HasPrefix(query, ">") {
Scott Baker5281d002019-05-16 10:45:26 -070062 return strings.TrimSpace(query[1:]), "GREATER_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070063 } else if strings.HasPrefix(query, "<=") {
Scott Baker5281d002019-05-16 10:45:26 -070064 return strings.TrimSpace(query[2:]), "LESS_THAN_OR_EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070065 } else if strings.HasPrefix(query, "<") {
Scott Baker5281d002019-05-16 10:45:26 -070066 return strings.TrimSpace(query[1:]), "LESS_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070067 } else {
Scott Baker5281d002019-05-16 10:45:26 -070068 return strings.TrimSpace(query), "EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070069 }
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
Scott Baker5281d002019-05-16 10:45:26 -070098 field_descriptor := h.Model.FindFieldByName(field_name)
99 if field_descriptor == nil {
100 return fmt.Errorf("Field %s does not exist", field_name)
101 }
102
103 field_type := field_descriptor.GetType()
104 switch field_type {
105 case descriptor.FieldDescriptorProto_TYPE_INT32:
106 var i int64
107 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700108 nm.SetFieldByName("iValue", int32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700109 case descriptor.FieldDescriptorProto_TYPE_UINT32:
110 var i int64
111 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700112 nm.SetFieldByName("iValue", uint32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700113 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
114 err = errors.New("Floating point filters are unsupported")
115 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
116 err = errors.New("Floating point filters are unsupported")
117 default:
Scott Baker5201c0b2019-05-15 15:35:56 -0700118 nm.SetFieldByName("sValue", value)
Scott Baker5281d002019-05-16 10:45:26 -0700119 err = nil
120 }
121
122 if err != nil {
123 return err
Scott Baker5201c0b2019-05-15 15:35:56 -0700124 }
125
126 nm.SetFieldByName("name", field_name)
127 nm.SetFieldByName("invert", invert)
128 SetEnumValue(nm, "operator", operator)
129 dmsg.AddRepeatedFieldByName("elements", nm)
130 }
131
132 SetEnumValue(dmsg, "kind", h.Kind)
133
134 h.EOF = true
135
136 return nil
137}
138
139// Take a string list of queries and turns it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700140func QueryStringsToMap(query_args []string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700141 queries := make(map[string]string)
142 for _, query_str := range query_args {
Scott Baker5281d002019-05-16 10:45:26 -0700143 query_str := strings.TrimSpace(query_str)
Scott Baker5201c0b2019-05-15 15:35:56 -0700144 operator_pos := -1
145 for i, ch := range query_str {
Scott Baker5281d002019-05-16 10:45:26 -0700146 if allow_inequality {
147 if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') {
148 operator_pos = i
149 break
150 }
151 } else {
152 if ch == '=' {
153 operator_pos = i
154 break
155 }
Scott Baker5201c0b2019-05-15 15:35:56 -0700156 }
157 }
158 if operator_pos == -1 {
Scott Baker5281d002019-05-16 10:45:26 -0700159 return nil, fmt.Errorf("Illegal operator/value string %s", query_str)
Scott Baker5201c0b2019-05-15 15:35:56 -0700160 }
Scott Baker5281d002019-05-16 10:45:26 -0700161 queries[strings.TrimSpace(query_str[:operator_pos])] = query_str[operator_pos:]
Scott Baker5201c0b2019-05-15 15:35:56 -0700162 }
163 return queries, nil
164}
165
166// Take a string of comma-separated queries and turn it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700167func CommaSeparatedQueryToMap(query_str string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700168 if query_str == "" {
169 return nil, nil
170 }
171
172 query_strings := strings.Split(query_str, ",")
Scott Baker5281d002019-05-16 10:45:26 -0700173 return QueryStringsToMap(query_strings, allow_inequality)
174}
175
176// Convert a string into the appropriate gRPC type for a given field
177func TypeConvert(source grpcurl.DescriptorSource, modelName string, field_name string, v string) (interface{}, error) {
178 model_descriptor, err := source.FindSymbol("xos." + modelName)
179 if err != nil {
180 return nil, err
181 }
182 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
183 if !ok {
184 return nil, fmt.Errorf("Failed to convert model %s to a messagedescriptor", modelName)
185 }
186 field_descriptor := model_md.FindFieldByName(field_name)
187 if field_descriptor == nil {
188 return nil, fmt.Errorf("Field %s does not exist in model %s", field_name, modelName)
189 }
190 field_type := field_descriptor.GetType()
191
192 var result interface{}
193
194 switch field_type {
195 case descriptor.FieldDescriptorProto_TYPE_INT32:
196 var i int64
197 i, err = strconv.ParseInt(v, 10, 32)
198 result = int32(i)
199 case descriptor.FieldDescriptorProto_TYPE_UINT32:
200 var i int64
201 i, err = strconv.ParseInt(v, 10, 32)
202 result = uint32(i)
203 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
204 var f float64
205 f, err = strconv.ParseFloat(v, 32)
206 result = float32(f)
207 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
208 var f float64
209 f, err = strconv.ParseFloat(v, 64)
210 result = f
211 default:
212 result = v
213 err = nil
214 }
215
216 return result, err
Scott Baker5201c0b2019-05-15 15:35:56 -0700217}
218
Scott Baker6cf525a2019-05-09 12:25:08 -0700219// Return a list of all available model names
220func GetModelNames(source grpcurl.DescriptorSource) (map[string]bool, error) {
221 models := make(map[string]bool)
222 methods, err := grpcurl.ListMethods(source, "xos.xos")
223
224 if err != nil {
225 return nil, err
226 }
227
228 for _, method := range methods {
229 if strings.HasPrefix(method, "xos.xos.Get") {
230 models[method[11:]] = true
231 }
232 }
233
234 return models, nil
235}
236
237// Check to see if a model name is valid
238func CheckModelName(source grpcurl.DescriptorSource, name string) error {
239 models, err := GetModelNames(source)
240 if err != nil {
241 return err
242 }
243 _, present := models[name]
244 if !present {
245 return errors.New("Model " + name + " does not exist. Use `cordctl models available` to get a list of available models")
246 }
247 return nil
248}
249
250// Create a model in XOS given a map of fields
251func CreateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
252 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
253 defer cancel()
254
255 headers := GenerateHeaders()
256
257 h := &RpcEventHandler{
258 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
259 }
260 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Create"+modelName, headers, h, h.GetParams)
261 if err != nil {
262 return err
263 } else if h.Status != nil && h.Status.Err() != nil {
264 return h.Status.Err()
265 }
266
267 resp, err := dynamic.AsDynamicMessage(h.Response)
268 if err != nil {
269 return err
270 }
271
272 fields["id"] = resp.GetFieldByName("id").(int32)
273
274 if resp.HasFieldName("uuid") {
275 fields["uuid"] = resp.GetFieldByName("uuid").(string)
276 }
277
278 return nil
279}
280
Scott Baker5281d002019-05-16 10:45:26 -0700281// Update a model in XOS given a map of fields
282func UpdateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
283 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
284 defer cancel()
285
286 headers := GenerateHeaders()
287
288 h := &RpcEventHandler{
289 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
290 }
291 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Update"+modelName, headers, h, h.GetParams)
292 if err != nil {
293 return err
294 } else if h.Status != nil && h.Status.Err() != nil {
295 return h.Status.Err()
296 }
297
298 resp, err := dynamic.AsDynamicMessage(h.Response)
299 if err != nil {
300 return err
301 }
302
303 // TODO: Do we need to do anything with the response?
304 _ = resp
305
306 return nil
307}
308
Scott Baker6cf525a2019-05-09 12:25:08 -0700309// Get a model from XOS given its ID
Scott Bakerc328cf12019-05-28 16:03:12 -0700310func GetModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
311 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker6cf525a2019-05-09 12:25:08 -0700312 defer cancel()
313
314 headers := GenerateHeaders()
315
316 h := &RpcEventHandler{
317 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
318 }
319 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Get"+modelName, headers, h, h.GetParams)
320 if err != nil {
321 return nil, err
322 }
323
324 if h.Status != nil && h.Status.Err() != nil {
325 return nil, h.Status.Err()
326 }
327
328 d, err := dynamic.AsDynamicMessage(h.Response)
329 if err != nil {
330 return nil, err
331 }
332
333 return d, nil
334}
335
336// Get a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700337func GetModelWithRetry(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
Scott Baker6cf525a2019-05-09 12:25:08 -0700338 quiet := (flags & GM_QUIET) != 0
339 until_found := (flags & GM_UNTIL_FOUND) != 0
340 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
341 until_status := (flags & GM_UNTIL_STATUS) != 0
342
343 for {
344 var err error
345
346 if conn == nil {
347 conn, err = NewConnection()
348 if err != nil {
349 return nil, nil, err
350 }
351 }
352
Scott Bakerc328cf12019-05-28 16:03:12 -0700353 model, err := GetModel(ctx, conn, descriptor, modelName, id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700354 if err != nil {
355 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
356 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
357 if !quiet {
358 fmt.Print(".")
359 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700360 select {
361 case <-time.After(100 * time.Millisecond):
362 case <-ctx.Done():
363 return nil, nil, ctx.Err()
364 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700365 conn.Close()
366 conn = nil
367 continue
368 }
369
370 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
371 if !quiet {
372 fmt.Print("x")
373 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700374 select {
375 case <-time.After(100 * time.Millisecond):
376 case <-ctx.Done():
377 return nil, nil, ctx.Err()
378 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700379 continue
380 }
381 return nil, nil, err
382 }
383
384 if until_enacted && !IsEnacted(model) {
385 if !quiet {
386 fmt.Print("o")
387 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700388 select {
389 case <-time.After(100 * time.Millisecond):
390 case <-ctx.Done():
391 return nil, nil, ctx.Err()
392 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700393 continue
394 }
395
396 if until_status && model.GetFieldByName("status") == nil {
397 if !quiet {
398 fmt.Print("O")
399 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700400 select {
401 case <-time.After(100 * time.Millisecond):
402 case <-ctx.Done():
403 return nil, nil, ctx.Err()
404 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700405 continue
406 }
407
408 return conn, model, nil
409 }
410}
411
Scott Baker5201c0b2019-05-15 15:35:56 -0700412func ItemsToDynamicMessageList(items interface{}) []*dynamic.Message {
413 result := make([]*dynamic.Message, len(items.([]interface{})))
414 for i, item := range items.([]interface{}) {
415 result[i] = item.(*dynamic.Message)
416 }
417 return result
418}
419
420// List all objects of a given model
Scott Bakerc328cf12019-05-28 16:03:12 -0700421func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
422 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker6cf525a2019-05-09 12:25:08 -0700423 defer cancel()
424
425 headers := GenerateHeaders()
426
Scott Baker6cf525a2019-05-09 12:25:08 -0700427 h := &RpcEventHandler{}
428 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.List"+modelName, headers, h, h.GetParams)
429 if err != nil {
430 return nil, err
431 }
432
433 if h.Status != nil && h.Status.Err() != nil {
434 return nil, h.Status.Err()
435 }
436
437 d, err := dynamic.AsDynamicMessage(h.Response)
438 if err != nil {
439 return nil, err
440 }
441
442 items, err := d.TryGetFieldByName("items")
443 if err != nil {
444 return nil, err
445 }
446
Scott Baker5201c0b2019-05-15 15:35:56 -0700447 return ItemsToDynamicMessageList(items), nil
448}
Scott Baker6cf525a2019-05-09 12:25:08 -0700449
Scott Baker5201c0b2019-05-15 15:35:56 -0700450// Filter models based on field values
451// queries is a map of <field_name> to <operator><query>
452// For example,
453// map[string]string{"name": "==mysite"}
Scott Bakerc328cf12019-05-28 16:03:12 -0700454func FilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
455 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker5201c0b2019-05-15 15:35:56 -0700456 defer cancel()
Scott Baker6cf525a2019-05-09 12:25:08 -0700457
Scott Baker5201c0b2019-05-15 15:35:56 -0700458 headers := GenerateHeaders()
459
460 model_descriptor, err := descriptor.FindSymbol("xos." + modelName)
461 if err != nil {
462 return nil, err
463 }
464 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
465 if !ok {
466 return nil, errors.New("Failed to convert model to a messagedescriptor")
Scott Baker6cf525a2019-05-09 12:25:08 -0700467 }
468
Scott Baker5201c0b2019-05-15 15:35:56 -0700469 h := &QueryEventHandler{
470 RpcEventHandler: RpcEventHandler{
471 Fields: map[string]map[string]interface{}{"xos.Query": map[string]interface{}{"kind": 0}},
472 },
473 Elements: queries,
474 Model: model_md,
475 Kind: "DEFAULT",
476 }
477 err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Filter"+modelName, headers, h, h.GetParams)
478 if err != nil {
479 return nil, err
480 }
481
482 if h.Status != nil && h.Status.Err() != nil {
483 return nil, h.Status.Err()
484 }
485
486 d, err := dynamic.AsDynamicMessage(h.Response)
487 if err != nil {
488 return nil, err
489 }
490
491 items, err := d.TryGetFieldByName("items")
492 if err != nil {
493 return nil, err
494 }
495
496 return ItemsToDynamicMessageList(items), nil
497}
498
Scott Baker5281d002019-05-16 10:45:26 -0700499// Call ListModels or FilterModels as appropriate
Scott Bakerc328cf12019-05-28 16:03:12 -0700500func ListOrFilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
Scott Baker5281d002019-05-16 10:45:26 -0700501 if len(queries) == 0 {
Scott Bakerc328cf12019-05-28 16:03:12 -0700502 return ListModels(ctx, conn, descriptor, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700503 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700504 return FilterModels(ctx, conn, descriptor, modelName, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700505 }
506}
507
Scott Baker5201c0b2019-05-15 15:35:56 -0700508// Get a model from XOS given a fieldName/fieldValue
Scott Bakerc328cf12019-05-28 16:03:12 -0700509func FindModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
510 models, err := FilterModels(ctx, conn, descriptor, modelName, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700511 if err != nil {
512 return nil, err
513 }
514
515 if len(models) == 0 {
516 return nil, errors.New("rpc error: code = NotFound")
517 }
518
519 return models[0], nil
Scott Baker6cf525a2019-05-09 12:25:08 -0700520}
521
522// Find a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700523func FindModelWithRetry(ctx context.Context, 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 -0700524 quiet := (flags & GM_QUIET) != 0
525 until_found := (flags & GM_UNTIL_FOUND) != 0
526 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
527 until_status := (flags & GM_UNTIL_STATUS) != 0
528
529 for {
530 var err error
531
532 if conn == nil {
533 conn, err = NewConnection()
534 if err != nil {
535 return nil, nil, err
536 }
537 }
538
Scott Bakerc328cf12019-05-28 16:03:12 -0700539 model, err := FindModel(ctx, conn, descriptor, modelName, queries)
Scott Baker6cf525a2019-05-09 12:25:08 -0700540 if err != nil {
541 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
542 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
543 if !quiet {
544 fmt.Print(".")
545 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700546 select {
547 case <-time.After(100 * time.Millisecond):
548 case <-ctx.Done():
549 return nil, nil, ctx.Err()
550 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700551 conn.Close()
552 conn = nil
553 continue
554 }
555
556 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
557 if !quiet {
558 fmt.Print("x")
559 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700560 select {
561 case <-time.After(100 * time.Millisecond):
562 case <-ctx.Done():
563 return nil, nil, ctx.Err()
564 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700565 continue
566 }
567 return nil, nil, err
568 }
569
570 if until_enacted && !IsEnacted(model) {
571 if !quiet {
572 fmt.Print("o")
573 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700574 select {
575 case <-time.After(100 * time.Millisecond):
576 case <-ctx.Done():
577 return nil, nil, ctx.Err()
578 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700579 continue
580 }
581
582 if until_status && model.GetFieldByName("status") == nil {
583 if !quiet {
584 fmt.Print("O")
585 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700586 select {
587 case <-time.After(100 * time.Millisecond):
588 case <-ctx.Done():
589 return nil, nil, ctx.Err()
590 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700591 continue
592 }
593
594 return conn, model, nil
595 }
596}
597
Scott Baker175cb402019-05-17 16:13:06 -0700598// Get a model from XOS given its ID
599func DeleteModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) error {
600 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
601 defer cancel()
602
603 headers := GenerateHeaders()
604
605 h := &RpcEventHandler{
606 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
607 }
608 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Delete"+modelName, headers, h, h.GetParams)
609 if err != nil {
610 return err
611 }
612
613 if h.Status != nil && h.Status.Err() != nil {
614 return h.Status.Err()
615 }
616
617 _, err = dynamic.AsDynamicMessage(h.Response)
618 if err != nil {
619 return err
620 }
621
622 return nil
623}
624
Scott Baker5201c0b2019-05-15 15:35:56 -0700625// Takes a *dynamic.Message and turns it into a map of fields to interfaces
626// TODO: Might be more useful to convert the values to strings and ints
Scott Baker6cf525a2019-05-09 12:25:08 -0700627func MessageToMap(d *dynamic.Message) map[string]interface{} {
628 fields := make(map[string]interface{})
629 for _, field_desc := range d.GetKnownFields() {
630 field_name := field_desc.GetName()
631 fields[field_name] = d.GetFieldByName(field_name)
632 }
633 return fields
634}
635
Scott Baker5201c0b2019-05-15 15:35:56 -0700636// Returns True if a message has been enacted
Scott Baker6cf525a2019-05-09 12:25:08 -0700637func IsEnacted(d *dynamic.Message) bool {
638 enacted := d.GetFieldByName("enacted").(float64)
639 updated := d.GetFieldByName("updated").(float64)
640
641 return (enacted >= updated)
642}