thumper | hi davecheney | 00:17 |
---|---|---|
davecheney | thumper: ack | 00:17 |
thumper | davecheney: unsurprisingly I suppose my branch has conflicts with trunk | 00:17 |
thumper | just fixing now | 00:17 |
thumper | then it may be worth proposing | 00:17 |
davecheney | kk | 00:17 |
davecheney | +1 | 00:18 |
* thumper sighs | 00:18 | |
thumper | go get lbox thingy still running | 00:18 |
davecheney | you may find those improvements also apply to jujuc, the hooks | 00:18 |
thumper | I'm so going to prepare some slides for the sprint about this | 00:18 |
* thumper nods | 00:18 | |
davecheney | thumper: GTFO, you're going to prepare an agenda | 00:18 |
davecheney | that is rocket surgery shit | 00:18 |
thumper | it is the merging of jujuc and jujud that conflicts mostly | 00:18 |
davecheney | yeah, that doesn't surprise me | 00:19 |
thumper | davecheney: we fly by the seat of our pants? | 00:19 |
* davecheney has no comment | 00:19 | |
davecheney | thumper: also got word by the grape vine, book your tickets to ATL | 00:19 |
thumper | ack | 00:19 |
thumper | waiting for them to get back to me about flights | 00:20 |
davecheney | mine were much cheeper than I thought | 00:20 |
davecheney | 1200 EUR | 00:20 |
davecheney | I was expecting to have my pants pulled down | 00:20 |
thumper | :) | 00:20 |
davecheney | no word on where we are staying | 00:20 |
davecheney | but _that_ is par for the course | 00:20 |
thumper | mine are around 1800 - 1900 | 00:21 |
davecheney | flying delta, which is unfortunate | 00:21 |
thumper | DL is delta? | 00:23 |
davecheney | yup | 00:23 |
thumper | me too, at least for one part | 00:23 |
davecheney | DL16/17 | 00:23 |
thumper | I'm going to be arriving late saturday night | 00:23 |
thumper | that was one of my options | 00:23 |
thumper | but nope | 00:24 |
thumper | it's funny | 00:24 |
thumper | but if I go "go test ./..." | 00:24 |
thumper | go tells me to run "go test -i ./..." to make stuff go faster | 00:24 |
thumper | but when I try that | 00:24 |
thumper | it barfs | 00:24 |
davecheney | paste ? | 00:25 |
thumper | it is way off the screen | 00:25 |
thumper | something about there being no files there | 00:25 |
thumper | davecheney: so... how does lbox propose work then? | 00:28 |
davecheney | it creates the merge proposal on LP, the makes a call to rietveld and sends the diff there | 00:29 |
davecheney | I don't really know how it works under the hood apart from automating a few bits and duplicate the code under review | 00:29 |
thumper | davecheney: here is a work in progress proposal I created already https://code.launchpad.net/~thumper/juju-core/command-set-flags/+merge/149183 | 00:31 |
thumper | (using normal LP tools) | 00:31 |
thumper | mainly so I could get a good look at the diff | 00:32 |
davecheney | thumper: understood, a diff -u is the most useful thing for me | 00:32 |
thumper | also, it seems that lbox gets the description and commit message all bungled up | 00:33 |
thumper | for me, the description should describe the what and why you did something | 00:33 |
thumper | the commit message is normally much more terse | 00:33 |
thumper | ok, lets manage the hoop jumping then... | 00:37 |
thumper | I can delete that merge proposal now I know it looks ok | 00:37 |
thumper | and 'lbox propose'... what other bits to I need there? | 00:37 |
davecheney | thumper: make sure you do a go fmt ./... before proposing | 00:39 |
davecheney | or people will freak out | 00:39 |
thumper | davecheney: I have go fmt as a pre-save hook in emacs | 00:39 |
davecheney | noice | 00:39 |
davecheney | if you like you can run the .lbox.check precommit hook | 00:39 |
davecheney | it might work | 00:39 |
davecheney | probably needs environment vars from BZR | 00:39 |
thumper | work for what? | 00:40 |
davecheney | that is the checks that lbox runs | 00:42 |
thumper | it seems that lbox tries to do too much | 00:44 |
thumper | I already push my work | 00:44 |
thumper | with a default location as defined by ~/.bazaar/locations.conf | 00:44 |
thumper | so I just go "bzr push" | 00:44 |
thumper | on a new branch, and it just works™ | 00:44 |
* thumper tries to understand lbox | 00:47 | |
davecheney | thumper: i wrote the README and CONTRIBUTINGs | 00:48 |
davecheney | please let me know what is missing or unclear | 00:48 |
thumper | davecheney: how does lbox infer the branch source? | 00:49 |
thumper | I don't like blindly running things I don't understand | 00:49 |
davecheney | it probably assumes cobzr was used | 00:49 |
davecheney | thumper: when you say source, do you mean -for=lp:juju-core | 00:50 |
thumper | davecheney: that is what I'm checking, because I don't do that | 00:51 |
thumper | it seems to run 'bzr info' in a shell, and looks for push location | 00:51 |
thumper | which will work for me | 00:51 |
thumper | gah, parent is going to be an issue though... | 00:52 |
thumper | unless I use -for | 00:52 |
thumper | well... lets see what happens | 00:53 |
* thumper goes for it | 00:53 | |
* davecheney checks. water still wet, world, still turning | 00:59 | |
thumper | it seemed to infer the correct branches | 00:59 |
thumper | so that is good | 00:59 |
thumper | edited a nice description (or at least I thought so) | 00:59 |
thumper | davecheney: why is this command wanting me to type in my google credentials? | 01:00 |
thumper | and what does it do with them? | 01:00 |
thumper | how can I be sure it isn't being stupid with my password? | 01:01 |
davecheney | because rietveld is a google app engine product | 01:02 |
davecheney | it is probably using some form of oauth | 01:02 |
thumper | I don't feel comfortable typing that into a terminal there | 01:03 |
thumper | :(( | 01:03 |
davecheney | thumper: then I thikn you need to stop and escalate at this point | 01:04 |
davecheney | or make a throw away google account | 01:04 |
thumper | I did type in the pwd | 01:04 |
thumper | but wasn't happy doing it | 01:05 |
thumper | will bitch more later | 01:05 |
* thumper is saving it all up | 01:05 | |
davecheney | oh, if you use 2FA | 01:05 |
thumper | davecheney: https://codereview.appspot.com/7338048 | 01:05 |
davecheney | it'll probably be a complete pain in the arse | 01:05 |
davecheney | from what I hear, not even googlers use rietveld with their 2FA accounts | 01:05 |
thumper | davecheney: why does the appspot thing say (do not edit description out of merge proposal) | 01:07 |
thumper | ? | 01:07 |
davecheney | because it's a one way transfer, LP -> Rietveld | 01:08 |
davecheney | the MP is on the reciepient list, so as you make comments in rietveld, they get cc'ed to the MP | 01:09 |
thumper | davecheney: how the hell does this tool work? | 01:09 |
davecheney | thumper: lots of rest calls | 01:09 |
davecheney | go get -v launchpad.net/lbox | 01:09 |
davecheney | ^ get source | 01:10 |
thumper | I have it | 01:10 |
davecheney | ok | 01:10 |
thumper | how do I see the whole diff through rietveld? | 01:10 |
davecheney | i suspect it does it client side, then rams the whole thing into rietveld | 01:10 |
davecheney | i haven't looked | 01:11 |
davecheney | maybe rietveld takes the who before and after, and doe sthe diff itself | 01:11 |
davecheney | it appears to work at a file level, not a diff level (if that matters) | 01:11 |
sidnei | iirc it submits the diff to rietveld | 01:11 |
thumper | so, can you see the whole diff in one go? | 01:12 |
thumper | hi sidnei | 01:12 |
thumper | sidnei: long time, no talk | 01:12 |
thumper | sidnei: at least I think we have talked before | 01:12 |
sidnei | indeed | 01:12 |
thumper | good, at least I'm not that crazy yet | 01:13 |
sidnei | probably when complete-fail sinzui a couple times *wink* | 01:13 |
thumper | sidnei: what are you doing these days? | 01:14 |
sidnei | thumper: untangling ubuntuone oopses, and polishing tu^H charms. lots of fun. | 01:15 |
sidnei | we're pretty close of moving all (well, like 80% of) the frontends for u1 into juju-managed services | 01:16 |
thumper | nice | 01:16 |
thumper | davecheney: I have noticed that my tollerance for repeated code seems to be a lot less than others | 01:18 |
sidnei | thumper: you? squading away? | 01:18 |
thumper | davecheney: if something is repeated more than twice, refactor | 01:18 |
thumper | sidnei: moved from product strategy -> juju-core a few weeks back | 01:18 |
thumper | sidnei: was unity engineering manager | 01:18 |
thumper | sidnei: and other manager type things for a while | 01:18 |
thumper | back to real coding now | 01:18 |
sidnei | thumper: oh, yeah, last comment from you i recall you were enjoying your c++ i think | 01:19 |
thumper | sidnei: yeah, was hacking on unity, then managed it for a while | 01:19 |
davecheney | thumper: +1 | 01:19 |
sidnei | alright. /me goes back to sip some mate. cheers | 01:21 |
thumper | sidnei: caio | 01:22 |
thumper | davecheney: with all that proposed, I suppose I should go back to my help command :) | 01:24 |
davecheney | thumper: just reviewing now | 01:24 |
davecheney | looks pretty good too me | 01:24 |
thumper | davecheney: cool | 01:24 |
davecheney | just a few style comments | 01:24 |
thumper | how many +1's do we need? | 01:24 |
davecheney | 2 | 01:24 |
* thumper nods | 01:28 | |
davecheney | thumper: I think there is a little more refactoring that can be done for some of the simpler commands | 01:32 |
davecheney | but that should be done in a followup branch | 01:32 |
thumper | I'm sure there is | 01:32 |
thumper | but I tried to limit it for my first branch :) | 01:32 |
thumper | still came in at almost 2k lines | 01:32 |
davecheney | thumper: ignore the ping, that wasn't intended for you | 01:36 |
* thumper didn't get a ping | 01:37 | |
davecheney | via email | 01:37 |
thumper | ah | 01:37 |
davecheney | gonna keep pinging till something goes pong | 01:38 |
thumper | :) | 01:42 |
thumper | davecheney: I'm off briefly to get the daughter and move home | 01:46 |
davecheney | thumper: understood, nearly lunch time here | 01:46 |
thumper | damn you python and your acceptance of both ' and " for strings | 03:50 |
thumper | davecheney: how do I store a pointer to a function in a struct? | 04:00 |
thumper | davecheney: Also, can I stick a closure in there? | 04:01 |
* thumper thinks some more... | 04:01 | |
* thumper goes to read the language spec part again | 04:02 | |
* thumper thinks some more... | 04:04 | |
wallyworld | thumper: i think you can declare a variable of type func() | 04:06 |
thumper | wallyworld: yeah got that bit | 04:06 |
wallyworld | eg var f func() | 04:06 |
thumper | but I now want to have a bound method | 04:06 |
thumper | so object.Method | 04:06 |
thumper | perhaps I need to make a closure? | 04:07 |
thumper | that seems stupid | 04:07 |
thumper | but perhaps needed | 04:07 |
wallyworld | i think you *may* need to pass it a closure which calls that method? | 04:07 |
wallyworld | that's how i'd currently do it in my ignorance | 04:07 |
wallyworld | not knowing any better | 04:07 |
thumper | yeah... | 04:07 |
thumper | it is | 04:07 |
* thumper goes to rewrite some code | 04:08 | |
davecheney | thumper: yeah, type S struct { f func() } | 04:09 |
davecheney | where func() can be anything, so func(bool, bool, bool) int | 04:09 |
davecheney | etc | 04:09 |
thumper | davecheney: however I can't assign obj.Method to the func | 04:09 |
thumper | without a closure | 04:09 |
davecheney | no, that may be resolved by 1.1, but you currently cannot do that | 04:09 |
* thumper nods | 04:10 | |
thumper | found that out :) | 04:10 |
davecheney | passing a closure would essentially be what the compiler would do behind your back | 04:10 |
thumper | it would be nice if a closure was created automagically for you | 04:10 |
thumper | right | 04:10 |
* thumper is using magic for help topics | 04:10 | |
thumper | FSVO magic | 04:10 |
davecheney | thumper: that will probably be done for 1.1 | 04:11 |
davecheney | function types will become a two word structure, so we can stash the receiver there | 04:11 |
thumper | davecheney: obviously I'm not the only one wanting to do this then :) | 04:11 |
davecheney | https://groups.google.com/d/topic/golang-dev/x328N8hiN5k/discussion | 04:12 |
wallyworld | thumper: there's lots in Go I'm finding is lacking :-) maybe by 2.0..... | 04:12 |
davecheney | wallyworld: 2.0 is a long way off | 04:13 |
davecheney | a long, long, long way off | 04:13 |
* thumper smacks wallyworld around a bit | 04:13 | |
wallyworld | i was attemptinh humour | 04:13 |
wallyworld | ouch! | 04:13 |
wallyworld | thumper: why smack me around? | 04:13 |
thumper | wallyworld: why not? | 04:13 |
wallyworld | fair point | 04:13 |
thumper | is the easiest way to create a long string to use a bytes.Buffer? | 04:22 |
thumper | how to I get a string from that? | 04:22 |
wallyworld | buffer.String() | 04:23 |
davecheney | bytes.Buffer is both an io.Reader and an io.Writer | 04:23 |
davecheney | you can write to it with fmt.Fprintf | 04:24 |
davecheney | and copy it to stdout/stderr with io.Copy | 04:24 |
davecheney | if that is of use | 04:24 |
thumper | ta | 04:24 |
davecheney | probably a few ways to skin that cat | 04:24 |
* thumper nods | 04:34 | |
thumper | hmm... now I'm causing a panic... | 04:34 |
thumper | found it... | 04:34 |
* thumper goes swimming | 04:44 | |
rogpeppe | morning all | 07:02 |
fwereade | rogpeppe, heyhey | 07:06 |
TheMue | Morning | 07:49 |
jam | TheMue: good morning | 07:56 |
TheMue | jam: Hiya | 07:56 |
jam | TheMue, fwereade, rogpeppe, dimitern, wallyworld, mgz, w7z: https://plus.google.com/hangouts/_/3054ca9e05632bb677f4a136cc956486d9aac90f | 07:56 |
dimitern | jam: cheers | 07:56 |
TheMue | jam: Thx | 07:57 |
wallyworld | different from the invite? | 07:57 |
jam | wallyworld: should be the same | 07:57 |
jam | but often the direct link helps people | 07:57 |
rogpeppe | fwereade: ping | 08:03 |
jam | mgz: poke | 08:04 |
mgz | was just doing the hangout dance :) | 08:04 |
jam | mgz: just some chewing noises from you | 08:10 |
jam | dimitern: your non-headset mic is picking up the surrounding noise | 08:11 |
fwereade | none of us can hear anything, I think | 08:42 |
mgz | "The hangout ended because of a server error" | 08:43 |
rogpeppe | i should have mentioned that i'll be on holiday thurs and fri this week | 08:55 |
jam | dimitern: poke about some tests in environs/openstack/local_test.go | 09:49 |
rogpeppe | i'd much appreciate a review or two of the following branches, if possible: https://codereview.appspot.com/7354044/ https://codereview.appspot.com/7327050/ https://codereview.appspot.com/7314116/ | 09:55 |
mariusko | Hi | 10:16 |
mariusko | I wrote a quick blueprint about git based deployment of user applications: https://blueprints.launchpad.net/juju/+spec/juju-charm-app-git-deployment | 10:16 |
mariusko | Does anyone have any comments? | 10:16 |
dimitern | jam: sorry, just saw that | 10:24 |
jam | rogpeppe: review up on the first one | 10:25 |
rogpeppe | jam: thanks | 10:25 |
jam | dimitern: np, so TestInstancesGathering fails if you run it by itself. | 10:25 |
jam | cd environs/openstack; go test -gocheck.v -gocheck.f InstancesGathering | 10:25 |
jam | I think the issue is that it doesn't call s.BootstrapOnce() and it relied on other tests doing that step. | 10:25 |
dimitern | jam: i suspect it' because of the need to bootstrap first in the local tests | 10:25 |
dimitern | jam: oh, this could be it yeah | 10:26 |
jam | It is also the test that has failed for me a couple of times with http://past.ubuntu.com/1681193/ | 10:26 |
rogpeppe | jam: you're correct about your supposition about that CL. it's so that i could generate a not-found error for the third branch in that series. | 10:27 |
jam | dimitern: which the error message is a bit hard to parse, but it looks to be the "Server "185" alreayd | 10:27 |
jam | already belongs to group "1"" | 10:27 |
dimitern | jam: the problem is a bit more obscure i think | 10:28 |
rogpeppe | jam: stupidly, i posted those links in the wrong order - the second is actually a prereq for the first | 10:28 |
dimitern | jam: we're creating more than one environ it seems | 10:28 |
jam | rogpeppe: https://codereview.appspot.com/7327050/patch/2001/3009 I don't see other ones that return a bool. PasswordValid does, because it is answering a question, similarly AgentAlive but AgentAlive returns both a bool and an error. | 10:29 |
dimitern | jam: who's the head of our business unit? robbiew? I'm asked by the travel agent | 10:30 |
jam | dimitern: Deryck is above me, then probably Robbie above that, yes. | 10:30 |
rogpeppe | jam: examples: Unit.DeployerName, Unit.PublicAddress, Unit.PrivateAddress | 10:30 |
jam | rogpeppe: none of which are on Machine, but I'll go look | 10:31 |
jam | rogpeppe: my immediate feeling is that you lose the ability to describe what the problem is by just having a bool | 10:31 |
jam | so an err is actually better. | 10:31 |
rogpeppe | jam: the thing is that there is only one possible problem here | 10:31 |
rogpeppe | jam: and lots of the other code was testing specifically for NotFound, and then another test for other errors (in some cases buggily) which could never happen | 10:32 |
jam | rogpeppe: but instead of having the code that is checking what the problem is generating the error, you have the code that is asking for the info creating the error. | 10:32 |
rogpeppe | jam: there are only two places that create an error as a result, AFAIR | 10:32 |
rogpeppe | jam: FWIW me and fwereade have been wanting to make this change for ages | 10:33 |
jam | rogpeppe: so in the first location cmd/juju/ssh.go it seems to always silently ignore failures, which seems odd. | 10:34 |
jam | (I can't say it is *wrong* because I don't really know the code, but it seems strange) | 10:34 |
mariusko | See https://bugs.launchpad.net/juju/+bug/891868 for a blocker to accomplish it | 10:34 |
_mup_ | Bug #891868: juju cli api should be invokable outside of units/hooks <juju:Confirmed> < https://launchpad.net/bugs/891868 > | 10:34 |
rogpeppe | jam: it waits for the instance id to become available | 10:35 |
rogpeppe | jam: actually though, i think that code *is* probably wrong (and was wrong before) | 10:35 |
rogpeppe | jam: because InstanceId only looks at the local machine state, which isn't changing in that loop | 10:36 |
jam | rogpeppe: is Changes() a channel? Or is it just a slice of changes that its found? | 10:37 |
rogpeppe | jam: it's a channel | 10:37 |
jam | (Is that loop "while things are still changing, handle the next change"? | 10:38 |
rogpeppe | jam: all watchers return a channel from the Changes method | 10:38 |
rogpeppe | jam: it should be fixed, but it's unlikely to be a problem in practice as no-one wants to ssh to a machine to which no units have ever been assigned. | 10:38 |
jam | rogpeppe: so you do explicitly trade one place for 2 places having the same formatting string of "instance id for machine %v not found" | 10:39 |
jam | in https://codereview.appspot.com/7327050/patch/2001/3013 | 10:39 |
jam | but the rest do all treat the error as essentially a boolean | 10:39 |
rogpeppe | jam: yeah, as suggested by dave cheney, i'll change it to return state.NotFoundf(...) | 10:39 |
rogpeppe | jam: (i can't do it in this CL because it's dependent on this one) | 10:40 |
jam | rogpeppe: you still have to have 2 copies of the error string, and make sure you don't mess up the formatting, and deal with updating both if you decide to change the message to users, etc. | 10:40 |
jam | rogpeppe: anyway, I won't block it, but it seems like you're pushing the "this is an error" up a level, when really it was at the correct level to start with. | 10:41 |
rogpeppe | jam: it's not really an error. it's more an indicator of availability. | 10:41 |
rogpeppe | jam: an error return says to me "anything can happen here" | 10:41 |
rogpeppe | jam: but that's not the case here | 10:41 |
rogpeppe | jam: to be honest, i'd be happy if it just returned a blank string when the instance id isn't assigned | 10:42 |
jam | rogpeppe: when you pointed me at the other examples where 'bool' is used, it did seem to make a lot more sense to just return the empty string, rather than having the callers check that the bool really does say the empty string is invalid. | 10:43 |
jam | (bool wasn't adding any information that the caller didn't already know) | 10:44 |
jam | unless there are other situations where the value could be invalid, that we just hadn't codified yet. | 10:44 |
rogpeppe | jam: yeah. niemeyer didn't like it though, so we added the extra return value. | 10:44 |
jam | rogpeppe: did you understand his rationale at the time? | 10:45 |
rogpeppe | jam: not really :-) | 10:45 |
rogpeppe | jam: but i come from a place where it's ok to use -1 to signal an error :) | 10:45 |
jam | rogpeppe: well that all depends if -1 is in the valid set :) | 10:47 |
rogpeppe | jam: indeed. assume it's not. | 10:48 |
jam | dimitern: so any idea about how we would still have a Server attached to a group at the time the test is starting up? | 10:53 |
dimitern | jam: not at the moment, have to think about it a bit - it's been a while | 10:54 |
dimitern | jam: | 11:03 |
jam | ? | 11:03 |
dimitern | oops :) I found it | 11:03 |
dimitern | so indeed, bootstrapping once before is enough | 11:03 |
dimitern | but not for the other tests | 11:04 |
dimitern | I'll propose a patch that fixes that now | 11:05 |
dimitern | jam: PTAL https://codereview.appspot.com/7303101 | 11:11 |
jam | dimitern: right, I have that change in the queue, but I was trying to find the isolation stuff. But I'll go ahead and approve it. | 11:12 |
dimitern | jam: the issue was a clash between the test bootstrap fails without public ip and test instance gathering | 11:12 |
jam | dimitern: LGTM trivial, though I don't think you want the extra whitespace. | 11:12 |
dimitern | jam: ok, i'll remove it | 11:13 |
dimitern | I wish bzr was more clever wrt command aliases, like hg | 11:15 |
jam | dimitern: in what sense? | 11:15 |
dimitern | i'm used to have things like "pul" or "sw", etc. | 11:15 |
dimitern | unambiguous prefixes | 11:15 |
jam | dimitern: you can 'bzr alias' them, but it does have a few (bzr di, bzr st, etc) | 11:15 |
dimitern | yeah, st and ci i use all the time - didn't even check - it just worked | 11:16 |
dimitern | ok cool, I'll set some aliases myself then :) | 11:16 |
dimitern | jam: are there global bazaar.conf, used by /usr/bin/bzr ? in addition to ~/.bazaar/bazaar.conf i mean | 11:23 |
jam | dimitern: no | 11:23 |
jam | no /etc conf | 11:23 |
dimitern | hmm cobzr doesn't play nicely with bzr aliases it seems | 11:23 |
dimitern | jam: standup? | 11:32 |
jam | dimitern: ah, I think cobzr re-implements the front-end and has to re-implement all the commands. | 11:34 |
rogpeppe | small branch to change the api to be able to log messages: https://codereview.appspot.com/7358045 | 11:51 |
jam | mgz: http://paste.ubuntu.com/1681193/ | 11:53 |
TheMue | rogpeppe: You've got a review. | 11:57 |
dimitern | rogpeppe: another review | 11:58 |
rogpeppe | TheMue, dimitern: thanks | 11:58 |
rogpeppe | TheMue: i started by using "read" and "write" but actually the -> and <- stand out much better in the log. | 11:59 |
dimitern | TheMue: you've got a review too | 12:06 |
TheMue | rogpeppe: ok, can live with it. ;) | 12:06 |
TheMue | dimitern: Great, thx. | 12:06 |
TheMue | dimitern: The storage test is so far a copy of the according part of environ/jujutest. | 12:10 |
TheMue | dimitern: It is intended to be later removed, when everything is integrated. | 12:10 |
dimitern | TheMue: yeah, i was wondering why copy that instead of just use it? | 12:10 |
TheMue | dimitern: This way I had a chance to check the behavior before the rest is done. | 12:11 |
dimitern | TheMue: ah, ok | 12:11 |
TheMue | dimitern: So I'll remove it later. ;) | 12:11 |
dimitern | TheMue: cool, so comment about it please, that there'll be a follow-up | 12:11 |
TheMue | dimitern: Detected a not expected behavior that way, when listing files. | 12:12 |
dimitern | TheMue: with ordering? | 12:12 |
TheMue | dimitern: Will do it, when I cover the rest of your hints. | 12:12 |
TheMue | dimitern: No, the handling of directories when the passed pattern matches. | 12:13 |
dimitern | TheMue: i see | 12:13 |
rogpeppe | lunch | 12:41 |
rogpeppe | looks like the "juju set" tests are crack *sigh* | 14:30 |
=== wedgwood_away is now known as wedgwood | ||
=== TheRealMue is now known as TheMue | ||
rogpeppe | as is juju get *deeper sigh* | 14:56 |
rogpeppe | https://bugs.launchpad.net/juju-core/+bug/1130149 | 15:02 |
_mup_ | Bug #1130149: juju get does not print default values correctly <juju-core:New> < https://launchpad.net/bugs/1130149 > | 15:02 |
TheMue | rogpeppe: Hangout? | 15:04 |
rogpeppe | TheMue: ah thanks | 15:05 |
benji | Hi all. I am trying to bootstrap using EC2 and it apparently works (the instance shows up in the EC2 console) but when I run "juju status" | 15:17 |
benji | ... I get this error: "panic: runtime error: invalid memory address or nil pointer dereference" plus a lot more. (I'm using go juju.) | 15:18 |
bac | and i am seeing the same using r908 of juju-core | 15:19 |
rogpeppe | benji: was that with bootstrap --upload-tools ? | 15:20 |
benji | rogpeppe: yep | 15:20 |
rogpeppe | benji: hmm, can you paste the "lot more", please? | 15:21 |
benji | heh, sure | 15:21 |
rogpeppe | benji: (i haven't ever seen that problem, FWIW) | 15:22 |
benji | rogpeppe: http://paste.ubuntu.com/1682852/ | 15:22 |
benji | rogpeppe: another data point: the api server doesn't seem to have started on the EC2 instance because if I point a websocket client at it I get connection refused | 15:23 |
rogpeppe | benji: i'll see if i can replicate the problem | 15:24 |
rogpeppe | benji: what do you see if you do juju status --debug ? | 15:36 |
rogpeppe | benji: (i think it's likely that the original bootstrap failed for some reason, and the dial has timed out after 10 minutes, and there's a bug which means that error isn't checked so ends up panicing) | 15:37 |
rogpeppe | benji: in fact, it might be useful to see what went on on the bootstrap machine. | 15:38 |
benji | rogpeppe: it looks that way | 15:38 |
benji | lots and lots of "2013/02/19 15:37:05 JUJU state: connection failed: dial tcp 50.19.23.159:37017: connection refused" | 15:39 |
rogpeppe | benji: you can do that by ssh'ing to it and catting the log file | 15:39 |
rogpeppe | benji: the log file in question being /var/log/cloud-init-output.log | 15:39 |
rogpeppe | benji: i use the PEM file from $HOME/.ec2 to do the ssh - presumably you have something similar | 15:40 |
benji | rogpeppe: let me try that... | 15:40 |
rogpeppe | benji: my "ec2ssh" script is something like this: ssh -i $HOME/.ec2/rog.pem ubuntu@$1 | 15:40 |
rogpeppe | benji: and you can find the dns name from the aws console | 15:41 |
benji | rogpeppe: no .pem files there for me; is that something I need to get from EC2? | 15:41 |
rogpeppe | benji: hmm, i can't remember at all! | 15:42 |
rogpeppe | benji: ah, i think i probably added the key to my aws account | 15:43 |
rogpeppe | benji: yeah, here: https://portal.aws.amazon.com/gp/aws/securityCredentials#access_credentials | 15:44 |
rogpeppe | benji: that might mean you can't get ssh access | 15:44 |
rogpeppe | benji: although... you could just try ssh'ing in and see what happens | 15:45 |
benji | rogpeppe: I tried, it refuses me | 15:45 |
rogpeppe | benji: as ubuntu@ ? | 15:45 |
benji | rogpeppe: oh! nope, let me try that | 15:45 |
benji | rogpeppe: it worked! | 15:46 |
rogpeppe | benji: yay | 15:46 |
benji | rogpeppe: I'm on a call at the moment, but I will dig into this in a couple of minutes. | 15:47 |
rogpeppe | benji: ok, cool. i suspect it's an amazon transient failure that we should work harder to be more resilient to. | 15:47 |
rogpeppe | benji: in particular, the metadata service doesn't work reliably | 15:48 |
rogpeppe | trivial CL to review anyone? https://codereview.appspot.com/7359044 | 16:02 |
rogpeppe | benji: that fixes the panic you saw, at any rate | 16:02 |
benji | rogpeppe: /var/log/juju/machine-0.log on the bootstrap node contains lots of "2013/02/19 15:07:57 JUJU state: connection failed: dial tcp 127.0.0.1:37017: connection refused" | 16:03 |
rogpeppe | benji: what does the cloud-init log say? | 16:03 |
rogpeppe | benji: (that one has all the messages printed by the commands run when bootstrapping) | 16:05 |
benji | rogpeppe: no obvious errors and it ends with "cloud-init[DEBUG]: Ran 9 modules with 0 failures" | 16:05 |
rogpeppe | benji: hmm. could you paste the last 100 lines or so of it? | 16:05 |
rogpeppe | benji: it looks like mongodb isn't running, but i'm not sure why | 16:06 |
benji | rogpeppe: http://paste.ubuntu.com/1683016/ | 16:06 |
rogpeppe | benji: ah, sorry, wrong log | 16:06 |
rogpeppe | benji: you want cloud-init-output.log | 16:06 |
rogpeppe | benji: that's the one that juju produces | 16:07 |
benji | rogpeppe: lots of "2013/02/19 15:07:53 JUJU state: connection failed: dial tcp 127.0.0.1:37017: connection refused" and then a panic | 16:07 |
rogpeppe | benji: could you paste the 50 lines *before* it started printing "connection failed" ? | 16:07 |
benji | "ps aux | grep mongo" does not show any mongo process running | 16:08 |
benji | k | 16:08 |
rogpeppe | benji: yeah, i'm pretty sure mongo never started - probably the download failed, but i'm interested to find out why, to add to the collection of transient errors we've seen | 16:08 |
benji | rogpeppe: yep: | 16:09 |
benji | http://juju-dist.s3.amazonaws.com/tools/mongo-2.2.0-quantal-i386.tgz: | 16:09 |
benji | 2013-02-19 14:57:53 ERROR 404: Not Found. | 16:09 |
rogpeppe | benji: ah! | 16:09 |
benji | should I be using something other than quantal? | 16:09 |
rogpeppe | benji: you should be using amd64 | 16:09 |
benji | mmm | 16:09 |
rogpeppe | benji: i don't think anyone's build the mongo version we need for 386 | 16:10 |
rogpeppe | s/build/built/ | 16:10 |
benji | how do I specify that in .juju/environments.yaml? | 16:10 |
rogpeppe | benji: it's a pity that the failure mode is so darned obtuse. we'll have to think how to make that better. | 16:10 |
rogpeppe | benji: if you're using --upload-tools, you can only use your current architecture | 16:11 |
rogpeppe | benji: because that's the arch that the tools are built for | 16:11 |
benji | hmm, so step 1) upgrade my development machine | 16:11 |
rogpeppe | benji: yeah. sorry we don't have a better story here. step 1 could be "compile mongo db with ssl enabled for i386"... | 16:12 |
rogpeppe | benji: hmm, maybe i'll have a go at doing that. it might have finished by morning. | 16:13 |
benji | it would be good, but realistically it won't be smart for me to be an outlier here, I need to match as closely as possible how you guys work so we don't continually run into these kinds of issues | 16:14 |
rogpeppe | benji: hmm, maybe it's too much distraction for now, sorry. | 16:14 |
benji | no worries | 16:14 |
rogpeppe | benji: somewhat surprisingly, there wasn't an outstanding bug, so here it is: https://bugs.launchpad.net/juju-core/+bug/1130209 | 16:18 |
_mup_ | Bug #1130209: we need a mongodb compiled for i386 <juju-core:New> < https://launchpad.net/bugs/1130209 > | 16:18 |
niemeyer | A bit late, but hello all! :) | 16:22 |
rogpeppe | niemeyer: hiya | 16:23 |
rogpeppe | niemeyer: william's sick today - can i bend your ear for a sanity check, by any chance? | 16:24 |
niemeyer | rogpeppe: Yeah, I'm just busy sorting something out ATM | 16:25 |
rogpeppe | niemeyer: np | 16:25 |
niemeyer | rogpeppe: and have to had to lunch in a second | 16:25 |
niemeyer | rogpeppe: Meeting at 16, in 3h.. I think we can do something between lunch and that | 16:25 |
rogpeppe | niemeyer: give us a ping when you have a mo, thanks! | 16:25 |
rogpeppe | review anyone? https://codereview.appspot.com/7326052 | 16:49 |
dimitern | rogpeppe: i got it | 16:49 |
rogpeppe | dimitern: thanks! | 16:49 |
bac | rogpeppe: i'm using this build of mongo+ssl from julian's PPA: https://launchpad.net/~julian-edwards/+archive/mongodb | 17:09 |
rogpeppe | bac: ok. presumably that doesn't help with bootstrap, because that binary isn't in the public bucket | 17:09 |
bac | ah | 17:10 |
rogpeppe | bac: i did have the credentials once.... i'll have a look and see if i kept them. | 17:10 |
rogpeppe | bac: ha, i think i might just be able to do it | 17:11 |
dimitern | rogpeppe: reviewed | 17:12 |
rogpeppe | dimitern: tyvm | 17:12 |
dimitern | rogpeppe: ym | 17:14 |
dimitern | i'm off, good night guys | 17:14 |
rogpeppe | dimitern: g'night | 17:14 |
bac | rogpeppe: that would be great if you could | 17:25 |
rogpeppe | bac: i'm just trying to work out which of those packages has the files i'm after | 17:25 |
rogpeppe | bac: and fumbling my way through unarchiving .deb files (i've managed to avoid delving into all the ppa stuff before) | 17:26 |
niemeyer | rogpeppe: Hey, what's up? | 17:30 |
rogpeppe | niemeyer: yo! | 17:30 |
rogpeppe | niemeyer: just came across severe crackness in juju set/get and just wanted to make sure it *is* crack and that i'm going to do the right thing | 17:30 |
niemeyer | rogpeppe: Ok | 17:32 |
rogpeppe | niemeyer: currently, the code in juju set does an explicit remove when a value is set to "" | 17:33 |
rogpeppe | niemeyer: but if you set it with a yaml file, the value just gets set to "" (and probably fails as a result) | 17:33 |
rogpeppe | niemeyer: i *think* that we probably want to have the two things be equivalent | 17:33 |
niemeyer | rogpeppe: how would it fail? | 17:34 |
rogpeppe | niemeyer: it would fail, i think, because the config validation would expect an integer, but see a blank string | 17:34 |
=== deryck is now known as deryck[lunch] | ||
niemeyer | rogpeppe: Agreed, they should be the same | 17:36 |
niemeyer | rogpeppe: An empty value should mean removal | 17:36 |
rogpeppe | niemeyer: so i'm thinking that juju set service --config config-file where config-file contains {foo=""} should be the same as juju set service 'foo=' | 17:36 |
rogpeppe | niemeyer: yeah | 17:36 |
rogpeppe | niemeyer: good | 17:36 |
rogpeppe | niemeyer: the other crackful thing (i think) is that juju get never shows default values unless they've been explicitly set | 17:36 |
rogpeppe | niemeyer: it shows 'em all as null | 17:37 |
niemeyer | rogpeppe: OTOH, it seems awkward to have a setting in a configuration file | 17:38 |
niemeyer | rogpeppe: with an empty string, on an integer field | 17:38 |
* rogpeppe swithers | 17:38 | |
rogpeppe | niemeyer: perhaps we shouldn't allow settings to be reset to their default from a conf file without explicitly setting them to their default values. | 17:39 |
rogpeppe | niemeyer: i dunno | 17:40 |
rogpeppe | niemeyer: i like the simplicity of having the yaml exactly equivalent to the command line | 17:40 |
niemeyer | rogpeppe: I actually think failing in that case is fine | 17:40 |
rogpeppe | niemeyer: and i did quick probe into #juju and found general agreement. | 17:41 |
niemeyer | rogpeppe: Actually.. hmm | 17:41 |
niemeyer | rogpeppe: There is no integer type.. | 17:41 |
niemeyer | rogpeppe: It can't fail | 17:41 |
niemeyer | rogpeppe: Oh, nevermind | 17:41 |
niemeyer | rogpeppe: I was thinking of relation-set | 17:41 |
rogpeppe | niemeyer: ah yes | 17:41 |
niemeyer | rogpeppe: Yeah | 17:41 |
niemeyer | rogpeppe: I'd prefer to see "" checked, and null meaning remove | 17:42 |
niemeyer | rogpeppe: They are inherently different formats.. we can't make them match precisely | 17:43 |
rogpeppe | niemeyer: the issue was brought home to me particular because the API provides access to yaml-format setting (to allow GUI clients to type in yaml settings and avoid js yaml parsing), and the difference seemed odd. | 17:43 |
rogpeppe | niemeyer: random opinion from #juju: | 17:44 |
rogpeppe | [12:24:03] <Daviey> null = None = "" = nonexistent IMO :_ | 17:44 |
rogpeppe | niemeyer: hmm, you're probably right about null being the right thing in yaml. but that means the yaml is strictly more expressive than setting string options (it can make a blank string explictly override a default), and i'm not sure that's right either. | 17:46 |
niemeyer | rogpeppe: Random opinions without reasoning are hard to evaluate | 17:47 |
niemeyer | rogpeppe: yaml *is* more expressive | 17:47 |
rogpeppe | niemeyer: here's the context: http://paste.ubuntu.com/1683371/ | 17:47 |
rogpeppe | niemeyer: yeah, but do we want it to be in this case? | 17:48 |
rogpeppe | niemeyer: thanks for the input anyway, it's very helpful | 17:53 |
niemeyer | rogpeppe: np | 17:56 |
=== wedgwood is now known as wedgwood_away | ||
=== wedgwood_away is now known as wedgwood | ||
rogpeppe | i'm off for the day now, have a good r.o.d. all | 18:45 |
rogpeppe | a new CL in case anyone fancies it: https://codereview.appspot.com/7305101 (and its prereq) | 18:45 |
rogpeppe | fwereade: hope you're feeling a bit better | 18:45 |
rogpeppe | fwereade: see ya tomorrow if so :-) | 18:46 |
fwereade | rogpeppe, somewhat iffy tbh | 18:46 |
rogpeppe | fwereade: well, don't push it | 18:46 |
rogpeppe | fwereade: i've got a fair few reviews out, nothing too taxing, if you want something not too hard to do :-) | 18:47 |
fwereade | rogpeppe, don't count on it, I'm afraid, I'm only on for a call | 18:47 |
rogpeppe | fwereade: np | 18:47 |
rogpeppe | fwereade: see ya when i see ya :-) | 18:48 |
fwereade | rogpeppe, cheers, enjoy your evening | 18:48 |
=== deryck[lunch] is now known as deryck | ||
thumper | morning | 20:37 |
bac | hi thumper | 20:55 |
thumper | hi bac | 20:55 |
thumper | bac: I see that I'll see you in Atlanta | 20:56 |
thumper | and more ex-launchpadders | 20:56 |
thumper | I'm looking forward to it | 20:56 |
bac | thumper: oh, good. hadn't seen you on the list | 20:57 |
thumper | bac: just added myself | 20:57 |
thumper | bac: and my OCD took over | 20:57 |
thumper | bac: I had to put the attendees in alphabetical order | 20:57 |
thumper | it drove me spare | 20:57 |
bac | oh, i did that yesterday | 20:57 |
bac | :) | 20:57 |
thumper | it was partially sorted | 20:57 |
bac | yes, the top is where i left off | 20:57 |
thumper | but people were obviously just appending to the end | 20:57 |
bac | or CDO as jeff-blue-hair-guy said | 20:58 |
* bac is sure that made no sense to anyone | 20:59 | |
bac | i've just brought up a new VM and am trying to get juju-core installed and working again. i get this when trying to install: | 20:59 |
bac | bac@quantal64:~$ go install -v launchpad.net/juju-core/... | 21:00 |
bac | work/src/launchpad.net/juju-core/state/api/apiserver.go:4:2: import "code.google.com/p/go.net/websocket": cannot find package | 21:00 |
bac | that appears to be the right way to import websocket, and i'm working with a fresh copy of trunk... | 21:00 |
bac | fwereade: any ideas about ^^^ ? | 21:01 |
fwereade | bac, sorry, that is new to me | 21:02 |
* fwereade pokes around | 21:02 | |
fwereade | bac, yeah, happen for me as well when I move the installed one out of the way | 21:03 |
bac | fwereade: this is a fresh vm. did i forget to grab something? | 21:04 |
fwereade | bac, hey, wait, did you go get it? | 21:05 |
fwereade | bac, if you go getted juju-core it would have done it I think | 21:06 |
bac | fwereade: i did | 21:06 |
bac | i go got it and then i tried to go install it | 21:06 |
bac | fwereade: i assume the imports from google get installed locally somewhere. should they be in my GOPATH too? | 21:07 |
fwereade | bac, yeah, they should have been | 21:07 |
bac | yeah i have GOPATH/src/code.google.com/p but it is empty | 21:08 |
bac | hmm | 21:08 |
bac | fwereade: i think i found it. i didn't install hg... | 21:09 |
fwereade | bac, ah! | 21:09 |
bac | or git | 21:09 |
fwereade | bac, it's a bit crap that they don't warn clearly about that though | 21:09 |
bac | that's what i get for working from memory | 21:09 |
fwereade | bac, that should surely be an error on go get | 21:10 |
bac | yeah | 21:10 |
* thumper was getting confused by the review dates shown | 21:13 | |
thumper | as the review comments were from before it was sumbitted | 21:13 |
thumper | or submitted even | 21:13 |
* thumper is pleased he ran the tests first... | 21:45 | |
thumper | fwereade: to update the diff in the special review tool, to I have to lbox propose again? | 21:46 |
thumper | normally I just push | 21:46 |
* thumper takes a stab that unary negation is ! | 21:46 | |
fwereade | thumper, yeah, lbox propose again -- it will take the opportunity to publish all your review responses at the same time | 21:47 |
thumper | I already did that | 21:47 |
fwereade | thumper, sorry, is it not working? | 21:48 |
thumper | fwereade: style question | 21:48 |
thumper | fwereade: is_super_command or isSuperCommand (for a variable) | 21:48 |
thumper | fwereade: what I meant was that I had already published my review responses | 21:48 |
thumper | and then I ran the tests before committing | 21:48 |
fwereade | thumper, underscores don't seem to be popular anywhere really except maybe ALL_CAPS_CONSTANTS | 21:49 |
thumper | and found I had a backwards bool expression | 21:49 |
* thumper has been doing python forever | 21:49 | |
thumper | and they are PEP8 | 21:49 |
thumper | I notice most local variables are not multiple words though | 21:49 |
thumper | I prefer to show intent | 21:49 |
thumper | to me, isSuperCommand is harder to read than is_super_command | 21:50 |
thumper | but that's just me | 21:50 |
davecheney | thumper: yeah, go style is camelCase | 21:50 |
thumper | or issupercommand | 21:50 |
davecheney | it is what it is | 21:50 |
* thumper understands | 21:50 | |
* thumper kicks the camel | 21:50 | |
davecheney | it sort of falls out of the export rules | 21:50 |
thumper | why? | 21:51 |
thumper | that's fine for functions | 21:51 |
thumper | but what about variables, why the same? | 21:51 |
davecheney | the export rule applies to all symbols | 21:52 |
fwereade | thumper, I do know where you're coming from but I guess I was lucky -- I found it relatively easy to dust off the camelCaseReading part of my brain | 21:52 |
thumper | I can get used to anything | 21:52 |
thumper | I was just wondering on the rationale | 21:52 |
* fwereade had never thought of it that way but it makes sense, thanks davecheney | 21:52 | |
thumper | fwereade: I find it hard to work out what the state of the review is... | 21:54 |
thumper | fwereade: https://codereview.appspot.com/7338048/ | 21:54 |
thumper | fwereade: had two people look | 21:54 |
thumper | I've replied to the comments, making changes | 21:54 |
thumper | one bit was a non-trivial change | 21:54 |
thumper | what's the situation now? | 21:55 |
thumper | also, if I set the commit message on the merge proposal, will lbox merge use it? | 21:55 |
fwereade | thumper, you've replied to the stuff in patch set 1 and it's now waiting for further review if required | 21:55 |
thumper | fwereade: but how do I know when it's all good? | 21:56 |
thumper | There were two lots of comments on rietveld, but only one on the merge proposal | 21:56 |
thumper | since I tend to drive from email, it is confusing | 21:56 |
fwereade | thumper, you have already got two LGTMs, indicating that the reviewers trust your taste/judgment enough to handle the suggestions appropriately and land it | 21:56 |
fwereade | thumper, yeah, launchpad rather fades into the background with lbox | 21:57 |
thumper | wow, trust on the first merge proposal | 21:57 |
thumper | that is something new to me :) | 21:57 |
davecheney | thumper: try something more audatious next time | 21:59 |
thumper | davecheney: heh | 22:00 |
thumper | hmm... | 22:03 |
thumper | just merged trunk | 22:03 |
thumper | and getting failures with openstack environ tests | 22:03 |
thumper | local_test.go line 271 | 22:03 |
thumper | I may hit timing issues differently to others | 22:04 |
thumper | I have a fast newish i7 with a fast SSD | 22:04 |
thumper | I hit issues booting the damn machine | 22:04 |
thumper | hitting a race condition in X initialisation | 22:04 |
thumper | the test seems to pass the second time through | 22:05 |
thumper | fwereade: so lbox merge is the way to go? | 22:07 |
* thumper takes a stab | 22:08 | |
thumper | the answer to the "does it use the commit message" is no, no it doesn't | 22:09 |
fwereade | thumper, sorry, lbox submit | 22:14 |
thumper | fwereade: I've dont it already :) | 22:14 |
thumper | and it says done. | 22:14 |
thumper | so I guess it is done | 22:14 |
* thumper looks at the trunk branch on lp | 22:14 | |
thumper | yep | 22:15 |
thumper | \o/ | 22:15 |
thumper | first commit into trunk | 22:15 |
* thumper does a little dance | 22:15 | |
* fwereade cheers at thumper | 22:16 | |
fwereade | thumper, I thought it was a really nice change btw, thank you | 22:16 |
thumper | fwereade: np, I have the help command lined up next | 22:16 |
fwereade | thumper, it seems to have the log message from the description? | 22:16 |
thumper | it is coming along really nicely | 22:16 |
fwereade | thumper, excellent | 22:16 |
* davecheney loves how 'software updater' doesn't actually update softeware, just segfaults | 22:17 | |
thumper | fwereade: I overrode the message that the 'lbox submit' popped up with what I had typed into the commit message | 22:19 |
thumper | that is why it looks the same | 22:19 |
thumper | davecheney: for the Assert methods... | 22:34 |
thumper | is there an AssertTrue and AssertFalse? | 22:34 |
thumper | and does Assert(value, IsNotNil) work? | 22:34 |
davecheney | thumper: no, there is no AssertTrue or AssertFalse in gocheck | 22:36 |
thumper | davecheney: so Assert(value, Equals, true) ? | 22:37 |
davecheney | thumper: that is correct | 22:37 |
davecheney | also | 22:37 |
davecheney | c.Assert(err, NotNil) | 22:37 |
thumper | so, can we add it? | 22:37 |
thumper | ta | 22:37 |
davecheney | there is also the more verbose | 22:37 |
davecheney | c.Assert(err, Not(Equal), nil) | 22:37 |
thumper | have you seen the matchers concept? | 22:37 |
davecheney | whihc problably won't work first time | 22:37 |
davecheney | i just guessed | 22:37 |
thumper | this is used by testtools (the python module) | 22:38 |
thumper | so... | 22:38 |
davecheney | can we add it | 22:38 |
davecheney | the project is owned by niemeyer on his private brnch | 22:38 |
thumper | <python> assertThat(some_value, Equals(10)) | 22:38 |
davecheney | i have a few patches waiting in the queue to land there that fix some data races | 22:38 |
thumper | assertThat(some_value, IsNil()) | 22:38 |
thumper | etc | 22:38 |
thumper | I always found the matchers a really nice way to write readable tests | 22:39 |
bigjools | matchers are awesome | 22:44 |
bigjools | porting testtools to Go would make Go a lot nicer to use | 22:44 |
thumper | agreed | 22:45 |
niemeyer | davecheney: Still on my queue, and shouldn't take long now. Sorry for the delay. | 22:57 |
niemeyer | NotNil exists, btw | 22:58 |
niemeyer | Ah, you said so | 22:58 |
niemeyer | AssertTrue vs. Equals, true is pretty much the same.. we don't need aliases to that, IMO | 22:59 |
Generated by irclog2html.py 2.7 by Marius Gedminas - find it at mg.pov.lt!