blob: d09f15ddef73fad7542433a5153c6a42dc6ed800 [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"
Scott Baker6cf525a2019-05-09 12:25:08 -070021 "fmt"
22 "github.com/fullstorydev/grpcurl"
Scott Baker5201c0b2019-05-15 15:35:56 -070023 "github.com/golang/protobuf/proto"
24 "github.com/golang/protobuf/protoc-gen-go/descriptor"
25 "github.com/jhump/protoreflect/desc"
Scott Baker6cf525a2019-05-09 12:25:08 -070026 "github.com/jhump/protoreflect/dynamic"
Scott Baker28a39562019-07-12 09:34:15 -070027 corderrors "github.com/opencord/cordctl/internal/pkg/error"
Scott Baker6cf525a2019-05-09 12:25:08 -070028 "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, "==") {
Scott Baker20481aa2019-06-20 11:00:54 -070068 return "", "", false, corderrors.NewInvalidInputError("Operator == is now allowed. Suggest using = instead.")
Scott Baker5201c0b2019-05-15 15:35:56 -070069 } 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 {
Scott Baker20481aa2019-06-20 11:00:54 -0700112 return corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: h.Model.GetName(), FieldName: field_name})
Scott Baker5281d002019-05-16 10:45:26 -0700113 }
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:
Scott Baker20481aa2019-06-20 11:00:54 -0700126 err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
Scott Baker5281d002019-05-16 10:45:26 -0700127 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
Scott Baker20481aa2019-06-20 11:00:54 -0700128 err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
Scott Baker5281d002019-05-16 10:45:26 -0700129 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 Baker20481aa2019-06-20 11:00:54 -0700171 return nil, corderrors.WithStackTrace(&corderrors.IllegalQueryError{Query: 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 {
Scott Baker20481aa2019-06-20 11:00:54 -0700196 return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
Scott Baker5281d002019-05-16 10:45:26 -0700197 }
198 field_descriptor := model_md.FindFieldByName(field_name)
199 if field_descriptor == nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700200 return nil, corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: modelName, FieldName: field_name})
Scott Baker5281d002019-05-16 10:45:26 -0700201 }
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 {
Scott Baker20481aa2019-06-20 11:00:54 -0700257 return corderrors.WithStackTrace(&corderrors.UnknownModelTypeError{Name: name})
Scott Baker6cf525a2019-05-09 12:25:08 -0700258 }
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 {
Scott Bakerce11c492019-07-30 15:53:34 -0700264 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker6cf525a2019-05-09 12:25:08 -0700265 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 {
Scott Bakera55e6452019-06-25 11:10:30 -0700274 return corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700275 } else if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700276 return corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700277 }
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 {
Scott Bakerce11c492019-07-30 15:53:34 -0700295 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker5281d002019-05-16 10:45:26 -0700296 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 {
Scott Bakera55e6452019-06-25 11:10:30 -0700305 return corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700306 } else if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700307 return corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700308 }
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) {
Scott Bakerce11c492019-07-30 15:53:34 -0700323 ctx, cancel := GrpcTimeoutContext(context.Background())
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 {
Scott Baker20481aa2019-06-20 11:00:54 -0700333 return nil, corderrors.RpcErrorWithIdToCordError(err, modelName, id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700334 }
335
336 if h.Status != nil && h.Status.Err() != nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700337 return nil, corderrors.RpcErrorWithIdToCordError(h.Status.Err(), modelName, id) //h.Status.Err()
Scott Baker6cf525a2019-05-09 12:25:08 -0700338 }
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
Scott Baker20481aa2019-06-20 11:00:54 -0700382 _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
383 if until_found && is_not_found_error {
Scott Baker6cf525a2019-05-09 12:25:08 -0700384 if !quiet {
385 fmt.Print("x")
386 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700387 select {
388 case <-time.After(100 * time.Millisecond):
389 case <-ctx.Done():
390 return nil, nil, ctx.Err()
391 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700392 continue
393 }
394 return nil, nil, err
395 }
396
397 if until_enacted && !IsEnacted(model) {
398 if !quiet {
399 fmt.Print("o")
400 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700401 select {
402 case <-time.After(100 * time.Millisecond):
403 case <-ctx.Done():
404 return nil, nil, ctx.Err()
405 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700406 continue
407 }
408
409 if until_status && model.GetFieldByName("status") == nil {
410 if !quiet {
411 fmt.Print("O")
412 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700413 select {
414 case <-time.After(100 * time.Millisecond):
415 case <-ctx.Done():
416 return nil, nil, ctx.Err()
417 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700418 continue
419 }
420
421 return conn, model, nil
422 }
423}
424
Scott Baker5201c0b2019-05-15 15:35:56 -0700425func ItemsToDynamicMessageList(items interface{}) []*dynamic.Message {
426 result := make([]*dynamic.Message, len(items.([]interface{})))
427 for i, item := range items.([]interface{}) {
428 result[i] = item.(*dynamic.Message)
429 }
430 return result
431}
432
433// List all objects of a given model
Scott Bakerc328cf12019-05-28 16:03:12 -0700434func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
Scott Bakerce11c492019-07-30 15:53:34 -0700435 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker6cf525a2019-05-09 12:25:08 -0700436 defer cancel()
437
438 headers := GenerateHeaders()
439
Scott Baker6cf525a2019-05-09 12:25:08 -0700440 h := &RpcEventHandler{}
441 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.List"+modelName, headers, h, h.GetParams)
442 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700443 return nil, corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700444 }
445
446 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700447 return nil, corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700448 }
449
450 d, err := dynamic.AsDynamicMessage(h.Response)
451 if err != nil {
452 return nil, err
453 }
454
455 items, err := d.TryGetFieldByName("items")
456 if err != nil {
457 return nil, err
458 }
459
Scott Baker5201c0b2019-05-15 15:35:56 -0700460 return ItemsToDynamicMessageList(items), nil
461}
Scott Baker6cf525a2019-05-09 12:25:08 -0700462
Scott Baker5201c0b2019-05-15 15:35:56 -0700463// Filter models based on field values
464// queries is a map of <field_name> to <operator><query>
465// For example,
466// map[string]string{"name": "==mysite"}
Scott Baker1dd06672019-06-14 15:40:56 -0700467func FilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
Scott Bakerce11c492019-07-30 15:53:34 -0700468 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker5201c0b2019-05-15 15:35:56 -0700469 defer cancel()
Scott Baker6cf525a2019-05-09 12:25:08 -0700470
Scott Baker5201c0b2019-05-15 15:35:56 -0700471 headers := GenerateHeaders()
472
473 model_descriptor, err := descriptor.FindSymbol("xos." + modelName)
474 if err != nil {
475 return nil, err
476 }
477 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
478 if !ok {
Scott Baker20481aa2019-06-20 11:00:54 -0700479 return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
Scott Baker6cf525a2019-05-09 12:25:08 -0700480 }
481
Scott Baker5201c0b2019-05-15 15:35:56 -0700482 h := &QueryEventHandler{
483 RpcEventHandler: RpcEventHandler{
484 Fields: map[string]map[string]interface{}{"xos.Query": map[string]interface{}{"kind": 0}},
485 },
486 Elements: queries,
487 Model: model_md,
Scott Baker1dd06672019-06-14 15:40:56 -0700488 Kind: kind,
Scott Baker5201c0b2019-05-15 15:35:56 -0700489 }
490 err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Filter"+modelName, headers, h, h.GetParams)
491 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700492 return nil, corderrors.RpcErrorWithQueriesToCordError(err, modelName, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700493 }
494
495 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700496 return nil, corderrors.RpcErrorWithQueriesToCordError(h.Status.Err(), modelName, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700497 }
498
499 d, err := dynamic.AsDynamicMessage(h.Response)
500 if err != nil {
501 return nil, err
502 }
503
504 items, err := d.TryGetFieldByName("items")
505 if err != nil {
506 return nil, err
507 }
508
509 return ItemsToDynamicMessageList(items), nil
510}
511
Scott Baker5281d002019-05-16 10:45:26 -0700512// Call ListModels or FilterModels as appropriate
Scott Baker1dd06672019-06-14 15:40:56 -0700513func ListOrFilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
514 if (len(queries) == 0) && (kind == FILTER_DEFAULT) {
Scott Bakerc328cf12019-05-28 16:03:12 -0700515 return ListModels(ctx, conn, descriptor, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700516 } else {
Scott Baker1dd06672019-06-14 15:40:56 -0700517 return FilterModels(ctx, conn, descriptor, modelName, kind, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700518 }
519}
520
Scott Baker5201c0b2019-05-15 15:35:56 -0700521// Get a model from XOS given a fieldName/fieldValue
Scott Bakerc328cf12019-05-28 16:03:12 -0700522func 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 -0700523 models, err := FilterModels(ctx, conn, descriptor, modelName, FILTER_DEFAULT, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700524 if err != nil {
525 return nil, err
526 }
527
528 if len(models) == 0 {
Scott Bakera55e6452019-06-25 11:10:30 -0700529 cordError := &corderrors.ModelNotFoundError{}
530 cordError.Obj = corderrors.ObjectReference{ModelName: modelName, Queries: queries}
531 return nil, corderrors.WithStackTrace(cordError)
Scott Baker5201c0b2019-05-15 15:35:56 -0700532 }
533
534 return models[0], nil
Scott Baker6cf525a2019-05-09 12:25:08 -0700535}
536
537// Find a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700538func 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 -0700539 quiet := (flags & GM_QUIET) != 0
540 until_found := (flags & GM_UNTIL_FOUND) != 0
541 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
542 until_status := (flags & GM_UNTIL_STATUS) != 0
543
544 for {
545 var err error
546
547 if conn == nil {
548 conn, err = NewConnection()
549 if err != nil {
550 return nil, nil, err
551 }
552 }
553
Scott Bakerc328cf12019-05-28 16:03:12 -0700554 model, err := FindModel(ctx, conn, descriptor, modelName, queries)
Scott Baker6cf525a2019-05-09 12:25:08 -0700555 if err != nil {
556 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
557 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
558 if !quiet {
559 fmt.Print(".")
560 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700561 select {
562 case <-time.After(100 * time.Millisecond):
563 case <-ctx.Done():
564 return nil, nil, ctx.Err()
565 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700566 conn.Close()
567 conn = nil
568 continue
569 }
570
Scott Baker20481aa2019-06-20 11:00:54 -0700571 _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
572 if until_found && is_not_found_error {
Scott Baker6cf525a2019-05-09 12:25:08 -0700573 if !quiet {
574 fmt.Print("x")
575 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700576 select {
577 case <-time.After(100 * time.Millisecond):
578 case <-ctx.Done():
579 return nil, nil, ctx.Err()
580 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700581 continue
582 }
583 return nil, nil, err
584 }
585
586 if until_enacted && !IsEnacted(model) {
587 if !quiet {
588 fmt.Print("o")
589 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700590 select {
591 case <-time.After(100 * time.Millisecond):
592 case <-ctx.Done():
593 return nil, nil, ctx.Err()
594 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700595 continue
596 }
597
598 if until_status && model.GetFieldByName("status") == nil {
599 if !quiet {
600 fmt.Print("O")
601 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700602 select {
603 case <-time.After(100 * time.Millisecond):
604 case <-ctx.Done():
605 return nil, nil, ctx.Err()
606 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700607 continue
608 }
609
610 return conn, model, nil
611 }
612}
613
Scott Baker175cb402019-05-17 16:13:06 -0700614// Get a model from XOS given its ID
615func DeleteModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) error {
Scott Bakerce11c492019-07-30 15:53:34 -0700616 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker175cb402019-05-17 16:13:06 -0700617 defer cancel()
618
619 headers := GenerateHeaders()
620
621 h := &RpcEventHandler{
622 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
623 }
624 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Delete"+modelName, headers, h, h.GetParams)
625 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700626 return corderrors.RpcErrorWithIdToCordError(err, modelName, id)
Scott Baker175cb402019-05-17 16:13:06 -0700627 }
628
629 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700630 return corderrors.RpcErrorWithIdToCordError(h.Status.Err(), modelName, id)
Scott Baker175cb402019-05-17 16:13:06 -0700631 }
632
633 _, err = dynamic.AsDynamicMessage(h.Response)
634 if err != nil {
635 return err
636 }
637
638 return nil
639}
640
Scott Baker5201c0b2019-05-15 15:35:56 -0700641// Takes a *dynamic.Message and turns it into a map of fields to interfaces
642// TODO: Might be more useful to convert the values to strings and ints
Scott Baker6cf525a2019-05-09 12:25:08 -0700643func MessageToMap(d *dynamic.Message) map[string]interface{} {
644 fields := make(map[string]interface{})
645 for _, field_desc := range d.GetKnownFields() {
646 field_name := field_desc.GetName()
647 fields[field_name] = d.GetFieldByName(field_name)
648 }
649 return fields
650}
651
Scott Baker5201c0b2019-05-15 15:35:56 -0700652// Returns True if a message has been enacted
Scott Baker6cf525a2019-05-09 12:25:08 -0700653func IsEnacted(d *dynamic.Message) bool {
654 enacted := d.GetFieldByName("enacted").(float64)
655 updated := d.GetFieldByName("updated").(float64)
656
657 return (enacted >= updated)
658}