github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 "strings" 8 9 "github.com/juju/utils/series" 10 "gopkg.in/juju/charm.v6-unstable" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/cmd/juju/common" 15 "github.com/juju/juju/state/multiwatcher" 16 "github.com/juju/juju/status" 17 ) 18 19 type statusFormatter struct { 20 status *params.FullStatus 21 controllerName string 22 relations map[int]params.RelationStatus 23 isoTime bool 24 } 25 26 // NewStatusFormatter takes stored model information (params.FullStatus) and populates 27 // the statusFormatter struct used in various status formatting methods 28 func NewStatusFormatter(status *params.FullStatus, isoTime bool) *statusFormatter { 29 return newStatusFormatter(status, "", isoTime) 30 } 31 32 func newStatusFormatter(status *params.FullStatus, controllerName string, isoTime bool) *statusFormatter { 33 sf := statusFormatter{ 34 status: status, 35 controllerName: controllerName, 36 relations: make(map[int]params.RelationStatus), 37 isoTime: isoTime, 38 } 39 for _, relation := range status.Relations { 40 sf.relations[relation.Id] = relation 41 } 42 return &sf 43 } 44 45 func (sf *statusFormatter) format() (formattedStatus, error) { 46 if sf.status == nil { 47 return formattedStatus{}, nil 48 } 49 cloudTag, err := names.ParseCloudTag(sf.status.Model.CloudTag) 50 if err != nil { 51 return formattedStatus{}, err 52 } 53 out := formattedStatus{ 54 Model: modelStatus{ 55 Name: sf.status.Model.Name, 56 Controller: sf.controllerName, 57 Cloud: cloudTag.Id(), 58 CloudRegion: sf.status.Model.CloudRegion, 59 Version: sf.status.Model.Version, 60 AvailableVersion: sf.status.Model.AvailableVersion, 61 Migration: sf.status.Model.Migration, 62 }, 63 Machines: make(map[string]machineStatus), 64 Applications: make(map[string]applicationStatus), 65 } 66 for k, m := range sf.status.Machines { 67 out.Machines[k] = sf.formatMachine(m) 68 } 69 for sn, s := range sf.status.Applications { 70 out.Applications[sn] = sf.formatApplication(sn, s) 71 } 72 return out, nil 73 } 74 75 // MachineFormat takes stored model information (params.FullStatus) and formats machine status info. 76 func (sf *statusFormatter) MachineFormat(machineId []string) formattedMachineStatus { 77 if sf.status == nil { 78 return formattedMachineStatus{} 79 } 80 out := formattedMachineStatus{ 81 Model: sf.status.Model.Name, 82 Machines: make(map[string]machineStatus), 83 } 84 for k, m := range sf.status.Machines { 85 if len(machineId) != 0 { 86 for i := 0; i < len(machineId); i++ { 87 if m.Id == machineId[i] { 88 out.Machines[k] = sf.formatMachine(m) 89 } 90 } 91 } else { 92 out.Machines[k] = sf.formatMachine(m) 93 } 94 } 95 return out 96 } 97 98 func (sf *statusFormatter) formatMachine(machine params.MachineStatus) machineStatus { 99 var out machineStatus 100 101 out = machineStatus{ 102 JujuStatus: sf.getStatusInfoContents(machine.AgentStatus), 103 DNSName: machine.DNSName, 104 IPAddresses: machine.IPAddresses, 105 InstanceId: machine.InstanceId, 106 MachineStatus: sf.getStatusInfoContents(machine.InstanceStatus), 107 Series: machine.Series, 108 Id: machine.Id, 109 Containers: make(map[string]machineStatus), 110 Hardware: machine.Hardware, 111 } 112 113 for k, m := range machine.Containers { 114 out.Containers[k] = sf.formatMachine(m) 115 } 116 117 for _, job := range machine.Jobs { 118 if job == multiwatcher.JobManageModel { 119 out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote) 120 break 121 } 122 } 123 return out 124 } 125 126 func (sf *statusFormatter) formatApplication(name string, application params.ApplicationStatus) applicationStatus { 127 appOS, _ := series.GetOSFromSeries(application.Series) 128 var ( 129 charmOrigin = "" 130 charmName = "" 131 charmRev = 0 132 ) 133 if curl, err := charm.ParseURL(application.Charm); err != nil { 134 // We should never fail to parse a charm url sent back 135 // but if we do, don't crash. 136 logger.Errorf("failed to parse charm: %v", err) 137 } else { 138 switch curl.Schema { 139 case "cs": 140 charmOrigin = "jujucharms" 141 case "local": 142 charmOrigin = "local" 143 default: 144 charmOrigin = "unknown" 145 } 146 charmName = curl.Name 147 charmRev = curl.Revision 148 } 149 150 out := applicationStatus{ 151 Err: application.Err, 152 Charm: application.Charm, 153 Series: application.Series, 154 OS: strings.ToLower(appOS.String()), 155 CharmOrigin: charmOrigin, 156 CharmName: charmName, 157 CharmRev: charmRev, 158 Exposed: application.Exposed, 159 Life: application.Life, 160 Relations: application.Relations, 161 CanUpgradeTo: application.CanUpgradeTo, 162 SubordinateTo: application.SubordinateTo, 163 Units: make(map[string]unitStatus), 164 StatusInfo: sf.getServiceStatusInfo(application), 165 Version: application.WorkloadVersion, 166 } 167 for k, m := range application.Units { 168 out.Units[k] = sf.formatUnit(unitFormatInfo{ 169 unit: m, 170 unitName: k, 171 applicationName: name, 172 meterStatuses: application.MeterStatuses, 173 }) 174 } 175 return out 176 } 177 178 func (sf *statusFormatter) getServiceStatusInfo(service params.ApplicationStatus) statusInfoContents { 179 // TODO(perrito66) add status validation. 180 info := statusInfoContents{ 181 Err: service.Status.Err, 182 Current: status.Status(service.Status.Status), 183 Message: service.Status.Info, 184 Version: service.Status.Version, 185 } 186 if service.Status.Since != nil { 187 info.Since = common.FormatTime(service.Status.Since, sf.isoTime) 188 } 189 return info 190 } 191 192 type unitFormatInfo struct { 193 unit params.UnitStatus 194 unitName string 195 applicationName string 196 meterStatuses map[string]params.MeterStatus 197 } 198 199 func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus { 200 // TODO(Wallyworld) - this should be server side but we still need to support older servers. 201 sf.updateUnitStatusInfo(&info.unit, info.applicationName) 202 203 out := unitStatus{ 204 WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit), 205 JujuStatusInfo: sf.getAgentStatusInfo(info.unit), 206 Machine: info.unit.Machine, 207 OpenedPorts: info.unit.OpenedPorts, 208 PublicAddress: info.unit.PublicAddress, 209 Charm: info.unit.Charm, 210 Subordinates: make(map[string]unitStatus), 211 Leader: info.unit.Leader, 212 } 213 214 if ms, ok := info.meterStatuses[info.unitName]; ok { 215 out.MeterStatus = &meterStatus{ 216 Color: ms.Color, 217 Message: ms.Message, 218 } 219 } 220 221 for k, m := range info.unit.Subordinates { 222 out.Subordinates[k] = sf.formatUnit(unitFormatInfo{ 223 unit: m, 224 unitName: k, 225 applicationName: info.applicationName, 226 meterStatuses: info.meterStatuses, 227 }) 228 } 229 return out 230 } 231 232 func (sf *statusFormatter) getStatusInfoContents(inst params.DetailedStatus) statusInfoContents { 233 // TODO(perrito66) add status validation. 234 info := statusInfoContents{ 235 Err: inst.Err, 236 Current: status.Status(inst.Status), 237 Message: inst.Info, 238 Version: inst.Version, 239 Life: inst.Life, 240 } 241 if inst.Since != nil { 242 info.Since = common.FormatTime(inst.Since, sf.isoTime) 243 } 244 return info 245 } 246 247 func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents { 248 // TODO(perrito66) add status validation. 249 info := statusInfoContents{ 250 Err: unit.WorkloadStatus.Err, 251 Current: status.Status(unit.WorkloadStatus.Status), 252 Message: unit.WorkloadStatus.Info, 253 Version: unit.WorkloadStatus.Version, 254 } 255 if unit.WorkloadStatus.Since != nil { 256 info.Since = common.FormatTime(unit.WorkloadStatus.Since, sf.isoTime) 257 } 258 return info 259 } 260 261 func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents { 262 // TODO(perrito66) add status validation. 263 info := statusInfoContents{ 264 Err: unit.AgentStatus.Err, 265 Current: status.Status(unit.AgentStatus.Status), 266 Message: unit.AgentStatus.Info, 267 Version: unit.AgentStatus.Version, 268 } 269 if unit.AgentStatus.Since != nil { 270 info.Since = common.FormatTime(unit.AgentStatus.Since, sf.isoTime) 271 } 272 return info 273 } 274 275 func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, applicationName string) { 276 // TODO(perrito66) add status validation. 277 if status.Status(unit.WorkloadStatus.Status) == status.Error { 278 if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok { 279 // Append the details of the other endpoint on to the status info string. 280 if ep, ok := findOtherEndpoint(relation.Endpoints, applicationName); ok { 281 unit.WorkloadStatus.Info = unit.WorkloadStatus.Info + " for " + ep.String() 282 } 283 } 284 } 285 } 286 287 func makeHAStatus(hasVote, wantsVote bool) string { 288 var s string 289 switch { 290 case hasVote && wantsVote: 291 s = "has-vote" 292 case hasVote && !wantsVote: 293 s = "removing-vote" 294 case !hasVote && wantsVote: 295 s = "adding-vote" 296 case !hasVote && !wantsVote: 297 s = "no-vote" 298 } 299 return s 300 } 301 302 func getRelationIdFromData(unit *params.UnitStatus) int { 303 if relationId_, ok := unit.WorkloadStatus.Data["relation-id"]; ok { 304 if relationId, ok := relationId_.(float64); ok { 305 return int(relationId) 306 } else { 307 logger.Infof("relation-id found status data but was unexpected "+ 308 "type: %q. Status output may be lacking some detail.", relationId_) 309 } 310 } 311 return -1 312 } 313 314 // findOtherEndpoint searches the provided endpoints for an endpoint 315 // that *doesn't* match applicationName. The returned bool indicates if 316 // such an endpoint was found. 317 func findOtherEndpoint(endpoints []params.EndpointStatus, applicationName string) (params.EndpointStatus, bool) { 318 for _, endpoint := range endpoints { 319 if endpoint.ApplicationName != applicationName { 320 return endpoint, true 321 } 322 } 323 return params.EndpointStatus{}, false 324 }