blob: eb7d64f0d7c299b5f49a5963cf5c2b9fd0517546 [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 Bakercbad9a92019-08-01 16:01:38 -070069 } else if strings.HasPrefix(query, "~=") {
70 return strings.TrimSpace(query[2:]), "REGEX", false, nil
71 } else if strings.HasPrefix(query, "[=") {
72 return strings.TrimSpace(query[2:]), "CONTAINS", 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:]), "EQUAL", 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:]), "GREATER_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:]), "GREATER_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070079 } else if strings.HasPrefix(query, "<=") {
Scott Baker5281d002019-05-16 10:45:26 -070080 return strings.TrimSpace(query[2:]), "LESS_THAN_OR_EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070081 } else if strings.HasPrefix(query, "<") {
Scott Baker5281d002019-05-16 10:45:26 -070082 return strings.TrimSpace(query[1:]), "LESS_THAN", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070083 } else {
Scott Baker5281d002019-05-16 10:45:26 -070084 return strings.TrimSpace(query), "EQUAL", false, nil
Scott Baker5201c0b2019-05-15 15:35:56 -070085 }
86}
87
88// Generate the parameters for Query messages.
89func (h *QueryEventHandler) GetParams(msg proto.Message) error {
90 dmsg, err := dynamic.AsDynamicMessage(msg)
91 if err != nil {
92 return err
93 }
94
95 //fmt.Printf("MessageName: %s\n", dmsg.XXX_MessageName())
96
97 if h.EOF {
98 return io.EOF
99 }
100
101 // Get the MessageType for the `elements` field
102 md := dmsg.GetMessageDescriptor()
103 elements_fld := md.FindFieldByName("elements")
104 elements_mt := elements_fld.GetMessageType()
105
106 for field_name, element := range h.Elements {
107 value, operator, invert, err := DecodeOperator(element)
108 if err != nil {
109 return err
110 }
111
112 nm := dynamic.NewMessage(elements_mt)
113
Scott Baker5281d002019-05-16 10:45:26 -0700114 field_descriptor := h.Model.FindFieldByName(field_name)
115 if field_descriptor == nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700116 return corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: h.Model.GetName(), FieldName: field_name})
Scott Baker5281d002019-05-16 10:45:26 -0700117 }
118
119 field_type := field_descriptor.GetType()
120 switch field_type {
121 case descriptor.FieldDescriptorProto_TYPE_INT32:
122 var i int64
123 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700124 nm.SetFieldByName("iValue", int32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700125 case descriptor.FieldDescriptorProto_TYPE_UINT32:
126 var i int64
127 i, err = strconv.ParseInt(value, 10, 32)
Scott Baker5201c0b2019-05-15 15:35:56 -0700128 nm.SetFieldByName("iValue", uint32(i))
Scott Baker5281d002019-05-16 10:45:26 -0700129 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
Scott Baker20481aa2019-06-20 11:00:54 -0700130 err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
Scott Baker5281d002019-05-16 10:45:26 -0700131 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
Scott Baker20481aa2019-06-20 11:00:54 -0700132 err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
Scott Baker5281d002019-05-16 10:45:26 -0700133 default:
Scott Baker5201c0b2019-05-15 15:35:56 -0700134 nm.SetFieldByName("sValue", value)
Scott Baker5281d002019-05-16 10:45:26 -0700135 err = nil
136 }
137
138 if err != nil {
139 return err
Scott Baker5201c0b2019-05-15 15:35:56 -0700140 }
141
142 nm.SetFieldByName("name", field_name)
143 nm.SetFieldByName("invert", invert)
144 SetEnumValue(nm, "operator", operator)
145 dmsg.AddRepeatedFieldByName("elements", nm)
146 }
147
148 SetEnumValue(dmsg, "kind", h.Kind)
149
150 h.EOF = true
151
152 return nil
153}
154
155// Take a string list of queries and turns it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700156func QueryStringsToMap(query_args []string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700157 queries := make(map[string]string)
158 for _, query_str := range query_args {
Scott Baker5281d002019-05-16 10:45:26 -0700159 query_str := strings.TrimSpace(query_str)
Scott Baker5201c0b2019-05-15 15:35:56 -0700160 operator_pos := -1
161 for i, ch := range query_str {
Scott Baker5281d002019-05-16 10:45:26 -0700162 if allow_inequality {
Scott Bakercbad9a92019-08-01 16:01:38 -0700163 if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') || (ch == '~') || (ch == '[') {
Scott Baker5281d002019-05-16 10:45:26 -0700164 operator_pos = i
165 break
166 }
167 } else {
168 if ch == '=' {
169 operator_pos = i
170 break
171 }
Scott Baker5201c0b2019-05-15 15:35:56 -0700172 }
173 }
174 if operator_pos == -1 {
Scott Baker20481aa2019-06-20 11:00:54 -0700175 return nil, corderrors.WithStackTrace(&corderrors.IllegalQueryError{Query: query_str})
Scott Baker5201c0b2019-05-15 15:35:56 -0700176 }
Scott Baker5281d002019-05-16 10:45:26 -0700177 queries[strings.TrimSpace(query_str[:operator_pos])] = query_str[operator_pos:]
Scott Baker5201c0b2019-05-15 15:35:56 -0700178 }
179 return queries, nil
180}
181
182// Take a string of comma-separated queries and turn it into a map of queries
Scott Baker5281d002019-05-16 10:45:26 -0700183func CommaSeparatedQueryToMap(query_str string, allow_inequality bool) (map[string]string, error) {
Scott Baker5201c0b2019-05-15 15:35:56 -0700184 if query_str == "" {
185 return nil, nil
186 }
187
188 query_strings := strings.Split(query_str, ",")
Scott Baker5281d002019-05-16 10:45:26 -0700189 return QueryStringsToMap(query_strings, allow_inequality)
190}
191
192// Convert a string into the appropriate gRPC type for a given field
193func TypeConvert(source grpcurl.DescriptorSource, modelName string, field_name string, v string) (interface{}, error) {
194 model_descriptor, err := source.FindSymbol("xos." + modelName)
195 if err != nil {
196 return nil, err
197 }
198 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
199 if !ok {
Scott Baker20481aa2019-06-20 11:00:54 -0700200 return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
Scott Baker5281d002019-05-16 10:45:26 -0700201 }
202 field_descriptor := model_md.FindFieldByName(field_name)
203 if field_descriptor == nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700204 return nil, corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: modelName, FieldName: field_name})
Scott Baker5281d002019-05-16 10:45:26 -0700205 }
206 field_type := field_descriptor.GetType()
207
208 var result interface{}
209
210 switch field_type {
211 case descriptor.FieldDescriptorProto_TYPE_INT32:
212 var i int64
213 i, err = strconv.ParseInt(v, 10, 32)
214 result = int32(i)
215 case descriptor.FieldDescriptorProto_TYPE_UINT32:
216 var i int64
217 i, err = strconv.ParseInt(v, 10, 32)
218 result = uint32(i)
219 case descriptor.FieldDescriptorProto_TYPE_FLOAT:
220 var f float64
221 f, err = strconv.ParseFloat(v, 32)
222 result = float32(f)
223 case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
224 var f float64
225 f, err = strconv.ParseFloat(v, 64)
226 result = f
227 default:
228 result = v
229 err = nil
230 }
231
232 return result, err
Scott Baker5201c0b2019-05-15 15:35:56 -0700233}
234
Scott Baker6cf525a2019-05-09 12:25:08 -0700235// Return a list of all available model names
236func GetModelNames(source grpcurl.DescriptorSource) (map[string]bool, error) {
237 models := make(map[string]bool)
238 methods, err := grpcurl.ListMethods(source, "xos.xos")
239
240 if err != nil {
241 return nil, err
242 }
243
244 for _, method := range methods {
245 if strings.HasPrefix(method, "xos.xos.Get") {
246 models[method[11:]] = true
247 }
248 }
249
250 return models, nil
251}
252
253// Check to see if a model name is valid
254func CheckModelName(source grpcurl.DescriptorSource, name string) error {
255 models, err := GetModelNames(source)
256 if err != nil {
257 return err
258 }
259 _, present := models[name]
260 if !present {
Scott Baker20481aa2019-06-20 11:00:54 -0700261 return corderrors.WithStackTrace(&corderrors.UnknownModelTypeError{Name: name})
Scott Baker6cf525a2019-05-09 12:25:08 -0700262 }
263 return nil
264}
265
266// Create a model in XOS given a map of fields
267func CreateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
Scott Bakerce11c492019-07-30 15:53:34 -0700268 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker6cf525a2019-05-09 12:25:08 -0700269 defer cancel()
270
271 headers := GenerateHeaders()
272
273 h := &RpcEventHandler{
274 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
275 }
276 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Create"+modelName, headers, h, h.GetParams)
277 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700278 return corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700279 } else if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700280 return corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700281 }
282
283 resp, err := dynamic.AsDynamicMessage(h.Response)
284 if err != nil {
285 return err
286 }
287
288 fields["id"] = resp.GetFieldByName("id").(int32)
289
290 if resp.HasFieldName("uuid") {
291 fields["uuid"] = resp.GetFieldByName("uuid").(string)
292 }
293
294 return nil
295}
296
Scott Baker5281d002019-05-16 10:45:26 -0700297// Update a model in XOS given a map of fields
298func UpdateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
Scott Bakerce11c492019-07-30 15:53:34 -0700299 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker5281d002019-05-16 10:45:26 -0700300 defer cancel()
301
302 headers := GenerateHeaders()
303
304 h := &RpcEventHandler{
305 Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
306 }
307 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Update"+modelName, headers, h, h.GetParams)
308 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700309 return corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700310 } else if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700311 return corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700312 }
313
314 resp, err := dynamic.AsDynamicMessage(h.Response)
315 if err != nil {
316 return err
317 }
318
319 // TODO: Do we need to do anything with the response?
320 _ = resp
321
322 return nil
323}
324
Scott Baker6cf525a2019-05-09 12:25:08 -0700325// Get a model from XOS given its ID
Scott Bakerc328cf12019-05-28 16:03:12 -0700326func GetModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
Scott Bakerce11c492019-07-30 15:53:34 -0700327 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker6cf525a2019-05-09 12:25:08 -0700328 defer cancel()
329
330 headers := GenerateHeaders()
331
332 h := &RpcEventHandler{
333 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
334 }
335 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Get"+modelName, headers, h, h.GetParams)
336 if err != nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700337 return nil, corderrors.RpcErrorWithIdToCordError(err, modelName, id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700338 }
339
340 if h.Status != nil && h.Status.Err() != nil {
Scott Baker20481aa2019-06-20 11:00:54 -0700341 return nil, corderrors.RpcErrorWithIdToCordError(h.Status.Err(), modelName, id) //h.Status.Err()
Scott Baker6cf525a2019-05-09 12:25:08 -0700342 }
343
344 d, err := dynamic.AsDynamicMessage(h.Response)
345 if err != nil {
346 return nil, err
347 }
348
349 return d, nil
350}
351
352// Get a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700353func 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 -0700354 quiet := (flags & GM_QUIET) != 0
355 until_found := (flags & GM_UNTIL_FOUND) != 0
356 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
357 until_status := (flags & GM_UNTIL_STATUS) != 0
358
359 for {
360 var err error
361
362 if conn == nil {
363 conn, err = NewConnection()
364 if err != nil {
365 return nil, nil, err
366 }
367 }
368
Scott Bakerc328cf12019-05-28 16:03:12 -0700369 model, err := GetModel(ctx, conn, descriptor, modelName, id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700370 if err != nil {
371 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
372 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
373 if !quiet {
374 fmt.Print(".")
375 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700376 select {
377 case <-time.After(100 * time.Millisecond):
378 case <-ctx.Done():
379 return nil, nil, ctx.Err()
380 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700381 conn.Close()
382 conn = nil
383 continue
384 }
385
Scott Baker20481aa2019-06-20 11:00:54 -0700386 _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
387 if until_found && is_not_found_error {
Scott Baker6cf525a2019-05-09 12:25:08 -0700388 if !quiet {
389 fmt.Print("x")
390 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700391 select {
392 case <-time.After(100 * time.Millisecond):
393 case <-ctx.Done():
394 return nil, nil, ctx.Err()
395 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700396 continue
397 }
398 return nil, nil, err
399 }
400
401 if until_enacted && !IsEnacted(model) {
402 if !quiet {
403 fmt.Print("o")
404 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700405 select {
406 case <-time.After(100 * time.Millisecond):
407 case <-ctx.Done():
408 return nil, nil, ctx.Err()
409 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700410 continue
411 }
412
413 if until_status && model.GetFieldByName("status") == nil {
414 if !quiet {
415 fmt.Print("O")
416 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700417 select {
418 case <-time.After(100 * time.Millisecond):
419 case <-ctx.Done():
420 return nil, nil, ctx.Err()
421 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700422 continue
423 }
424
425 return conn, model, nil
426 }
427}
428
Scott Baker5201c0b2019-05-15 15:35:56 -0700429func ItemsToDynamicMessageList(items interface{}) []*dynamic.Message {
430 result := make([]*dynamic.Message, len(items.([]interface{})))
431 for i, item := range items.([]interface{}) {
432 result[i] = item.(*dynamic.Message)
433 }
434 return result
435}
436
437// List all objects of a given model
Scott Bakerc328cf12019-05-28 16:03:12 -0700438func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
Scott Bakerce11c492019-07-30 15:53:34 -0700439 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker6cf525a2019-05-09 12:25:08 -0700440 defer cancel()
441
442 headers := GenerateHeaders()
443
Scott Baker6cf525a2019-05-09 12:25:08 -0700444 h := &RpcEventHandler{}
445 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.List"+modelName, headers, h, h.GetParams)
446 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700447 return nil, corderrors.RpcErrorWithModelNameToCordError(err, modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700448 }
449
450 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700451 return nil, corderrors.RpcErrorWithModelNameToCordError(h.Status.Err(), modelName)
Scott Baker6cf525a2019-05-09 12:25:08 -0700452 }
453
454 d, err := dynamic.AsDynamicMessage(h.Response)
455 if err != nil {
456 return nil, err
457 }
458
459 items, err := d.TryGetFieldByName("items")
460 if err != nil {
461 return nil, err
462 }
463
Scott Baker5201c0b2019-05-15 15:35:56 -0700464 return ItemsToDynamicMessageList(items), nil
465}
Scott Baker6cf525a2019-05-09 12:25:08 -0700466
Scott Baker5201c0b2019-05-15 15:35:56 -0700467// Filter models based on field values
468// queries is a map of <field_name> to <operator><query>
469// For example,
470// map[string]string{"name": "==mysite"}
Scott Baker1dd06672019-06-14 15:40:56 -0700471func 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 -0700472 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker5201c0b2019-05-15 15:35:56 -0700473 defer cancel()
Scott Baker6cf525a2019-05-09 12:25:08 -0700474
Scott Baker5201c0b2019-05-15 15:35:56 -0700475 headers := GenerateHeaders()
476
477 model_descriptor, err := descriptor.FindSymbol("xos." + modelName)
478 if err != nil {
479 return nil, err
480 }
481 model_md, ok := model_descriptor.(*desc.MessageDescriptor)
482 if !ok {
Scott Baker20481aa2019-06-20 11:00:54 -0700483 return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
Scott Baker6cf525a2019-05-09 12:25:08 -0700484 }
485
Scott Baker5201c0b2019-05-15 15:35:56 -0700486 h := &QueryEventHandler{
487 RpcEventHandler: RpcEventHandler{
488 Fields: map[string]map[string]interface{}{"xos.Query": map[string]interface{}{"kind": 0}},
489 },
490 Elements: queries,
491 Model: model_md,
Scott Baker1dd06672019-06-14 15:40:56 -0700492 Kind: kind,
Scott Baker5201c0b2019-05-15 15:35:56 -0700493 }
494 err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Filter"+modelName, headers, h, h.GetParams)
495 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700496 return nil, corderrors.RpcErrorWithQueriesToCordError(err, modelName, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700497 }
498
499 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700500 return nil, corderrors.RpcErrorWithQueriesToCordError(h.Status.Err(), modelName, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700501 }
502
503 d, err := dynamic.AsDynamicMessage(h.Response)
504 if err != nil {
505 return nil, err
506 }
507
508 items, err := d.TryGetFieldByName("items")
509 if err != nil {
510 return nil, err
511 }
512
513 return ItemsToDynamicMessageList(items), nil
514}
515
Scott Baker5281d002019-05-16 10:45:26 -0700516// Call ListModels or FilterModels as appropriate
Scott Baker1dd06672019-06-14 15:40:56 -0700517func ListOrFilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
518 if (len(queries) == 0) && (kind == FILTER_DEFAULT) {
Scott Bakerc328cf12019-05-28 16:03:12 -0700519 return ListModels(ctx, conn, descriptor, modelName)
Scott Baker5281d002019-05-16 10:45:26 -0700520 } else {
Scott Baker1dd06672019-06-14 15:40:56 -0700521 return FilterModels(ctx, conn, descriptor, modelName, kind, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700522 }
523}
524
Scott Baker5201c0b2019-05-15 15:35:56 -0700525// Get a model from XOS given a fieldName/fieldValue
Scott Bakerc328cf12019-05-28 16:03:12 -0700526func 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 -0700527 models, err := FilterModels(ctx, conn, descriptor, modelName, FILTER_DEFAULT, queries)
Scott Baker5201c0b2019-05-15 15:35:56 -0700528 if err != nil {
529 return nil, err
530 }
531
532 if len(models) == 0 {
Scott Bakera55e6452019-06-25 11:10:30 -0700533 cordError := &corderrors.ModelNotFoundError{}
534 cordError.Obj = corderrors.ObjectReference{ModelName: modelName, Queries: queries}
535 return nil, corderrors.WithStackTrace(cordError)
Scott Baker5201c0b2019-05-15 15:35:56 -0700536 }
537
538 return models[0], nil
Scott Baker6cf525a2019-05-09 12:25:08 -0700539}
540
541// Find a model, but retry under a variety of circumstances
Scott Bakerc328cf12019-05-28 16:03:12 -0700542func 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 -0700543 quiet := (flags & GM_QUIET) != 0
544 until_found := (flags & GM_UNTIL_FOUND) != 0
545 until_enacted := (flags & GM_UNTIL_ENACTED) != 0
546 until_status := (flags & GM_UNTIL_STATUS) != 0
547
548 for {
549 var err error
550
551 if conn == nil {
552 conn, err = NewConnection()
553 if err != nil {
554 return nil, nil, err
555 }
556 }
557
Scott Bakerc328cf12019-05-28 16:03:12 -0700558 model, err := FindModel(ctx, conn, descriptor, modelName, queries)
Scott Baker6cf525a2019-05-09 12:25:08 -0700559 if err != nil {
560 if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
561 strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
562 if !quiet {
563 fmt.Print(".")
564 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700565 select {
566 case <-time.After(100 * time.Millisecond):
567 case <-ctx.Done():
568 return nil, nil, ctx.Err()
569 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700570 conn.Close()
571 conn = nil
572 continue
573 }
574
Scott Baker20481aa2019-06-20 11:00:54 -0700575 _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
576 if until_found && is_not_found_error {
Scott Baker6cf525a2019-05-09 12:25:08 -0700577 if !quiet {
578 fmt.Print("x")
579 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700580 select {
581 case <-time.After(100 * time.Millisecond):
582 case <-ctx.Done():
583 return nil, nil, ctx.Err()
584 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700585 continue
586 }
587 return nil, nil, err
588 }
589
590 if until_enacted && !IsEnacted(model) {
591 if !quiet {
592 fmt.Print("o")
593 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700594 select {
595 case <-time.After(100 * time.Millisecond):
596 case <-ctx.Done():
597 return nil, nil, ctx.Err()
598 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700599 continue
600 }
601
602 if until_status && model.GetFieldByName("status") == nil {
603 if !quiet {
604 fmt.Print("O")
605 }
Scott Bakerc328cf12019-05-28 16:03:12 -0700606 select {
607 case <-time.After(100 * time.Millisecond):
608 case <-ctx.Done():
609 return nil, nil, ctx.Err()
610 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700611 continue
612 }
613
614 return conn, model, nil
615 }
616}
617
Scott Baker175cb402019-05-17 16:13:06 -0700618// Get a model from XOS given its ID
619func DeleteModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) error {
Scott Bakerce11c492019-07-30 15:53:34 -0700620 ctx, cancel := GrpcTimeoutContext(context.Background())
Scott Baker175cb402019-05-17 16:13:06 -0700621 defer cancel()
622
623 headers := GenerateHeaders()
624
625 h := &RpcEventHandler{
626 Fields: map[string]map[string]interface{}{"xos.ID": map[string]interface{}{"id": id}},
627 }
628 err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Delete"+modelName, headers, h, h.GetParams)
629 if err != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700630 return corderrors.RpcErrorWithIdToCordError(err, modelName, id)
Scott Baker175cb402019-05-17 16:13:06 -0700631 }
632
633 if h.Status != nil && h.Status.Err() != nil {
Scott Bakera55e6452019-06-25 11:10:30 -0700634 return corderrors.RpcErrorWithIdToCordError(h.Status.Err(), modelName, id)
Scott Baker175cb402019-05-17 16:13:06 -0700635 }
636
637 _, err = dynamic.AsDynamicMessage(h.Response)
638 if err != nil {
639 return err
640 }
641
642 return nil
643}
644
Scott Baker5201c0b2019-05-15 15:35:56 -0700645// Takes a *dynamic.Message and turns it into a map of fields to interfaces
646// TODO: Might be more useful to convert the values to strings and ints
Scott Baker6cf525a2019-05-09 12:25:08 -0700647func MessageToMap(d *dynamic.Message) map[string]interface{} {
648 fields := make(map[string]interface{})
649 for _, field_desc := range d.GetKnownFields() {
650 field_name := field_desc.GetName()
651 fields[field_name] = d.GetFieldByName(field_name)
652 }
653 return fields
654}
655
Scott Baker5201c0b2019-05-15 15:35:56 -0700656// Returns True if a message has been enacted
Scott Baker6cf525a2019-05-09 12:25:08 -0700657func IsEnacted(d *dynamic.Message) bool {
658 enacted := d.GetFieldByName("enacted").(float64)
659 updated := d.GetFieldByName("updated").(float64)
660
661 return (enacted >= updated)
662}