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