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