github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 InstanceId: machine.InstanceId, 105 MachineStatus: sf.getStatusInfoContents(machine.InstanceStatus), 106 Series: machine.Series, 107 Id: machine.Id, 108 Containers: make(map[string]machineStatus), 109 Hardware: machine.Hardware, 110 } 111 112 for k, m := range machine.Containers { 113 out.Containers[k] = sf.formatMachine(m) 114 } 115 116 for _, job := range machine.Jobs { 117 if job == multiwatcher.JobManageModel { 118 out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote) 119 break 120 } 121 } 122 return out 123 } 124 125 func (sf *statusFormatter) formatApplication(name string, application params.ApplicationStatus) applicationStatus { 126 appOS, _ := series.GetOSFromSeries(application.Series) 127 var ( 128 charmOrigin = "" 129 charmName = "" 130 charmRev = 0 131 ) 132 if curl, err := charm.ParseURL(application.Charm); err != nil { 133 // We should never fail to parse a charm url sent back 134 // but if we do, don't crash. 135 logger.Errorf("failed to parse charm: %v", err) 136 } else { 137 switch curl.Schema { 138 case "cs": 139 charmOrigin = "jujucharms" 140 case "local": 141 charmOrigin = "local" 142 default: 143 charmOrigin = "unknown" 144 } 145 charmName = curl.Name 146 charmRev = curl.Revision 147 } 148 149 out := applicationStatus{ 150 Err: application.Err, 151 Charm: application.Charm, 152 Series: application.Series, 153 OS: strings.ToLower(appOS.String()), 154 CharmOrigin: charmOrigin, 155 CharmName: charmName, 156 CharmRev: charmRev, 157 Exposed: application.Exposed, 158 Life: application.Life, 159 Relations: application.Relations, 160 CanUpgradeTo: application.CanUpgradeTo, 161 SubordinateTo: application.SubordinateTo, 162 Units: make(map[string]unitStatus), 163 StatusInfo: sf.getServiceStatusInfo(application), 164 Version: application.WorkloadVersion, 165 } 166 for k, m := range application.Units { 167 out.Units[k] = sf.formatUnit(unitFormatInfo{ 168 unit: m, 169 unitName: k, 170 applicationName: name, 171 meterStatuses: application.MeterStatuses, 172 }) 173 } 174 return out 175 } 176 177 func (sf *statusFormatter) getServiceStatusInfo(service params.ApplicationStatus) statusInfoContents { 178 // TODO(perrito66) add status validation. 179 info := statusInfoContents{ 180 Err: service.Status.Err, 181 Current: status.Status(service.Status.Status), 182 Message: service.Status.Info, 183 Version: service.Status.Version, 184 } 185 if service.Status.Since != nil { 186 info.Since = common.FormatTime(service.Status.Since, sf.isoTime) 187 } 188 return info 189 } 190 191 type unitFormatInfo struct { 192 unit params.UnitStatus 193 unitName string 194 applicationName string 195 meterStatuses map[string]params.MeterStatus 196 } 197 198 func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus { 199 // TODO(Wallyworld) - this should be server side but we still need to support older servers. 200 sf.updateUnitStatusInfo(&info.unit, info.applicationName) 201 202 out := unitStatus{ 203 WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit), 204 JujuStatusInfo: sf.getAgentStatusInfo(info.unit), 205 Machine: info.unit.Machine, 206 OpenedPorts: info.unit.OpenedPorts, 207 PublicAddress: info.unit.PublicAddress, 208 Charm: info.unit.Charm, 209 Subordinates: make(map[string]unitStatus), 210 Leader: info.unit.Leader, 211 } 212 213 if ms, ok := info.meterStatuses[info.unitName]; ok { 214 out.MeterStatus = &meterStatus{ 215 Color: ms.Color, 216 Message: ms.Message, 217 } 218 } 219 220 for k, m := range info.unit.Subordinates { 221 out.Subordinates[k] = sf.formatUnit(unitFormatInfo{ 222 unit: m, 223 unitName: k, 224 applicationName: info.applicationName, 225 meterStatuses: info.meterStatuses, 226 }) 227 } 228 return out 229 } 230 231 func (sf *statusFormatter) getStatusInfoContents(inst params.DetailedStatus) statusInfoContents { 232 // TODO(perrito66) add status validation. 233 info := statusInfoContents{ 234 Err: inst.Err, 235 Current: status.Status(inst.Status), 236 Message: inst.Info, 237 Version: inst.Version, 238 Life: inst.Life, 239 } 240 if inst.Since != nil { 241 info.Since = common.FormatTime(inst.Since, sf.isoTime) 242 } 243 return info 244 } 245 246 func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents { 247 // TODO(perrito66) add status validation. 248 info := statusInfoContents{ 249 Err: unit.WorkloadStatus.Err, 250 Current: status.Status(unit.WorkloadStatus.Status), 251 Message: unit.WorkloadStatus.Info, 252 Version: unit.WorkloadStatus.Version, 253 } 254 if unit.WorkloadStatus.Since != nil { 255 info.Since = common.FormatTime(unit.WorkloadStatus.Since, sf.isoTime) 256 } 257 return info 258 } 259 260 func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents { 261 // TODO(perrito66) add status validation. 262 info := statusInfoContents{ 263 Err: unit.AgentStatus.Err, 264 Current: status.Status(unit.AgentStatus.Status), 265 Message: unit.AgentStatus.Info, 266 Version: unit.AgentStatus.Version, 267 } 268 if unit.AgentStatus.Since != nil { 269 info.Since = common.FormatTime(unit.AgentStatus.Since, sf.isoTime) 270 } 271 return info 272 } 273 274 func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, applicationName string) { 275 // TODO(perrito66) add status validation. 276 if status.Status(unit.WorkloadStatus.Status) == status.Error { 277 if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok { 278 // Append the details of the other endpoint on to the status info string. 279 if ep, ok := findOtherEndpoint(relation.Endpoints, applicationName); ok { 280 unit.WorkloadStatus.Info = unit.WorkloadStatus.Info + " for " + ep.String() 281 } 282 } 283 } 284 } 285 286 func makeHAStatus(hasVote, wantsVote bool) string { 287 var s string 288 switch { 289 case hasVote && wantsVote: 290 s = "has-vote" 291 case hasVote && !wantsVote: 292 s = "removing-vote" 293 case !hasVote && wantsVote: 294 s = "adding-vote" 295 case !hasVote && !wantsVote: 296 s = "no-vote" 297 } 298 return s 299 } 300 301 func getRelationIdFromData(unit *params.UnitStatus) int { 302 if relationId_, ok := unit.WorkloadStatus.Data["relation-id"]; ok { 303 if relationId, ok := relationId_.(float64); ok { 304 return int(relationId) 305 } else { 306 logger.Infof("relation-id found status data but was unexpected "+ 307 "type: %q. Status output may be lacking some detail.", relationId_) 308 } 309 } 310 return -1 311 } 312 313 // findOtherEndpoint searches the provided endpoints for an endpoint 314 // that *doesn't* match applicationName. The returned bool indicates if 315 // such an endpoint was found. 316 func findOtherEndpoint(endpoints []params.EndpointStatus, applicationName string) (params.EndpointStatus, bool) { 317 for _, endpoint := range endpoints { 318 if endpoint.ApplicationName != applicationName { 319 return endpoint, true 320 } 321 } 322 return params.EndpointStatus{}, false 323 }