forked from Juniper/contrail
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathevent_encoding.tmpl
456 lines (422 loc) · 15.3 KB
/
event_encoding.tmpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
package services
import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/Juniper/contrail/pkg/common"
"github.com/Juniper/contrail/pkg/services/baseservices"
"{{ option.PackagePath }}/pkg/models"
)
const (
//OperationCreate for create operation.
OperationCreate = "CREATE"
//OperationUpdate for update operation.
OperationUpdate = "UPDATE"
//OperationDelete for delete operation.
OperationDelete = "DELETE"
)
// EventOption contains options for Event.
type EventOption struct {
UUID string
Operation string
Kind string
Data map[string]interface{}
}
// HasResource defines methods that might be implemented by Event.
type HasResource interface {
GetResource() Resource
Operation() string
}
// CanProcessService is interface for process service.
type CanProcessService interface {
Process(ctx context.Context, service Service) (*Event, error)
}
// Resource is a generic resource interface.
type Resource interface {
GetUUID() string
GetFQName() []string
GetParentUUID() string
ToMap() map[string]interface{}
// Kind returns id of schema. For non resource objects it returns empty string
Kind() string
// Depends returns UUIDs of children and back references
Depends() []string
// AddDependency adds child/backref to model
AddDependency(i interface{})
// RemoveDependency removes child/backref from model
RemoveDependency(i interface{})
}
// EventList has multiple rest requests.
type EventList struct {
Events []*Event `json:"resources" yaml:"resources"`
}
type state int
const (
notVisited state = iota
visited
temporaryVisited
)
//reorder request using Tarjan's algorithm
func visitResource(uuid string, sorted []*Event,
eventMap map[string]*Event, stateGraph map[string]state,
) (sortedList []*Event, err error) {
if stateGraph[uuid] == temporaryVisited {
return nil, errors.New("dependency loop found in sync request")
}
if stateGraph[uuid] == visited {
return sorted, nil
}
stateGraph[uuid] = temporaryVisited
event, found := eventMap[uuid]
if !found {
return nil, fmt.Errorf("Resource with uuid: %s not found in eventMap", uuid)
}
depends := event.GetResource().Depends()
for _, refUUID := range depends {
sorted, err = visitResource(refUUID, sorted, eventMap, stateGraph)
if err != nil {
return nil, err
}
break
}
stateGraph[uuid] = visited
sorted = append(sorted, event)
return sorted, nil
}
// Sort sorts Events by dependency using Tarjan's algorithm.
// TODO: support parent-child relationship while checking dependencies.
func (e *EventList) Sort() (err error) {
var sorted []*Event
stateGraph := map[string]state{}
eventMap := map[string]*Event{}
for _, event := range e.Events {
uuid := event.GetResource().GetUUID()
stateGraph[uuid] = notVisited
eventMap[uuid] = event
}
foundNotVisited := true
for foundNotVisited {
foundNotVisited = false
for _, event := range e.Events {
uuid := event.GetResource().GetUUID()
state := stateGraph[uuid]
if state == notVisited {
sorted, err = visitResource(uuid, sorted, eventMap, stateGraph)
if err != nil {
return err
}
foundNotVisited = true
break
}
}
}
e.Events = sorted
return nil
}
// Process dispatches resource event to call corresponding service functions.
func (e *Event) Process(ctx context.Context, service Service) (*Event, error) {
return e.Request.(CanProcessService).Process(ctx, service)
}
// Process process list of events.
func (e *EventList) Process(ctx context.Context, service Service) (*EventList, error) {
var responses []*Event
for _, event := range e.Events {
response, err := event.Process(ctx, service)
if err != nil {
return nil, err
}
responses = append(responses, response)
}
return &EventList{
Events: responses,
}, nil
}
// GetResource returns event on resource.
func (e *Event) GetResource() Resource {
if e == nil {
return nil
}
resourceEvent, ok := e.Request.(HasResource)
if !ok {
return nil
}
return resourceEvent.GetResource()
}
// Operation returns operation type.
func (e *Event) Operation() string {
if e == nil {
return ""
}
resourceEvent, ok := e.Request.(HasResource)
if !ok {
return ""
}
return resourceEvent.Operation()
}
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
//GetResource returns resource.
func (e *Event_Create{{ schema.JSONSchema.GoName }}Request) GetResource() Resource {
return e.Create{{ schema.JSONSchema.GoName }}Request.Get{{ schema.JSONSchema.GoName }}()
}
//GetResource returns resource.
func (e *Event_Update{{ schema.JSONSchema.GoName }}Request) GetResource() Resource {
return e.Update{{ schema.JSONSchema.GoName }}Request.Get{{ schema.JSONSchema.GoName }}()
}
//GetResource returns resource.
func (e *Event_Delete{{ schema.JSONSchema.GoName }}Request) GetResource() Resource {
return &models.{{ schema.JSONSchema.GoName }}{
UUID: e.Delete{{ schema.JSONSchema.GoName }}Request.ID,
}
}
//Operation returns event type.
func (e *Event_Create{{ schema.JSONSchema.GoName }}Request) Operation() string {
return OperationCreate
}
//Operation returns event type.
func (e *Event_Update{{ schema.JSONSchema.GoName }}Request) Operation() string {
return OperationUpdate
}
//Operation returns event type.
func (e *Event_Delete{{ schema.JSONSchema.GoName }}Request) Operation() string {
return OperationDelete
}
{% endif%}{% endfor %}
//NewEvent makes event from interface
func NewEvent(option *EventOption) (*Event, error) {
e := &Event{}
kind := option.Kind
data := option.Data
uuid := option.UUID
operation := option.Operation
if operation == "" {
operation = OperationCreate
}
switch (kind) {
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
case "{{ schema.ID }}":
switch (operation) {
case OperationCreate:
obj := models.InterfaceTo{{ schema.JSONSchema.GoName }}(data)
if uuid != "" {
obj.UUID = uuid
}
e.Request = &Event_Create{{ schema.JSONSchema.GoName }}Request{
Create{{ schema.JSONSchema.GoName }}Request: &Create{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: obj,
}}
case OperationUpdate:
obj := models.InterfaceTo{{ schema.JSONSchema.GoName }}(data)
if uuid != "" {
obj.UUID = uuid
}
e.Request = &Event_Update{{ schema.JSONSchema.GoName }}Request{
Update{{ schema.JSONSchema.GoName }}Request: &Update{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: obj,
FieldMask: baseservices.MapToFieldMask(data),
}}
case OperationDelete:
e.Request = &Event_Delete{{ schema.JSONSchema.GoName }}Request{
Delete{{ schema.JSONSchema.GoName }}Request: &Delete{{ schema.JSONSchema.GoName }}Request{
ID: uuid,
}}
}{% endif%}{% endfor %}
default:
return nil, errors.Errorf("type %s does not exist", kind)
}
return e, nil
}
// NewEventFromRefUpdate creates ref create or delete event from RefUpdate object.
func NewEventFromRefUpdate(refUpdate *RefUpdate) (*Event, error) {
e := &Event{}
kind := fmt.Sprintf("%s-%s", refUpdate.Type, refUpdate.RefType)
operation := refUpdate.Operation
if operation == "" {
operation = RefOperationAdd
}
switch (kind) {
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
{% for _, reference in schema.References %}
{% set refType = schema.JSONSchema.GoName| add:reference.GoName | add:"Ref" %}
case "{{ schema.TypeName }}-{{ reference.LinkTo.TypeName }}":
switch (operation) {
case RefOperationAdd:
request := &Event_Create{{ refType }}Request{
Create{{ refType }}Request: &Create{{ refType }}Request{
ID: refUpdate.UUID,
{{ refType }}: &models.{{ refType }}{
UUID: refUpdate.RefUUID,
},
},
}
{% if reference.RefType %}
if len(refUpdate.Attr) > 0 {
err := json.Unmarshal(refUpdate.Attr, &request.Create{{ refType }}Request.{{ refType }}.Attr)
if err != nil {
return nil, errors.Wrap(err, "bad attr data")
}
}
{% endif %}
e.Request = request
case RefOperationDelete:
e.Request = &Event_Delete{{ refType }}Request{
Delete{{ refType }}Request: &Delete{{ refType }}Request{
ID: refUpdate.UUID,
{{ refType }}: &models.{{ refType }}{
UUID: refUpdate.RefUUID,
},
},
}
}{% endfor %}{% endif%}{% endfor %}
default:
return nil, errors.Errorf("ref from type %s to type %s does not exist", refUpdate.Type, refUpdate.RefType)
}
return e, nil
}
//UnmarshalJSON unmarshal event.
func (e *Event) UnmarshalJSON(data []byte) error {
raw := make(map[string]json.RawMessage)
err := json.Unmarshal(data, &raw)
if err != nil {
return err
}
t, ok := raw["kind"]
if !ok {
return nil
}
var kind string
err = json.Unmarshal(t, &kind)
if err != nil {
return err
}
d, ok := raw["data"]
if !ok {
return nil
}
o := raw["operation"]
var operation string
json.Unmarshal(o, &operation)
if operation == "" {
operation = OperationCreate
}
switch (kind) {
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
case "{{ schema.ID }}":
switch (operation) {
case OperationCreate:
var m models.{{ schema.JSONSchema.GoName }}
err = json.Unmarshal(d, &m)
if err != nil {
return err
}
e.Request = &Event_Create{{ schema.JSONSchema.GoName }}Request{
Create{{ schema.JSONSchema.GoName }}Request: &Create{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: &m,
},
}
case OperationUpdate:
m := map[string]interface{}{}
err = json.Unmarshal(d, &m)
if err != nil {
return err
}
e.Request = &Event_Update{{ schema.JSONSchema.GoName }}Request{
Update{{ schema.JSONSchema.GoName }}Request: &Update{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: models.InterfaceTo{{ schema.JSONSchema.GoName }}(m),
FieldMask: baseservices.MapToFieldMask(m),
},
}
case OperationDelete:
m := map[string]interface{}{}
err = json.Unmarshal(d, &m)
if err != nil {
return err
}
e.Request = &Event_Delete{{ schema.JSONSchema.GoName }}Request{
Delete{{ schema.JSONSchema.GoName }}Request: &Delete{{ schema.JSONSchema.GoName }}Request{
ID: m["uuid"].(string),
},
}
}
{% endif%}{% endfor %}
}
return nil
}
//ToMap translate event to map.
func (e *Event) ToMap() map[string]interface{} {
m := map[string]interface{}{}
switch t := e.Request.(type) {
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
case *Event_Create{{ schema.JSONSchema.GoName }}Request:
m["kind"] = "{{ schema.ID }}"
m["operation"] = OperationCreate
m["data"] = t.Create{{ schema.JSONSchema.GoName }}Request.{{ schema.JSONSchema.GoName }}
case *Event_Update{{ schema.JSONSchema.GoName }}Request:
m["kind"] = "{{ schema.ID }}"
m["operation"] = OperationUpdate
m["data"] = t.Update{{ schema.JSONSchema.GoName }}Request.{{ schema.JSONSchema.GoName }}
case *Event_Delete{{ schema.JSONSchema.GoName }}Request:
m["kind"] = "{{ schema.ID }}"
m["operation"] = OperationDelete
m["data"] = map[string]interface{}{
"uuid": t.Delete{{ schema.JSONSchema.GoName }}Request.ID,
}
{% endif%}{% endfor %}
}
return m
}
//MarshalJSON marshal event.
func (e *Event) MarshalJSON() ([]byte, error) {
return json.Marshal(e.ToMap())
}
//MarshalYAML marshal event to yaml.
func (e *Event) MarshalYAML() (interface{}, error) {
return e.ToMap(), nil
}
//UnmarshalYAML unmarshal event.
func (e *Event) UnmarshalYAML(unmarshal func(interface{}) error) error {
var r interface{}
err := unmarshal(&r)
if err != nil {
return err
}
raw := r.(map[interface{}]interface{})
kind := raw["kind"]
data := common.YAMLtoJSONCompat(raw["data"])
operation := common.InterfaceToString(raw["operation"])
if operation == "" {
operation = OperationCreate
}
switch (kind) {
{% for schema in schemas %}{% if schema.Type != "abstract" and schema.ID %}
case "{{ schema.ID }}":
switch (operation) {
case OperationCreate:
m := models.InterfaceTo{{ schema.JSONSchema.GoName }}(data)
if err != nil {
return err
}
e.Request = &Event_Create{{ schema.JSONSchema.GoName }}Request{
Create{{ schema.JSONSchema.GoName }}Request: &Create{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: m,
},
}
case OperationUpdate:
e.Request = &Event_Update{{ schema.JSONSchema.GoName }}Request{
Update{{ schema.JSONSchema.GoName }}Request: &Update{{ schema.JSONSchema.GoName }}Request{
{{ schema.JSONSchema.GoName }}: models.InterfaceTo{{ schema.JSONSchema.GoName }}(data),
FieldMask: baseservices.MapToFieldMask(data.(map[string]interface{})),
},
}
case OperationDelete:
m := data.(map[string]interface{})
e.Request = &Event_Delete{{ schema.JSONSchema.GoName }}Request{
Delete{{ schema.JSONSchema.GoName }}Request: &Delete{{ schema.JSONSchema.GoName }}Request{
ID: m["uuid"].(string),
},
}
}
{% endif%}{% endfor %}
}
return nil
}