github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/status.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "encoding/json" 8 "fmt" 9 10 "github.com/juju/cmd" 11 "launchpad.net/gnuflag" 12 13 "github.com/juju/errors" 14 15 "github.com/juju/juju/api" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/cmd/envcmd" 18 "github.com/juju/juju/instance" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/state/multiwatcher" 21 ) 22 23 type StatusCommand struct { 24 envcmd.EnvCommandBase 25 out cmd.Output 26 patterns []string 27 } 28 29 var statusDoc = ` 30 This command will report on the runtime state of various system entities. 31 32 There are a number of ways to format the status output: 33 34 - {short|line|oneline}: List units and their subordinates. For each 35 unit, the IP address and agent status are listed. 36 - summary: Displays the subnet(s) and port(s) the environment utilizes. 37 Also displays aggregate information about: 38 - MACHINES: total #, and # in each state. 39 - UNITS: total #, and # in each state. 40 - SERVICES: total #, and # exposed of each service. 41 - tabular: Displays information in a tabular format in these sections: 42 - Machines: ID, STATE, VERSION, DNS, INS-ID, SERIES, HARDWARE 43 - Services: NAME, EXPOSED, CHARM 44 - Units: ID, STATE, VERSION, MACHINE, PORTS, PUBLIC-ADDRESS 45 - Also displays subordinate units. 46 - yaml (DEFAULT): Displays information on machines, services, and units 47 in the yaml format. 48 49 Service or unit names may be specified to filter the status to only those 50 services and units that match, along with the related machines, services 51 and units. If a subordinate unit is matched, then its principal unit will 52 be displayed. If a principal unit is matched, then all of its subordinates 53 will be displayed. 54 55 Wildcards ('*') may be specified in service/unit names to match any sequence 56 of characters. For example, 'nova-*' will match any service whose name begins 57 with 'nova-': 'nova-compute', 'nova-volume', etc. 58 ` 59 60 func (c *StatusCommand) Info() *cmd.Info { 61 return &cmd.Info{ 62 Name: "status", 63 Args: "[pattern ...]", 64 Purpose: "output status information about an environment", 65 Doc: statusDoc, 66 Aliases: []string{"stat"}, 67 } 68 } 69 70 func (c *StatusCommand) SetFlags(f *gnuflag.FlagSet) { 71 c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{ 72 "yaml": cmd.FormatYaml, 73 "json": cmd.FormatJson, 74 "short": FormatOneline, 75 "oneline": FormatOneline, 76 "line": FormatOneline, 77 "tabular": FormatTabular, 78 "summary": FormatSummary, 79 }) 80 } 81 82 func (c *StatusCommand) Init(args []string) error { 83 c.patterns = args 84 return nil 85 } 86 87 var connectionError = `Unable to connect to environment %q. 88 Please check your credentials or use 'juju bootstrap' to create a new environment. 89 90 Error details: 91 %v 92 ` 93 94 type statusAPI interface { 95 Status(patterns []string) (*api.Status, error) 96 Close() error 97 } 98 99 var newApiClientForStatus = func(c *StatusCommand) (statusAPI, error) { 100 return c.NewAPIClient() 101 } 102 103 func (c *StatusCommand) Run(ctx *cmd.Context) error { 104 105 apiclient, err := newApiClientForStatus(c) 106 if err != nil { 107 return fmt.Errorf(connectionError, c.ConnectionName(), err) 108 } 109 defer apiclient.Close() 110 111 status, err := apiclient.Status(c.patterns) 112 if err != nil { 113 if status == nil { 114 // Status call completely failed, there is nothing to report 115 return err 116 } 117 // Display any error, but continue to print status if some was returned 118 fmt.Fprintf(ctx.Stderr, "%v\n", err) 119 } else if status == nil { 120 return errors.Errorf("unable to obtain the current status") 121 } 122 123 result := newStatusFormatter(status).format() 124 return c.out.Write(ctx, result) 125 } 126 127 type formattedStatus struct { 128 Environment string `json:"environment"` 129 Machines map[string]machineStatus `json:"machines"` 130 Services map[string]serviceStatus `json:"services"` 131 Networks map[string]networkStatus `json:"networks,omitempty" yaml:",omitempty"` 132 } 133 134 type errorStatus struct { 135 StatusError string `json:"status-error" yaml:"status-error"` 136 } 137 138 type machineStatus struct { 139 Err error `json:"-" yaml:",omitempty"` 140 AgentState params.Status `json:"agent-state,omitempty" yaml:"agent-state,omitempty"` 141 AgentStateInfo string `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"` 142 AgentVersion string `json:"agent-version,omitempty" yaml:"agent-version,omitempty"` 143 DNSName string `json:"dns-name,omitempty" yaml:"dns-name,omitempty"` 144 InstanceId instance.Id `json:"instance-id,omitempty" yaml:"instance-id,omitempty"` 145 InstanceState string `json:"instance-state,omitempty" yaml:"instance-state,omitempty"` 146 Life string `json:"life,omitempty" yaml:"life,omitempty"` 147 Series string `json:"series,omitempty" yaml:"series,omitempty"` 148 Id string `json:"-" yaml:"-"` 149 Containers map[string]machineStatus `json:"containers,omitempty" yaml:"containers,omitempty"` 150 Hardware string `json:"hardware,omitempty" yaml:"hardware,omitempty"` 151 HAStatus string `json:"state-server-member-status,omitempty" yaml:"state-server-member-status,omitempty"` 152 } 153 154 // A goyaml bug means we can't declare these types 155 // locally to the GetYAML methods. 156 type machineStatusNoMarshal machineStatus 157 158 func (s machineStatus) MarshalJSON() ([]byte, error) { 159 if s.Err != nil { 160 return json.Marshal(errorStatus{s.Err.Error()}) 161 } 162 return json.Marshal(machineStatusNoMarshal(s)) 163 } 164 165 func (s machineStatus) GetYAML() (tag string, value interface{}) { 166 if s.Err != nil { 167 return "", errorStatus{s.Err.Error()} 168 } 169 // TODO(rog) rename mNoMethods to noMethods (and also in 170 // the other GetYAML methods) when people are using the non-buggy 171 // goyaml version. 172 type mNoMethods machineStatus 173 return "", mNoMethods(s) 174 } 175 176 type serviceStatus struct { 177 Err error `json:"-" yaml:",omitempty"` 178 Charm string `json:"charm" yaml:"charm"` 179 CanUpgradeTo string `json:"can-upgrade-to,omitempty" yaml:"can-upgrade-to,omitempty"` 180 Exposed bool `json:"exposed" yaml:"exposed"` 181 Life string `json:"life,omitempty" yaml:"life,omitempty"` 182 Relations map[string][]string `json:"relations,omitempty" yaml:"relations,omitempty"` 183 Networks map[string][]string `json:"networks,omitempty" yaml:"networks,omitempty"` 184 SubordinateTo []string `json:"subordinate-to,omitempty" yaml:"subordinate-to,omitempty"` 185 Units map[string]unitStatus `json:"units,omitempty" yaml:"units,omitempty"` 186 } 187 188 type serviceStatusNoMarshal serviceStatus 189 190 func (s serviceStatus) MarshalJSON() ([]byte, error) { 191 if s.Err != nil { 192 return json.Marshal(errorStatus{s.Err.Error()}) 193 } 194 type sNoMethods serviceStatus 195 return json.Marshal(sNoMethods(s)) 196 } 197 198 func (s serviceStatus) GetYAML() (tag string, value interface{}) { 199 if s.Err != nil { 200 return "", errorStatus{s.Err.Error()} 201 } 202 type sNoMethods serviceStatus 203 return "", sNoMethods(s) 204 } 205 206 type unitStatus struct { 207 Err error `json:"-" yaml:",omitempty"` 208 Charm string `json:"upgrading-from,omitempty" yaml:"upgrading-from,omitempty"` 209 AgentState params.Status `json:"agent-state,omitempty" yaml:"agent-state,omitempty"` 210 AgentStateInfo string `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"` 211 AgentVersion string `json:"agent-version,omitempty" yaml:"agent-version,omitempty"` 212 Life string `json:"life,omitempty" yaml:"life,omitempty"` 213 Machine string `json:"machine,omitempty" yaml:"machine,omitempty"` 214 OpenedPorts []string `json:"open-ports,omitempty" yaml:"open-ports,omitempty"` 215 PublicAddress string `json:"public-address,omitempty" yaml:"public-address,omitempty"` 216 Subordinates map[string]unitStatus `json:"subordinates,omitempty" yaml:"subordinates,omitempty"` 217 } 218 219 type unitStatusNoMarshal unitStatus 220 221 func (s unitStatus) MarshalJSON() ([]byte, error) { 222 if s.Err != nil { 223 return json.Marshal(errorStatus{s.Err.Error()}) 224 } 225 return json.Marshal(unitStatusNoMarshal(s)) 226 } 227 228 func (s unitStatus) GetYAML() (tag string, value interface{}) { 229 if s.Err != nil { 230 return "", errorStatus{s.Err.Error()} 231 } 232 type uNoMethods unitStatus 233 return "", unitStatusNoMarshal(s) 234 } 235 236 type networkStatus struct { 237 Err error `json:"-" yaml:",omitempty"` 238 ProviderId network.Id `json:"provider-id" yaml:"provider-id"` 239 CIDR string `json:"cidr,omitempty" yaml:"cidr,omitempty"` 240 VLANTag int `json:"vlan-tag,omitempty" yaml:"vlan-tag,omitempty"` 241 } 242 243 type networkStatusNoMarshal networkStatus 244 245 func (n networkStatus) MarshalJSON() ([]byte, error) { 246 if n.Err != nil { 247 return json.Marshal(errorStatus{n.Err.Error()}) 248 } 249 type nNoMethods networkStatus 250 return json.Marshal(nNoMethods(n)) 251 } 252 253 func (n networkStatus) GetYAML() (tag string, value interface{}) { 254 if n.Err != nil { 255 return "", errorStatus{n.Err.Error()} 256 } 257 type nNoMethods networkStatus 258 return "", nNoMethods(n) 259 } 260 261 type statusFormatter struct { 262 status *api.Status 263 relations map[int]api.RelationStatus 264 } 265 266 func newStatusFormatter(status *api.Status) *statusFormatter { 267 sf := statusFormatter{ 268 status: status, 269 relations: make(map[int]api.RelationStatus), 270 } 271 for _, relation := range status.Relations { 272 sf.relations[relation.Id] = relation 273 } 274 return &sf 275 } 276 277 func (sf *statusFormatter) format() formattedStatus { 278 if sf.status == nil { 279 return formattedStatus{} 280 } 281 out := formattedStatus{ 282 Environment: sf.status.EnvironmentName, 283 Machines: make(map[string]machineStatus), 284 Services: make(map[string]serviceStatus), 285 } 286 for k, m := range sf.status.Machines { 287 out.Machines[k] = sf.formatMachine(m) 288 } 289 for sn, s := range sf.status.Services { 290 out.Services[sn] = sf.formatService(sn, s) 291 } 292 for k, n := range sf.status.Networks { 293 if out.Networks == nil { 294 out.Networks = make(map[string]networkStatus) 295 } 296 out.Networks[k] = sf.formatNetwork(n) 297 } 298 return out 299 } 300 301 func (sf *statusFormatter) formatMachine(machine api.MachineStatus) machineStatus { 302 var out machineStatus 303 304 if machine.Agent.Status == "" { 305 // Older server 306 // TODO: this will go away at some point (v1.21?). 307 out = machineStatus{ 308 AgentState: machine.AgentState, 309 AgentStateInfo: machine.AgentStateInfo, 310 AgentVersion: machine.AgentVersion, 311 Life: machine.Life, 312 Err: machine.Err, 313 DNSName: machine.DNSName, 314 InstanceId: machine.InstanceId, 315 InstanceState: machine.InstanceState, 316 Series: machine.Series, 317 Id: machine.Id, 318 Containers: make(map[string]machineStatus), 319 Hardware: machine.Hardware, 320 } 321 } else { 322 // New server 323 agent := machine.Agent 324 out = machineStatus{ 325 AgentState: machine.AgentState, 326 AgentStateInfo: adjustInfoIfAgentDown(machine.AgentState, agent.Status, agent.Info), 327 AgentVersion: agent.Version, 328 Life: agent.Life, 329 Err: agent.Err, 330 DNSName: machine.DNSName, 331 InstanceId: machine.InstanceId, 332 InstanceState: machine.InstanceState, 333 Series: machine.Series, 334 Id: machine.Id, 335 Containers: make(map[string]machineStatus), 336 Hardware: machine.Hardware, 337 } 338 } 339 340 for k, m := range machine.Containers { 341 out.Containers[k] = sf.formatMachine(m) 342 } 343 344 for _, job := range machine.Jobs { 345 if job == multiwatcher.JobManageEnviron { 346 out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote) 347 break 348 } 349 } 350 return out 351 } 352 353 func (sf *statusFormatter) formatService(name string, service api.ServiceStatus) serviceStatus { 354 out := serviceStatus{ 355 Err: service.Err, 356 Charm: service.Charm, 357 Exposed: service.Exposed, 358 Life: service.Life, 359 Relations: service.Relations, 360 Networks: make(map[string][]string), 361 CanUpgradeTo: service.CanUpgradeTo, 362 SubordinateTo: service.SubordinateTo, 363 Units: make(map[string]unitStatus), 364 } 365 if len(service.Networks.Enabled) > 0 { 366 out.Networks["enabled"] = service.Networks.Enabled 367 } 368 if len(service.Networks.Disabled) > 0 { 369 out.Networks["disabled"] = service.Networks.Disabled 370 } 371 for k, m := range service.Units { 372 out.Units[k] = sf.formatUnit(m, name) 373 } 374 return out 375 } 376 377 func (sf *statusFormatter) formatUnit(unit api.UnitStatus, serviceName string) unitStatus { 378 out := unitStatus{ 379 Err: unit.Err, 380 AgentState: unit.AgentState, 381 AgentStateInfo: sf.getUnitStatusInfo(unit, serviceName), 382 AgentVersion: unit.AgentVersion, 383 Life: unit.Life, 384 Machine: unit.Machine, 385 OpenedPorts: unit.OpenedPorts, 386 PublicAddress: unit.PublicAddress, 387 Charm: unit.Charm, 388 Subordinates: make(map[string]unitStatus), 389 } 390 for k, m := range unit.Subordinates { 391 out.Subordinates[k] = sf.formatUnit(m, serviceName) 392 } 393 return out 394 } 395 396 func (sf *statusFormatter) getUnitStatusInfo(unit api.UnitStatus, serviceName string) string { 397 if unit.Agent.Status == "" { 398 // Old server that doesn't support this field and others. 399 // Just return the info string as-is. 400 return unit.AgentStateInfo 401 } 402 statusInfo := unit.Agent.Info 403 if unit.Agent.Status == params.StatusError { 404 if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok { 405 // Append the details of the other endpoint on to the status info string. 406 if ep, ok := findOtherEndpoint(relation.Endpoints, serviceName); ok { 407 statusInfo = statusInfo + " for " + ep.String() 408 } 409 } 410 } 411 return adjustInfoIfAgentDown(unit.AgentState, unit.Agent.Status, statusInfo) 412 } 413 414 func (sf *statusFormatter) formatNetwork(network api.NetworkStatus) networkStatus { 415 return networkStatus{ 416 Err: network.Err, 417 ProviderId: network.ProviderId, 418 CIDR: network.CIDR, 419 VLANTag: network.VLANTag, 420 } 421 } 422 423 func makeHAStatus(hasVote, wantsVote bool) string { 424 var s string 425 switch { 426 case hasVote && wantsVote: 427 s = "has-vote" 428 case hasVote && !wantsVote: 429 s = "removing-vote" 430 case !hasVote && wantsVote: 431 s = "adding-vote" 432 case !hasVote && !wantsVote: 433 s = "no-vote" 434 } 435 return s 436 } 437 438 func getRelationIdFromData(unit api.UnitStatus) int { 439 if relationId_, ok := unit.Agent.Data["relation-id"]; ok { 440 if relationId, ok := relationId_.(float64); ok { 441 return int(relationId) 442 } else { 443 logger.Infof("relation-id found status data but was unexpected "+ 444 "type: %q. Status output may be lacking some detail.", relationId_) 445 } 446 } 447 return -1 448 } 449 450 // findOtherEndpoint searches the provided endpoints for an endpoint 451 // that *doesn't* match serviceName. The returned bool indicates if 452 // such an endpoint was found. 453 func findOtherEndpoint(endpoints []api.EndpointStatus, serviceName string) (api.EndpointStatus, bool) { 454 for _, endpoint := range endpoints { 455 if endpoint.ServiceName != serviceName { 456 return endpoint, true 457 } 458 } 459 return api.EndpointStatus{}, false 460 } 461 462 // adjustInfoIfAgentDown modifies the agent status info string if the 463 // agent is down. The original status and info is included in 464 // parentheses. 465 func adjustInfoIfAgentDown(status, origStatus params.Status, info string) string { 466 if status == params.StatusDown || status == params.StatusFailed { 467 if info == "" { 468 return fmt.Sprintf("(%s)", origStatus) 469 } 470 return fmt.Sprintf("(%s: %s)", origStatus, info) 471 } 472 return info 473 }