github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/multiwatcher/multiwatcher.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package multiwatcher 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "time" 11 12 "github.com/juju/errors" 13 "gopkg.in/juju/charm.v6-unstable" 14 15 "github.com/juju/juju/constraints" 16 "github.com/juju/juju/instance" 17 "github.com/juju/juju/status" 18 ) 19 20 // Life describes the lifecycle state of an entity ("alive", "dying" 21 // or "dead"). 22 type Life string 23 24 // EntityInfo is implemented by all entity Info types. 25 type EntityInfo interface { 26 // EntityId returns an identifier that will uniquely 27 // identify the entity within its kind 28 EntityId() EntityId 29 } 30 31 // EntityId uniquely identifies an entity being tracked by the 32 // multiwatcherStore. 33 type EntityId struct { 34 Kind string `json:"kind"` 35 ModelUUID string `json:"model-uuid"` 36 Id string `json:"id"` 37 } 38 39 // Delta holds details of a change to the model. 40 type Delta struct { 41 // If Removed is true, the entity has been removed; 42 // otherwise it has been created or changed. 43 Removed bool `json:"removed"` 44 // Entity holds data about the entity that has changed. 45 Entity EntityInfo `json:"entity"` 46 } 47 48 // MarshalJSON implements json.Marshaler. 49 func (d *Delta) MarshalJSON() ([]byte, error) { 50 b, err := json.Marshal(d.Entity) 51 if err != nil { 52 return nil, err 53 } 54 var buf bytes.Buffer 55 buf.WriteByte('[') 56 c := "change" 57 if d.Removed { 58 c = "remove" 59 } 60 fmt.Fprintf(&buf, "%q,%q,", d.Entity.EntityId().Kind, c) 61 buf.Write(b) 62 buf.WriteByte(']') 63 return buf.Bytes(), nil 64 } 65 66 // UnmarshalJSON implements json.Unmarshaler. 67 func (d *Delta) UnmarshalJSON(data []byte) error { 68 var elements []json.RawMessage 69 if err := json.Unmarshal(data, &elements); err != nil { 70 return err 71 } 72 if len(elements) != 3 { 73 return fmt.Errorf( 74 "Expected 3 elements in top-level of JSON but got %d", 75 len(elements)) 76 } 77 var entityKind, operation string 78 if err := json.Unmarshal(elements[0], &entityKind); err != nil { 79 return err 80 } 81 if err := json.Unmarshal(elements[1], &operation); err != nil { 82 return err 83 } 84 if operation == "remove" { 85 d.Removed = true 86 } else if operation != "change" { 87 return fmt.Errorf("Unexpected operation %q", operation) 88 } 89 switch entityKind { 90 case "model": 91 d.Entity = new(ModelInfo) 92 case "machine": 93 d.Entity = new(MachineInfo) 94 case "application": 95 d.Entity = new(ApplicationInfo) 96 case "unit": 97 d.Entity = new(UnitInfo) 98 case "relation": 99 d.Entity = new(RelationInfo) 100 case "annotation": 101 d.Entity = new(AnnotationInfo) 102 case "block": 103 d.Entity = new(BlockInfo) 104 case "action": 105 d.Entity = new(ActionInfo) 106 default: 107 return errors.Errorf("Unexpected entity name %q", entityKind) 108 } 109 return json.Unmarshal(elements[2], &d.Entity) 110 } 111 112 // Address describes a network address. 113 type Address struct { 114 Value string `json:"value"` 115 Type string `json:"type"` 116 Scope string `json:"scope"` 117 SpaceName string `json:"space-name,omitempty"` 118 SpaceProviderId string `json:"space-provider-id,omitempty"` 119 } 120 121 // MachineInfo holds the information about a machine 122 // that is tracked by multiwatcherStore. 123 type MachineInfo struct { 124 ModelUUID string `json:"model-uuid"` 125 Id string `json:"id"` 126 InstanceId string `json:"instance-id"` 127 AgentStatus StatusInfo `json:"agent-status"` 128 InstanceStatus StatusInfo `json:"instance-status"` 129 Life Life `json:"life"` 130 Series string `json:"series"` 131 SupportedContainers []instance.ContainerType `json:"supported-containers"` 132 SupportedContainersKnown bool `json:"supported-containers-known"` 133 HardwareCharacteristics *instance.HardwareCharacteristics `json:"hardware-characteristics,omitempty"` 134 Jobs []MachineJob `json:"jobs"` 135 Addresses []Address `json:"addresses"` 136 HasVote bool `json:"has-vote"` 137 WantsVote bool `json:"wants-vote"` 138 } 139 140 // EntityId returns a unique identifier for a machine across 141 // models. 142 func (i *MachineInfo) EntityId() EntityId { 143 return EntityId{ 144 Kind: "machine", 145 ModelUUID: i.ModelUUID, 146 Id: i.Id, 147 } 148 } 149 150 // StatusInfo holds the unit and machine status information. It is 151 // used by ApplicationInfo and UnitInfo. 152 type StatusInfo struct { 153 Err error `json:"err,omitempty"` 154 Current status.Status `json:"current"` 155 Message string `json:"message"` 156 Since *time.Time `json:"since,omitempty"` 157 Version string `json:"version"` 158 Data map[string]interface{} `json:"data,omitempty"` 159 } 160 161 // NewStatusInfo return a new multiwatcher StatusInfo from a 162 // status StatusInfo. 163 func NewStatusInfo(s status.StatusInfo, err error) StatusInfo { 164 return StatusInfo{ 165 Err: err, 166 Current: s.Status, 167 Message: s.Message, 168 Since: s.Since, 169 Data: s.Data, 170 } 171 } 172 173 // ApplicationInfo holds the information about an application that is tracked 174 // by multiwatcherStore. 175 type ApplicationInfo struct { 176 ModelUUID string `json:"model-uuid"` 177 Name string `json:"name"` 178 Exposed bool `json:"exposed"` 179 CharmURL string `json:"charm-url"` 180 OwnerTag string `json:"owner-tag"` 181 Life Life `json:"life"` 182 MinUnits int `json:"min-units"` 183 Constraints constraints.Value `json:"constraints"` 184 Config map[string]interface{} `json:"config,omitempty"` 185 Subordinate bool `json:"subordinate"` 186 Status StatusInfo `json:"status"` 187 } 188 189 // EntityId returns a unique identifier for an application across 190 // models. 191 func (i *ApplicationInfo) EntityId() EntityId { 192 return EntityId{ 193 Kind: "application", 194 ModelUUID: i.ModelUUID, 195 Id: i.Name, 196 } 197 } 198 199 // Port identifies a network port number for a particular protocol. 200 type Port struct { 201 Protocol string `json:"protocol"` 202 Number int `json:"number"` 203 } 204 205 // PortRange represents a single range of ports. 206 type PortRange struct { 207 FromPort int `json:"from-port"` 208 ToPort int `json:"to-port"` 209 Protocol string `json:"protocol"` 210 } 211 212 // UnitInfo holds the information about a unit 213 // that is tracked by multiwatcherStore. 214 type UnitInfo struct { 215 ModelUUID string `json:"model-uuid"` 216 Name string `json:"name"` 217 Application string `json:"application"` 218 Series string `json:"series"` 219 CharmURL string `json:"charm-url"` 220 PublicAddress string `json:"public-address"` 221 PrivateAddress string `json:"private-address"` 222 MachineId string `json:"machine-id"` 223 Ports []Port `json:"ports"` 224 PortRanges []PortRange `json:"port-ranges"` 225 Subordinate bool `json:"subordinate"` 226 // Workload and agent state are modelled separately. 227 WorkloadStatus StatusInfo `json:"workload-status"` 228 AgentStatus StatusInfo `json:"agent-status"` 229 } 230 231 // EntityId returns a unique identifier for a unit across 232 // models. 233 func (i *UnitInfo) EntityId() EntityId { 234 return EntityId{ 235 Kind: "unit", 236 ModelUUID: i.ModelUUID, 237 Id: i.Name, 238 } 239 } 240 241 // ActionInfo holds the information about a action that is tracked by 242 // multiwatcherStore. 243 type ActionInfo struct { 244 ModelUUID string `json:"model-uuid"` 245 Id string `json:"id"` 246 Receiver string `json:"receiver"` 247 Name string `json:"name"` 248 Parameters map[string]interface{} `json:"parameters,omitempty"` 249 Status string `json:"status"` 250 Message string `json:"message"` 251 Results map[string]interface{} `json:"results,omitempty"` 252 Enqueued time.Time `json:"enqueued"` 253 Started time.Time `json:"started"` 254 Completed time.Time `json:"completed"` 255 } 256 257 // EntityId returns a unique identifier for an action across 258 // models. 259 func (i *ActionInfo) EntityId() EntityId { 260 return EntityId{ 261 Kind: "action", 262 ModelUUID: i.ModelUUID, 263 Id: i.Id, 264 } 265 } 266 267 // RelationInfo holds the information about a relation that is tracked 268 // by multiwatcherStore. 269 type RelationInfo struct { 270 ModelUUID string `json:"model-uuid"` 271 Key string `json:"key"` 272 Id int `json:"id"` 273 Endpoints []Endpoint `json:"endpoints"` 274 } 275 276 // CharmRelation is a mirror struct for charm.Relation. 277 type CharmRelation struct { 278 Name string `json:"name"` 279 Role string `json:"role"` 280 Interface string `json:"interface"` 281 Optional bool `json:"optional"` 282 Limit int `json:"limit"` 283 Scope string `json:"scope"` 284 } 285 286 // NewCharmRelation creates a new local CharmRelation structure from the 287 // charm.Relation structure. NOTE: when we update the database to not store a 288 // charm.Relation directly in the database, this method should take the state 289 // structure type. 290 func NewCharmRelation(cr charm.Relation) CharmRelation { 291 return CharmRelation{ 292 Name: cr.Name, 293 Role: string(cr.Role), 294 Interface: cr.Interface, 295 Optional: cr.Optional, 296 Limit: cr.Limit, 297 Scope: string(cr.Scope), 298 } 299 } 300 301 // Endpoint holds an application-relation pair. 302 type Endpoint struct { 303 ApplicationName string `json:"application-name"` 304 Relation CharmRelation `json:"relation"` 305 } 306 307 // EntityId returns a unique identifier for a relation across 308 // models. 309 func (i *RelationInfo) EntityId() EntityId { 310 return EntityId{ 311 Kind: "relation", 312 ModelUUID: i.ModelUUID, 313 Id: i.Key, 314 } 315 } 316 317 // AnnotationInfo holds the information about an annotation that is 318 // tracked by multiwatcherStore. 319 type AnnotationInfo struct { 320 ModelUUID string `json:"model-uuid"` 321 Tag string `json:"tag"` 322 Annotations map[string]string `json:"annotations"` 323 } 324 325 // EntityId returns a unique identifier for an annotation across 326 // models. 327 func (i *AnnotationInfo) EntityId() EntityId { 328 return EntityId{ 329 Kind: "annotation", 330 ModelUUID: i.ModelUUID, 331 Id: i.Tag, 332 } 333 } 334 335 // MachineJob values define responsibilities that machines may be 336 // expected to fulfil. 337 type MachineJob string 338 339 const ( 340 JobHostUnits MachineJob = "JobHostUnits" 341 JobManageModel MachineJob = "JobManageModel" 342 ) 343 344 // NeedsState returns true if the job requires a state connection. 345 func (job MachineJob) NeedsState() bool { 346 return job == JobManageModel 347 } 348 349 // AnyJobNeedsState returns true if any of the provided jobs 350 // require a state connection. 351 func AnyJobNeedsState(jobs ...MachineJob) bool { 352 for _, j := range jobs { 353 if j.NeedsState() { 354 return true 355 } 356 } 357 return false 358 } 359 360 // BlockInfo holds the information about a block that is tracked by 361 // multiwatcherStore. 362 type BlockInfo struct { 363 ModelUUID string `json:"model-uuid"` 364 Id string `json:"id"` 365 Type BlockType `json:"type"` 366 Message string `json:"message"` 367 Tag string `json:"tag"` 368 } 369 370 // EntityId returns a unique identifier for a block across 371 // models. 372 func (i *BlockInfo) EntityId() EntityId { 373 return EntityId{ 374 Kind: "block", 375 ModelUUID: i.ModelUUID, 376 Id: i.Id, 377 } 378 } 379 380 // BlockType values define model block type. 381 type BlockType string 382 383 const ( 384 // BlockDestroy type identifies destroy blocks. 385 BlockDestroy BlockType = "BlockDestroy" 386 387 // BlockRemove type identifies remove blocks. 388 BlockRemove BlockType = "BlockRemove" 389 390 // BlockChange type identifies change blocks. 391 BlockChange BlockType = "BlockChange" 392 ) 393 394 // ModelInfo holds the information about an model that is 395 // tracked by multiwatcherStore. 396 type ModelInfo struct { 397 ModelUUID string `json:"model-uuid"` 398 Name string `json:"name"` 399 Life Life `json:"life"` 400 Owner string `json:"owner"` 401 ControllerUUID string `json:"controller-uuid"` 402 } 403 404 // EntityId returns a unique identifier for an model. 405 func (i *ModelInfo) EntityId() EntityId { 406 return EntityId{ 407 Kind: "model", 408 ModelUUID: i.ModelUUID, 409 Id: i.ModelUUID, 410 } 411 }