Merge lp:~axwalk/juju-core/lp1121916-juju-status-filters into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 1615
Proposed branch: lp:~axwalk/juju-core/lp1121916-juju-status-filters
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 949 lines (+670/-40)
9 files modified
charm/meta_test.go (+25/-21)
cmd/juju/cmd_test.go (+3/-1)
cmd/juju/status.go (+172/-14)
cmd/juju/status_test.go (+402/-3)
state/service_test.go (+13/-1)
state/unit.go (+6/-0)
state/unit_test.go (+30/-0)
testing/repo/series/monitoring/metadata.yaml (+16/-0)
testing/repo/series/wordpress/metadata.yaml (+3/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/lp1121916-juju-status-filters
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+178004@code.launchpad.net

Commit message

juju status: add service/unit filters

Any positional arguments after juju status are
now interpreted as filters for either service or
unit. If the filter includes a '/', then it is
considered a unit filter, else a service filter.
Service filters are transformed to unit filters
by appending '/*'.

A unit passes a filter if one of the following
conditions is true:
 - Its name matches the pattern.
 - The unit is a principal, and one of its
   subordinates matches the pattern.
 - The unit is a subordinate, and its principal
   matches the pattern.

If all units of a service are filtered out, then
the service is filtered out; if all units of a
machine are filtered out, the machine is filtered
out (except to fill out the container hierarchy).

Unlike in pyjuju, this implementation allows
only the '*' wildcard in patterns. '?' and
character ranges are not permitted.

Partially fixes bug 1121916

https://codereview.appspot.com/12235043/

Description of the change

juju status: add service/unit filters

Any positional arguments after juju status are
now interpreted as filters for either service or
unit. If the filter includes a '/', then it is
considered a unit filter, else a service filter.
Service filters are transformed to unit filters
by appending '/*'.

A unit passes a filter if one of the following
conditions is true:
 - Its name matches the pattern.
 - The unit is a principal, and one of its
   subordinates matches the pattern.
 - The unit is a subordinate, and its principal
   matches the pattern.

If all units of a service are filtered out, then
the service is filtered out; if all units of a
machine are filtered out, the machine is filtered
out (except to fill out the container hierarchy).

Unlike in pyjuju, this implementation allows
only the '*' wildcard in patterns. '?' and
character ranges are not permitted.

Partially fixes bug 1121916

https://codereview.appspot.com/12235043/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+178004_code.launchpad.net,

Message:
Please take a look.

Description:
juju status: add service/unit filters

Any positional arguments after juju status are
now interpreted as filters for either service or
unit. If the filter includes a '/', then it is
considered a unit filter, else a service filter.

If all units of a service are filtered out, then
the service is filtered out; if all units of a
machine are filtered out, the machine is filtered
out (except to fill out the container hierarchy).

Filters are implemented using using shell file
name pattern matching, like in pyjuju. See:
     http://golang.org/pkg/path/#Match

Partially fixes bug 1121916

https://code.launchpad.net/~axwalk/juju-core/lp1121916-juju-status-filters/+merge/178004

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/12235043/

Affected files:
   [revision details]
   cmd/juju/cmd_test.go
   cmd/juju/status.go
   cmd/juju/status_test.go

Revision history for this message
Martin Packman (gz) wrote :

Rietveld has screwed the diff, you'll need to run `lbox propose -cr -v`
again which should fix it.

One comment from looking at the changes on launchpad, this code ignores
a potential ErrBadPattern from path.Match() - seems like for user sanity
it might be nice to propogate that up to where they can see it. At the
least I'd like to see a test that has a borked pattern (unmatched [ or
similar should do) and asserts that things don't blow up.

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2013/08/01 15:39:18, gz wrote:
> Rietveld has screwed the diff, you'll need to run `lbox propose -cr
-v` again
> which should fix it.

Odd - the diffs show up fine to me. I've re-proposed anyway.

> One comment from looking at the changes on launchpad, this code
ignores a
> potential ErrBadPattern from path.Match() - seems like for user sanity
it might
> be nice to propogate that up to where they can see it. At the least
I'd like to
> see a test that has a borked pattern (unmatched [ or similar should
do) and
> asserts that things don't blow up.

Done. Go's path.Match function checks validity lazily, and I didn't
think it worthwhile expending effort to validate it ahead of time. Thus,
pattern validity will not be checked if it is never considered (i.e. a
preceding pattern matches). I've codified this in a test.

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
William Reade (fwereade) wrote :

Thanks, this is a great start, but we need to think about subordinates
in a bit more detail. Thoughts below.

https://codereview.appspot.com/12235043/diff/7001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/7001/cmd/juju/status.go#newcode113
cmd/juju/status.go:113: mid, err := unit.AssignedMachineId()
There's something here that demands a bit more thought.

* principal units have to(?) be included if the filter matches one of
their subordinates (because they're displayed in the principal's
"subordinates" field)
* subordinate units should be included if the filter matches their
principal (because otherwise we're stripping relevant info from the
requested unit)
* if a principal is included only because one of its subordinates is, we
probably shouldn't display its other subordinates, because they'renot
relevant to the user's request
* AssignedMachineId is expensive for subordinate units, because it
roundtrips to get the machine id from the principal

I *think* that if we tweak unit filtering as above (which remains open
to argument, but is I think consistent with the analogous behaviour for
machines) we can just skip all subordinate units here and still be
correct, and not waste any further time chatting to the database.

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Andrew Wilkins (axwalk) wrote :

https://codereview.appspot.com/12235043/diff/7001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/7001/cmd/juju/status.go#newcode113
cmd/juju/status.go:113: mid, err := unit.AssignedMachineId()
On 2013/08/02 16:25:03, fwereade wrote:
> There's something here that demands a bit more thought.

> * principal units have to(?) be included if the filter matches one of
their
> subordinates (because they're displayed in the principal's
"subordinates" field)
> * subordinate units should be included if the filter matches their
principal
> (because otherwise we're stripping relevant info from the requested
unit)
> * if a principal is included only because one of its subordinates is,
we
> probably shouldn't display its other subordinates, because they'renot
relevant
> to the user's request
> * AssignedMachineId is expensive for subordinate units, because it
roundtrips to
> get the machine id from the principal

> I *think* that if we tweak unit filtering as above (which remains open
to
> argument, but is I think consistent with the analogous behaviour for
machines)
> we can just skip all subordinate units here and still be correct, and
not waste
> any further time chatting to the database.

Done. Your suggestions SGTM, so I've implemented them.

https://codereview.appspot.com/12235043/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (4.8 KiB)

I like the way this is going - I think the matching
semantics are nice and will be useful in practice
(although it might be nice to have a way of showing
info on a single unit, ignoring its machine and principal/subs - that
can easily be done later)

I have a few suggestions below.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode29
cmd/juju/status.go:29: scope []string
"scope" seems like a slightly odd name here.
"patterns" perhaps?

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode32
cmd/juju/status.go:32: var statusDoc = "This command will report on the
runtime state of various system entities."
I think we should add some documentation on the pattern-matching
behaviour here.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode36
cmd/juju/status.go:36: Name: "status",
Args: "[pattern...]",

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode69
cmd/juju/status.go:69: func (m *unitMatcher) match(u *state.Unit,
subordinateTo string) (bool, error) {
I'm not sure we need the subordinateTo argument here.
A unit already knows its own principal unit; we
could either use u.DeployerTag to find it out,
or implement a trivial Unit.PrincipalName
function in state.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode97
cmd/juju/status.go:97: return false, err
we usually use a colon only for a following error.
perhaps:

return false, fmt.Errorf("pattern syntax error in %q", pattern)

?

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode105
cmd/juju/status.go:105: func processScope(scope []string) (m
*unitMatcher) {
It'd be nice to see a comment explaining the purpose of this function
here.

Rather than have the lazy initialisation, it might be
nicer to have something like this:

// newUnitMatcher returns a unitMatcher
// that matches units with the specified patterns.
func newUnitMatcher(patterns []string) *unitMatcher

// matchesAll reports whether the unit matcher
// matches any unit.
func (um *unitMatcher) matchesAny() bool

Then in fetchAllServicesAndUnits, you can call
um.matchesAny rather than using the somewhat
oblique um!=nil test (assuming I have the
intent correct)

Alternatively continue to use the ==nil test
and only call newUnitMatcher if len(patterns)>0.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode137
cmd/juju/status.go:137: mid, err := unit.AssignedMachineId()
I think we should try to avoid calling for subordinate units, because we
have all the information in the principal units.

Since we are guaranteed to have a principal unit if the unit matcher has
matched its subordinate, we could just do:

if unit.IsPrincipal() {
       mid, err := unit.AssignedMachineId()
       // The only error we can get from AssignedMachineId
       // on a principal unit is if there is no machine id
       // assigned to the unit.
       if err == nil {
           machineIds.Add(mid)
       }
}

You might want to move some or all the logic in this um!=nil
block in...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (5.3 KiB)

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode29
cmd/juju/status.go:29: scope []string
On 2013/08/05 14:23:02, rog wrote:
> "scope" seems like a slightly odd name here.
> "patterns" perhaps?

Done. I was following pyjuju, but I agree that it sounds slightly odd.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode32
cmd/juju/status.go:32: var statusDoc = "This command will report on the
runtime state of various system entities."
On 2013/08/05 14:23:02, rog wrote:
> I think we should add some documentation on the pattern-matching
behaviour here.

Done.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode69
cmd/juju/status.go:69: func (m *unitMatcher) match(u *state.Unit,
subordinateTo string) (bool, error) {
On 2013/08/05 14:23:02, rog wrote:
> I'm not sure we need the subordinateTo argument here.
> A unit already knows its own principal unit; we
> could either use u.DeployerTag to find it out,
> or implement a trivial Unit.PrincipalName
> function in state.

Good point, I've added Unit.PrincipalName.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode97
cmd/juju/status.go:97: return false, err
On 2013/08/05 14:23:02, rog wrote:
> we usually use a colon only for a following error.
> perhaps:

> return false, fmt.Errorf("pattern syntax error in %q", pattern)

> ?

Done.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode105
cmd/juju/status.go:105: func processScope(scope []string) (m
*unitMatcher) {
On 2013/08/05 14:23:02, rog wrote:
> It'd be nice to see a comment explaining the purpose of this function
here.

> Rather than have the lazy initialisation, it might be
> nicer to have something like this:

> // newUnitMatcher returns a unitMatcher
> // that matches units with the specified patterns.
> func newUnitMatcher(patterns []string) *unitMatcher

> // matchesAll reports whether the unit matcher
> // matches any unit.
> func (um *unitMatcher) matchesAny() bool

> Then in fetchAllServicesAndUnits, you can call
> um.matchesAny rather than using the somewhat
> oblique um!=nil test (assuming I have the
> intent correct)

> Alternatively continue to use the ==nil test
> and only call newUnitMatcher if len(patterns)>0.

Done.

https://codereview.appspot.com/12235043/diff/18001/cmd/juju/status.go#newcode137
cmd/juju/status.go:137: mid, err := unit.AssignedMachineId()
On 2013/08/05 14:23:02, rog wrote:
> I think we should try to avoid calling for subordinate units, because
we have
> all the information in the principal units.

> Since we are guaranteed to have a principal unit if the unit matcher
has matched
> its subordinate, we could just do:

> if unit.IsPrincipal() {
> mid, err := unit.AssignedMachineId()
> // The only error we can get from AssignedMachineId
> // on a principal unit is if there is no machine id
> // assigned to the unit.
> if err == nil {
> machineIds.Add(mid)
> }
> }

> You might want to move some or all the logic in this um!=nil
> block ...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

LGTM with the second fix below, and one other passing thought.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode130
cmd/juju/status.go:130: ok, err := path.Match(pattern, s)
It's a pity that because path.Match is lazy
about checking pattern validity, we need to return an
error all the way back.

For consideration: validate the glob patterns
with a regexp, and then we can panic here instead,
and lose the error returns from matchString
and matchUnit (and provide more reliable errors
to the user too)

Something like:

^([^\\[]|\[(\\.|[^-\]]-[^-\]])*\]|\\.)*$

might be sufficient.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode277
cmd/juju/status.go:277: mid, err := unit.AssignedMachineId()
This still isn't quite right - it should only call AssignedMachineId if
the unit is a principal (that way it doesn't need to do a network call)

just:

if !unit.IsPrincipal() {
     continue
}

should do the job.

https://codereview.appspot.com/12235043/

Revision history for this message
William Reade (fwereade) wrote :

LGTM, thanks -- sorry for the delay, it's a busy week.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode47
cmd/juju/status.go:47: if and when they are tried.`[1:]
Maybe add a little bit more explaining which bits are filtered out (and
which bits are not).

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode52
cmd/juju/status.go:52: Args: "[pattern...]",
s/n./n ./

?

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode116
cmd/juju/status.go:116: } else {
lose the else?

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go
File cmd/juju/status_test.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go#newcode34
cmd/juju/status_test.go:34: stderr = ctx.Stderr.(*bytes.Buffer).String()
I'd prefer it if stderr and stdout were consistently typed...

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go#newcode170
cmd/juju/status_test.go:170: "hardware": "arch=amd64 cpu-cores=1
mem=1024M",
Orthogonal... but this hardware data is not helpfully structured. Please
add a bug.

https://codereview.appspot.com/12235043/diff/28001/testing/repo/series/wordpress/metadata.yaml
File testing/repo/series/wordpress/metadata.yaml (right):

https://codereview.appspot.com/12235043/diff/28001/testing/repo/series/wordpress/metadata.yaml#newcode14
testing/repo/series/wordpress/metadata.yaml:14: scope: container
doesn't anything test for endpoints of wordpress elsewhere? If not,
cool, I'm just a bit surprised :).

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2013/08/07 17:02:30, rog wrote:
> LGTM with the second fix below, and one other passing thought.

> https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go
> File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode130
> cmd/juju/status.go:130: ok, err := path.Match(pattern, s)
> It's a pity that because path.Match is lazy
> about checking pattern validity, we need to return an
> error all the way back.

> For consideration: validate the glob patterns
> with a regexp, and then we can panic here instead,
> and lose the error returns from matchString
> and matchUnit (and provide more reliable errors
> to the user too)

> Something like:

> ^([^\\[]|\[(\\.|[^-\]]-[^-\]])*\]|\\.)*$

> might be sufficient.

That would be better, but can we simplify this?

The use of path matching is overkill IMHO. I think the only real
use-case
for wildcards is to match services with a common prefix or suffix,
e.g. redis-*, which would match redis-master and redis-slave.

How about we simplify it to just allow a '*' wildcard, and nothing else?
Then we can just check that the pattern matches "[a-z0-9-*]". This will
also simplify the documentation.

If we find that people really want complex matching, we could later add
a
flag to interpret patterns as REs.

I went ahead and implemented this. If this was wrong, let me know and
I'll revert.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode277
> cmd/juju/status.go:277: mid, err := unit.AssignedMachineId()
> This still isn't quite right - it should only call AssignedMachineId
if the unit
> is a principal (that way it doesn't need to do a network call)

> just:

> if !unit.IsPrincipal() {
> continue
> }

> should do the job.

Sorry! I missed that from last time. Done.

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2013/08/07 17:06:53, fwereade wrote:
> LGTM, thanks -- sorry for the delay, it's a busy week.

No worries, thanks for the review.

> https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go
> File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode47
> cmd/juju/status.go:47: if and when they are tried.`[1:]
> Maybe add a little bit more explaining which bits are filtered out
(and which
> bits are not).

Done.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode52
> cmd/juju/status.go:52: Args: "[pattern...]",
> s/n./n ./

> ?

Done.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status.go#newcode116
> cmd/juju/status.go:116: } else {
> lose the else?

Done.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go
> File cmd/juju/status_test.go (right):

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go#newcode34
> cmd/juju/status_test.go:34: stderr =
ctx.Stderr.(*bytes.Buffer).String()
> I'd prefer it if stderr and stdout were consistently typed...

Done.

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go#newcode170
> cmd/juju/status_test.go:170: "hardware": "arch=amd64 cpu-cores=1
mem=1024M",
> Orthogonal... but this hardware data is not helpfully structured.
Please add a
> bug.

Done: https://bugs.launchpad.net/juju-core/+bug/1209452
(I hope that's what you meant)

https://codereview.appspot.com/12235043/diff/28001/testing/repo/series/wordpress/metadata.yaml
> File testing/repo/series/wordpress/metadata.yaml (right):

https://codereview.appspot.com/12235043/diff/28001/testing/repo/series/wordpress/metadata.yaml#newcode14
> testing/repo/series/wordpress/metadata.yaml:14: scope: container
> doesn't anything test for endpoints of wordpress elsewhere? If not,
cool, I'm
> just a bit surprised :).

Sorry, I;m not sure I understand your comment. There are tests for
subordinates relating to wordpress, but there was only a test with one
subordinate (logging). I needed a test involving two subordinates for
one principal, hence this addition.

https://codereview.appspot.com/12235043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
William Reade (fwereade) wrote :

On 2013/08/08 02:46:27, axw1 wrote:
> On 2013/08/07 17:06:53, fwereade wrote:

https://codereview.appspot.com/12235043/diff/28001/cmd/juju/status_test.go#newcode170
> > cmd/juju/status_test.go:170: "hardware": "arch=amd64 cpu-cores=1
> mem=1024M",
> > Orthogonal... but this hardware data is not helpfully structured.
Please add a
> > bug.

> Done: https://bugs.launchpad.net/juju-core/+bug/1209452
> (I hope that's what you meant)

Perfect, thanks.

https://codereview.appspot.com/12235043/diff/28001/testing/repo/series/wordpress/metadata.yaml#newcode14
> > testing/repo/series/wordpress/metadata.yaml:14: scope: container
> > doesn't anything test for endpoints of wordpress elsewhere? If not,
cool, I'm
> > just a bit surprised :).

> Sorry, I;m not sure I understand your comment. There are tests for
subordinates
> relating to wordpress, but there was only a test with one subordinate
(logging).
> I needed a test involving two subordinates for one principal, hence
this
> addition.

I was just expressing mild surprise that this change didn't cause any
other tests to fail. It's not a problem that they didn't :).

Unless there's anything specific you need me to check, my previous LGTM
can be assumed to stand and you can go ahead and merge.

https://codereview.appspot.com/12235043/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

still LGTM with two final thoughts.

https://codereview.appspot.com/12235043/diff/42001/cmd/juju/status.go
File cmd/juju/status.go (right):

https://codereview.appspot.com/12235043/diff/42001/cmd/juju/status.go#newcode137
cmd/juju/status.go:137: const valid =
"abcdefghijklmnopqrstuvwxyz0123456789-*"
or:

var validPattern = regexp.MustCompile("^[a-z0-9-*]$")

and use if !validPattern.MatchString(s) instead of validatePattern.

which would make it easier to add extra stuff if, for instance,
we started to allow unicode characters in service names.

https://codereview.appspot.com/12235043/diff/42001/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/12235043/diff/42001/state/unit.go#newcode96
state/unit.go:96: var _ AgentEntity = (*Unit)(nil)
This has moved into interface.go now.

https://codereview.appspot.com/12235043/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (42.3 KiB)

The attempt to merge lp:~axwalk/juju-core/lp1121916-juju-status-filters into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.681s
ok launchpad.net/juju-core/agent/tools 25.280s
ok launchpad.net/juju-core/bzr 6.573s
ok launchpad.net/juju-core/cert 2.508s

----------------------------------------------------------------------
FAIL: meta_test.go:329: MetaSuite.TestMetaHooks

meta_test.go:356:
    c.Assert(hooks, DeepEquals, expectedHooks)
... obtained map[string]bool = map[string]bool{"upgrade-charm":true, "stop":true, "url-relation-joined":true, "url-relation-broken":true, "logging-dir-relation-departed":true, "monitoring-port-relation-changed":true, "cache-relation-changed":true, "config-changed":true, "logging-dir-relation-changed":true, "monitoring-port-relation-joined":true, "monitoring-port-relation-broken":true, "db-relation-departed":true, "cache-relation-joined":true, "cache-relation-broken":true, "start":true, "url-relation-departed":true, "logging-dir-relation-joined":true, "logging-dir-relation-broken":true, "db-relation-changed":true, "install":true, "url-relation-changed":true, "monitoring-port-relation-departed":true, "db-relation-joined":true, "db-relation-broken":true, "cache-relation-departed":true}
... expected map[string]bool = map[string]bool{"start":true, "db-relation-changed":true, "logging-dir-relation-joined":true, "logging-dir-relation-broken":true, "url-relation-departed":true, "install":true, "cache-relation-departed":true, "db-relation-joined":true, "db-relation-broken":true, "url-relation-changed":true, "upgrade-charm":true, "stop":true, "cache-relation-changed":true, "logging-dir-relation-departed":true, "url-relation-joined":true, "url-relation-broken":true, "config-changed":true, "cache-relation-joined":true, "cache-relation-broken":true, "db-relation-departed":true, "logging-dir-relation-changed":true}

OOPS: 76 passed, 1 FAILED
--- FAIL: Test (0.33 seconds)
FAIL
FAIL launchpad.net/juju-core/charm 0.538s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.019s
ok launchpad.net/juju-core/cmd 0.251s
? launchpad.net/juju-core/cmd/builddb [no test files]
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
listing available tools
found 4 tools
found 4 recent tools (version 1.0.0)
listing target bucket
found 3 tools in target; 4 tools to be copied
copying 1.0.0-defaultseries-amd64 from http://127.0.0.1:53845/tools/juju-1.0.0-defaultseries-amd64.tgz
copying tools/juju-1.0.0-defaultseries-amd64.tgz
downloaded tools/juju-1.0.0-defaultseries-amd64.tgz (0kB), uploading
download 0kB, uploading
copying 1.0.0-precise-amd64 from http://127.0.0.1:53845/tools/juju-1.0.0-precise-amd64.tgz
copying tools/juju-1.0.0-precise-amd64.tgz
downloaded tools/juju-1.0.0-precise-amd64.tgz (0kB), uploading
download 0kB, uploading
copying 1.0.0-quantal-amd64 from http://127.0.0.1:53845/tools/juju-1.0.0-quantal-amd64.tgz
copying tools/juju-1.0.0-quantal-amd64.tgz
downloaded tools/juju-1.0.0-quantal-amd64.tgz (0kB), uploading
download 0kB, uploading
copying 1.0.0-quantal-i386...

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (5.9 KiB)

The attempt to merge lp:~axwalk/juju-core/lp1121916-juju-status-filters into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.773s
ok launchpad.net/juju-core/agent/tools 25.298s
ok launchpad.net/juju-core/bzr 6.957s
ok launchpad.net/juju-core/cert 2.051s
ok launchpad.net/juju-core/charm 0.891s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.027s
ok launchpad.net/juju-core/cmd 0.216s
? launchpad.net/juju-core/cmd/builddb [no test files]
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 105.739s
ok launchpad.net/juju-core/cmd/jujud 51.464s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 0.743s
ok launchpad.net/juju-core/constraints 0.027s
ok launchpad.net/juju-core/container/lxc 0.298s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.278s
ok launchpad.net/juju-core/environs 1.161s
? launchpad.net/juju-core/environs/all [no test files]
ok launchpad.net/juju-core/environs/azure 3.838s
ok launchpad.net/juju-core/environs/cloudinit 0.431s
ok launchpad.net/juju-core/environs/config 0.678s
ok launchpad.net/juju-core/environs/dummy 15.907s
ok launchpad.net/juju-core/environs/ec2 181.924s
ok launchpad.net/juju-core/environs/imagemetadata 0.259s
ok launchpad.net/juju-core/environs/instances 0.223s
ok launchpad.net/juju-core/environs/jujutest 0.222s
ok launchpad.net/juju-core/environs/local 1.195s
? launchpad.net/juju-core/environs/local/storage [no test files]
ok launchpad.net/juju-core/environs/localstorage 0.244s
ok launchpad.net/juju-core/environs/maas 2.030s
ok launchpad.net/juju-core/environs/openstack 3.851s
? launchpad.net/juju-core/environs/provider [no test files]
ok launchpad.net/juju-core/environs/sync 0.241s
ok launchpad.net/juju-core/environs/testing 0.012s
? launchpad.net/juju-core/errors [no test files]
ok launchpad.net/juju-core/instance 0.039s
ok launchpad.net/juju-core/juju 11.900s
ok launchpad.net/juju-core/juju/osenv 0.236s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.018s
ok launchpad.net/juju-core/log/syslog 0.019s
ok launchpad.net/juju-core/names 0.030s
ok launchpad.net/juju-core/rpc 0.267s
ok launchpad.net/juju-core/rpc/jsoncodec 0.188s
ok launchpad.net/juju-core/schema 0.030s

----------------------------------------------------------------------
FAIL: service_test.go:1389: ServiceSuite.TestWatchService

[LOG] 9.66034 INFO juju state: opening state; mongo addresses: ["localhost:51241"]; entity ""
[LOG] 9.66789 INFO juju state: connection established
[LOG] 9.69983 INFO juju state: initializing environment
service_test.go:1420:
    testing.NewNotifyWatcherC(c, s.State, w).AssertOneChange()
testing/watcher.go:85:
    c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
... Error: watcher sent unexpected change: (_, true)

OOPS: 308 passed, 1 FAILED
--- FAIL: TestPackage (68.00 seconds)
FAIL
FAIL launchpad.net/juju-core/state 68.190s
? lau...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm/meta_test.go'
2--- charm/meta_test.go 2013-07-09 10:32:23 +0000
3+++ charm/meta_test.go 2013-08-08 09:25:42 +0000
4@@ -331,27 +331,31 @@
5 c.Assert(err, IsNil)
6 hooks := meta.Hooks()
7 expectedHooks := map[string]bool{
8- "install": true,
9- "start": true,
10- "config-changed": true,
11- "upgrade-charm": true,
12- "stop": true,
13- "cache-relation-joined": true,
14- "cache-relation-changed": true,
15- "cache-relation-departed": true,
16- "cache-relation-broken": true,
17- "db-relation-joined": true,
18- "db-relation-changed": true,
19- "db-relation-departed": true,
20- "db-relation-broken": true,
21- "logging-dir-relation-joined": true,
22- "logging-dir-relation-changed": true,
23- "logging-dir-relation-departed": true,
24- "logging-dir-relation-broken": true,
25- "url-relation-joined": true,
26- "url-relation-changed": true,
27- "url-relation-departed": true,
28- "url-relation-broken": true,
29+ "install": true,
30+ "start": true,
31+ "config-changed": true,
32+ "upgrade-charm": true,
33+ "stop": true,
34+ "cache-relation-joined": true,
35+ "cache-relation-changed": true,
36+ "cache-relation-departed": true,
37+ "cache-relation-broken": true,
38+ "db-relation-joined": true,
39+ "db-relation-changed": true,
40+ "db-relation-departed": true,
41+ "db-relation-broken": true,
42+ "logging-dir-relation-joined": true,
43+ "logging-dir-relation-changed": true,
44+ "logging-dir-relation-departed": true,
45+ "logging-dir-relation-broken": true,
46+ "monitoring-port-relation-joined": true,
47+ "monitoring-port-relation-changed": true,
48+ "monitoring-port-relation-departed": true,
49+ "monitoring-port-relation-broken": true,
50+ "url-relation-joined": true,
51+ "url-relation-changed": true,
52+ "url-relation-departed": true,
53+ "url-relation-broken": true,
54 }
55 c.Assert(hooks, DeepEquals, expectedHooks)
56 }
57
58=== modified file 'cmd/juju/cmd_test.go'
59--- cmd/juju/cmd_test.go 2013-08-06 05:22:59 +0000
60+++ cmd/juju/cmd_test.go 2013-08-08 09:25:42 +0000
61@@ -115,7 +115,9 @@
62 assertConnName(c, com, "walthamstow")
63
64 com, args = cmdFunc()
65- testInit(c, com, append(args, "hotdog"), "unrecognized args.*")
66+ if _, ok := com.(*StatusCommand); !ok {
67+ testInit(c, com, append(args, "hotdog"), "unrecognized args.*")
68+ }
69 }
70 }
71
72
73=== modified file 'cmd/juju/status.go'
74--- cmd/juju/status.go 2013-08-06 14:17:24 +0000
75+++ cmd/juju/status.go 2013-08-08 09:25:42 +0000
76@@ -6,6 +6,8 @@
77 import (
78 "encoding/json"
79 "fmt"
80+ "path"
81+ "regexp"
82 "strings"
83
84 "launchpad.net/gnuflag"
85@@ -24,14 +26,27 @@
86
87 type StatusCommand struct {
88 cmd.EnvCommandBase
89- out cmd.Output
90+ out cmd.Output
91+ patterns []string
92 }
93
94-var statusDoc = "This command will report on the runtime state of various system entities."
95+var statusDoc = `
96+This command will report on the runtime state of various system entities.
97+
98+Service or unit names may be specified to filter the status to only those
99+services and units that match, along with the related machines, services
100+and units. If a subordinate unit is matched, then its principal unit will
101+be displayed. If a principal unit is matched, then all of its subordinates
102+will be displayed.
103+
104+Wildcards ('*') may be specified in service/unit names to match any sequence
105+of characters. For example, 'nova-*' will match any service whose name begins
106+with 'nova-': 'nova-compute', 'nova-volume', etc.`[1:]
107
108 func (c *StatusCommand) Info() *cmd.Info {
109 return &cmd.Info{
110 Name: "status",
111+ Args: "[pattern ...]",
112 Purpose: "output status information about an environment",
113 Doc: statusDoc,
114 Aliases: []string{"stat"},
115@@ -46,6 +61,11 @@
116 })
117 }
118
119+func (c *StatusCommand) Init(args []string) error {
120+ c.patterns = args
121+ return nil
122+}
123+
124 type statusContext struct {
125 instances map[instance.Id]instance.Instance
126 machines map[string][]*state.Machine
127@@ -53,6 +73,95 @@
128 units map[string]map[string]*state.Unit
129 }
130
131+type unitMatcher struct {
132+ patterns []string
133+}
134+
135+// matchesAny returns true if the unitMatcher will
136+// match any unit, regardless of its attributes.
137+func (m unitMatcher) matchesAny() bool {
138+ return len(m.patterns) == 0
139+}
140+
141+// matchUnit attempts to match a state.Unit to one of
142+// a set of patterns, taking into account subordinate
143+// relationships.
144+func (m unitMatcher) matchUnit(u *state.Unit) bool {
145+ if m.matchesAny() {
146+ return true
147+ }
148+
149+ // Keep the unit if:
150+ // (a) its name matches a pattern, or
151+ // (b) it's a principal and one of its subordinates matches, or
152+ // (c) it's a subordinate and its principal matches.
153+ //
154+ // Note: do *not* include a second subordinate if the principal is
155+ // only matched on account of a first subordinate matching.
156+ if m.matchString(u.Name()) {
157+ return true
158+ }
159+ if u.IsPrincipal() {
160+ for _, s := range u.SubordinateNames() {
161+ if m.matchString(s) {
162+ return true
163+ }
164+ }
165+ return false
166+ }
167+ principal, valid := u.PrincipalName()
168+ if !valid {
169+ panic("PrincipalName failed for subordinate unit")
170+ }
171+ return m.matchString(principal)
172+}
173+
174+// matchString matches a string to one of the patterns in
175+// the unit matcher, returning an error if a pattern with
176+// invalid syntax is encountered.
177+func (m unitMatcher) matchString(s string) bool {
178+ for _, pattern := range m.patterns {
179+ ok, err := path.Match(pattern, s)
180+ if err != nil {
181+ // We validate patterns, so should never get here.
182+ panic(fmt.Errorf("pattern syntax error in %q", pattern))
183+ } else if ok {
184+ return true
185+ }
186+ }
187+ return false
188+}
189+
190+// validPattern must match the parts of a unit or service name
191+// pattern either side of the '/' for it to be valid.
192+var validPattern = regexp.MustCompile("^[a-z0-9-*]+$")
193+
194+// newUnitMatcher returns a unitMatcher that matches units
195+// with one of the specified patterns, or all units if no
196+// patterns are specified.
197+//
198+// An error will be returned if any of the specified patterns
199+// is invalid. Patterns are valid if they contain only
200+// alpha-numeric characters, hyphens, or asterisks (and one
201+// optional '/' to separate service/unit).
202+func newUnitMatcher(patterns []string) (unitMatcher, error) {
203+ for i, pattern := range patterns {
204+ fields := strings.Split(pattern, "/")
205+ if len(fields) > 2 {
206+ return unitMatcher{}, fmt.Errorf("pattern %q contains too many '/' characters", pattern)
207+ }
208+ for _, f := range fields {
209+ if !validPattern.MatchString(f) {
210+ return unitMatcher{}, fmt.Errorf("pattern %q contains invalid characters", pattern)
211+ }
212+ }
213+ if len(fields) == 1 {
214+ patterns[i] += "/*"
215+ }
216+ }
217+ return unitMatcher{patterns}, nil
218+}
219+
220 func (c *StatusCommand) Run(ctx *cmd.Context) error {
221 conn, err := juju.NewConnFromName(c.EnvName)
222 if err != nil {
223@@ -61,12 +170,26 @@
224 defer conn.Close()
225
226 var context statusContext
227- if context.machines, err = fetchAllMachines(conn.State); err != nil {
228- return err
229- }
230- if context.services, context.units, err = fetchAllServicesAndUnits(conn.State); err != nil {
231- return err
232- }
233+ unitMatcher, err := newUnitMatcher(c.patterns)
234+ if err != nil {
235+ return err
236+ }
237+ if context.services, context.units, err = fetchAllServicesAndUnits(conn.State, unitMatcher); err != nil {
238+ return err
239+ }
240+
241+ // Filter machines by units in scope.
242+ var machineIds *set.Strings
243+ if !unitMatcher.matchesAny() {
244+ machineIds, err = fetchUnitMachineIds(context.units)
245+ if err != nil {
246+ return err
247+ }
248+ }
249+ if context.machines, err = fetchMachines(conn.State, machineIds); err != nil {
250+ return err
251+ }
252+
253 context.instances, err = fetchAllInstances(conn.Environ)
254 if err != nil {
255 // We cannot see instances from the environment, but
256@@ -98,9 +221,11 @@
257 return m, nil
258 }
259
260-// fetchAllMachines returns a map from top level machine id to machines, where machines[0] is the host
261+// fetchMachines returns a map from top level machine id to machines, where machines[0] is the host
262 // machine and machines[1..n] are any containers (including nested ones).
263-func fetchAllMachines(st *state.State) (map[string][]*state.Machine, error) {
264+//
265+// If machineIds is non-nil, only machines whose IDs are in the set are returned.
266+func fetchMachines(st *state.State, machineIds *set.Strings) (map[string][]*state.Machine, error) {
267 v := make(map[string][]*state.Machine)
268 machines, err := st.AllMachines()
269 if err != nil {
270@@ -108,6 +233,9 @@
271 }
272 // AllMachines gives us machines sorted by id.
273 for _, m := range machines {
274+ if machineIds != nil && !machineIds.Contains(m.Id()) {
275+ continue
276+ }
277 parentId, ok := m.ParentId()
278 if !ok {
279 // Only top level host machines go directly into the machine map.
280@@ -127,7 +255,7 @@
281
282 // fetchAllServicesAndUnits returns a map from service name to service
283 // and a map from service name to unit name to unit.
284-func fetchAllServicesAndUnits(st *state.State) (map[string]*state.Service, map[string]map[string]*state.Unit, error) {
285+func fetchAllServicesAndUnits(st *state.State, unitMatcher unitMatcher) (map[string]*state.Service, map[string]map[string]*state.Unit, error) {
286 svcMap := make(map[string]*state.Service)
287 unitMap := make(map[string]map[string]*state.Unit)
288 services, err := st.AllServices()
289@@ -135,20 +263,47 @@
290 return nil, nil, err
291 }
292 for _, s := range services {
293- svcMap[s.Name()] = s
294 units, err := s.AllUnits()
295 if err != nil {
296 return nil, nil, err
297 }
298 svcUnitMap := make(map[string]*state.Unit)
299 for _, u := range units {
300+ if !unitMatcher.matchUnit(u) {
301+ continue
302+ }
303 svcUnitMap[u.Name()] = u
304 }
305- unitMap[s.Name()] = svcUnitMap
306+ if unitMatcher.matchesAny() || len(svcUnitMap) > 0 {
307+ unitMap[s.Name()] = svcUnitMap
308+ svcMap[s.Name()] = s
309+ }
310 }
311 return svcMap, unitMap, nil
312 }
313
314+// fetchUnitMachineIds returns a set of IDs for machines that
315+// the specified units reside on, and those machines' ancestors.
316+func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (*set.Strings, error) {
317+ machineIds := new(set.Strings)
318+ for _, svcUnitMap := range units {
319+ for _, unit := range svcUnitMap {
320+ if !unit.IsPrincipal() {
321+ continue
322+ }
323+ mid, err := unit.AssignedMachineId()
324+ if err != nil {
325+ return nil, err
326+ }
327+ for mid != "" {
328+ machineIds.Add(mid)
329+ mid = state.ParentId(mid)
330+ }
331+ }
332+ }
333+ return machineIds, nil
334+}
335+
336 func (context *statusContext) processMachines() map[string]machineStatus {
337 machinesMap := make(map[string]machineStatus)
338 for id, machines := range context.machines {
339@@ -275,7 +430,10 @@
340 status.Subordinates = make(map[string]unitStatus)
341 for _, name := range subUnits {
342 subUnit := context.unitByName(name)
343- status.Subordinates[name] = context.processUnit(subUnit)
344+ // subUnit may be nil if subordinate was filtered out.
345+ if subUnit != nil {
346+ status.Subordinates[name] = context.processUnit(subUnit)
347+ }
348 }
349 }
350 return
351
352=== modified file 'cmd/juju/status_test.go'
353--- cmd/juju/status_test.go 2013-08-06 14:17:24 +0000
354+++ cmd/juju/status_test.go 2013-08-08 09:25:42 +0000
355@@ -8,6 +8,7 @@
356 "encoding/json"
357 "fmt"
358 "net/url"
359+ "strings"
360
361 . "launchpad.net/gocheck"
362 "launchpad.net/goyaml"
363@@ -153,6 +154,21 @@
364 "series": "series",
365 "hardware": "arch=amd64 cpu-cores=1 mem=1024M",
366 }
367+ machine1WithContainersScoped = M{
368+ "agent-state": "started",
369+ "containers": M{
370+ "1/lxc/0": M{
371+ "agent-state": "started",
372+ "dns-name": "dummyenv-2.dns",
373+ "instance-id": "dummyenv-2",
374+ "series": "series",
375+ },
376+ },
377+ "dns-name": "dummyenv-1.dns",
378+ "instance-id": "dummyenv-1",
379+ "series": "series",
380+ "hardware": "arch=amd64 cpu-cores=1 mem=1024M",
381+ }
382 unexposedService = M{
383 "charm": "local:series/dummy-1",
384 "exposed": false,
385@@ -497,6 +513,146 @@
386 },
387 },
388 },
389+
390+ scopedExpect{
391+ "scope status on dummy-service/0 unit",
392+ []string{"dummy-service/0"},
393+ M{
394+ "environment": "dummyenv",
395+ "machines": M{
396+ "1": machine1,
397+ },
398+ "services": M{
399+ "dummy-service": M{
400+ "charm": "local:series/dummy-1",
401+ "exposed": false,
402+ "units": M{
403+ "dummy-service/0": M{
404+ "machine": "1",
405+ "life": "dying",
406+ "agent-state": "down",
407+ "agent-state-info": "(started)",
408+ },
409+ },
410+ },
411+ },
412+ },
413+ },
414+ scopedExpect{
415+ "scope status on exposed-service service",
416+ []string{"exposed-service"},
417+ M{
418+ "environment": "dummyenv",
419+ "machines": M{
420+ "2": machine2,
421+ },
422+ "services": M{
423+ "exposed-service": M{
424+ "charm": "local:series/dummy-1",
425+ "exposed": true,
426+ "units": M{
427+ "exposed-service/0": M{
428+ "machine": "2",
429+ "agent-state": "error",
430+ "agent-state-info": "You Require More Vespene Gas",
431+ "open-ports": L{
432+ "2/tcp", "3/tcp", "2/udp", "10/udp",
433+ },
434+ },
435+ },
436+ },
437+ },
438+ },
439+ },
440+ scopedExpect{
441+ "scope status on service pattern",
442+ []string{"d*-service"},
443+ M{
444+ "environment": "dummyenv",
445+ "machines": M{
446+ "1": machine1,
447+ },
448+ "services": M{
449+ "dummy-service": M{
450+ "charm": "local:series/dummy-1",
451+ "exposed": false,
452+ "units": M{
453+ "dummy-service/0": M{
454+ "machine": "1",
455+ "life": "dying",
456+ "agent-state": "down",
457+ "agent-state-info": "(started)",
458+ },
459+ },
460+ },
461+ },
462+ },
463+ },
464+ scopedExpect{
465+ "scope status on unit pattern",
466+ []string{"e*posed-service/*"},
467+ M{
468+ "environment": "dummyenv",
469+ "machines": M{
470+ "2": machine2,
471+ },
472+ "services": M{
473+ "exposed-service": M{
474+ "charm": "local:series/dummy-1",
475+ "exposed": true,
476+ "units": M{
477+ "exposed-service/0": M{
478+ "machine": "2",
479+ "agent-state": "error",
480+ "agent-state-info": "You Require More Vespene Gas",
481+ "open-ports": L{
482+ "2/tcp", "3/tcp", "2/udp", "10/udp",
483+ },
484+ },
485+ },
486+ },
487+ },
488+ },
489+ },
490+ scopedExpect{
491+ "scope status on combination of service and unit patterns",
492+ []string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"},
493+ M{
494+ "environment": "dummyenv",
495+ "machines": M{
496+ "1": machine1,
497+ "2": machine2,
498+ },
499+ "services": M{
500+ "dummy-service": M{
501+ "charm": "local:series/dummy-1",
502+ "exposed": false,
503+ "units": M{
504+ "dummy-service/0": M{
505+ "machine": "1",
506+ "life": "dying",
507+ "agent-state": "down",
508+ "agent-state-info": "(started)",
509+ },
510+ },
511+ },
512+ "exposed-service": M{
513+ "charm": "local:series/dummy-1",
514+ "exposed": true,
515+ "units": M{
516+ "exposed-service/0": M{
517+ "machine": "2",
518+ "agent-state": "error",
519+ "agent-state-info": "You Require More Vespene Gas",
520+ "open-ports": L{
521+ "2/tcp", "3/tcp", "2/udp", "10/udp",
522+ },
523+ },
524+ },
525+ },
526+ },
527+ },
528+ },
529 ),
530 test(
531 "add a dying service",
532@@ -808,6 +964,185 @@
533 },
534 },
535 },
536+
537+ // scoped on 'logging'
538+ scopedExpect{
539+ "subordinates scoped on logging",
540+ []string{"logging"},
541+ M{
542+ "environment": "dummyenv",
543+ "machines": M{
544+ "1": machine1,
545+ "2": machine2,
546+ },
547+ "services": M{
548+ "wordpress": M{
549+ "charm": "local:series/wordpress-3",
550+ "exposed": true,
551+ "units": M{
552+ "wordpress/0": M{
553+ "machine": "1",
554+ "agent-state": "started",
555+ "subordinates": M{
556+ "logging/0": M{
557+ "agent-state": "started",
558+ },
559+ },
560+ },
561+ },
562+ "relations": M{
563+ "db": L{"mysql"},
564+ "logging-dir": L{"logging"},
565+ },
566+ },
567+ "mysql": M{
568+ "charm": "local:series/mysql-1",
569+ "exposed": true,
570+ "units": M{
571+ "mysql/0": M{
572+ "machine": "2",
573+ "agent-state": "started",
574+ "subordinates": M{
575+ "logging/1": M{
576+ "agent-state": "error",
577+ "agent-state-info": "somehow lost in all those logs",
578+ },
579+ },
580+ },
581+ },
582+ "relations": M{
583+ "server": L{"wordpress"},
584+ "juju-info": L{"logging"},
585+ },
586+ },
587+ "logging": M{
588+ "charm": "local:series/logging-1",
589+ "exposed": true,
590+ "relations": M{
591+ "logging-directory": L{"wordpress"},
592+ "info": L{"mysql"},
593+ },
594+ "subordinate-to": L{"mysql", "wordpress"},
595+ },
596+ },
597+ },
598+ },
599+
600+ // scoped on wordpress/0
601+ scopedExpect{
602+ "subordinates scoped on logging",
603+ []string{"wordpress/0"},
604+ M{
605+ "environment": "dummyenv",
606+ "machines": M{
607+ "1": machine1,
608+ },
609+ "services": M{
610+ "wordpress": M{
611+ "charm": "local:series/wordpress-3",
612+ "exposed": true,
613+ "units": M{
614+ "wordpress/0": M{
615+ "machine": "1",
616+ "agent-state": "started",
617+ "subordinates": M{
618+ "logging/0": M{
619+ "agent-state": "started",
620+ },
621+ },
622+ },
623+ },
624+ "relations": M{
625+ "db": L{"mysql"},
626+ "logging-dir": L{"logging"},
627+ },
628+ },
629+ "logging": M{
630+ "charm": "local:series/logging-1",
631+ "exposed": true,
632+ "relations": M{
633+ "logging-directory": L{"wordpress"},
634+ "info": L{"mysql"},
635+ },
636+ "subordinate-to": L{"mysql", "wordpress"},
637+ },
638+ },
639+ },
640+ },
641+ ), test(
642+ "one service with two subordinate services",
643+ addMachine{machineId: "0", job: state.JobManageEnviron},
644+ startAliveMachine{"0"},
645+ setMachineStatus{"0", params.StatusStarted, ""},
646+ addCharm{"wordpress"},
647+ addCharm{"logging"},
648+ addCharm{"monitoring"},
649+
650+ addService{"wordpress", "wordpress"},
651+ setServiceExposed{"wordpress", true},
652+ addMachine{machineId: "1", job: state.JobHostUnits},
653+ startAliveMachine{"1"},
654+ setMachineStatus{"1", params.StatusStarted, ""},
655+ addAliveUnit{"wordpress", "1"},
656+ setUnitStatus{"wordpress/0", params.StatusStarted, ""},
657+
658+ addService{"logging", "logging"},
659+ setServiceExposed{"logging", true},
660+ addService{"monitoring", "monitoring"},
661+ setServiceExposed{"monitoring", true},
662+
663+ relateServices{"wordpress", "logging"},
664+ relateServices{"wordpress", "monitoring"},
665+
666+ addSubordinate{"wordpress/0", "logging"},
667+ addSubordinate{"wordpress/0", "monitoring"},
668+
669+ setUnitsAlive{"logging"},
670+ setUnitStatus{"logging/0", params.StatusStarted, ""},
671+
672+ setUnitsAlive{"monitoring"},
673+ setUnitStatus{"monitoring/0", params.StatusStarted, ""},
674+
675+ // scoped on monitoring; make sure logging doesn't show up.
676+ scopedExpect{
677+ "subordinates scoped on:",
678+ []string{"monitoring"},
679+ M{
680+ "environment": "dummyenv",
681+ "machines": M{
682+ "1": machine1,
683+ },
684+ "services": M{
685+ "wordpress": M{
686+ "charm": "local:series/wordpress-3",
687+ "exposed": true,
688+ "units": M{
689+ "wordpress/0": M{
690+ "machine": "1",
691+ "agent-state": "started",
692+ "subordinates": M{
693+ "monitoring/0": M{
694+ "agent-state": "started",
695+ },
696+ },
697+ },
698+ },
699+ "relations": M{
700+ "logging-dir": L{"logging"},
701+ "monitoring-port": L{"monitoring"},
702+ },
703+ },
704+ "monitoring": M{
705+ "charm": "local:series/monitoring-0",
706+ "exposed": true,
707+ "relations": M{
708+ "monitoring-port": L{"wordpress"},
709+ },
710+ "subordinate-to": L{"wordpress"},
711+ },
712+ },
713+ },
714+ },
715 ), test(
716 "machines with containers",
717 addMachine{machineId: "0", job: state.JobManageEnviron},
718@@ -862,6 +1197,30 @@
719 },
720 },
721 },
722+
723+ // once again, with a scope on mysql/1
724+ scopedExpect{
725+ "machines with nested containers",
726+ []string{"mysql/1"},
727+ M{
728+ "environment": "dummyenv",
729+ "machines": M{
730+ "1": machine1WithContainersScoped,
731+ },
732+ "services": M{
733+ "mysql": M{
734+ "charm": "local:series/mysql-1",
735+ "exposed": true,
736+ "units": M{
737+ "mysql/1": M{
738+ "machine": "1/lxc/0",
739+ "agent-state": "started",
740+ },
741+ },
742+ },
743+ },
744+ },
745+ },
746 ),
747 }
748
749@@ -1175,19 +1534,26 @@
750 c.Assert(err, IsNil)
751 }
752
753+type scopedExpect struct {
754+ what string
755+ scope []string
756+ output M
757+}
758+
759 type expect struct {
760 what string
761 output M
762 }
763
764-func (e expect) step(c *C, ctx *context) {
765- c.Logf("expect: %s", e.what)
766+func (e scopedExpect) step(c *C, ctx *context) {
767+ c.Logf("expect: %s %s", e.what, strings.Join(e.scope, " "))
768
769 // Now execute the command for each format.
770 for _, format := range statusFormats {
771 c.Logf("format %q", format.name)
772 // Run command with the required format.
773- code, stdout, stderr := runStatus(c, "--format", format.name)
774+ args := append([]string{"--format", format.name}, e.scope...)
775+ code, stdout, stderr := runStatus(c, args...)
776 c.Assert(code, Equals, 0)
777 c.Assert(stderr, HasLen, 0)
778
779@@ -1206,6 +1572,10 @@
780 }
781 }
782
783+func (e expect) step(c *C, ctx *context) {
784+ scopedExpect{e.what, nil, e.output}.step(c, ctx)
785+}
786+
787 func (s *StatusSuite) TestStatusAllFormats(c *C) {
788 for i, t := range statusTests {
789 c.Logf("test %d: %s", i, t.summary)
790@@ -1217,3 +1587,32 @@
791 }()
792 }
793 }
794+
795+func (s *StatusSuite) TestStatusFilterErrors(c *C) {
796+ steps := []stepper{
797+ addMachine{machineId: "0", job: state.JobManageEnviron},
798+ addMachine{machineId: "1", job: state.JobHostUnits},
799+ addCharm{"mysql"},
800+ addService{"mysql", "mysql"},
801+ addAliveUnit{"mysql", "1"},
802+ }
803+ ctx := s.newContext()
804+ defer s.resetContext(c, ctx)
805+ ctx.run(c, steps)
806+
807+ // Status filters can only fail if the patterns are invalid.
808+ code, _, stderr := runStatus(c, "[*")
809+ c.Assert(code, Not(Equals), 0)
810+ c.Assert(string(stderr), Equals, `error: pattern "[*" contains invalid characters`+"\n")
811+
812+ code, _, stderr = runStatus(c, "//")
813+ c.Assert(code, Not(Equals), 0)
814+ c.Assert(string(stderr), Equals, `error: pattern "//" contains too many '/' characters`+"\n")
815+
816+ // Pattern validity is checked eagerly; if a bad pattern
817+ // proceeds a valid, matching pattern, then the bad pattern
818+ // will still cause an error.
819+ code, _, stderr = runStatus(c, "*", "[*")
820+ c.Assert(code, Not(Equals), 0)
821+ c.Assert(string(stderr), Equals, `error: pattern "[*" contains invalid characters`+"\n")
822+}
823
824=== modified file 'state/service_test.go'
825--- state/service_test.go 2013-07-18 16:46:59 +0000
826+++ state/service_test.go 2013-08-08 09:25:42 +0000
827@@ -652,6 +652,18 @@
828 },
829 })
830
831+ mpEP, err := wordpress.Endpoint("monitoring-port")
832+ c.Assert(err, IsNil)
833+ c.Assert(mpEP, DeepEquals, state.Endpoint{
834+ ServiceName: "wordpress",
835+ Relation: charm.Relation{
836+ Interface: "monitoring",
837+ Name: "monitoring-port",
838+ Role: charm.RoleProvider,
839+ Scope: charm.ScopeContainer,
840+ },
841+ })
842+
843 dbEP, err := wordpress.Endpoint("db")
844 c.Assert(err, IsNil)
845 c.Assert(dbEP, DeepEquals, state.Endpoint{
846@@ -681,7 +693,7 @@
847
848 eps, err := wordpress.Endpoints()
849 c.Assert(err, IsNil)
850- c.Assert(eps, DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, urlEP})
851+ c.Assert(eps, DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP})
852 }
853
854 func (s *ServiceSuite) TestServiceRefresh(c *C) {
855
856=== modified file 'state/unit.go'
857--- state/unit.go 2013-08-06 17:08:26 +0000
858+++ state/unit.go 2013-08-08 09:25:42 +0000
859@@ -442,6 +442,12 @@
860 return "", false
861 }
862
863+// PrincipalName returns the name of the unit's principal.
864+// If the unit is not a subordinate, false is returned.
865+func (u *Unit) PrincipalName() (string, bool) {
866+ return u.doc.Principal, u.doc.Principal != ""
867+}
868+
869 // PublicAddress returns the public address of the unit and whether it is valid.
870 func (u *Unit) PublicAddress() (string, bool) {
871 return u.doc.PublicAddress, u.doc.PublicAddress != ""
872
873=== modified file 'state/unit_test.go'
874--- state/unit_test.go 2013-07-31 14:19:29 +0000
875+++ state/unit_test.go 2013-08-08 09:25:42 +0000
876@@ -886,6 +886,36 @@
877 c.Assert(err, IsNil)
878 }
879
880+func (s *UnitSuite) TestPrincipalName(c *C) {
881+ subCharm := s.AddTestingCharm(c, "logging")
882+ _, err := s.State.AddService("logging", subCharm)
883+ c.Assert(err, IsNil)
884+ eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})
885+ c.Assert(err, IsNil)
886+ rel, err := s.State.AddRelation(eps...)
887+ c.Assert(err, IsNil)
888+ ru, err := rel.Unit(s.unit)
889+ c.Assert(err, IsNil)
890+ err = ru.EnterScope(nil)
891+ c.Assert(err, IsNil)
892+
893+ err = s.unit.Refresh()
894+ c.Assert(err, IsNil)
895+ subordinates := s.unit.SubordinateNames()
896+ c.Assert(subordinates, DeepEquals, []string{"logging/0"})
897+
898+ su, err := s.State.Unit("logging/0")
899+ c.Assert(err, IsNil)
900+ principal, valid := su.PrincipalName()
901+ c.Assert(valid, Equals, true)
902+ c.Assert(principal, Equals, s.unit.Name())
903+
904+ // Calling PrincipalName on a principal unit yields "", false.
905+ principal, valid = s.unit.PrincipalName()
906+ c.Assert(valid, Equals, false)
907+ c.Assert(principal, Equals, "")
908+}
909+
910 func (s *UnitSuite) TestRemove(c *C) {
911 err := s.unit.Remove()
912 c.Assert(err, ErrorMatches, `cannot remove unit "wordpress/0": unit is not dead`)
913
914=== added directory 'testing/repo/series/monitoring'
915=== added directory 'testing/repo/series/monitoring/hooks'
916=== added file 'testing/repo/series/monitoring/metadata.yaml'
917--- testing/repo/series/monitoring/metadata.yaml 1970-01-01 00:00:00 +0000
918+++ testing/repo/series/monitoring/metadata.yaml 2013-08-08 09:25:42 +0000
919@@ -0,0 +1,16 @@
920+name: monitoring
921+summary: "Subordinate monitoring test charm"
922+description: |
923+ This is a longer description which
924+ potentially contains multiple lines.
925+subordinate: true
926+provides:
927+ monitoring-client:
928+ interface: monitoring
929+requires:
930+ monitoring-port:
931+ interface: monitoring
932+ scope: container
933+ info:
934+ interface: juju-info
935+ scope: container
936
937=== modified file 'testing/repo/series/wordpress/metadata.yaml'
938--- testing/repo/series/wordpress/metadata.yaml 2012-10-30 16:14:26 +0000
939+++ testing/repo/series/wordpress/metadata.yaml 2013-08-08 09:25:42 +0000
940@@ -9,6 +9,9 @@
941 logging-dir:
942 interface: logging
943 scope: container
944+ monitoring-port:
945+ interface: monitoring
946+ scope: container
947 requires:
948 db:
949 interface: mysql

Subscribers

People subscribed via source and target branches

to status/vote changes: