blob: 5938698dd4b95f000c7f65bf243442eaa371341c [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 Baker1dd06672019-06-14 15:40:56 -070036const (
37 GM_QUIET = 1
38 GM_UNTIL_FOUND = 2
39 GM_UNTIL_ENACTED = 4
40 GM_UNTIL_STATUS = 8
41)
42
43// Valid choices for FilterModels `Kind` argument
44const (
45 FILTER_DEFAULT = "DEFAULT"
46 FILTER_ALL = "ALL"
47 FILTER_DIRTY = "SYNCHRONIZER_DIRTY_OBJECTS"
48 FILTER_DELETED = "SYNCHRONIZER_DELETED_OBJECTS"
49 FILTER_DIRTYPOL = "SYNCHRONIZER_DIRTY_POLICIES"
50 FILTER_DELETEDPOL = "SYNCHRONIZER_DELETED_POLICIES"
51)
Scott Baker6cf525a2019-05-09 12:25:08 -070052
Scott Baker5201c0b2019-05-15 15:35:56 -070053type QueryEventHandler struct {
54 RpcEventHandler
55 Elements map[string]string
56 Model *desc.MessageDescriptor
57 Kind string
58 EOF bool
59}
60
61// Separate the operator from the query value.
62// For example,
63// "==foo" --> "EQUAL", "foo"
64func DecodeOperator(query string) (string, string, bool, error) {
65 if strings.HasPrefix(query, "!=") {
Scott Baker5281d002019-05-16 10:45:26 -070066 return strings.TrimSpace(query[2:]), "EQUAL", true, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070067 } else if strings.HasPrefix(query, "==") {
68 return "", "", false, errors.New("Operator == is now allowed. Suggest using = instead.")
69 } else if strings.HasPrefix(query, "=") {
Scott Baker5281d002019-05-16 10:45:26 -070070 return strings.TrimSpace(query[1:]), "EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070071 } else if strings.HasPrefix(query, ">=") {
Scott Baker5281d002019-05-16 10:45:26 -070072 return strings.TrimSpace(query[2:]), "GREATER_THAN_OR_EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070073 } else if strings.HasPrefix(query, ">") {
Scott Baker5281d002019-05-16 10:45:26 -070074 return strings.TrimSpace(query[1:]), "GREATER_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070075 } else if strings.HasPrefix(query, "<=") {
Scott Baker5281d002019-05-16 10:45:26 -070076 return strings.TrimSpace(query[2:]), "LESS_THAN_OR_EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070077 } else if strings.HasPrefix(query, "<") {
Scott Baker5281d002019-05-16 10:45:26 -070078 return strings.TrimSpace(query[1:]), "LESS_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070079 } else {
Scott Baker5281d002019-05-16 10:45:26 -070080 return strings.TrimSpace(query), "EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070081 }
82}
83
84// Generate the parameters for Query messages.
85func (h *QueryEventHandler) GetParams(msg proto.Message) error {
86 dmsg, err := dynamic.AsDynamicMessage(msg)
87 if err != nil {
88 return err
89 }
90
91 //fmt.Printf("MessageName: %s\n", dmsg.XXX_MessageName())
92
93 if h.EOF {
94 return io.EOF
95 }
96
97 // Get the MessageType for the `elements` field
98 md := dmsg.GetMessageDescriptor()
99 elements_fld := md.FindFieldByName("elements")
100 elements_mt := elements_fld.GetMessageType()
101
102 for field_name, element := range h.Elements {
103 value, operator, invert, err := DecodeOperator(element)
104 if err != nil {
105 return err
106 }
107
108 nm := dynamic.NewMessage(elements_mt)
109
Scott Baker5281d002019-05-16 10:45:26 -0700110 field_descriptor := h.Model.FindFieldByName(field_name)
111 if field_descriptor == nil {
112 return fmt.Errorf("Field %s does not exist", field_name)
113 }
114
115 field_type := field_descriptor.GetType()
116 switch field_type {
117 case descriptor.FieldDescriptorProto_TYPE_INT32:
118 var i int64
119 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700120 nm.SetFieldByName("iValue", int32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700121 case descriptor.FieldDescriptorProto_TYPE_UINT32:
122 var i int64
123 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700124 nm.SetFieldByName("iValue", uint32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700125 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
126 err = errors.New("Floating point filters are unsupported")
127 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
128 err = errors.New("Floating point filters are unsupported")
129 default:
Scott Baker5201c0b2019-05-15 15:35:56 -0700130 nm.SetFieldByName("sValue", value)
Scott Baker5281d002019-05-16 10:45:26 -0700131 err = nil
132 }
133
134 if err != nil {
135 return err
Scott Baker5201c0b2019-05-15 15:35:56 -0700136 }
137
138 nm.SetFieldByName("name", field_name)
139 nm.SetFieldByName("invert", invert)
140 SetEnumValue(nm, "operator", operator)
141 dmsg.AddRepeatedFieldByName("elements", nm)
142 }
143
144 SetEnumValue(dmsg, "kind", h.Kind)
145
146 h.EOF = true
147
148 return nil
149}
150
151// Take a string list of queries and turns it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700152func QueryStringsToMap(query_args []string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700153 queries := make(map[string]string)
154 for _, query_str := range query_args {
Scott Baker5281d002019-05-16 10:45:26 -0700155 query_str := strings.TrimSpace(query_str)
Scott Baker5201c0b2019-05-15 15:35:56 -0700156 operator_pos := -1
157 for i, ch := range query_str {
Scott Baker5281d002019-05-16 10:45:26 -0700158 if allow_inequality {
159 if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') {
160 operator_pos = i
161 break
162 }
163 } else {
164 if ch == '=' {
165 operator_pos = i
166 break
167 }
Scott Baker5201c0b2019-05-15 15:35:56 -0700168 }
169 }
170 if operator_pos == -1 {
Scott Baker5281d002019-05-16 10:45:26 -0700171 return nil, fmt.Errorf("Illegal operator/value string %s", query_str)
Scott Baker5201c0b2019-05-15 15:35:56 -0700172 }
Scott Baker5281d002019-05-16 10:45:26 -0700173 queries[strings.TrimSpace(query_str[:operator_pos])] = query_str[operator_pos:]
Scott Baker5201c0b2019-05-15 15:35:56 -0700174 }
175 return queries, nil
176}
177
178// Take a string of comma-separated queries and turn it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700179func CommaSeparatedQueryToMap(query_str string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700180 if query_str == "" {
181 return nil, nil
182 }
183
184 query_strings := strings.Split(query_str, ",")
Scott Baker5281d002019-05-16 10:45:26 -0700185 return QueryStringsToMap(query_strings, allow_inequality)
186}
187
188// Convert a string into the appropriate gRPC type for a given field
189func TypeConvert(source grpcurl.DescriptorSource, modelName string, field_name string, v string) (interface{}, error) {
190 model_descriptor, err := source.FindSymbol("xos." + modelName)
191 if err != nil {
192 return nil, err
193 }
194 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
195 if !ok {
196 return nil, fmt.Errorf("Failed to convert model %s to a messagedescriptor", modelName)
197 }
198 field_descriptor := model_md.FindFieldByName(field_name)
199 if field_descriptor == nil {
200 return nil, fmt.Errorf("Field %s does not exist in model %s", field_name, modelName)
201 }
202 field_type := field_descriptor.GetType()
203
204 var result interface{}
205
206 switch field_type {
207 case descriptor.FieldDescriptorProto_TYPE_INT32:
208 var i int64
209 i, err = strconv.ParseInt(v, 10, 32)
210 result = int32(i)
211 case descriptor.FieldDescriptorProto_TYPE_UINT32:
212 var i int64
213 i, err = strconv.ParseInt(v, 10, 32)
214 result = uint32(i)
215 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
216 var f float64
217 f, err = strconv.ParseFloat(v, 32)
218 result = float32(f)
219 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
220 var f float64
221 f, err = strconv.ParseFloat(v, 64)
222 result = f
223 default:
224 result = v
225 err = nil
226 }
227
228 return result, err
Scott Baker5201c0b2019-05-15 15:35:56 -0700229}
230
Scott Baker6cf525a2019-05-09 12:25:08 -0700231// Return a list of all available model names
232func GetModelNames(source grpcurl.DescriptorSource) (map[string]bool, error) {
233 models := make(map[string]bool)
234 methods, err := grpcurl.ListMethods(source, "xos.xos")
235
236 if err != nil {
237 return nil, err
238 }
239
240 for _, method := range methods {
241 if strings.HasPrefix(method, "xos.xos.Get") {
242 models[method[11:]] = true
243 }
244 }
245
246 return models, nil
247}
248
249// Check to see if a model name is valid
250func CheckModelName(source grpcurl.DescriptorSource, name string) error {
251 models, err := GetModelNames(source)
252 if err != nil {
253 return err
254 }
255 _, present := models[name]
256 if !present {
257 return errors.New("Model " + name + " does not exist. Use `cordctl models available` to get a list of available models")
258 }
259 return nil
260}
261
262// Create a model in XOS given a map of fields
263func CreateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
264 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
265 defer cancel()
266
267 headers := GenerateHeaders()
268
269 h := &RpcEventHandler{
270 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
271 }
272 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Create"+modelName, headers, h, h.GetParams)
273 if err != nil {
274 return err
275 } else if h.Status != nil && h.Status.Err() != nil {
276 return h.Status.Err()
277 }
278
279 resp, err := dynamic.AsDynamicMessage(h.Response)
280 if err != nil {
281 return err
282 }
283
284 fields["id"] = resp.GetFieldByName("id").(int32)
285
286 if resp.HasFieldName("uuid") {
287 fields["uuid"] = resp.GetFieldByName("uuid").(string)
288 }
289
290 return nil
291}
292
Scott Baker5281d002019-05-16 10:45:26 -0700293// Update a model in XOS given a map of fields
294func UpdateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
295 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
296 defer cancel()
297
298 headers := GenerateHeaders()
299
300 h := &RpcEventHandler{
301 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
302 }
303 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Update"+modelName, headers, h, h.GetParams)
304 if err != nil {
305 return err
306 } else if h.Status != nil && h.Status.Err() != nil {
307 return h.Status.Err()
308 }
309
310 resp, err := dynamic.AsDynamicMessage(h.Response)
311 if err != nil {
312 return err
313 }
314
315 // TODO: Do we need to do anything with the response?
316 _ = resp
317
318 return nil
319}
320
Scott Baker6cf525a2019-05-09 12:25:08 -0700321// Get a model from XOS given its ID
Scott Bakerc328cf12019-05-28 16:03:12 -0700322func GetModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
323 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker6cf525a2019-05-09 12:25:08 -0700324 defer cancel()
325
326 headers := GenerateHeaders()
327
328 h := &RpcEventHandler{
329 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
330 }
331 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Get"+modelName, headers, h, h.GetParams)
332 if err != nil {
333 return nil, err
334 }
335
336 if h.Status != nil && h.Status.Err() != nil {
337 return nil, h.Status.Err()
338 }
339
340 d, err := dynamic.AsDynamicMessage(h.Response)
341 if err != nil {
342 return nil, err
343 }
344
345 return d, nil
346}
347
348// Get a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700349func 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 -0700350 quiet := (flags & GM_QUIET) != 0
351 until_found := (flags & GM_UNTIL_FOUND) != 0
352 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
353 until_status := (flags & GM_UNTIL_STATUS) != 0
354
355 for {
356 var err error
357
358 if conn == nil {
359 conn, err = NewConnection()
360 if err != nil {
361 return nil, nil, err
362 }
363 }
364
Scott Bakerc328cf12019-05-28 16:03:12 -0700365 model, err := GetModel(ctx, conn, descriptor, modelName, id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700366 if err != nil {
367 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
368 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
369 if !quiet {
370 fmt.Print(".")
371 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700372 select {
373 case <-time.After(100 * time.Millisecond):
374 case <-ctx.Done():
375 return nil, nil, ctx.Err()
376 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700377 conn.Close()
378 conn = nil
379 continue
380 }
381
382 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
383 if !quiet {
384 fmt.Print("x")
385 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700386 select {
387 case <-time.After(100 * time.Millisecond):
388 case <-ctx.Done():
389 return nil, nil, ctx.Err()
390 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700391 continue
392 }
393 return nil, nil, err
394 }
395
396 if until_enacted && !IsEnacted(model) {
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 if until_status && model.GetFieldByName("status") == nil {
409 if !quiet {
410 fmt.Print("O")
411 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700412 select {
413 case <-time.After(100 * time.Millisecond):
414 case <-ctx.Done():
415 return nil, nil, ctx.Err()
416 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700417 continue
418 }
419
420 return conn, model, nil
421 }
422}
423
Scott Baker5201c0b2019-05-15 15:35:56 -0700424func ItemsToDynamicMessageList(items interface{}) []*dynamic.Message {
425 result := make([]*dynamic.Message, len(items.([]interface{})))
426 for i, item := range items.([]interface{}) {
427 result[i] = item.(*dynamic.Message)
428 }
429 return result
430}
431
432// List all objects of a given model
Scott Bakerc328cf12019-05-28 16:03:12 -0700433func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
434 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker6cf525a2019-05-09 12:25:08 -0700435 defer cancel()
436
437 headers := GenerateHeaders()
438
Scott Baker6cf525a2019-05-09 12:25:08 -0700439 h := &RpcEventHandler{}
440 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.List"+modelName, headers, h, h.GetParams)
441 if err != nil {
442 return nil, err
443 }
444
445 if h.Status != nil && h.Status.Err() != nil {
446 return nil, h.Status.Err()
447 }
448
449 d, err := dynamic.AsDynamicMessage(h.Response)
450 if err != nil {
451 return nil, err
452 }
453
454 items, err := d.TryGetFieldByName("items")
455 if err != nil {
456 return nil, err
457 }
458
Scott Baker5201c0b2019-05-15 15:35:56 -0700459 return ItemsToDynamicMessageList(items), nil
460}
Scott Baker6cf525a2019-05-09 12:25:08 -0700461
Scott Baker5201c0b2019-05-15 15:35:56 -0700462// Filter models based on field values
463// queries is a map of <field_name> to <operator><query>
464// For example,
465// map[string]string{"name": "==mysite"}
Scott Baker1dd06672019-06-14 15:40:56 -0700466func FilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
Scott Bakerc328cf12019-05-28 16:03:12 -0700467 ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
Scott Baker5201c0b2019-05-15 15:35:56 -0700468 defer cancel()
Scott Baker6cf525a2019-05-09 12:25:08 -0700469
Scott Baker5201c0b2019-05-15 15:35:56 -0700470 headers := GenerateHeaders()
471
472 model_descriptor, err := descriptor.FindSymbol("xos." + modelName)
473 if err != nil {
474 return nil, err
475 }
476 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
477 if !ok {
478 return nil, errors.New("Failed to convert model to a messagedescriptor")
Scott Baker6cf525a2019-05-09 12:25:08 -0700479 }
480
Scott Baker5201c0b2019-05-15 15:35:56 -0700481 h := &QueryEventHandler{
482 RpcEventHandler: RpcEventHandler{
483 Fields: map[string]map[string]interface{}{"xos.Query": map[string]interface{}{"kind": 0}},
484 },
485 Elements: queries,
486 Model: model_md,
Scott Baker1dd06672019-06-14 15:40:56 -0700487 Kind: kind,
Scott Baker5201c0b2019-05-15 15:35:56 -0700488 }
489 err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Filter"+modelName, headers, h, h.GetParams)
490 if err != nil {
491 return nil, err
492 }
493
494 if h.Status != nil && h.Status.Err() != nil {
495 return nil, h.Status.Err()
496 }
497
498 d, err := dynamic.AsDynamicMessage(h.Response)
499 if err != nil {
500 return nil, err
501 }
502
503 items, err := d.TryGetFieldByName("items")
504 if err != nil {
505 return nil, err
506 }
507
508 return ItemsToDynamicMessageList(items), nil
509}
510
Scott Baker5281d002019-05-16 10:45:26 -0700511// Call ListModels or FilterModels as appropriate
Scott Baker1dd06672019-06-14 15:40:56 -0700512func ListOrFilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
513 if (len(queries) == 0) && (kind == FILTER_DEFAULT) {
Scott Bakerc328cf12019-05-28 16:03:12 -0700514 return ListModels(ctx, conn, descriptor, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700515 } else {
Scott Baker1dd06672019-06-14 15:40:56 -0700516 return FilterModels(ctx, conn, descriptor, modelName, kind, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700517 }
518}
519
Scott Baker5201c0b2019-05-15 15:35:56 -0700520// Get a model from XOS given a fieldName/fieldValue
Scott Bakerc328cf12019-05-28 16:03:12 -0700521func FindModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
Scott Baker1dd06672019-06-14 15:40:56 -0700522 models, err := FilterModels(ctx, conn, descriptor, modelName, FILTER_DEFAULT, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700523 if err != nil {
524 return nil, err
525 }
526
527 if len(models) == 0 {
528 return nil, errors.New("rpc error: code = NotFound")
529 }
530
531 return models[0], nil
Scott Baker6cf525a2019-05-09 12:25:08 -0700532}
533
534// Find a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700535func 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 -0700536 quiet := (flags & GM_QUIET) != 0
537 until_found := (flags & GM_UNTIL_FOUND) != 0
538 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
539 until_status := (flags & GM_UNTIL_STATUS) != 0
540
541 for {
542 var err error
543
544 if conn == nil {
545 conn, err = NewConnection()
546 if err != nil {
547 return nil, nil, err
548 }
549 }
550
Scott Bakerc328cf12019-05-28 16:03:12 -0700551 model, err := FindModel(ctx, conn, descriptor, modelName, queries)
Scott Baker6cf525a2019-05-09 12:25:08 -0700552 if err != nil {
553 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
554 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
555 if !quiet {
556 fmt.Print(".")
557 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700558 select {
559 case <-time.After(100 * time.Millisecond):
560 case <-ctx.Done():
561 return nil, nil, ctx.Err()
562 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700563 conn.Close()
564 conn = nil
565 continue
566 }
567
568 if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
569 if !quiet {
570 fmt.Print("x")
571 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700572 select {
573 case <-time.After(100 * time.Millisecond):
574 case <-ctx.Done():
575 return nil, nil, ctx.Err()
576 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700577 continue
578 }
579 return nil, nil, err
580 }
581
582 if until_enacted && !IsEnacted(model) {
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 if until_status && model.GetFieldByName("status") == nil {
595 if !quiet {
596 fmt.Print("O")
597 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700598 select {
599 case <-time.After(100 * time.Millisecond):
600 case <-ctx.Done():
601 return nil, nil, ctx.Err()
602 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700603 continue
604 }
605
606 return conn, model, nil
607 }
608}
609
Scott Baker175cb402019-05-17 16:13:06 -0700610// Get a model from XOS given its ID
611func DeleteModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) error {
612 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
613 defer cancel()
614
615 headers := GenerateHeaders()
616
617 h := &RpcEventHandler{
618 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
619 }
620 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Delete"+modelName, headers, h, h.GetParams)
621 if err != nil {
622 return err
623 }
624
625 if h.Status != nil && h.Status.Err() != nil {
626 return h.Status.Err()
627 }
628
629 _, err = dynamic.AsDynamicMessage(h.Response)
630 if err != nil {
631 return err
632 }
633
634 return nil
635}
636
Scott Baker5201c0b2019-05-15 15:35:56 -0700637// Takes a *dynamic.Message and turns it into a map of fields to interfaces
638// TODO: Might be more useful to convert the values to strings and ints
Scott Baker6cf525a2019-05-09 12:25:08 -0700639func MessageToMap(d *dynamic.Message) map[string]interface{} {
640 fields := make(map[string]interface{})
641 for _, field_desc := range d.GetKnownFields() {
642 field_name := field_desc.GetName()
643 fields[field_name] = d.GetFieldByName(field_name)
644 }
645 return fields
646}
647
Scott Baker5201c0b2019-05-15 15:35:56 -0700648// Returns True if a message has been enacted
Scott Baker6cf525a2019-05-09 12:25:08 -0700649func IsEnacted(d *dynamic.Message) bool {
650 enacted := d.GetFieldByName("enacted").(float64)
651 updated := d.GetFieldByName("updated").(float64)
652
653 return (enacted >= updated)
654}