github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 9 "github.com/juju/juju/apiserver/params" 10 "github.com/juju/juju/cmd/juju/common" 11 "github.com/juju/juju/state/multiwatcher" 12 ) 13 14 type statusFormatter struct { 15 status *params.FullStatus 16 relations map[int]params.RelationStatus 17 isoTime bool 18 compatVersion int 19 } 20 21 func newStatusFormatter(status *params.FullStatus, compatVersion int, isoTime bool) *statusFormatter { 22 sf := statusFormatter{ 23 status: status, 24 relations: make(map[int]params.RelationStatus), 25 compatVersion: compatVersion, 26 isoTime: isoTime, 27 } 28 for _, relation := range status.Relations { 29 sf.relations[relation.Id] = relation 30 } 31 return &sf 32 } 33 34 func (sf *statusFormatter) format() formattedStatus { 35 if sf.status == nil { 36 return formattedStatus{} 37 } 38 out := formattedStatus{ 39 Environment: sf.status.EnvironmentName, 40 Machines: make(map[string]machineStatus), 41 Services: make(map[string]serviceStatus), 42 } 43 for k, m := range sf.status.Machines { 44 out.Machines[k] = sf.formatMachine(m) 45 } 46 for sn, s := range sf.status.Services { 47 out.Services[sn] = sf.formatService(sn, s) 48 } 49 for k, n := range sf.status.Networks { 50 if out.Networks == nil { 51 out.Networks = make(map[string]networkStatus) 52 } 53 out.Networks[k] = sf.formatNetwork(n) 54 } 55 return out 56 } 57 58 func (sf *statusFormatter) formatMachine(machine params.MachineStatus) machineStatus { 59 var out machineStatus 60 61 if machine.Agent.Status == "" { 62 // Older server 63 // TODO: this will go away at some point (v1.21?). 64 out = machineStatus{ 65 AgentState: machine.AgentState, 66 AgentStateInfo: machine.AgentStateInfo, 67 AgentVersion: machine.AgentVersion, 68 Life: machine.Life, 69 Err: machine.Err, 70 DNSName: machine.DNSName, 71 InstanceId: machine.InstanceId, 72 InstanceState: machine.InstanceState, 73 Series: machine.Series, 74 Id: machine.Id, 75 Containers: make(map[string]machineStatus), 76 Hardware: machine.Hardware, 77 } 78 } else { 79 // New server 80 agent := machine.Agent 81 out = machineStatus{ 82 AgentState: machine.AgentState, 83 AgentStateInfo: adjustInfoIfMachineAgentDown(machine.AgentState, agent.Status, agent.Info), 84 AgentVersion: agent.Version, 85 Life: agent.Life, 86 Err: agent.Err, 87 DNSName: machine.DNSName, 88 InstanceId: machine.InstanceId, 89 InstanceState: machine.InstanceState, 90 Series: machine.Series, 91 Id: machine.Id, 92 Containers: make(map[string]machineStatus), 93 Hardware: machine.Hardware, 94 } 95 } 96 97 for k, m := range machine.Containers { 98 out.Containers[k] = sf.formatMachine(m) 99 } 100 101 for _, job := range machine.Jobs { 102 if job == multiwatcher.JobManageEnviron { 103 out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote) 104 break 105 } 106 } 107 return out 108 } 109 110 func (sf *statusFormatter) formatService(name string, service params.ServiceStatus) serviceStatus { 111 out := serviceStatus{ 112 Err: service.Err, 113 Charm: service.Charm, 114 Exposed: service.Exposed, 115 Life: service.Life, 116 Relations: service.Relations, 117 Networks: make(map[string][]string), 118 CanUpgradeTo: service.CanUpgradeTo, 119 SubordinateTo: service.SubordinateTo, 120 Units: make(map[string]unitStatus), 121 StatusInfo: sf.getServiceStatusInfo(service), 122 } 123 if len(service.Networks.Enabled) > 0 { 124 out.Networks["enabled"] = service.Networks.Enabled 125 } 126 if len(service.Networks.Disabled) > 0 { 127 out.Networks["disabled"] = service.Networks.Disabled 128 } 129 for k, m := range service.Units { 130 out.Units[k] = sf.formatUnit(unitFormatInfo{ 131 unit: m, 132 unitName: k, 133 serviceName: name, 134 meterStatuses: service.MeterStatuses, 135 }) 136 } 137 return out 138 } 139 140 func (sf *statusFormatter) getServiceStatusInfo(service params.ServiceStatus) statusInfoContents { 141 info := statusInfoContents{ 142 Err: service.Status.Err, 143 Current: service.Status.Status, 144 Message: service.Status.Info, 145 Version: service.Status.Version, 146 } 147 if service.Status.Since != nil { 148 info.Since = common.FormatTime(service.Status.Since, sf.isoTime) 149 } 150 return info 151 } 152 153 type unitFormatInfo struct { 154 unit params.UnitStatus 155 unitName string 156 serviceName string 157 meterStatuses map[string]params.MeterStatus 158 } 159 160 func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus { 161 // TODO(Wallyworld) - this should be server side but we still need to support older servers. 162 sf.updateUnitStatusInfo(&info.unit, info.serviceName) 163 164 out := unitStatus{ 165 WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit), 166 AgentStatusInfo: sf.getAgentStatusInfo(info.unit), 167 Machine: info.unit.Machine, 168 OpenedPorts: info.unit.OpenedPorts, 169 PublicAddress: info.unit.PublicAddress, 170 Charm: info.unit.Charm, 171 Subordinates: make(map[string]unitStatus), 172 } 173 174 if ms, ok := info.meterStatuses[info.unitName]; ok { 175 out.MeterStatus = &meterStatus{ 176 Color: ms.Color, 177 Message: ms.Message, 178 } 179 } 180 181 // These legacy fields will be dropped for Juju 2.0. 182 if sf.compatVersion < 2 || out.AgentStatusInfo.Current == "" { 183 out.Err = info.unit.Err 184 out.AgentState = info.unit.AgentState 185 out.AgentStateInfo = info.unit.AgentStateInfo 186 out.Life = info.unit.Life 187 out.AgentVersion = info.unit.AgentVersion 188 } 189 190 for k, m := range info.unit.Subordinates { 191 out.Subordinates[k] = sf.formatUnit(unitFormatInfo{ 192 unit: m, 193 unitName: k, 194 serviceName: info.serviceName, 195 meterStatuses: info.meterStatuses, 196 }) 197 } 198 return out 199 } 200 201 func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents { 202 info := statusInfoContents{ 203 Err: unit.Workload.Err, 204 Current: unit.Workload.Status, 205 Message: unit.Workload.Info, 206 Version: unit.Workload.Version, 207 } 208 if unit.Workload.Since != nil { 209 info.Since = common.FormatTime(unit.Workload.Since, sf.isoTime) 210 } 211 return info 212 } 213 214 func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents { 215 info := statusInfoContents{ 216 Err: unit.UnitAgent.Err, 217 Current: unit.UnitAgent.Status, 218 Message: unit.UnitAgent.Info, 219 Version: unit.UnitAgent.Version, 220 } 221 if unit.UnitAgent.Since != nil { 222 info.Since = common.FormatTime(unit.UnitAgent.Since, sf.isoTime) 223 } 224 return info 225 } 226 227 func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, serviceName string) { 228 // This logic has no business here but can't be moved until Juju 2.0. 229 statusInfo := unit.Workload.Info 230 if unit.Workload.Status == "" { 231 // Old server that doesn't support this field and others. 232 // Just use the info string as-is. 233 statusInfo = unit.AgentStateInfo 234 } 235 if unit.Workload.Status == params.StatusError { 236 if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok { 237 // Append the details of the other endpoint on to the status info string. 238 if ep, ok := findOtherEndpoint(relation.Endpoints, serviceName); ok { 239 unit.Workload.Info = statusInfo + " for " + ep.String() 240 unit.AgentStateInfo = unit.Workload.Info 241 } 242 } 243 } 244 } 245 246 func (sf *statusFormatter) formatNetwork(network params.NetworkStatus) networkStatus { 247 return networkStatus{ 248 Err: network.Err, 249 ProviderId: network.ProviderId, 250 CIDR: network.CIDR, 251 VLANTag: network.VLANTag, 252 } 253 } 254 255 func makeHAStatus(hasVote, wantsVote bool) string { 256 var s string 257 switch { 258 case hasVote && wantsVote: 259 s = "has-vote" 260 case hasVote && !wantsVote: 261 s = "removing-vote" 262 case !hasVote && wantsVote: 263 s = "adding-vote" 264 case !hasVote && !wantsVote: 265 s = "no-vote" 266 } 267 return s 268 } 269 270 func getRelationIdFromData(unit *params.UnitStatus) int { 271 if relationId_, ok := unit.Workload.Data["relation-id"]; ok { 272 if relationId, ok := relationId_.(float64); ok { 273 return int(relationId) 274 } else { 275 logger.Infof("relation-id found status data but was unexpected "+ 276 "type: %q. Status output may be lacking some detail.", relationId_) 277 } 278 } 279 return -1 280 } 281 282 // findOtherEndpoint searches the provided endpoints for an endpoint 283 // that *doesn't* match serviceName. The returned bool indicates if 284 // such an endpoint was found. 285 func findOtherEndpoint(endpoints []params.EndpointStatus, serviceName string) (params.EndpointStatus, bool) { 286 for _, endpoint := range endpoints { 287 if endpoint.ServiceName != serviceName { 288 return endpoint, true 289 } 290 } 291 return params.EndpointStatus{}, false 292 } 293 294 // adjustInfoIfMachineAgentDown modifies the agent status info string if the 295 // agent is down. The original status and info is included in 296 // parentheses. 297 func adjustInfoIfMachineAgentDown(status, origStatus params.Status, info string) string { 298 if status == params.StatusDown { 299 if info == "" { 300 return fmt.Sprintf("(%s)", origStatus) 301 } 302 return fmt.Sprintf("(%s: %s)", origStatus, info) 303 } 304 return info 305 }