forked from juju/juju
/
multiwatcher.go
449 lines (411 loc) · 14.6 KB
/
multiwatcher.go
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
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package params
import (
"bytes"
"encoding/json"
"fmt"
"time"
"github.com/juju/charm/v9"
"github.com/juju/errors"
"github.com/DavinZhang/juju/core/constraints"
"github.com/DavinZhang/juju/core/instance"
"github.com/DavinZhang/juju/core/life"
"github.com/DavinZhang/juju/core/model"
"github.com/DavinZhang/juju/core/status"
)
// EntityInfo is implemented by all entity Info types.
type EntityInfo interface {
// EntityId returns an identifier that will uniquely
// identify the entity within its kind
EntityId() EntityId
}
// EntityId uniquely identifies an entity being tracked by the
// multiwatcherStore.
type EntityId struct {
Kind string `json:"kind"`
ModelUUID string `json:"model-uuid"`
Id string `json:"id"`
}
// Delta holds details of a change to the model.
type Delta struct {
// If Removed is true, the entity has been removed;
// otherwise it has been created or changed.
Removed bool `json:"removed"`
// Entity holds data about the entity that has changed.
Entity EntityInfo `json:"entity"`
}
// MarshalJSON implements json.Marshaler.
func (d *Delta) MarshalJSON() ([]byte, error) {
b, err := json.Marshal(d.Entity)
if err != nil {
return nil, err
}
var buf bytes.Buffer
buf.WriteByte('[')
c := "change"
if d.Removed {
c = "remove"
}
fmt.Fprintf(&buf, "%q,%q,", d.Entity.EntityId().Kind, c)
buf.Write(b)
buf.WriteByte(']')
return buf.Bytes(), nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (d *Delta) UnmarshalJSON(data []byte) error {
var elements []json.RawMessage
if err := json.Unmarshal(data, &elements); err != nil {
return err
}
if len(elements) != 3 {
return fmt.Errorf(
"Expected 3 elements in top-level of JSON but got %d",
len(elements))
}
var entityKind, operation string
if err := json.Unmarshal(elements[0], &entityKind); err != nil {
return err
}
if err := json.Unmarshal(elements[1], &operation); err != nil {
return err
}
if operation == "remove" {
d.Removed = true
} else if operation != "change" {
return fmt.Errorf("Unexpected operation %q", operation)
}
switch entityKind {
case "action":
d.Entity = new(ActionInfo)
case "annotation":
d.Entity = new(AnnotationInfo)
case "application":
d.Entity = new(ApplicationInfo)
case "applicationOffer":
d.Entity = new(ApplicationOfferInfo)
case "block":
d.Entity = new(BlockInfo)
case "branch":
d.Entity = new(BranchInfo)
case "charm":
d.Entity = new(CharmInfo)
case "machine":
d.Entity = new(MachineInfo)
case "model":
d.Entity = new(ModelUpdate)
case "relation":
d.Entity = new(RelationInfo)
case "remoteApplication":
d.Entity = new(RemoteApplicationUpdate)
case "unit":
d.Entity = new(UnitInfo)
default:
return errors.Errorf("Unexpected entity name %q", entityKind)
}
return json.Unmarshal(elements[2], &d.Entity)
}
// MachineInfo holds the information about a machine
// that is tracked by multiwatcherStore.
type MachineInfo struct {
ModelUUID string `json:"model-uuid"`
Id string `json:"id"`
InstanceId string `json:"instance-id"`
AgentStatus StatusInfo `json:"agent-status"`
InstanceStatus StatusInfo `json:"instance-status"`
Life life.Value `json:"life"`
Config map[string]interface{} `json:"config,omitempty"`
Series string `json:"series"`
ContainerType string `json:"container-type"`
IsManual bool `json:"-"` // internal use only
SupportedContainers []instance.ContainerType `json:"supported-containers"`
SupportedContainersKnown bool `json:"supported-containers-known"`
HardwareCharacteristics *instance.HardwareCharacteristics `json:"hardware-characteristics,omitempty"`
CharmProfiles []string `json:"charm-profiles,omitempty"`
Jobs []model.MachineJob `json:"jobs"`
Addresses []Address `json:"addresses"`
HasVote bool `json:"has-vote"`
WantsVote bool `json:"wants-vote"`
Hostname string `json:"hostname,omitempty"`
}
// EntityId returns a unique identifier for a machine across
// models.
func (i *MachineInfo) EntityId() EntityId {
return EntityId{
Kind: "machine",
ModelUUID: i.ModelUUID,
Id: i.Id,
}
}
// StatusInfo holds the unit and machine status information. It is
// used by ApplicationInfo and UnitInfo.
type StatusInfo struct {
Err error `json:"err,omitempty"`
Current status.Status `json:"current"`
Message string `json:"message"`
Since *time.Time `json:"since,omitempty"`
Version string `json:"version"`
Data map[string]interface{} `json:"data,omitempty"`
}
// ApplicationInfo holds the information about an application that is tracked
// by multiwatcherStore.
type ApplicationInfo struct {
ModelUUID string `json:"model-uuid"`
Name string `json:"name"`
Exposed bool `json:"exposed"`
CharmURL string `json:"charm-url"`
OwnerTag string `json:"owner-tag"`
Life life.Value `json:"life"`
MinUnits int `json:"min-units"`
Constraints constraints.Value `json:"constraints"`
Config map[string]interface{} `json:"config,omitempty"`
Subordinate bool `json:"subordinate"`
Status StatusInfo `json:"status"`
WorkloadVersion string `json:"workload-version"`
}
// EntityId returns a unique identifier for an application across
// models.
func (i *ApplicationInfo) EntityId() EntityId {
return EntityId{
Kind: "application",
ModelUUID: i.ModelUUID,
Id: i.Name,
}
}
// Profile is a representation of charm.v6 LXDProfile
type Profile struct {
Config map[string]string `json:"config,omitempty"`
Description string `json:"description,omitempty"`
Devices map[string]map[string]string `json:"devices,omitempty"`
}
// CharmInfo holds the information about a charm that is tracked by the
// multiwatcher.
type CharmInfo struct {
ModelUUID string `json:"model-uuid"`
CharmURL string `json:"charm-url"`
CharmVersion string `json:"charm-version"`
Life life.Value `json:"life"`
LXDProfile *Profile `json:"profile"`
// DefaultConfig is derived from state-stored *charm.Config.
DefaultConfig map[string]interface{} `json:"config,omitempty"`
}
// EntityId returns a unique identifier for an charm across
// models.
func (i *CharmInfo) EntityId() EntityId {
return EntityId{
Kind: "charm",
ModelUUID: i.ModelUUID,
Id: i.CharmURL,
}
}
// RemoteApplicationUpdate holds the information about a remote application that is
// tracked by multiwatcherStore.
type RemoteApplicationUpdate struct {
ModelUUID string `json:"model-uuid"`
Name string `json:"name"`
OfferUUID string `json:"offer-uuid"`
OfferURL string `json:"offer-url"`
Life life.Value `json:"life"`
Status StatusInfo `json:"status"`
}
// EntityId returns a unique identifier for a remote application across models.
func (i *RemoteApplicationUpdate) EntityId() EntityId {
return EntityId{
Kind: "remoteApplication",
ModelUUID: i.ModelUUID,
Id: i.Name,
}
}
// ApplicationOfferInfo holds the information about an application offer that is
// tracked by multiwatcherStore.
type ApplicationOfferInfo struct {
ModelUUID string `json:"model-uuid"`
OfferName string `json:"offer-name"`
OfferUUID string `json:"offer-uuid"`
ApplicationName string `json:"application-name"`
CharmName string `json:"charm-name"`
TotalConnectedCount int `json:"total-connected-count"`
ActiveConnectedCount int `json:"active-connected-count"`
}
// EntityId returns a unique identifier for an application offer across models.
func (i *ApplicationOfferInfo) EntityId() EntityId {
return EntityId{
Kind: "applicationOffer",
ModelUUID: i.ModelUUID,
Id: i.OfferName,
}
}
// UnitInfo holds the information about a unit
// that is tracked by multiwatcherStore.
type UnitInfo struct {
ModelUUID string `json:"model-uuid"`
Name string `json:"name"`
Application string `json:"application"`
Series string `json:"series"`
CharmURL string `json:"charm-url"`
Life life.Value `json:"life"`
PublicAddress string `json:"public-address"`
PrivateAddress string `json:"private-address"`
MachineId string `json:"machine-id"`
Ports []Port `json:"ports"`
PortRanges []PortRange `json:"port-ranges"`
Principal string `json:"principal"`
Subordinate bool `json:"subordinate"`
// Workload and agent state are modelled separately.
WorkloadStatus StatusInfo `json:"workload-status"`
AgentStatus StatusInfo `json:"agent-status"`
}
// EntityId returns a unique identifier for a unit across
// models.
func (i *UnitInfo) EntityId() EntityId {
return EntityId{
Kind: "unit",
ModelUUID: i.ModelUUID,
Id: i.Name,
}
}
// ActionInfo holds the information about a action that is tracked by
// multiwatcherStore.
type ActionInfo struct {
ModelUUID string `json:"model-uuid"`
Id string `json:"id"`
Receiver string `json:"receiver"`
Name string `json:"name"`
Parameters map[string]interface{} `json:"parameters,omitempty"`
Status string `json:"status"`
Message string `json:"message"`
Results map[string]interface{} `json:"results,omitempty"`
Enqueued time.Time `json:"enqueued"`
Started time.Time `json:"started"`
Completed time.Time `json:"completed"`
}
// EntityId returns a unique identifier for an action across
// models.
func (i *ActionInfo) EntityId() EntityId {
return EntityId{
Kind: "action",
ModelUUID: i.ModelUUID,
Id: i.Id,
}
}
// RelationInfo holds the information about a relation that is tracked
// by multiwatcherStore.
type RelationInfo struct {
ModelUUID string `json:"model-uuid"`
Key string `json:"key"`
Id int `json:"id"`
Endpoints []Endpoint `json:"endpoints"`
}
// NewCharmRelation creates a new local CharmRelation structure from the
// charm.Relation structure. NOTE: when we update the database to not store a
// charm.Relation directly in the database, this method should take the state
// structure type.
func NewCharmRelation(cr charm.Relation) CharmRelation {
return CharmRelation{
Name: cr.Name,
Role: string(cr.Role),
Interface: cr.Interface,
Optional: cr.Optional,
Limit: cr.Limit,
Scope: string(cr.Scope),
}
}
// Endpoint holds an application-relation pair.
type Endpoint struct {
ApplicationName string `json:"application-name"`
Relation CharmRelation `json:"relation"`
}
// EntityId returns a unique identifier for a relation across
// models.
func (i *RelationInfo) EntityId() EntityId {
return EntityId{
Kind: "relation",
ModelUUID: i.ModelUUID,
Id: i.Key,
}
}
// AnnotationInfo holds the information about an annotation that is
// tracked by multiwatcherStore.
type AnnotationInfo struct {
ModelUUID string `json:"model-uuid"`
Tag string `json:"tag"`
Annotations map[string]string `json:"annotations"`
}
// EntityId returns a unique identifier for an annotation across
// models.
func (i *AnnotationInfo) EntityId() EntityId {
return EntityId{
Kind: "annotation",
ModelUUID: i.ModelUUID,
Id: i.Tag,
}
}
// BlockInfo holds the information about a block that is tracked by
// multiwatcherStore.
type BlockInfo struct {
ModelUUID string `json:"model-uuid"`
Id string `json:"id"`
Type model.BlockType `json:"type"`
Message string `json:"message"`
Tag string `json:"tag"`
}
// EntityId returns a unique identifier for a block across
// models.
func (i *BlockInfo) EntityId() EntityId {
return EntityId{
Kind: "block",
ModelUUID: i.ModelUUID,
Id: i.Id,
}
}
// ModelUpdate holds the information about a model that is
// tracked by multiwatcherStore.
type ModelUpdate struct {
ModelUUID string `json:"model-uuid"`
Name string `json:"name"`
Life life.Value `json:"life"`
Owner string `json:"owner"`
ControllerUUID string `json:"controller-uuid"`
IsController bool `json:"is-controller"`
Config map[string]interface{} `json:"config,omitempty"`
Status StatusInfo `json:"status"`
Constraints constraints.Value `json:"constraints"`
SLA ModelSLAInfo `json:"sla"`
}
// EntityId returns a unique identifier for a model.
func (i *ModelUpdate) EntityId() EntityId {
return EntityId{
Kind: "model",
ModelUUID: i.ModelUUID,
Id: i.ModelUUID,
}
}
// ItemChange is the multiwatcher representation of a core settings ItemChange.
type ItemChange struct {
Type int `json:"type"`
Key string `json:"key"`
OldValue interface{} `json:"old,omitempty"`
NewValue interface{} `json:"new,omitempty"`
}
// BranchInfo holds data about a model generation (branch)
// that is tracked by multiwatcherStore.
type BranchInfo struct {
ModelUUID string `json:"model-uuid"`
Id string `json:"id"`
Name string `json:"name"`
AssignedUnits map[string][]string `json:"assigned-units"`
Config map[string][]ItemChange `json:"charm-config"`
Created int64 `json:"created"`
CreatedBy string `json:"created-by"`
Completed int64 `json:"completed"`
CompletedBy string `json:"completed-by"`
GenerationId int `json:"generation-id"`
}
// EntityId returns a unique identifier for a generation.
func (i *BranchInfo) EntityId() EntityId {
return EntityId{
Kind: "branch",
ModelUUID: i.ModelUUID,
Id: i.Id,
}
}