github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/formatter.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package status 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/os" 11 "github.com/juju/os/series" 12 "gopkg.in/juju/charm.v6" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/cmd/juju/common" 17 "github.com/juju/juju/cmd/juju/storage" 18 "github.com/juju/juju/core/status" 19 "github.com/juju/juju/state/multiwatcher" 20 ) 21 22 type statusFormatter struct { 23 status *params.FullStatus 24 controllerName string 25 relations map[int]params.RelationStatus 26 storage *storage.CombinedStorage 27 isoTime, showRelations bool 28 } 29 30 // NewStatusFormatter takes stored model information (params.FullStatus) and populates 31 // the statusFormatter struct used in various status formatting methods 32 func NewStatusFormatter(status *params.FullStatus, isoTime bool) *statusFormatter { 33 return newStatusFormatter( 34 newStatusFormatterParams{ 35 status: status, 36 isoTime: isoTime, 37 showRelations: true, 38 }) 39 } 40 41 type newStatusFormatterParams struct { 42 storage *storage.CombinedStorage 43 status *params.FullStatus 44 controllerName string 45 isoTime, showRelations bool 46 } 47 48 func newStatusFormatter(p newStatusFormatterParams) *statusFormatter { 49 sf := statusFormatter{ 50 storage: p.storage, 51 status: p.status, 52 controllerName: p.controllerName, 53 relations: make(map[int]params.RelationStatus), 54 isoTime: p.isoTime, 55 showRelations: p.showRelations, 56 } 57 if p.showRelations { 58 for _, relation := range p.status.Relations { 59 sf.relations[relation.Id] = relation 60 } 61 } 62 return &sf 63 } 64 65 func (sf *statusFormatter) format() (formattedStatus, error) { 66 if sf.status == nil { 67 return formattedStatus{}, nil 68 } 69 cloudTag, err := names.ParseCloudTag(sf.status.Model.CloudTag) 70 if err != nil { 71 return formattedStatus{}, err 72 } 73 out := formattedStatus{ 74 Model: modelStatus{ 75 Name: sf.status.Model.Name, 76 Type: sf.status.Model.Type, 77 Controller: sf.controllerName, 78 Cloud: cloudTag.Id(), 79 CloudRegion: sf.status.Model.CloudRegion, 80 Version: sf.status.Model.Version, 81 AvailableVersion: sf.status.Model.AvailableVersion, 82 Status: sf.getStatusInfoContents(sf.status.Model.ModelStatus), 83 SLA: sf.status.Model.SLA, 84 }, 85 Machines: make(map[string]machineStatus), 86 Applications: make(map[string]applicationStatus), 87 RemoteApplications: make(map[string]remoteApplicationStatus), 88 Offers: make(map[string]offerStatus), 89 Relations: make([]relationStatus, len(sf.relations)), 90 } 91 if sf.status.Model.MeterStatus.Color != "" { 92 out.Model.MeterStatus = &meterStatus{ 93 Color: sf.status.Model.MeterStatus.Color, 94 Message: sf.status.Model.MeterStatus.Message, 95 } 96 } 97 if sf.status.ControllerTimestamp != nil { 98 out.Controller = &controllerStatus{ 99 Timestamp: common.FormatTimeAsTimestamp(sf.status.ControllerTimestamp, sf.isoTime), 100 } 101 } 102 for k, m := range sf.status.Machines { 103 out.Machines[k] = sf.formatMachine(m) 104 } 105 for sn, s := range sf.status.Applications { 106 out.Applications[sn] = sf.formatApplication(sn, s) 107 } 108 for sn, s := range sf.status.RemoteApplications { 109 out.RemoteApplications[sn] = sf.formatRemoteApplication(sn, s) 110 } 111 for name, offer := range sf.status.Offers { 112 out.Offers[name] = sf.formatOffer(name, offer) 113 } 114 i := 0 115 for _, rel := range sf.relations { 116 out.Relations[i] = sf.formatRelation(rel) 117 i++ 118 } 119 if sf.storage != nil { 120 out.Storage = sf.storage 121 } 122 return out, nil 123 } 124 125 // MachineFormat takes stored model information (params.FullStatus) and formats machine status info. 126 func (sf *statusFormatter) MachineFormat(machineId []string) formattedMachineStatus { 127 if sf.status == nil { 128 return formattedMachineStatus{} 129 } 130 out := formattedMachineStatus{ 131 Model: sf.status.Model.Name, 132 Machines: make(map[string]machineStatus), 133 } 134 for k, m := range sf.status.Machines { 135 if len(machineId) != 0 { 136 for i := 0; i < len(machineId); i++ { 137 if m.Id == machineId[i] { 138 out.Machines[k] = sf.formatMachine(m) 139 } 140 } 141 } else { 142 out.Machines[k] = sf.formatMachine(m) 143 } 144 } 145 return out 146 } 147 148 func (sf *statusFormatter) formatMachine(machine params.MachineStatus) machineStatus { 149 var out machineStatus 150 151 out = machineStatus{ 152 JujuStatus: sf.getStatusInfoContents(machine.AgentStatus), 153 DNSName: machine.DNSName, 154 IPAddresses: machine.IPAddresses, 155 InstanceId: machine.InstanceId, 156 DisplayName: machine.DisplayName, 157 MachineStatus: sf.getStatusInfoContents(machine.InstanceStatus), 158 Series: machine.Series, 159 Id: machine.Id, 160 NetworkInterfaces: make(map[string]networkInterface), 161 Containers: make(map[string]machineStatus), 162 Constraints: machine.Constraints, 163 Hardware: machine.Hardware, 164 LXDProfiles: make(map[string]lxdProfileContents), 165 } 166 167 for k, d := range machine.NetworkInterfaces { 168 out.NetworkInterfaces[k] = networkInterface{ 169 IPAddresses: d.IPAddresses, 170 MACAddress: d.MACAddress, 171 Gateway: d.Gateway, 172 DNSNameservers: d.DNSNameservers, 173 Space: d.Space, 174 IsUp: d.IsUp, 175 } 176 } 177 178 for k, m := range machine.Containers { 179 out.Containers[k] = sf.formatMachine(m) 180 } 181 182 for _, job := range machine.Jobs { 183 if job == multiwatcher.JobManageModel { 184 out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote) 185 break 186 } 187 } 188 189 for k, v := range machine.LXDProfiles { 190 out.LXDProfiles[k] = lxdProfileContents{ 191 Config: v.Config, 192 Description: v.Description, 193 Devices: v.Devices, 194 } 195 } 196 197 return out 198 } 199 200 func (sf *statusFormatter) formatApplication(name string, application params.ApplicationStatus) applicationStatus { 201 var osInfo string 202 appOS, _ := series.GetOSFromSeries(application.Series) 203 osInfo = strings.ToLower(appOS.String()) 204 205 // TODO(caas) - enhance GetOSFromSeries 206 if appOS == os.Unknown && sf.status.Model.Type == "caas" { 207 osInfo = application.Series 208 } 209 var ( 210 charmOrigin = "" 211 charmName = "" 212 charmRev = 0 213 ) 214 if curl, err := charm.ParseURL(application.Charm); err != nil { 215 // We should never fail to parse a charm url sent back 216 // but if we do, don't crash. 217 logger.Errorf("failed to parse charm: %v", err) 218 } else { 219 switch curl.Schema { 220 case "cs": 221 charmOrigin = "jujucharms" 222 case "local": 223 charmOrigin = "local" 224 default: 225 charmOrigin = "unknown" 226 } 227 charmName = curl.Name 228 charmRev = curl.Revision 229 } 230 231 out := applicationStatus{ 232 Err: application.Err, 233 Charm: application.Charm, 234 Series: application.Series, 235 OS: osInfo, 236 CharmOrigin: charmOrigin, 237 CharmName: charmName, 238 CharmRev: charmRev, 239 CharmVersion: application.CharmVersion, 240 Exposed: application.Exposed, 241 Life: application.Life, 242 Scale: application.Scale, 243 Placement: application.Placement, 244 ProviderId: application.ProviderId, 245 Address: application.PublicAddress, 246 Relations: application.Relations, 247 CanUpgradeTo: application.CanUpgradeTo, 248 SubordinateTo: application.SubordinateTo, 249 Units: make(map[string]unitStatus), 250 StatusInfo: sf.getApplicationStatusInfo(application), 251 Version: application.WorkloadVersion, 252 EndpointBindings: application.EndpointBindings, 253 } 254 for k, m := range application.Units { 255 out.Units[k] = sf.formatUnit(unitFormatInfo{ 256 unit: m, 257 unitName: k, 258 applicationName: name, 259 meterStatuses: application.MeterStatuses, 260 }) 261 } 262 263 return out 264 } 265 266 func (sf *statusFormatter) formatRemoteApplication(name string, application params.RemoteApplicationStatus) remoteApplicationStatus { 267 out := remoteApplicationStatus{ 268 Err: application.Err, 269 OfferURL: application.OfferURL, 270 Life: application.Life, 271 Relations: application.Relations, 272 StatusInfo: sf.getRemoteApplicationStatusInfo(application), 273 } 274 out.Endpoints = make(map[string]remoteEndpoint) 275 for _, ep := range application.Endpoints { 276 out.Endpoints[ep.Name] = remoteEndpoint{ 277 Interface: ep.Interface, 278 Role: string(ep.Role), 279 } 280 } 281 return out 282 } 283 284 func (sf *statusFormatter) formatRelation(rel params.RelationStatus) relationStatus { 285 var provider, requirer params.EndpointStatus 286 for _, ep := range rel.Endpoints { 287 switch charm.RelationRole(ep.Role) { 288 case charm.RolePeer: 289 provider = ep 290 requirer = ep 291 case charm.RoleProvider: 292 provider = ep 293 case charm.RoleRequirer: 294 requirer = ep 295 } 296 } 297 var relType string 298 switch { 299 case rel.Scope == "container": 300 relType = "subordinate" 301 case provider.ApplicationName == requirer.ApplicationName: 302 relType = "peer" 303 default: 304 relType = "regular" 305 } 306 out := relationStatus{ 307 Provider: fmt.Sprintf("%s:%s", provider.ApplicationName, provider.Name), 308 Requirer: fmt.Sprintf("%s:%s", requirer.ApplicationName, requirer.Name), 309 Interface: rel.Interface, 310 Type: relType, 311 Status: rel.Status.Status, 312 Message: rel.Status.Info, 313 } 314 return out 315 } 316 317 func (sf *statusFormatter) getApplicationStatusInfo(application params.ApplicationStatus) statusInfoContents { 318 // TODO(perrito66) add status validation. 319 info := statusInfoContents{ 320 Err: application.Status.Err, 321 Current: status.Status(application.Status.Status), 322 Message: application.Status.Info, 323 Version: application.Status.Version, 324 } 325 if application.Status.Since != nil { 326 info.Since = common.FormatTime(application.Status.Since, sf.isoTime) 327 } 328 return info 329 } 330 331 func (sf *statusFormatter) getRemoteApplicationStatusInfo(application params.RemoteApplicationStatus) statusInfoContents { 332 // TODO(perrito66) add status validation. 333 info := statusInfoContents{ 334 Err: application.Status.Err, 335 Current: status.Status(application.Status.Status), 336 Message: application.Status.Info, 337 Version: application.Status.Version, 338 } 339 if application.Status.Since != nil { 340 info.Since = common.FormatTime(application.Status.Since, sf.isoTime) 341 } 342 return info 343 } 344 345 func (sf *statusFormatter) formatOffer(name string, offer params.ApplicationOfferStatus) offerStatus { 346 out := offerStatus{ 347 Err: offer.Err, 348 ApplicationName: offer.ApplicationName, 349 CharmURL: offer.CharmURL, 350 ActiveConnectedCount: offer.ActiveConnectedCount, 351 TotalConnectedCount: offer.TotalConnectedCount, 352 } 353 out.Endpoints = make(map[string]remoteEndpoint) 354 for alias, ep := range offer.Endpoints { 355 out.Endpoints[alias] = remoteEndpoint{ 356 Name: ep.Name, 357 Interface: ep.Interface, 358 Role: string(ep.Role), 359 } 360 } 361 return out 362 } 363 364 type unitFormatInfo struct { 365 unit params.UnitStatus 366 unitName string 367 applicationName string 368 meterStatuses map[string]params.MeterStatus 369 } 370 371 func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus { 372 // TODO(Wallyworld) - this should be server side but we still need to support older servers. 373 sf.updateUnitStatusInfo(&info.unit, info.applicationName) 374 375 out := unitStatus{ 376 WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit), 377 JujuStatusInfo: sf.getAgentStatusInfo(info.unit), 378 Machine: info.unit.Machine, 379 OpenedPorts: info.unit.OpenedPorts, 380 ProviderId: info.unit.ProviderId, 381 Address: info.unit.Address, 382 PublicAddress: info.unit.PublicAddress, 383 Charm: info.unit.Charm, 384 Subordinates: make(map[string]unitStatus), 385 Leader: info.unit.Leader, 386 } 387 388 if ms, ok := info.meterStatuses[info.unitName]; ok { 389 out.MeterStatus = &meterStatus{ 390 Color: ms.Color, 391 Message: ms.Message, 392 } 393 } 394 395 for k, m := range info.unit.Subordinates { 396 out.Subordinates[k] = sf.formatUnit(unitFormatInfo{ 397 unit: m, 398 unitName: k, 399 applicationName: info.applicationName, 400 meterStatuses: info.meterStatuses, 401 }) 402 } 403 return out 404 } 405 406 func (sf *statusFormatter) getStatusInfoContents(inst params.DetailedStatus) statusInfoContents { 407 // TODO(perrito66) add status validation. 408 info := statusInfoContents{ 409 Err: inst.Err, 410 Current: status.Status(inst.Status), 411 Message: inst.Info, 412 Version: inst.Version, 413 Life: inst.Life, 414 } 415 if inst.Since != nil { 416 info.Since = common.FormatTime(inst.Since, sf.isoTime) 417 } 418 return info 419 } 420 421 func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents { 422 if unit.WorkloadStatus.Status == "" { 423 return statusInfoContents{} 424 } 425 // TODO(perrito66) add status validation. 426 info := statusInfoContents{ 427 Err: unit.WorkloadStatus.Err, 428 Current: status.Status(unit.WorkloadStatus.Status), 429 Message: unit.WorkloadStatus.Info, 430 Version: unit.WorkloadStatus.Version, 431 } 432 if unit.WorkloadStatus.Since != nil { 433 info.Since = common.FormatTime(unit.WorkloadStatus.Since, sf.isoTime) 434 } 435 return info 436 } 437 438 func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents { 439 // TODO(perrito66) add status validation. 440 info := statusInfoContents{ 441 Err: unit.AgentStatus.Err, 442 Current: status.Status(unit.AgentStatus.Status), 443 Message: unit.AgentStatus.Info, 444 Version: unit.AgentStatus.Version, 445 } 446 if unit.AgentStatus.Since != nil { 447 info.Since = common.FormatTime(unit.AgentStatus.Since, sf.isoTime) 448 } 449 return info 450 } 451 452 func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, applicationName string) { 453 // TODO(perrito66) add status validation. 454 if status.Status(unit.WorkloadStatus.Status) == status.Error { 455 if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok { 456 // Append the details of the other endpoint on to the status info string. 457 if ep, ok := findOtherEndpoint(relation.Endpoints, applicationName); ok { 458 unit.WorkloadStatus.Info = unit.WorkloadStatus.Info + " for " + ep.String() 459 } 460 } 461 } 462 } 463 464 func makeHAStatus(hasVote, wantsVote bool) string { 465 var s string 466 switch { 467 case hasVote && wantsVote: 468 s = "has-vote" 469 case hasVote && !wantsVote: 470 s = "removing-vote" 471 case !hasVote && wantsVote: 472 s = "adding-vote" 473 case !hasVote && !wantsVote: 474 s = "no-vote" 475 } 476 return s 477 } 478 479 func getRelationIdFromData(unit *params.UnitStatus) int { 480 if relationId_, ok := unit.WorkloadStatus.Data["relation-id"]; ok { 481 if relationId, ok := relationId_.(float64); ok { 482 return int(relationId) 483 } else { 484 logger.Infof("relation-id found status data but was unexpected "+ 485 "type: %q. Status output may be lacking some detail.", relationId_) 486 } 487 } 488 return -1 489 } 490 491 // findOtherEndpoint searches the provided endpoints for an endpoint 492 // that *doesn't* match applicationName. The returned bool indicates if 493 // such an endpoint was found. 494 func findOtherEndpoint(endpoints []params.EndpointStatus, applicationName string) (params.EndpointStatus, bool) { 495 for _, endpoint := range endpoints { 496 if endpoint.ApplicationName != applicationName { 497 return endpoint, true 498 } 499 } 500 return params.EndpointStatus{}, false 501 }