Merge lp:~gary/charms/precise/juju-gui/update into lp:charms/juju-gui

Proposed by Gary Poster
Status: Merged
Merged at revision: 67
Proposed branch: lp:~gary/charms/precise/juju-gui/update
Merge into: lp:charms/juju-gui
Diff against target: 2940 lines (+2185/-234)
31 files modified
.bzrignore (+1/-0)
.lbox (+1/-0)
.lbox.check (+3/-0)
COPYING (+661/-0)
Dependencies.md (+9/-2)
HACKING.md (+70/-82)
Makefile (+63/-0)
README.md (+10/-1)
config.yaml (+44/-1)
config/config.js.template (+4/-1)
copyright (+3/-3)
hooks/backend.py (+21/-1)
hooks/bootstrap_utils.py (+16/-0)
hooks/config-changed (+15/-2)
hooks/install (+16/-0)
hooks/start (+16/-0)
hooks/stop (+16/-0)
hooks/utils.py (+40/-9)
hooks/web-relation-joined (+16/-0)
metadata.yaml (+16/-0)
revision (+1/-1)
tests/00-setup (+36/-0)
tests/10-unit.test (+21/-3)
tests/20-functional.test (+61/-99)
tests/deploy.py (+80/-0)
tests/helpers.py (+185/-0)
tests/requirements.pip (+28/-0)
tests/test_backends.py (+24/-6)
tests/test_deploy.py (+196/-0)
tests/test_helpers.py (+397/-0)
tests/test_utils.py (+115/-23)
To merge this branch: bzr merge lp:~gary/charms/precise/juju-gui/update
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+175407@code.launchpad.net

Commit message

Merge work from ~juju-gui. This adds support for a number of GUI options, license file changes, and new test infrastructure.

Description of the change

Merge work from ~juju-gui. This adds support for a number of GUI options, license file changes, and new test infrastructure.

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2013-05-02 09:49:51 +0000
3+++ .bzrignore 2013-07-17 21:35:43 +0000
4@@ -2,3 +2,4 @@
5 tags
6 exec.d/*
7 npm-cache.tgz
8+tests/.venv
9
10=== added file '.lbox'
11--- .lbox 1970-01-01 00:00:00 +0000
12+++ .lbox 2013-07-17 21:35:43 +0000
13@@ -0,0 +1,1 @@
14+propose -cr -for lp:~juju-gui/charms/precise/juju-gui/trunk
15
16=== added file '.lbox.check'
17--- .lbox.check 1970-01-01 00:00:00 +0000
18+++ .lbox.check 2013-07-17 21:35:43 +0000
19@@ -0,0 +1,3 @@
20+#!/bin/sh
21+
22+make lint unittest
23
24=== added file 'COPYING'
25--- COPYING 1970-01-01 00:00:00 +0000
26+++ COPYING 2013-07-17 21:35:43 +0000
27@@ -0,0 +1,661 @@
28+ GNU AFFERO GENERAL PUBLIC LICENSE
29+ Version 3, 19 November 2007
30+
31+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
32+ Everyone is permitted to copy and distribute verbatim copies
33+ of this license document, but changing it is not allowed.
34+
35+ Preamble
36+
37+ The GNU Affero General Public License is a free, copyleft license for
38+software and other kinds of works, specifically designed to ensure
39+cooperation with the community in the case of network server software.
40+
41+ The licenses for most software and other practical works are designed
42+to take away your freedom to share and change the works. By contrast,
43+our General Public Licenses are intended to guarantee your freedom to
44+share and change all versions of a program--to make sure it remains free
45+software for all its users.
46+
47+ When we speak of free software, we are referring to freedom, not
48+price. Our General Public Licenses are designed to make sure that you
49+have the freedom to distribute copies of free software (and charge for
50+them if you wish), that you receive source code or can get it if you
51+want it, that you can change the software or use pieces of it in new
52+free programs, and that you know you can do these things.
53+
54+ Developers that use our General Public Licenses protect your rights
55+with two steps: (1) assert copyright on the software, and (2) offer
56+you this License which gives you legal permission to copy, distribute
57+and/or modify the software.
58+
59+ A secondary benefit of defending all users' freedom is that
60+improvements made in alternate versions of the program, if they
61+receive widespread use, become available for other developers to
62+incorporate. Many developers of free software are heartened and
63+encouraged by the resulting cooperation. However, in the case of
64+software used on network servers, this result may fail to come about.
65+The GNU General Public License permits making a modified version and
66+letting the public access it on a server without ever releasing its
67+source code to the public.
68+
69+ The GNU Affero General Public License is designed specifically to
70+ensure that, in such cases, the modified source code becomes available
71+to the community. It requires the operator of a network server to
72+provide the source code of the modified version running there to the
73+users of that server. Therefore, public use of a modified version, on
74+a publicly accessible server, gives the public access to the source
75+code of the modified version.
76+
77+ An older license, called the Affero General Public License and
78+published by Affero, was designed to accomplish similar goals. This is
79+a different license, not a version of the Affero GPL, but Affero has
80+released a new version of the Affero GPL which permits relicensing under
81+this license.
82+
83+ The precise terms and conditions for copying, distribution and
84+modification follow.
85+
86+ TERMS AND CONDITIONS
87+
88+ 0. Definitions.
89+
90+ "This License" refers to version 3 of the GNU Affero General Public License.
91+
92+ "Copyright" also means copyright-like laws that apply to other kinds of
93+works, such as semiconductor masks.
94+
95+ "The Program" refers to any copyrightable work licensed under this
96+License. Each licensee is addressed as "you". "Licensees" and
97+"recipients" may be individuals or organizations.
98+
99+ To "modify" a work means to copy from or adapt all or part of the work
100+in a fashion requiring copyright permission, other than the making of an
101+exact copy. The resulting work is called a "modified version" of the
102+earlier work or a work "based on" the earlier work.
103+
104+ A "covered work" means either the unmodified Program or a work based
105+on the Program.
106+
107+ To "propagate" a work means to do anything with it that, without
108+permission, would make you directly or secondarily liable for
109+infringement under applicable copyright law, except executing it on a
110+computer or modifying a private copy. Propagation includes copying,
111+distribution (with or without modification), making available to the
112+public, and in some countries other activities as well.
113+
114+ To "convey" a work means any kind of propagation that enables other
115+parties to make or receive copies. Mere interaction with a user through
116+a computer network, with no transfer of a copy, is not conveying.
117+
118+ An interactive user interface displays "Appropriate Legal Notices"
119+to the extent that it includes a convenient and prominently visible
120+feature that (1) displays an appropriate copyright notice, and (2)
121+tells the user that there is no warranty for the work (except to the
122+extent that warranties are provided), that licensees may convey the
123+work under this License, and how to view a copy of this License. If
124+the interface presents a list of user commands or options, such as a
125+menu, a prominent item in the list meets this criterion.
126+
127+ 1. Source Code.
128+
129+ The "source code" for a work means the preferred form of the work
130+for making modifications to it. "Object code" means any non-source
131+form of a work.
132+
133+ A "Standard Interface" means an interface that either is an official
134+standard defined by a recognized standards body, or, in the case of
135+interfaces specified for a particular programming language, one that
136+is widely used among developers working in that language.
137+
138+ The "System Libraries" of an executable work include anything, other
139+than the work as a whole, that (a) is included in the normal form of
140+packaging a Major Component, but which is not part of that Major
141+Component, and (b) serves only to enable use of the work with that
142+Major Component, or to implement a Standard Interface for which an
143+implementation is available to the public in source code form. A
144+"Major Component", in this context, means a major essential component
145+(kernel, window system, and so on) of the specific operating system
146+(if any) on which the executable work runs, or a compiler used to
147+produce the work, or an object code interpreter used to run it.
148+
149+ The "Corresponding Source" for a work in object code form means all
150+the source code needed to generate, install, and (for an executable
151+work) run the object code and to modify the work, including scripts to
152+control those activities. However, it does not include the work's
153+System Libraries, or general-purpose tools or generally available free
154+programs which are used unmodified in performing those activities but
155+which are not part of the work. For example, Corresponding Source
156+includes interface definition files associated with source files for
157+the work, and the source code for shared libraries and dynamically
158+linked subprograms that the work is specifically designed to require,
159+such as by intimate data communication or control flow between those
160+subprograms and other parts of the work.
161+
162+ The Corresponding Source need not include anything that users
163+can regenerate automatically from other parts of the Corresponding
164+Source.
165+
166+ The Corresponding Source for a work in source code form is that
167+same work.
168+
169+ 2. Basic Permissions.
170+
171+ All rights granted under this License are granted for the term of
172+copyright on the Program, and are irrevocable provided the stated
173+conditions are met. This License explicitly affirms your unlimited
174+permission to run the unmodified Program. The output from running a
175+covered work is covered by this License only if the output, given its
176+content, constitutes a covered work. This License acknowledges your
177+rights of fair use or other equivalent, as provided by copyright law.
178+
179+ You may make, run and propagate covered works that you do not
180+convey, without conditions so long as your license otherwise remains
181+in force. You may convey covered works to others for the sole purpose
182+of having them make modifications exclusively for you, or provide you
183+with facilities for running those works, provided that you comply with
184+the terms of this License in conveying all material for which you do
185+not control copyright. Those thus making or running the covered works
186+for you must do so exclusively on your behalf, under your direction
187+and control, on terms that prohibit them from making any copies of
188+your copyrighted material outside their relationship with you.
189+
190+ Conveying under any other circumstances is permitted solely under
191+the conditions stated below. Sublicensing is not allowed; section 10
192+makes it unnecessary.
193+
194+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
195+
196+ No covered work shall be deemed part of an effective technological
197+measure under any applicable law fulfilling obligations under article
198+11 of the WIPO copyright treaty adopted on 20 December 1996, or
199+similar laws prohibiting or restricting circumvention of such
200+measures.
201+
202+ When you convey a covered work, you waive any legal power to forbid
203+circumvention of technological measures to the extent such circumvention
204+is effected by exercising rights under this License with respect to
205+the covered work, and you disclaim any intention to limit operation or
206+modification of the work as a means of enforcing, against the work's
207+users, your or third parties' legal rights to forbid circumvention of
208+technological measures.
209+
210+ 4. Conveying Verbatim Copies.
211+
212+ You may convey verbatim copies of the Program's source code as you
213+receive it, in any medium, provided that you conspicuously and
214+appropriately publish on each copy an appropriate copyright notice;
215+keep intact all notices stating that this License and any
216+non-permissive terms added in accord with section 7 apply to the code;
217+keep intact all notices of the absence of any warranty; and give all
218+recipients a copy of this License along with the Program.
219+
220+ You may charge any price or no price for each copy that you convey,
221+and you may offer support or warranty protection for a fee.
222+
223+ 5. Conveying Modified Source Versions.
224+
225+ You may convey a work based on the Program, or the modifications to
226+produce it from the Program, in the form of source code under the
227+terms of section 4, provided that you also meet all of these conditions:
228+
229+ a) The work must carry prominent notices stating that you modified
230+ it, and giving a relevant date.
231+
232+ b) The work must carry prominent notices stating that it is
233+ released under this License and any conditions added under section
234+ 7. This requirement modifies the requirement in section 4 to
235+ "keep intact all notices".
236+
237+ c) You must license the entire work, as a whole, under this
238+ License to anyone who comes into possession of a copy. This
239+ License will therefore apply, along with any applicable section 7
240+ additional terms, to the whole of the work, and all its parts,
241+ regardless of how they are packaged. This License gives no
242+ permission to license the work in any other way, but it does not
243+ invalidate such permission if you have separately received it.
244+
245+ d) If the work has interactive user interfaces, each must display
246+ Appropriate Legal Notices; however, if the Program has interactive
247+ interfaces that do not display Appropriate Legal Notices, your
248+ work need not make them do so.
249+
250+ A compilation of a covered work with other separate and independent
251+works, which are not by their nature extensions of the covered work,
252+and which are not combined with it such as to form a larger program,
253+in or on a volume of a storage or distribution medium, is called an
254+"aggregate" if the compilation and its resulting copyright are not
255+used to limit the access or legal rights of the compilation's users
256+beyond what the individual works permit. Inclusion of a covered work
257+in an aggregate does not cause this License to apply to the other
258+parts of the aggregate.
259+
260+ 6. Conveying Non-Source Forms.
261+
262+ You may convey a covered work in object code form under the terms
263+of sections 4 and 5, provided that you also convey the
264+machine-readable Corresponding Source under the terms of this License,
265+in one of these ways:
266+
267+ a) Convey the object code in, or embodied in, a physical product
268+ (including a physical distribution medium), accompanied by the
269+ Corresponding Source fixed on a durable physical medium
270+ customarily used for software interchange.
271+
272+ b) Convey the object code in, or embodied in, a physical product
273+ (including a physical distribution medium), accompanied by a
274+ written offer, valid for at least three years and valid for as
275+ long as you offer spare parts or customer support for that product
276+ model, to give anyone who possesses the object code either (1) a
277+ copy of the Corresponding Source for all the software in the
278+ product that is covered by this License, on a durable physical
279+ medium customarily used for software interchange, for a price no
280+ more than your reasonable cost of physically performing this
281+ conveying of source, or (2) access to copy the
282+ Corresponding Source from a network server at no charge.
283+
284+ c) Convey individual copies of the object code with a copy of the
285+ written offer to provide the Corresponding Source. This
286+ alternative is allowed only occasionally and noncommercially, and
287+ only if you received the object code with such an offer, in accord
288+ with subsection 6b.
289+
290+ d) Convey the object code by offering access from a designated
291+ place (gratis or for a charge), and offer equivalent access to the
292+ Corresponding Source in the same way through the same place at no
293+ further charge. You need not require recipients to copy the
294+ Corresponding Source along with the object code. If the place to
295+ copy the object code is a network server, the Corresponding Source
296+ may be on a different server (operated by you or a third party)
297+ that supports equivalent copying facilities, provided you maintain
298+ clear directions next to the object code saying where to find the
299+ Corresponding Source. Regardless of what server hosts the
300+ Corresponding Source, you remain obligated to ensure that it is
301+ available for as long as needed to satisfy these requirements.
302+
303+ e) Convey the object code using peer-to-peer transmission, provided
304+ you inform other peers where the object code and Corresponding
305+ Source of the work are being offered to the general public at no
306+ charge under subsection 6d.
307+
308+ A separable portion of the object code, whose source code is excluded
309+from the Corresponding Source as a System Library, need not be
310+included in conveying the object code work.
311+
312+ A "User Product" is either (1) a "consumer product", which means any
313+tangible personal property which is normally used for personal, family,
314+or household purposes, or (2) anything designed or sold for incorporation
315+into a dwelling. In determining whether a product is a consumer product,
316+doubtful cases shall be resolved in favor of coverage. For a particular
317+product received by a particular user, "normally used" refers to a
318+typical or common use of that class of product, regardless of the status
319+of the particular user or of the way in which the particular user
320+actually uses, or expects or is expected to use, the product. A product
321+is a consumer product regardless of whether the product has substantial
322+commercial, industrial or non-consumer uses, unless such uses represent
323+the only significant mode of use of the product.
324+
325+ "Installation Information" for a User Product means any methods,
326+procedures, authorization keys, or other information required to install
327+and execute modified versions of a covered work in that User Product from
328+a modified version of its Corresponding Source. The information must
329+suffice to ensure that the continued functioning of the modified object
330+code is in no case prevented or interfered with solely because
331+modification has been made.
332+
333+ If you convey an object code work under this section in, or with, or
334+specifically for use in, a User Product, and the conveying occurs as
335+part of a transaction in which the right of possession and use of the
336+User Product is transferred to the recipient in perpetuity or for a
337+fixed term (regardless of how the transaction is characterized), the
338+Corresponding Source conveyed under this section must be accompanied
339+by the Installation Information. But this requirement does not apply
340+if neither you nor any third party retains the ability to install
341+modified object code on the User Product (for example, the work has
342+been installed in ROM).
343+
344+ The requirement to provide Installation Information does not include a
345+requirement to continue to provide support service, warranty, or updates
346+for a work that has been modified or installed by the recipient, or for
347+the User Product in which it has been modified or installed. Access to a
348+network may be denied when the modification itself materially and
349+adversely affects the operation of the network or violates the rules and
350+protocols for communication across the network.
351+
352+ Corresponding Source conveyed, and Installation Information provided,
353+in accord with this section must be in a format that is publicly
354+documented (and with an implementation available to the public in
355+source code form), and must require no special password or key for
356+unpacking, reading or copying.
357+
358+ 7. Additional Terms.
359+
360+ "Additional permissions" are terms that supplement the terms of this
361+License by making exceptions from one or more of its conditions.
362+Additional permissions that are applicable to the entire Program shall
363+be treated as though they were included in this License, to the extent
364+that they are valid under applicable law. If additional permissions
365+apply only to part of the Program, that part may be used separately
366+under those permissions, but the entire Program remains governed by
367+this License without regard to the additional permissions.
368+
369+ When you convey a copy of a covered work, you may at your option
370+remove any additional permissions from that copy, or from any part of
371+it. (Additional permissions may be written to require their own
372+removal in certain cases when you modify the work.) You may place
373+additional permissions on material, added by you to a covered work,
374+for which you have or can give appropriate copyright permission.
375+
376+ Notwithstanding any other provision of this License, for material you
377+add to a covered work, you may (if authorized by the copyright holders of
378+that material) supplement the terms of this License with terms:
379+
380+ a) Disclaiming warranty or limiting liability differently from the
381+ terms of sections 15 and 16 of this License; or
382+
383+ b) Requiring preservation of specified reasonable legal notices or
384+ author attributions in that material or in the Appropriate Legal
385+ Notices displayed by works containing it; or
386+
387+ c) Prohibiting misrepresentation of the origin of that material, or
388+ requiring that modified versions of such material be marked in
389+ reasonable ways as different from the original version; or
390+
391+ d) Limiting the use for publicity purposes of names of licensors or
392+ authors of the material; or
393+
394+ e) Declining to grant rights under trademark law for use of some
395+ trade names, trademarks, or service marks; or
396+
397+ f) Requiring indemnification of licensors and authors of that
398+ material by anyone who conveys the material (or modified versions of
399+ it) with contractual assumptions of liability to the recipient, for
400+ any liability that these contractual assumptions directly impose on
401+ those licensors and authors.
402+
403+ All other non-permissive additional terms are considered "further
404+restrictions" within the meaning of section 10. If the Program as you
405+received it, or any part of it, contains a notice stating that it is
406+governed by this License along with a term that is a further
407+restriction, you may remove that term. If a license document contains
408+a further restriction but permits relicensing or conveying under this
409+License, you may add to a covered work material governed by the terms
410+of that license document, provided that the further restriction does
411+not survive such relicensing or conveying.
412+
413+ If you add terms to a covered work in accord with this section, you
414+must place, in the relevant source files, a statement of the
415+additional terms that apply to those files, or a notice indicating
416+where to find the applicable terms.
417+
418+ Additional terms, permissive or non-permissive, may be stated in the
419+form of a separately written license, or stated as exceptions;
420+the above requirements apply either way.
421+
422+ 8. Termination.
423+
424+ You may not propagate or modify a covered work except as expressly
425+provided under this License. Any attempt otherwise to propagate or
426+modify it is void, and will automatically terminate your rights under
427+this License (including any patent licenses granted under the third
428+paragraph of section 11).
429+
430+ However, if you cease all violation of this License, then your
431+license from a particular copyright holder is reinstated (a)
432+provisionally, unless and until the copyright holder explicitly and
433+finally terminates your license, and (b) permanently, if the copyright
434+holder fails to notify you of the violation by some reasonable means
435+prior to 60 days after the cessation.
436+
437+ Moreover, your license from a particular copyright holder is
438+reinstated permanently if the copyright holder notifies you of the
439+violation by some reasonable means, this is the first time you have
440+received notice of violation of this License (for any work) from that
441+copyright holder, and you cure the violation prior to 30 days after
442+your receipt of the notice.
443+
444+ Termination of your rights under this section does not terminate the
445+licenses of parties who have received copies or rights from you under
446+this License. If your rights have been terminated and not permanently
447+reinstated, you do not qualify to receive new licenses for the same
448+material under section 10.
449+
450+ 9. Acceptance Not Required for Having Copies.
451+
452+ You are not required to accept this License in order to receive or
453+run a copy of the Program. Ancillary propagation of a covered work
454+occurring solely as a consequence of using peer-to-peer transmission
455+to receive a copy likewise does not require acceptance. However,
456+nothing other than this License grants you permission to propagate or
457+modify any covered work. These actions infringe copyright if you do
458+not accept this License. Therefore, by modifying or propagating a
459+covered work, you indicate your acceptance of this License to do so.
460+
461+ 10. Automatic Licensing of Downstream Recipients.
462+
463+ Each time you convey a covered work, the recipient automatically
464+receives a license from the original licensors, to run, modify and
465+propagate that work, subject to this License. You are not responsible
466+for enforcing compliance by third parties with this License.
467+
468+ An "entity transaction" is a transaction transferring control of an
469+organization, or substantially all assets of one, or subdividing an
470+organization, or merging organizations. If propagation of a covered
471+work results from an entity transaction, each party to that
472+transaction who receives a copy of the work also receives whatever
473+licenses to the work the party's predecessor in interest had or could
474+give under the previous paragraph, plus a right to possession of the
475+Corresponding Source of the work from the predecessor in interest, if
476+the predecessor has it or can get it with reasonable efforts.
477+
478+ You may not impose any further restrictions on the exercise of the
479+rights granted or affirmed under this License. For example, you may
480+not impose a license fee, royalty, or other charge for exercise of
481+rights granted under this License, and you may not initiate litigation
482+(including a cross-claim or counterclaim in a lawsuit) alleging that
483+any patent claim is infringed by making, using, selling, offering for
484+sale, or importing the Program or any portion of it.
485+
486+ 11. Patents.
487+
488+ A "contributor" is a copyright holder who authorizes use under this
489+License of the Program or a work on which the Program is based. The
490+work thus licensed is called the contributor's "contributor version".
491+
492+ A contributor's "essential patent claims" are all patent claims
493+owned or controlled by the contributor, whether already acquired or
494+hereafter acquired, that would be infringed by some manner, permitted
495+by this License, of making, using, or selling its contributor version,
496+but do not include claims that would be infringed only as a
497+consequence of further modification of the contributor version. For
498+purposes of this definition, "control" includes the right to grant
499+patent sublicenses in a manner consistent with the requirements of
500+this License.
501+
502+ Each contributor grants you a non-exclusive, worldwide, royalty-free
503+patent license under the contributor's essential patent claims, to
504+make, use, sell, offer for sale, import and otherwise run, modify and
505+propagate the contents of its contributor version.
506+
507+ In the following three paragraphs, a "patent license" is any express
508+agreement or commitment, however denominated, not to enforce a patent
509+(such as an express permission to practice a patent or covenant not to
510+sue for patent infringement). To "grant" such a patent license to a
511+party means to make such an agreement or commitment not to enforce a
512+patent against the party.
513+
514+ If you convey a covered work, knowingly relying on a patent license,
515+and the Corresponding Source of the work is not available for anyone
516+to copy, free of charge and under the terms of this License, through a
517+publicly available network server or other readily accessible means,
518+then you must either (1) cause the Corresponding Source to be so
519+available, or (2) arrange to deprive yourself of the benefit of the
520+patent license for this particular work, or (3) arrange, in a manner
521+consistent with the requirements of this License, to extend the patent
522+license to downstream recipients. "Knowingly relying" means you have
523+actual knowledge that, but for the patent license, your conveying the
524+covered work in a country, or your recipient's use of the covered work
525+in a country, would infringe one or more identifiable patents in that
526+country that you have reason to believe are valid.
527+
528+ If, pursuant to or in connection with a single transaction or
529+arrangement, you convey, or propagate by procuring conveyance of, a
530+covered work, and grant a patent license to some of the parties
531+receiving the covered work authorizing them to use, propagate, modify
532+or convey a specific copy of the covered work, then the patent license
533+you grant is automatically extended to all recipients of the covered
534+work and works based on it.
535+
536+ A patent license is "discriminatory" if it does not include within
537+the scope of its coverage, prohibits the exercise of, or is
538+conditioned on the non-exercise of one or more of the rights that are
539+specifically granted under this License. You may not convey a covered
540+work if you are a party to an arrangement with a third party that is
541+in the business of distributing software, under which you make payment
542+to the third party based on the extent of your activity of conveying
543+the work, and under which the third party grants, to any of the
544+parties who would receive the covered work from you, a discriminatory
545+patent license (a) in connection with copies of the covered work
546+conveyed by you (or copies made from those copies), or (b) primarily
547+for and in connection with specific products or compilations that
548+contain the covered work, unless you entered into that arrangement,
549+or that patent license was granted, prior to 28 March 2007.
550+
551+ Nothing in this License shall be construed as excluding or limiting
552+any implied license or other defenses to infringement that may
553+otherwise be available to you under applicable patent law.
554+
555+ 12. No Surrender of Others' Freedom.
556+
557+ If conditions are imposed on you (whether by court order, agreement or
558+otherwise) that contradict the conditions of this License, they do not
559+excuse you from the conditions of this License. If you cannot convey a
560+covered work so as to satisfy simultaneously your obligations under this
561+License and any other pertinent obligations, then as a consequence you may
562+not convey it at all. For example, if you agree to terms that obligate you
563+to collect a royalty for further conveying from those to whom you convey
564+the Program, the only way you could satisfy both those terms and this
565+License would be to refrain entirely from conveying the Program.
566+
567+ 13. Remote Network Interaction; Use with the GNU General Public License.
568+
569+ Notwithstanding any other provision of this License, if you modify the
570+Program, your modified version must prominently offer all users
571+interacting with it remotely through a computer network (if your version
572+supports such interaction) an opportunity to receive the Corresponding
573+Source of your version by providing access to the Corresponding Source
574+from a network server at no charge, through some standard or customary
575+means of facilitating copying of software. This Corresponding Source
576+shall include the Corresponding Source for any work covered by version 3
577+of the GNU General Public License that is incorporated pursuant to the
578+following paragraph.
579+
580+ Notwithstanding any other provision of this License, you have
581+permission to link or combine any covered work with a work licensed
582+under version 3 of the GNU General Public License into a single
583+combined work, and to convey the resulting work. The terms of this
584+License will continue to apply to the part which is the covered work,
585+but the work with which it is combined will remain governed by version
586+3 of the GNU General Public License.
587+
588+ 14. Revised Versions of this License.
589+
590+ The Free Software Foundation may publish revised and/or new versions of
591+the GNU Affero General Public License from time to time. Such new versions
592+will be similar in spirit to the present version, but may differ in detail to
593+address new problems or concerns.
594+
595+ Each version is given a distinguishing version number. If the
596+Program specifies that a certain numbered version of the GNU Affero General
597+Public License "or any later version" applies to it, you have the
598+option of following the terms and conditions either of that numbered
599+version or of any later version published by the Free Software
600+Foundation. If the Program does not specify a version number of the
601+GNU Affero General Public License, you may choose any version ever published
602+by the Free Software Foundation.
603+
604+ If the Program specifies that a proxy can decide which future
605+versions of the GNU Affero General Public License can be used, that proxy's
606+public statement of acceptance of a version permanently authorizes you
607+to choose that version for the Program.
608+
609+ Later license versions may give you additional or different
610+permissions. However, no additional obligations are imposed on any
611+author or copyright holder as a result of your choosing to follow a
612+later version.
613+
614+ 15. Disclaimer of Warranty.
615+
616+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
617+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
618+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
619+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
620+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
621+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
622+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
623+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
624+
625+ 16. Limitation of Liability.
626+
627+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
628+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
629+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
630+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
631+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
632+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
633+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
634+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
635+SUCH DAMAGES.
636+
637+ 17. Interpretation of Sections 15 and 16.
638+
639+ If the disclaimer of warranty and limitation of liability provided
640+above cannot be given local legal effect according to their terms,
641+reviewing courts shall apply local law that most closely approximates
642+an absolute waiver of all civil liability in connection with the
643+Program, unless a warranty or assumption of liability accompanies a
644+copy of the Program in return for a fee.
645+
646+ END OF TERMS AND CONDITIONS
647+
648+ How to Apply These Terms to Your New Programs
649+
650+ If you develop a new program, and you want it to be of the greatest
651+possible use to the public, the best way to achieve this is to make it
652+free software which everyone can redistribute and change under these terms.
653+
654+ To do so, attach the following notices to the program. It is safest
655+to attach them to the start of each source file to most effectively
656+state the exclusion of warranty; and each file should have at least
657+the "copyright" line and a pointer to where the full notice is found.
658+
659+ <one line to give the program's name and a brief idea of what it does.>
660+ Copyright (C) <year> <name of author>
661+
662+ This program is free software: you can redistribute it and/or modify
663+ it under the terms of the GNU Affero General Public License as published by
664+ the Free Software Foundation, either version 3 of the License, or
665+ (at your option) any later version.
666+
667+ This program is distributed in the hope that it will be useful,
668+ but WITHOUT ANY WARRANTY; without even the implied warranty of
669+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
670+ GNU Affero General Public License for more details.
671+
672+ You should have received a copy of the GNU Affero General Public License
673+ along with this program. If not, see <http://www.gnu.org/licenses/>.
674+
675+Also add information on how to contact you by electronic and paper mail.
676+
677+ If your software can interact with users remotely through a computer
678+network, you should also make sure that it provides a way for users to
679+get its source. For example, if your program is a web application, its
680+interface could display a "Source" link that leads users to an archive
681+of the code. There are many ways you could offer source, and different
682+solutions will be better for different programs; see section 13 for the
683+specific requirements.
684+
685+ You should also get your employer (if you work as a programmer) or school,
686+if any, to sign a "copyright disclaimer" for the program, if necessary.
687+For more information on this, and how to apply and follow the GNU AGPL, see
688+<http://www.gnu.org/licenses/>.
689
690=== modified file 'Dependencies.md'
691--- Dependencies.md 2013-06-03 14:27:05 +0000
692+++ Dependencies.md 2013-07-17 21:35:43 +0000
693@@ -1,3 +1,12 @@
694+<!--
695+Dependencies.md
696+Copyright 2013 Canonical Ltd.
697+This work is licensed under the Creative Commons Attribution-Share Alike 3.0
698+Unported License. To view a copy of this license, visit
699+http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
700+Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
701+-->
702+
703 # Juju GUI Charm external dependencies #
704
705 The Juju GUI has a number of external dependencies including packages that are
706@@ -42,5 +51,3 @@
707 single starting place to obtain QA'd software. Dev ops can grab the subset of
708 packages they need, audit, test, and then serve them locally. The
709 `repository-location` config variable can be used to point to the local repo.
710-
711-
712
713=== modified file 'HACKING.md'
714--- HACKING.md 2013-06-27 18:25:15 +0000
715+++ HACKING.md 2013-07-17 21:35:43 +0000
716@@ -1,3 +1,12 @@
717+<!--
718+HACKING.md
719+Copyright 2013 Canonical Ltd.
720+This work is licensed under the Creative Commons Attribution-Share Alike 3.0
721+Unported License. To view a copy of this license, visit
722+http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
723+Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
724+-->
725+
726 # Juju GUI Charm Development #
727
728 ## Contacting the Developers ##
729@@ -16,8 +25,8 @@
730
731 You'll also need some dependencies and developer basics.
732
733- sudo apt-get install bzr autoconf libtool python-charmhelpers python-yaml \
734- xvfb
735+ sudo apt-get install build-essential bzr libapt-pkg-dev python-pip \
736+ python-virtualenv xvfb
737
738 Next, you need the bzr branch. We work from
739 [lp:~juju-gui/charms/precise/juju-gui/trunk](https://code.launchpad.net/~juju-gui/charms/precise/juju-gui/trunk).
740@@ -25,49 +34,45 @@
741 You could start hacking now, but there's a bit more to do to prepare for
742 running and writing tests.
743
744-We use the Jitsu test command to run our functional tests. At the time of
745-this writing it is not yet released. To run it you must first install it
746-locally. The files may be installed globally, or into your home directory (as
747-here):
748-
749- sudo apt-get install autoconf libtool python-charmhelpers python-tempita
750- bzr branch lp:juju-jitsu juju-jitsu
751- cd juju-jitsu
752- autoreconf
753- ./configure --prefix=$HOME
754+We use the juju-test test command to run our functional and unit tests. At the
755+time of this writing it is not yet released. To run it you must first install
756+it locally, e.g.:
757+
758+ bzr checkout --lightweight lp:juju-plugins
759+
760+At this point, link "juju-plugins/plugins/juju_test.py" as "juju-test"
761+somewhere in your PATH, so that it is possible to execute "juju-test".
762+
763+Before being able to run the suite, test requirements need to be installed
764+running the command:
765+
766 make
767- make install
768-
769-Functional tests make use of Selenium and xvfbwrapper. To install the latest
770-version of these packages, as required by the test suite, you can use pip or
771-easy_install, e.g.:
772-
773- sudo pip install selenium xvfbwrapper
774-
775-The current incarnation of the Jitsu test command requires that the current
776-directory name match the charm name, so you must check out the charm into a
777-directory named "juju-gui":
778-
779- bzr branch lp:~juju-gui/charms/precise/juju-gui/trunk juju-gui
780-
781-The branch directory must be placed (or linked from) within a local charm
782-repository. It consists of a directory, itself containing a number of
783-directories, one for each distribution codename, e.g. `precise`. In turn, the
784-codename directories will contain the charm directories. Therefore, you
785-should put your charm in a path like this: `[REPO]/precise/juju-gui`.
786-
787-Now you are ready to run the functional tests (see the next section).
788+
789+The command above will create a ".venv" directory inside "juju-gui/tests/",
790+ignored by DVCSes, containing the development virtual environment with all the
791+testing dependencies. Run "make help" to see all the available make targets.
792+
793+Now you are ready to run the functional and unit tests (see the next section).
794
795 ## Testing ##
796
797 There are two types of tests for the charm: unit tests and functional tests.
798+Long story short, to run all the tests:
799+
800+ make test JUJU_ENV="myenv"
801+
802+In the command above, "myenv" is the juju environment, as it is specified in
803+your `~/.juju/environments.yaml`, that will be bootstrapped before running the
804+tests and destroyed at the end of the test run.
805+
806+Please read further for additional details.
807
808 ### Unit Tests ###
809
810 The unit tests do not require a functional Juju environment, and can be run
811 with this command::
812
813- python tests/unit.test
814+ make unittest
815
816 Unit tests should be created in the "tests" subdirectory and be named in the
817 customary way (i.e., "test_*.py").
818@@ -75,33 +80,15 @@
819 ### Functional Tests ###
820
821 Running the functional tests requires a Juju testing environment as provided
822-by the Jitsu test command (see "Getting Started", above). All files in the
823-tests directory which end with ".test" will be run in a Juju Jitsu test
824-environment.
825-
826-Jitsu requires the charm directory to be named the same as the charm and to be
827-the current working directory when the tests are run:
828-
829- JUJU_REPOSITORY=/path/to/charm/repo ~/bin/jitsu test juju-gui \
830- --logdir /tmp --timeout 40m
831-
832-This command will bootstrap the default Juju environment specified in your
833-`~/.juju/environments.yaml`.
834-
835-Functional tests deploy, by default, the latest stable release of Juju GUI.
836-However, it is possible to provide a different Juju GUI source by setting the
837-environment variable `JUJU_GUI_SOURCE` while running tests, e.g.:
838-
839- JUJU_GUI_SOURCE=lp:mybranch JUJU_REPOSITORY=/path/to/charm/repo \
840- ~/bin/jitsu test juju-gui ...
841-
842-To test a new trunk release:
843-
844- JUJU_GUI_SOURCE=trunk JUJU_REPOSITORY=/path/to/charm/repo \
845- ~/bin/jitsu test juju-gui ...
846-
847-This can be useful to verify that a given branch works with the charm, which is
848-one of the tasks that release QA should include.
849+by the juju-test command (see "Getting Started", above).
850+
851+To run only the functional tests:
852+
853+ make ftest JUJU_ENV="myenv"
854+
855+As seen before, "myenv" is the juju environment, as it is specified in your
856+`~/.juju/environments.yaml`, that will be bootstrapped before running the
857+tests and destroyed at the end of the test run.
858
859 #### LXC ####
860
861@@ -109,33 +96,34 @@
862 for these tests. At this time, we recommend using other environments, such as
863 OpenStack; but we will periodically check the tests in LXC environments
864 because it would be great to be able to use it. If you do want to use LXC,
865-you will need to install the `apt-cacher-ng` and `lxc` packages.
866-
867-Currently running tests on a local environment is quite slow (with quantal
868-host and precise container at least), so you may want to further increase the
869-`jitsu test` command timeout.
870-
871-If Jitsu generates errors about not being able to bootstrap:
872-
873- CalledProcessError: Command '['juju', 'bootstrap']'...
874-
875-or if it hangs, then you may need to bootstrap the environment yourself and
876-pass the --no-bootstrap switch to Jitsu.
877+you will need to install the `apt-cacher-ng` and `lxc` packages. Also note
878+that at this time juju-core does not support the local provider.
879
880 ## Running the Charm From Development ##
881
882-If you have set up your environment to run functional tests, you also have set
883-it up to run your local development charm. Developing and debugging with this
884-is much easier than trying to develop and debug with the tests, unfortunately.
885-
886-To get started, first, simply do a `juju bootstrap`. Using a non-LXC
887-environment probably will reduce frustrations. Then, deploy your charm like
888-this (again, assuming you have set up your repo the way the functional tests
889-need them, as described above).
890-
891+If you have set up your environment to run your local development charm,
892+deploying the charm fails if attempted after the testing virtualenv has been
893+created: juju deploy exits with an error due to ".venv" directory containing
894+an absolute symbolic link. There are two ways to work around this problem.
895+
896+The first one is running "make clean" before deploying the charm:
897+
898+ make clean
899+ juju bootstrap
900 juju deploy --repository=/path/to/charm/repo local:precise/juju-gui
901 juju expose juju-gui
902
903+The second one is just running "make deploy":
904+
905+ juju bootstrap
906+ make deploy
907+
908+The "make deploy" command creates a temporary Juju repository (excluding
909+the ".venv" directory), deploys the Juju GUI charm from that repository and
910+exposes the juju-gui service. Also note that "make deploy" does not require
911+you to manually set up a local Juju environment, and preserves, if already
912+created, the testing virtualenv.
913+
914 Now you are working with a test run, as described in
915 <https://juju.ubuntu.com/docs/write-charm.html#test-run>. The
916 `juju debug-hooks` command, described in the same web page, is your most
917
918=== added file 'Makefile'
919--- Makefile 1970-01-01 00:00:00 +0000
920+++ Makefile 2013-07-17 21:35:43 +0000
921@@ -0,0 +1,63 @@
922+# This file is part of the Juju GUI, which lets users view and manage Juju
923+# environments within a graphical interface (https://launchpad.net/juju-gui).
924+# Copyright (C) 2012-2013 Canonical Ltd.
925+#
926+# This program is free software: you can redistribute it and/or modify it under
927+# the terms of the GNU Affero General Public License version 3, as published by
928+# the Free Software Foundation.
929+#
930+# This program is distributed in the hope that it will be useful, but WITHOUT
931+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
932+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
933+# Affero General Public License for more details.
934+#
935+# You should have received a copy of the GNU Affero General Public License
936+# along with this program. If not, see <http://www.gnu.org/licenses/>.
937+
938+JUJUTEST = juju-test --timeout=30m -v -e "$(JUJU_ENV)" --upload-tools
939+VENV = ./tests/.venv
940+
941+all: setup
942+
943+setup:
944+ @./tests/00-setup
945+
946+unittest: setup
947+ ./tests/10-unit.test
948+
949+ftest: setup
950+ $(JUJUTEST) 20-functional.test
951+
952+# This will be eventually removed when we have juju-test --clean-state.
953+test: unittest ftest
954+
955+# This will be eventually renamed as test when we have juju-test --clean-state.
956+jujutest:
957+ $(JUJUTEST)
958+
959+lint: setup
960+ @$(VENV)/bin/flake8 --show-source --exclude=.venv ./hooks/ ./tests/
961+
962+clean:
963+ find . -name '*.pyc' -delete
964+ rm -rf $(VENV)
965+
966+deploy: setup
967+ $(VENV)/bin/python ./tests/deploy.py
968+
969+help:
970+ @echo -e 'Juju GUI charm - list of make targets:\n'
971+ @echo -e 'make - Set up development and testing environment.\n'
972+ @echo 'make test JUJU_ENV="my-juju-env" - Run functional and unit tests.'
973+ @echo -e ' JUJU_ENV is the Juju environment that will be bootstrapped.\n'
974+ @echo -e 'make unittest - Run unit tests.\n'
975+ @echo 'make ftest JUJU_ENV="my-juju-env" - Run functional tests.'
976+ @echo -e ' JUJU_ENV is the Juju environment that will be bootstrapped.\n'
977+ @echo -e 'make lint - Run linter and pep8.\n'
978+ @echo -e 'make clean - Remove bytecode files and virtualenvs.\n'
979+ @echo 'make deploy [JUJU_ENV="my-juju-env]" - Deploy and expose the Juju'
980+ @echo ' GUI charm setting up a temporary Juju repository. Wait for the'
981+ @echo ' service to be started. If JUJU_ENV is not passed, the charm will'
982+ @echo ' be deployed in the default Juju environment.'
983+
984+.PHONY: all clean deploy ftest help jujutest lint setup test unittest
985
986=== modified file 'README.md'
987--- README.md 2013-06-26 20:37:57 +0000
988+++ README.md 2013-07-17 21:35:43 +0000
989@@ -1,3 +1,12 @@
990+<!--
991+README.md
992+Copyright 2013 Canonical Ltd.
993+This work is licensed under the Creative Commons Attribution-Share Alike 3.0
994+Unported License. To view a copy of this license, visit
995+http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
996+Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
997+-->
998+
999 # Juju GUI Charm #
1000
1001 This charm makes it easy to deploy a Juju GUI into an existing environment.
1002@@ -23,7 +32,7 @@
1003 Next, you simply need to deploy the charm and expose it. (See also "Deploying
1004 with Jitsu" below, for another option.)
1005
1006- juju deploy cs:precise/juju-gui
1007+ juju deploy juju-gui
1008 juju expose juju-gui
1009
1010 Finally, you need to identify the GUI's URL. It can take a few minutes for the
1011
1012=== modified file 'config.yaml'
1013--- config.yaml 2013-05-31 19:54:41 +0000
1014+++ config.yaml 2013-07-17 21:35:43 +0000
1015@@ -1,3 +1,19 @@
1016+# This file is part of the Juju GUI, which lets users view and manage Juju
1017+# environments within a graphical interface (https://launchpad.net/juju-gui).
1018+# Copyright (C) 2012-2013 Canonical Ltd.
1019+#
1020+# This program is free software: you can redistribute it and/or modify it under
1021+# the terms of the GNU Affero General Public License version 3, as published by
1022+# the Free Software Foundation.
1023+#
1024+# This program is distributed in the hope that it will be useful, but WITHOUT
1025+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1026+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1027+# Affero General Public License for more details.
1028+#
1029+# You should have received a copy of the GNU Affero General Public License
1030+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1031+
1032 options:
1033 juju-gui-source:
1034 description: |
1035@@ -111,7 +127,7 @@
1036 The URL of the charm catalog site ("charmworld") from which charm
1037 catalog data will be drawn.
1038 type: string
1039- default: https://manage.jujucharms.com
1040+ default: https://manage.jujucharms.com/
1041 repository-location:
1042 description: |
1043 The charm depends on several software packages that are not packaged in
1044@@ -124,3 +140,30 @@
1045 'add-apt-repository'.
1046 type: string
1047 default: ppa:juju-gui-charmers/stable
1048+ use-analytics:
1049+ description: |
1050+ The team developing the Juju GUI benefits from understanding how
1051+ different deployments use the tool. By enabling this setting,
1052+ anonymized usage data is reported back using Google Analytics. The type
1053+ of data collected includes the charms that are deployed and the number
1054+ of units per service. Use of analytics is optional but we hope you will
1055+ allow us to improve our tool based on your experience.
1056+ type: boolean
1057+ default: true
1058+ default-viewmode:
1059+ description: |
1060+ What the charmbrowser's default viewmode should be. Possible options are:
1061+ - 'sidebar' (default): the charmwbrowser will appear as a sidebar. This
1062+ is also known as build mode.
1063+ - 'fullscreen': the charmbrowser will appear in full screen, hiding the
1064+ canvas. This is also known as browse mode.
1065+ - 'minimized': the charmbrowser will be minimized by default, and hidden.
1066+ type: string
1067+ default: sidebar
1068+ show-get-juju-button:
1069+ description: |
1070+ There are deployment modes for Juju GUI which are not intended as regular
1071+ use mode. In these cases, login/logout are disabled and instead there is a
1072+ link to juju.ubuntu.com
1073+ type: boolean
1074+ default: false
1075
1076=== modified file 'config/config.js.template'
1077--- config/config.js.template 2013-04-26 21:05:03 +0000
1078+++ config/config.js.template 2013-07-17 21:35:43 +0000
1079@@ -22,5 +22,8 @@
1080 apiBackend: {{api_backend}}, // Value can be 'python' or 'go'.
1081 readOnly: {{readonly}},
1082 sandbox: {{sandbox}},
1083- login_help: {{login_help}}
1084+ useAnalytics: {{use_analytics}},
1085+ login_help: {{login_help}},
1086+ defaultViewmode: {{default_viewmode}},
1087+ showGetJujuButton: {{show_get_juju_button}}
1088 };
1089
1090=== modified file 'copyright'
1091--- copyright 2013-01-21 16:56:10 +0000
1092+++ copyright 2013-07-17 21:35:43 +0000
1093@@ -2,10 +2,10 @@
1094
1095 Files: *
1096 Copyright: Copyright 2013, Canonical Ltd., All Rights Reserved.
1097-License: GPL-3
1098+License: Affero GPL-3
1099 This program is free software: you can redistribute it and/or modify
1100- it under the terms of the GNU General Public License as published by
1101- the Free Software Foundation, either version 3 of the License, or
1102+ it under the terms of the GNU Affero General Public License as published
1103+ by the Free Software Foundation, either version 3 of the License, or
1104 (at your option) any later version.
1105 .
1106 This program is distributed in the hope that it will be useful,
1107
1108=== modified file 'hooks/backend.py'
1109--- hooks/backend.py 2013-07-11 21:02:07 +0000
1110+++ hooks/backend.py 2013-07-17 21:35:43 +0000
1111@@ -1,3 +1,19 @@
1112+# This file is part of the Juju GUI, which lets users view and manage Juju
1113+# environments within a graphical interface (https://launchpad.net/juju-gui).
1114+# Copyright (C) 2012-2013 Canonical Ltd.
1115+#
1116+# This program is free software: you can redistribute it and/or modify it under
1117+# the terms of the GNU Affero General Public License version 3, as published by
1118+# the Free Software Foundation.
1119+#
1120+# This program is distributed in the hope that it will be useful, but WITHOUT
1121+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1122+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1123+# Affero General Public License for more details.
1124+#
1125+# You should have received a copy of the GNU Affero General Public License
1126+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1127+
1128 """
1129 A composition system for creating backend objects.
1130
1131@@ -84,7 +100,11 @@
1132 config['juju-gui-console-enabled'], config['login-help'],
1133 config['read-only'], config['staging'], config['ssl-cert-path'],
1134 config['charmworld-url'], config['serve-tests'],
1135- secure=config['secure'], sandbox=config['sandbox'])
1136+ secure=config['secure'], sandbox=config['sandbox'],
1137+ use_analytics=config['use-analytics'],
1138+ default_viewmode=config['default-viewmode'],
1139+ show_get_juju_button=config['show-get-juju-button'])
1140+
1141 charmhelpers.open_port(80)
1142 charmhelpers.open_port(443)
1143
1144
1145=== modified file 'hooks/bootstrap_utils.py'
1146--- hooks/bootstrap_utils.py 2013-04-24 10:46:20 +0000
1147+++ hooks/bootstrap_utils.py 2013-07-17 21:35:43 +0000
1148@@ -1,3 +1,19 @@
1149+# This file is part of the Juju GUI, which lets users view and manage Juju
1150+# environments within a graphical interface (https://launchpad.net/juju-gui).
1151+# Copyright (C) 2012-2013 Canonical Ltd.
1152+#
1153+# This program is free software: you can redistribute it and/or modify it under
1154+# the terms of the GNU Affero General Public License version 3, as published by
1155+# the Free Software Foundation.
1156+#
1157+# This program is distributed in the hope that it will be useful, but WITHOUT
1158+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1159+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1160+# Affero General Public License for more details.
1161+#
1162+# You should have received a copy of the GNU Affero General Public License
1163+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1164+
1165 """
1166 These are actually maintained in python-shelltoolbox. Precise does not have
1167 that package, so we need to bootstrap the process by copying the functions
1168
1169=== modified file 'hooks/config-changed'
1170--- hooks/config-changed 2013-05-03 09:13:45 +0000
1171+++ hooks/config-changed 2013-07-17 21:35:43 +0000
1172@@ -1,8 +1,21 @@
1173 #!/usr/bin/env python2
1174 # -*- python -*-
1175
1176-# Copyright 2012 Canonical Ltd. This software is licensed under the
1177-# GNU Affero General Public License version 3 (see the file LICENSE).
1178+# This file is part of the Juju GUI, which lets users view and manage Juju
1179+# environments within a graphical interface (https://launchpad.net/juju-gui).
1180+# Copyright (C) 2012-2013 Canonical Ltd.
1181+#
1182+# This program is free software: you can redistribute it and/or modify it under
1183+# the terms of the GNU Affero General Public License version 3, as published by
1184+# the Free Software Foundation.
1185+#
1186+# This program is distributed in the hope that it will be useful, but WITHOUT
1187+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1188+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1189+# Affero General Public License for more details.
1190+#
1191+# You should have received a copy of the GNU Affero General Public License
1192+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1193
1194 import sys
1195
1196
1197=== modified file 'hooks/install'
1198--- hooks/install 2013-05-31 19:54:41 +0000
1199+++ hooks/install 2013-07-17 21:35:43 +0000
1200@@ -1,6 +1,22 @@
1201 #!/usr/bin/env python2
1202 # -*- python -*-
1203
1204+# This file is part of the Juju GUI, which lets users view and manage Juju
1205+# environments within a graphical interface (https://launchpad.net/juju-gui).
1206+# Copyright (C) 2012-2013 Canonical Ltd.
1207+#
1208+# This program is free software: you can redistribute it and/or modify it under
1209+# the terms of the GNU Affero General Public License version 3, as published by
1210+# the Free Software Foundation.
1211+#
1212+# This program is distributed in the hope that it will be useful, but WITHOUT
1213+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1214+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1215+# Affero General Public License for more details.
1216+#
1217+# You should have received a copy of the GNU Affero General Public License
1218+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1219+
1220 import errno
1221 import json
1222 import os
1223
1224=== modified file 'hooks/start'
1225--- hooks/start 2013-05-03 09:13:45 +0000
1226+++ hooks/start 2013-07-17 21:35:43 +0000
1227@@ -1,6 +1,22 @@
1228 #!/usr/bin/env python2
1229 #-*- python -*-
1230
1231+# This file is part of the Juju GUI, which lets users view and manage Juju
1232+# environments within a graphical interface (https://launchpad.net/juju-gui).
1233+# Copyright (C) 2012-2013 Canonical Ltd.
1234+#
1235+# This program is free software: you can redistribute it and/or modify it under
1236+# the terms of the GNU Affero General Public License version 3, as published by
1237+# the Free Software Foundation.
1238+#
1239+# This program is distributed in the hope that it will be useful, but WITHOUT
1240+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1241+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1242+# Affero General Public License for more details.
1243+#
1244+# You should have received a copy of the GNU Affero General Public License
1245+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1246+
1247 from charmhelpers import get_config
1248
1249 from backend import Backend
1250
1251=== modified file 'hooks/stop'
1252--- hooks/stop 2013-05-03 10:37:06 +0000
1253+++ hooks/stop 2013-07-17 21:35:43 +0000
1254@@ -1,6 +1,22 @@
1255 #!/usr/bin/env python2
1256 #-*- python -*-
1257
1258+# This file is part of the Juju GUI, which lets users view and manage Juju
1259+# environments within a graphical interface (https://launchpad.net/juju-gui).
1260+# Copyright (C) 2012-2013 Canonical Ltd.
1261+#
1262+# This program is free software: you can redistribute it and/or modify it under
1263+# the terms of the GNU Affero General Public License version 3, as published by
1264+# the Free Software Foundation.
1265+#
1266+# This program is distributed in the hope that it will be useful, but WITHOUT
1267+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1268+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1269+# Affero General Public License for more details.
1270+#
1271+# You should have received a copy of the GNU Affero General Public License
1272+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1273+
1274 from backend import Backend
1275 from utils import log_hook
1276
1277
1278=== modified file 'hooks/utils.py'
1279--- hooks/utils.py 2013-07-11 19:10:30 +0000
1280+++ hooks/utils.py 2013-07-17 21:35:43 +0000
1281@@ -1,3 +1,19 @@
1282+# This file is part of the Juju GUI, which lets users view and manage Juju
1283+# environments within a graphical interface (https://launchpad.net/juju-gui).
1284+# Copyright (C) 2012-2013 Canonical Ltd.
1285+#
1286+# This program is free software: you can redistribute it and/or modify it under
1287+# the terms of the GNU Affero General Public License version 3, as published by
1288+# the Free Software Foundation.
1289+#
1290+# This program is distributed in the hope that it will be useful, but WITHOUT
1291+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1292+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1293+# Affero General Public License for more details.
1294+#
1295+# You should have received a copy of the GNU Affero General Public License
1296+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1297+
1298 """Juju GUI charm utilities."""
1299
1300 __all__ = [
1301@@ -112,13 +128,24 @@
1302 cmd_log(apt_get_install(*DEB_BUILD_DEPENDENCIES))
1303
1304
1305-def get_api_address(unit_dir):
1306- """Return the Juju API address stored in the uniter agent.conf file."""
1307+def get_api_address(unit_dir=None):
1308+ """Return the Juju API address.
1309+
1310+ If not present in the hook context as an environment variable, try to
1311+ retrieve the address parsing the machiner agent.conf file.
1312+ """
1313+ api_addresses = os.getenv("JUJU_API_ADDRESSES")
1314+ if api_addresses is not None:
1315+ return api_addresses.split()[0]
1316+ # The JUJU_API_ADDRESSES environment variable is not included in the hooks
1317+ # context in older releases of juju-core. Retrieve it from the machiner
1318+ # agent file instead.
1319 import yaml # python-yaml is only installed if juju-core is used.
1320- # XXX 2013-03-27 frankban bug=1161443:
1321- # currently the uniter agent.conf file does not include the API
1322- # address. For now retrieve it from the machine agent file.
1323- base_dir = os.path.abspath(os.path.join(unit_dir, '..'))
1324+ if unit_dir is None:
1325+ base_dir = os.path.join(CURRENT_DIR, '..', '..')
1326+ else:
1327+ base_dir = os.path.join(unit_dir, '..')
1328+ base_dir = os.path.abspath(base_dir)
1329 for dirname in os.listdir(base_dir):
1330 if dirname.startswith('machine-'):
1331 agent_conf = os.path.join(base_dir, dirname, 'agent.conf')
1332@@ -333,11 +360,12 @@
1333 def start_gui(
1334 console_enabled, login_help, readonly, in_staging, ssl_cert_path,
1335 charmworld_url, serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg',
1336- config_js_path=None, secure=True, sandbox=False):
1337+ config_js_path=None, secure=True, sandbox=False, use_analytics=False,
1338+ default_viewmode='sidebar', show_get_juju_button=False):
1339 """Set up and start the Juju GUI server."""
1340 with su('root'):
1341 run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR)
1342- # XXX 2013-02-05 frankban bug=1116320:
1343+ # XXX 2013-02-05 frankban bug=1116320:
1344 # External insecure resources are still loaded when testing in the
1345 # debug environment. For now, switch to the production environment if
1346 # the charm is configured to serve tests.
1347@@ -373,6 +401,9 @@
1348 'protocol': json.dumps(protocol),
1349 'sandbox': json.dumps(sandbox),
1350 'charmworld_url': json.dumps(charmworld_url),
1351+ 'use_analytics': json.dumps(use_analytics),
1352+ 'default_viewmode': json.dumps(default_viewmode),
1353+ 'show_get_juju_button': json.dumps(show_get_juju_button),
1354 }
1355 if config_js_path is None:
1356 config_js_path = os.path.join(
1357@@ -387,7 +418,7 @@
1358 api_address = '127.0.0.1:{0}'.format(API_PORT)
1359 else:
1360 # Retrieve the juju-core API server address.
1361- api_address = get_api_address(os.path.join(CURRENT_DIR, '..'))
1362+ api_address = get_api_address()
1363 context = {
1364 'api_address': api_address,
1365 'api_pem': JUJU_PEM,
1366
1367=== modified file 'hooks/web-relation-joined'
1368--- hooks/web-relation-joined 2013-05-03 09:13:45 +0000
1369+++ hooks/web-relation-joined 2013-07-17 21:35:43 +0000
1370@@ -1,6 +1,22 @@
1371 #!/usr/bin/env python2
1372 #-*- python -*-
1373
1374+# This file is part of the Juju GUI, which lets users view and manage Juju
1375+# environments within a graphical interface (https://launchpad.net/juju-gui).
1376+# Copyright (C) 2012-2013 Canonical Ltd.
1377+#
1378+# This program is free software: you can redistribute it and/or modify it under
1379+# the terms of the GNU Affero General Public License version 3, as published by
1380+# the Free Software Foundation.
1381+#
1382+# This program is distributed in the hope that it will be useful, but WITHOUT
1383+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1384+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1385+# Affero General Public License for more details.
1386+#
1387+# You should have received a copy of the GNU Affero General Public License
1388+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1389+
1390 import socket
1391
1392 from charmhelpers import (
1393
1394=== modified file 'metadata.yaml'
1395--- metadata.yaml 2013-04-23 07:32:46 +0000
1396+++ metadata.yaml 2013-07-17 21:35:43 +0000
1397@@ -1,3 +1,19 @@
1398+# This file is part of the Juju GUI, which lets users view and manage Juju
1399+# environments within a graphical interface (https://launchpad.net/juju-gui).
1400+# Copyright (C) 2012-2013 Canonical Ltd.
1401+#
1402+# This program is free software: you can redistribute it and/or modify it under
1403+# the terms of the GNU Affero General Public License version 3, as published by
1404+# the Free Software Foundation.
1405+#
1406+# This program is distributed in the hope that it will be useful, but WITHOUT
1407+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1408+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1409+# Affero General Public License for more details.
1410+#
1411+# You should have received a copy of the GNU Affero General Public License
1412+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1413+
1414 name: juju-gui
1415 summary: User interface for Juju (development)
1416 maintainer: Juju GUI Team <juju-gui@lists.launchpad.net>
1417
1418=== modified file 'revision'
1419--- revision 2013-07-12 18:37:59 +0000
1420+++ revision 2013-07-17 21:35:43 +0000
1421@@ -1,1 +1,1 @@
1422-52
1423+53
1424\ No newline at end of file
1425
1426=== added file 'tests/00-setup'
1427--- tests/00-setup 1970-01-01 00:00:00 +0000
1428+++ tests/00-setup 2013-07-17 21:35:43 +0000
1429@@ -0,0 +1,36 @@
1430+#!/bin/sh
1431+
1432+# This file is part of the Juju GUI, which lets users view and manage Juju
1433+# environments within a graphical interface (https://launchpad.net/juju-gui).
1434+# Copyright (C) 2012-2013 Canonical Ltd.
1435+#
1436+# This program is free software: you can redistribute it and/or modify it under
1437+# the terms of the GNU Affero General Public License version 3, as published by
1438+# the Free Software Foundation.
1439+#
1440+# This program is distributed in the hope that it will be useful, but WITHOUT
1441+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1442+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1443+# Affero General Public License for more details.
1444+#
1445+# You should have received a copy of the GNU Affero General Public License
1446+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1447+
1448+TESTDIR=`dirname $0`
1449+VENV="$TESTDIR/.venv"
1450+ACTIVATE="$VENV/bin/activate"
1451+REQUIREMENTS="$TESTDIR/requirements.pip"
1452+
1453+
1454+createvenv() {
1455+ # Create a virtualenv if it does not exist, or it is older than requirements.
1456+ if [ ! -f "$ACTIVATE" -o "$REQUIREMENTS" -nt "$ACTIVATE" ]; then
1457+ virtualenv --distribute $VENV
1458+ . $VENV/bin/activate && \
1459+ yes w | pip install --use-mirrors -r $REQUIREMENTS
1460+ touch $VENV/bin/activate
1461+ fi
1462+}
1463+
1464+
1465+createvenv
1466
1467=== renamed file 'tests/unit.test' => 'tests/10-unit.test'
1468--- tests/unit.test 2012-12-07 21:16:55 +0000
1469+++ tests/10-unit.test 2013-07-17 21:35:43 +0000
1470@@ -1,5 +1,22 @@
1471-#!/usr/bin/env python2
1472-# -*- python -*-
1473+#!tests/.venv/bin/python
1474+
1475+# This file is part of the Juju GUI, which lets users view and manage Juju
1476+# environments within a graphical interface (https://launchpad.net/juju-gui).
1477+# Copyright (C) 2012-2013 Canonical Ltd.
1478+#
1479+# This program is free software: you can redistribute it and/or modify it under
1480+# the terms of the GNU Affero General Public License version 3, as published by
1481+# the Free Software Foundation.
1482+#
1483+# This program is distributed in the hope that it will be useful, but WITHOUT
1484+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1485+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1486+# Affero General Public License for more details.
1487+#
1488+# You should have received a copy of the GNU Affero General Public License
1489+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1490+
1491+"""Unit test suite."""
1492
1493 import os
1494 import sys
1495@@ -8,4 +25,5 @@
1496
1497 runner = unittest.TextTestRunner(verbosity=2)
1498 suite = unittest.TestLoader().discover(os.path.dirname(__file__))
1499-sys.exit(runner.run(suite))
1500+result = runner.run(suite)
1501+sys.exit(not result.wasSuccessful())
1502
1503=== renamed file 'tests/deploy.test' => 'tests/20-functional.test'
1504--- tests/deploy.test 2013-04-27 18:57:12 +0000
1505+++ tests/20-functional.test 2013-07-17 21:35:43 +0000
1506@@ -1,30 +1,47 @@
1507-#!/usr/bin/env python2
1508-#-*- python -*-
1509-
1510-import os
1511+#!tests/.venv/bin/python
1512+
1513+# This file is part of the Juju GUI, which lets users view and manage Juju
1514+# environments within a graphical interface (https://launchpad.net/juju-gui).
1515+# Copyright (C) 2012-2013 Canonical Ltd.
1516+#
1517+# This program is free software: you can redistribute it and/or modify it under
1518+# the terms of the GNU Affero General Public License version 3, as published by
1519+# the Free Software Foundation.
1520+#
1521+# This program is distributed in the hope that it will be useful, but WITHOUT
1522+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1523+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1524+# Affero General Public License for more details.
1525+#
1526+# You should have received a copy of the GNU Affero General Public License
1527+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1528+
1529 import unittest
1530 import urlparse
1531
1532-from charmhelpers import make_charm_config_file
1533 from selenium.webdriver import Firefox
1534 from selenium.webdriver.support import ui
1535-from shelltoolbox import command
1536 from xvfbwrapper import Xvfb
1537
1538-
1539-JUJU_GUI_SOURCE = os.getenv('JUJU_GUI_SOURCE')
1540+from deploy import juju_deploy
1541+from helpers import (
1542+ juju_destroy_service,
1543+ juju_version,
1544+ ssh,
1545+)
1546+
1547+
1548 JUJU_GUI_TEST_BRANCH = 'lp:~juju-gui/juju-gui/charm-tests-branch'
1549 STAGING_SERVICES = ('haproxy', 'mediawiki', 'memcached', 'mysql', 'wordpress')
1550-jitsu = command('jitsu')
1551-juju = command('juju')
1552-ssh = command('ssh')
1553+is_legacy_juju = juju_version().major == 0
1554
1555
1556 class DeployTestMixin(object):
1557
1558+ charm = 'juju-gui'
1559+ port = '443'
1560+
1561 def setUp(self):
1562- self.charm = 'juju-gui'
1563- self.port = '443'
1564 # Perform all graphical operations in memory.
1565 vdisplay = Xvfb(width=1280, height=720)
1566 vdisplay.start()
1567@@ -34,29 +51,14 @@
1568 self.addCleanup(selenium.quit)
1569
1570 def tearDown(self):
1571- juju('destroy-service', self.charm)
1572+ juju_destroy_service(self.charm)
1573
1574 def assertEnvironmentIsConnected(self):
1575 """Assert the GUI environment is connected to the Juju API agent."""
1576 self.wait_for_script(
1577- 'return app.env.get("connected");',
1578+ 'return app && app.env && app.env.get("connected");',
1579 error='Environment not connected.')
1580
1581- def make_config_file(self, options=None):
1582- """Create a charm config file adding, if required, the Juju GUI source.
1583-
1584- Return the created config file object.
1585-
1586- The Juju GUI source can be provided by setting the environment variable
1587- *JUJU_GUI_SOURCE*. This is just a simple wrapper around
1588- ``charmhelpers.make_charm_config_file``.
1589- """
1590- if options is None:
1591- options = {}
1592- if JUJU_GUI_SOURCE is not None:
1593- options.setdefault('juju-gui-source', JUJU_GUI_SOURCE)
1594- return make_charm_config_file({self.charm: options})
1595-
1596 def handle_browser_warning(self):
1597 """Overstep the browser warning dialog if required."""
1598 self.wait_for_script(
1599@@ -85,7 +87,7 @@
1600 return driver.title == 'Juju Admin'
1601 self.wait_for(page_ready, error='Juju GUI not found.')
1602
1603- def wait_for(self, condition, error=None, timeout=20):
1604+ def wait_for(self, condition, error=None, timeout=30):
1605 """Wait for condition to be True.
1606
1607 The argument condition is a callable accepting a driver object.
1608@@ -95,7 +97,7 @@
1609 wait = ui.WebDriverWait(self.selenium, timeout)
1610 return wait.until(condition, error)
1611
1612- def wait_for_css_selector(self, selector, error=None, timeout=20):
1613+ def wait_for_css_selector(self, selector, error=None, timeout=30):
1614 """Wait until the provided CSS selector is found.
1615
1616 Fail printing the provided error if timeout is exceeded.
1617@@ -106,7 +108,7 @@
1618 elements = self.wait_for(condition, error=error, timeout=timeout)
1619 return elements[0]
1620
1621- def wait_for_script(self, script, error=None, timeout=20):
1622+ def wait_for_script(self, script, error=None, timeout=30):
1623 """Wait for the given JavaScript snippet to return a True value.
1624
1625 Fail printing the provided error if timeout is exceeded.
1626@@ -115,14 +117,6 @@
1627 condition = lambda driver: driver.execute_script(script)
1628 return self.wait_for(condition, error=error, timeout=timeout)
1629
1630- def login(self, password):
1631- """Log in to access the Juju GUI using the provided *password*."""
1632- form = self.wait_for_css_selector('form', 'Login form not found.')
1633- passwd = form.find_element_by_css_selector('input[type=password]')
1634- passwd.send_keys(password)
1635- submit = form.find_element_by_css_selector('input[type=submit]')
1636- submit.click()
1637-
1638 def get_service_names(self):
1639 """Return the set of services' names displayed in the current page."""
1640 def services_found(driver):
1641@@ -135,8 +129,8 @@
1642 # Just invoking ``juju destroy-service juju-gui`` in tearDown
1643 # should execute the ``stop`` hook, stopping all the services
1644 # started by the charm in the machine. Right now this does not
1645- # work, so the same behavior is accomplished keeping track of
1646- # started services and manually stopping them here.
1647+ # work in pyJuju, so the same behavior is accomplished keeping
1648+ # track of started services and manually stopping them here.
1649 target = 'ubuntu@{0}'.format(hostname)
1650 for service in services:
1651 ssh(target, 'sudo', 'service', service, 'stop')
1652@@ -144,36 +138,24 @@
1653
1654 class DeployTest(DeployTestMixin, unittest.TestCase):
1655
1656- def deploy(self, config=None):
1657- """Deploy and expose the Juju GUI charm. Return the service host name.
1658-
1659- Also wait until the service is started.
1660- If *config_path* is provided, it will be used when deploying the charm.
1661- """
1662- config_file = self.make_config_file(config)
1663- juju('deploy', 'local:{0}'.format(self.charm),
1664- '--config', config_file.name)
1665- juju('expose', self.charm)
1666- jitsu(
1667- 'watch', '--failfast', self.charm,
1668- '--state', 'started', '--open-port', self.port)
1669- address = jitsu('get-service-info', self.charm, 'public-address')
1670- return address.strip().split(':')[1]
1671-
1672 def test_api_agent(self):
1673 # Ensure the Juju GUI and API agent services are correctly set up.
1674- hostname = self.deploy()
1675- # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1676- self.addCleanup(
1677- self.stop_services,
1678- hostname, ['haproxy', 'apache2', 'juju-api-agent'])
1679+ unit_info = juju_deploy(self.charm)
1680+ hostname = unit_info['public-address']
1681+ if is_legacy_juju:
1682+ # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1683+ self.addCleanup(
1684+ self.stop_services,
1685+ hostname, ['haproxy', 'apache2', 'juju-api-agent'])
1686 self.navigate_to(hostname)
1687 self.handle_browser_warning()
1688 self.assertEnvironmentIsConnected()
1689
1690+ @unittest.skipUnless(is_legacy_juju, 'staging only works in pyJuju')
1691 def test_staging(self):
1692 # Ensure the Juju GUI and improv services are correctly set up.
1693- hostname = self.deploy({'staging': 'true'})
1694+ unit_info = juju_deploy(self.charm, options={'staging': 'true'})
1695+ hostname = unit_info['public-address']
1696 # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1697 self.addCleanup(
1698 self.stop_services,
1699@@ -186,44 +168,24 @@
1700
1701 def test_branch_source(self):
1702 # Ensure the Juju GUI is correctly deployed from a Bazaar branch.
1703- hostname = self.deploy({'juju-gui-source': JUJU_GUI_TEST_BRANCH})
1704- # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1705- self.addCleanup(
1706- self.stop_services,
1707- hostname, ['haproxy', 'apache2', 'juju-api-agent'])
1708+ unit_info = juju_deploy(
1709+ self.charm, options={'juju-gui-source': JUJU_GUI_TEST_BRANCH})
1710+ hostname = unit_info['public-address']
1711+ if is_legacy_juju:
1712+ # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1713+ self.addCleanup(
1714+ self.stop_services,
1715+ hostname, ['haproxy', 'apache2', 'juju-api-agent'])
1716 self.navigate_to(hostname)
1717 self.handle_browser_warning()
1718 self.assertEnvironmentIsConnected()
1719
1720-
1721-class DeployToTest(DeployTestMixin, unittest.TestCase):
1722-
1723- def deploy_to(self):
1724- """Deploy the Juju GUI charm in the Juju bootstrap node.
1725-
1726- Expose it and return the service host name.
1727- Also wait until the service is started.
1728- """
1729- config_file = self.make_config_file()
1730- # The id of the bootstrap node is 0 (the first node started by Juju).
1731- jitsu('deploy-to', '0', 'local:{0}'.format(self.charm),
1732- '--config', config_file.name)
1733- juju('expose', self.charm)
1734- jitsu(
1735- 'watch', '--failfast', self.charm,
1736- '--state', 'started', '--open-port', self.port)
1737- address = jitsu('get-service-info', self.charm, 'public-address')
1738- return address.strip().split(':')[1]
1739-
1740- def test_api_agent(self):
1741- # Ensure the Juju GUI and API agent services are correctly set up in
1742- # the Juju bootstrap node.
1743- hostname = self.deploy_to()
1744- # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1745- self.addCleanup(
1746- self.stop_services,
1747- hostname, ['haproxy', 'apache2', 'juju-api-agent'])
1748- self.navigate_to(hostname)
1749+ @unittest.skipIf(is_legacy_juju, 'force-machine only works in juju-core')
1750+ def test_force_machine(self):
1751+ # Ensure the Juju GUI is correctly set up in the Juju bootstrap node.
1752+ unit_info = juju_deploy(self.charm, force_machine=0)
1753+ self.assertEqual('0', unit_info['machine'])
1754+ self.navigate_to(unit_info['public-address'])
1755 self.handle_browser_warning()
1756 self.assertEnvironmentIsConnected()
1757
1758
1759=== added file 'tests/deploy.py'
1760--- tests/deploy.py 1970-01-01 00:00:00 +0000
1761+++ tests/deploy.py 2013-07-17 21:35:43 +0000
1762@@ -0,0 +1,80 @@
1763+# This file is part of the Juju GUI, which lets users view and manage Juju
1764+# environments within a graphical interface (https://launchpad.net/juju-gui).
1765+# Copyright (C) 2012-2013 Canonical Ltd.
1766+#
1767+# This program is free software: you can redistribute it and/or modify it under
1768+# the terms of the GNU Affero General Public License version 3, as published by
1769+# the Free Software Foundation.
1770+#
1771+# This program is distributed in the hope that it will be useful, but WITHOUT
1772+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1773+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1774+# Affero General Public License for more details.
1775+#
1776+# You should have received a copy of the GNU Affero General Public License
1777+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1778+
1779+"""Juju GUI deploy helper."""
1780+
1781+from __future__ import print_function
1782+import json
1783+import os
1784+import tempfile
1785+
1786+from charmhelpers import make_charm_config_file
1787+
1788+from helpers import (
1789+ command,
1790+ juju,
1791+ wait_for_unit,
1792+)
1793+
1794+
1795+rsync = command('rsync', '-a', '--exclude', '.bzr', '--exclude', '.venv')
1796+
1797+
1798+def setup_repository(name, source, series='precise'):
1799+ """Create a temporary Juju repository to use for charm deployment.
1800+
1801+ Copy the charm files in source in the precise repository section, using the
1802+ provided charm name and excluding the virtualenv and Bazaar directories.
1803+
1804+ Return the repository path.
1805+ """
1806+ source = os.path.abspath(source) + os.path.sep
1807+ repo = tempfile.mkdtemp()
1808+ destination = os.path.join(repo, series, name)
1809+ os.makedirs(destination)
1810+ rsync(source, destination)
1811+ return repo
1812+
1813+
1814+def juju_deploy(
1815+ charm, options=None, force_machine=None, charm_source=None,
1816+ series='precise'):
1817+ """Deploy and expose the charm. Return the first unit's public address.
1818+
1819+ Also wait until the service is exposed and the first unit started.
1820+ If options are provided, they will be used when deploying the charm.
1821+ If force_machine is not None, create the unit in the specified machine.
1822+ If charm_source is None, dynamically retrieve the charm source directory.
1823+ """
1824+ if charm_source is None:
1825+ # Dynamically retrieve the charm source based on the path of this file.
1826+ charm_source = os.path.join(os.path.dirname(__file__), '..')
1827+ repo = setup_repository(charm, charm_source, series=series)
1828+ args = ['deploy', '--repository', repo]
1829+ if options is not None:
1830+ config_file = make_charm_config_file({charm: options})
1831+ args.extend(['--config', config_file.name])
1832+ if force_machine is not None:
1833+ args.extend(['--force-machine', str(force_machine)])
1834+ args.append('local:{}/{}'.format(series, charm))
1835+ juju(*args)
1836+ juju('expose', charm)
1837+ return wait_for_unit(charm)
1838+
1839+
1840+if __name__ == '__main__':
1841+ unit = juju_deploy('juju-gui')
1842+ print(json.dumps(unit, indent=2))
1843
1844=== added file 'tests/helpers.py'
1845--- tests/helpers.py 1970-01-01 00:00:00 +0000
1846+++ tests/helpers.py 2013-07-17 21:35:43 +0000
1847@@ -0,0 +1,185 @@
1848+# This file is part of the Juju GUI, which lets users view and manage Juju
1849+# environments within a graphical interface (https://launchpad.net/juju-gui).
1850+# Copyright (C) 2012-2013 Canonical Ltd.
1851+#
1852+# This program is free software: you can redistribute it and/or modify it under
1853+# the terms of the GNU Affero General Public License version 3, as published by
1854+# the Free Software Foundation.
1855+#
1856+# This program is distributed in the hope that it will be useful, but WITHOUT
1857+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1858+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1859+# Affero General Public License for more details.
1860+#
1861+# You should have received a copy of the GNU Affero General Public License
1862+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1863+
1864+"""Juju GUI test helpers."""
1865+
1866+from collections import namedtuple
1867+from functools import wraps
1868+import json
1869+import os
1870+import re
1871+import subprocess
1872+import time
1873+
1874+
1875+class ProcessError(subprocess.CalledProcessError):
1876+ """Error running a shell command."""
1877+
1878+ def __init__(self, retcode, cmd, output, error):
1879+ super(ProcessError, self).__init__(retcode, cmd, output)
1880+ self.error = error
1881+
1882+ def __str__(self):
1883+ msg = super(ProcessError, self).__str__()
1884+ return '{}. Output: {!r}. Error: {!r}.'.format(
1885+ msg, self.output, self.error)
1886+
1887+
1888+def command(*base_args):
1889+ """Return a callable that will run the given command with any arguments.
1890+
1891+ The first argument is the path to the command to run, subsequent arguments
1892+ are command-line arguments to "bake into" the returned callable.
1893+
1894+ The callable runs the given executable and also takes arguments that will
1895+ be appended to the "baked in" arguments.
1896+
1897+ For example, this code will list a file named "foo" (if it exists):
1898+
1899+ ls_foo = command('/bin/ls', 'foo')
1900+ ls_foo()
1901+
1902+ While this invocation will list "foo" and "bar" (assuming they exist):
1903+
1904+ ls_foo('bar')
1905+ """
1906+ PIPE = subprocess.PIPE
1907+
1908+ def runner(*args, **kwargs):
1909+ cmd = base_args + args
1910+ process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE, **kwargs)
1911+ output, error = process.communicate()
1912+ retcode = process.poll()
1913+ if retcode:
1914+ raise ProcessError(retcode, cmd, output, error)
1915+ return output
1916+
1917+ return runner
1918+
1919+
1920+juju_command = command('juju')
1921+juju_env = lambda: os.getenv('JUJU_ENV') # This is propagated by juju-test.
1922+ssh = command('ssh')
1923+Version = namedtuple('Version', 'major minor patch')
1924+
1925+
1926+def retry(exception, tries=10, delay=1):
1927+ """If the decorated function raises the exception, wait and try it again.
1928+
1929+ Raise the exception raised by the last call if the function does not
1930+ exit normally after the specified number of tries.
1931+
1932+ Original from http://wiki.python.org/moin/PythonDecoratorLibrary#Retry.
1933+ """
1934+ def decorator(func):
1935+ @wraps(func)
1936+ def decorated(*args, **kwargs):
1937+ mtries = tries
1938+ while mtries:
1939+ try:
1940+ return func(*args, **kwargs)
1941+ except exception as err:
1942+ time.sleep(delay)
1943+ mtries -= 1
1944+ raise err
1945+ return decorated
1946+ return decorator
1947+
1948+
1949+@retry(ProcessError)
1950+def juju(command, *args):
1951+ """Call the juju command, passing the environment parameters if required.
1952+
1953+ The environment value can be provided in args, or can be found in the
1954+ context as JUJU_ENV.
1955+ """
1956+ arguments = [command]
1957+ if ('-e' not in args) and ('--environment' not in args):
1958+ env = juju_env()
1959+ if env is not None:
1960+ arguments.extend(['-e', env])
1961+ arguments.extend(args)
1962+ return juju_command(*arguments)
1963+
1964+
1965+def juju_destroy_service(service):
1966+ """Destroy the given service and wait for the service to be removed."""
1967+ juju('destroy-service', service)
1968+ while True:
1969+ services = juju_status().get('services', {})
1970+ if service not in services:
1971+ return
1972+
1973+
1974+def juju_status():
1975+ """Return the Juju status as a dictionary."""
1976+ status = juju('status', '--format', 'json')
1977+ return json.loads(status)
1978+
1979+
1980+_juju_version_expression = re.compile(r"""
1981+ ^ # Beginning of line.
1982+ (?:juju\s+)? # Optional juju prefix.
1983+ (\d+)\.(\d+) # Major and minor versions.
1984+ (?:\.(\d+))? # Optional patch version.
1985+ .* # Optional suffix.
1986+ $ # End of line.
1987+""", re.VERBOSE)
1988+
1989+
1990+def juju_version():
1991+ """Return the currently used Juju version.
1992+
1993+ The version is returned as a named tuple (major, minor, patch).
1994+ If the patch number is missing, it is set to zero.
1995+ """
1996+ try:
1997+ # In pyJuju, version info is printed to stderr.
1998+ output = subprocess.check_output(
1999+ ['juju', '--version'], stderr=subprocess.STDOUT)
2000+ except subprocess.CalledProcessError:
2001+ # Current juju-core exposes a version subcommand.
2002+ output = subprocess.check_output(['juju', 'version'])
2003+ match = _juju_version_expression.match(output)
2004+ if match is None:
2005+ raise ValueError('invalid juju version: {!r}'.format(output))
2006+ to_int = lambda num: 0 if num is None else int(num)
2007+ return Version._make(map(to_int, match.groups()))
2008+
2009+
2010+def wait_for_unit(sevice):
2011+ """Wait for the first unit of the given service to be started.
2012+
2013+ Also wait for the service to be exposed.
2014+ Raise a RuntimeError if the unit is found in an error state.
2015+ Return info about the first unit as a dict containing at least the
2016+ following keys: agent-state, machine, and public-address.
2017+ """
2018+ while True:
2019+ status = juju_status()
2020+ service = status.get('services', {}).get(sevice)
2021+ if service is None or not service.get('exposed'):
2022+ continue
2023+ units = service.get('units', {})
2024+ if not len(units):
2025+ continue
2026+ unit = units.values()[0]
2027+ state = unit['agent-state']
2028+ if 'error' in state:
2029+ raise RuntimeError(
2030+ 'the service unit is in an error state: {}'.format(state))
2031+ if state == 'started':
2032+ return unit
2033
2034=== added file 'tests/requirements.pip'
2035--- tests/requirements.pip 1970-01-01 00:00:00 +0000
2036+++ tests/requirements.pip 2013-07-17 21:35:43 +0000
2037@@ -0,0 +1,28 @@
2038+# Juju GUI test requirements.
2039+
2040+# This file is part of the Juju GUI, which lets users view and manage Juju
2041+# environments within a graphical interface (https://launchpad.net/juju-gui).
2042+# Copyright (C) 2012-2013 Canonical Ltd.
2043+#
2044+# This program is free software: you can redistribute it and/or modify it under
2045+# the terms of the GNU Affero General Public License version 3, as published by
2046+# the Free Software Foundation.
2047+#
2048+# This program is distributed in the hope that it will be useful, but WITHOUT
2049+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2050+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2051+# Affero General Public License for more details.
2052+#
2053+# You should have received a copy of the GNU Affero General Public License
2054+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2055+
2056+-e bzr+http://launchpad.net/charm-tools#egg=charm-tools
2057+flake8==2.0
2058+launchpadlib==1.10.2
2059+mock==1.0.1
2060+python-apt==0.8.5
2061+-e bzr+http://launchpad.net/python-shelltoolbox#egg=python-shelltoolbox
2062+PyYAML==3.10
2063+selenium==2.33.0
2064+Tempita==0.5.1
2065+xvfbwrapper==0.2.2
2066
2067=== modified file 'tests/test_backends.py'
2068--- tests/test_backends.py 2013-05-02 16:51:14 +0000
2069+++ tests/test_backends.py 2013-07-17 21:35:43 +0000
2070@@ -1,3 +1,19 @@
2071+# This file is part of the Juju GUI, which lets users view and manage Juju
2072+# environments within a graphical interface (https://launchpad.net/juju-gui).
2073+# Copyright (C) 2012-2013 Canonical Ltd.
2074+#
2075+# This program is free software: you can redistribute it and/or modify it under
2076+# the terms of the GNU Affero General Public License version 3, as published by
2077+# the Free Software Foundation.
2078+#
2079+# This program is distributed in the hope that it will be useful, but WITHOUT
2080+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2081+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2082+# Affero General Public License for more details.
2083+#
2084+# You should have received a copy of the GNU Affero General Public License
2085+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2086+
2087 from collections import defaultdict
2088 from contextlib import contextmanager
2089 import os
2090@@ -34,7 +50,7 @@
2091 frozenset(('apache2', 'curl', 'haproxy', 'openssl', 'zookeeper')),
2092 test_backend.debs)
2093 self.assertEqual(
2094- frozenset(('ppa:juju-gui/ppa',)),
2095+ frozenset(()),
2096 test_backend.repositories)
2097 self.assertEqual(
2098 frozenset(('haproxy.conf',)),
2099@@ -51,7 +67,7 @@
2100 frozenset(('apache2', 'curl', 'haproxy', 'openssl')),
2101 test_backend.debs)
2102 self.assertEqual(
2103- frozenset(('ppa:juju-gui/ppa',)),
2104+ frozenset(()),
2105 test_backend.repositories)
2106 self.assertEqual(
2107 frozenset(('haproxy.conf',)),
2108@@ -68,7 +84,7 @@
2109 frozenset(('apache2', 'curl', 'haproxy', 'openssl')),
2110 test_backend.debs)
2111 self.assertEqual(
2112- frozenset(('ppa:juju-gui/ppa',)),
2113+ frozenset(()),
2114 test_backend.repositories)
2115 self.assertEqual(
2116 frozenset(('haproxy.conf',)),
2117@@ -97,7 +113,7 @@
2118 ('apache2', 'curl', 'haproxy', 'openssl', 'python-yaml')),
2119 test_backend.debs)
2120 self.assertEqual(
2121- frozenset(('ppa:juju-gui/ppa',)),
2122+ frozenset(()),
2123 test_backend.repositories)
2124 self.assertEqual(
2125 frozenset(('haproxy.conf',)),
2126@@ -192,14 +208,16 @@
2127 def test_start_agent(self):
2128 test_backend = backend.Backend(config=self.alwaysFalse)
2129 test_backend.start()
2130- for mocked in ('service_control', 'start_agent', 'start_gui',
2131+ for mocked in (
2132+ 'service_control', 'start_agent', 'start_gui',
2133 'open_port', 'su'):
2134 self.assertTrue(mocked, '{} was not called'.format(mocked))
2135
2136 def test_start_improv(self):
2137 test_backend = backend.Backend(config=self.alwaysTrue)
2138 test_backend.start()
2139- for mocked in ('service_control', 'start_improv', 'start_gui',
2140+ for mocked in (
2141+ 'service_control', 'start_improv', 'start_gui',
2142 'open_port', 'su'):
2143 self.assertTrue(mocked, '{} was not called'.format(mocked))
2144
2145
2146=== added file 'tests/test_deploy.py'
2147--- tests/test_deploy.py 1970-01-01 00:00:00 +0000
2148+++ tests/test_deploy.py 2013-07-17 21:35:43 +0000
2149@@ -0,0 +1,196 @@
2150+# This file is part of the Juju GUI, which lets users view and manage Juju
2151+# environments within a graphical interface (https://launchpad.net/juju-gui).
2152+# Copyright (C) 2012-2013 Canonical Ltd.
2153+#
2154+# This program is free software: you can redistribute it and/or modify it under
2155+# the terms of the GNU Affero General Public License version 3, as published by
2156+# the Free Software Foundation.
2157+#
2158+# This program is distributed in the hope that it will be useful, but WITHOUT
2159+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2160+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2161+# Affero General Public License for more details.
2162+#
2163+# You should have received a copy of the GNU Affero General Public License
2164+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2165+
2166+"""Juju GUI deploy module tests."""
2167+
2168+import os
2169+import shutil
2170+import tempfile
2171+import unittest
2172+
2173+import mock
2174+
2175+from deploy import (
2176+ juju_deploy,
2177+ setup_repository,
2178+)
2179+
2180+
2181+class TestSetupRepository(unittest.TestCase):
2182+
2183+ name = 'test-charm'
2184+
2185+ def setUp(self):
2186+ # Create a directory structure for the charm source.
2187+ self.source = tempfile.mkdtemp()
2188+ self.addCleanup(shutil.rmtree, self.source)
2189+ # Create a file in the source dir.
2190+ _, self.root_file = tempfile.mkstemp(dir=self.source)
2191+ # Create a Bazaar repository directory with a file in it.
2192+ bzr_dir = os.path.join(self.source, '.bzr')
2193+ os.mkdir(bzr_dir)
2194+ tempfile.mkstemp(dir=bzr_dir)
2195+ # Create a tests directory including a .venv directory and a file.
2196+ self.tests_dir = os.path.join(self.source, 'tests')
2197+ venv_dir = os.path.join(self.tests_dir, '.venv')
2198+ os.makedirs(venv_dir)
2199+ tempfile.mkstemp(dir=venv_dir)
2200+ # Create a test file.
2201+ _, self.tests_file = tempfile.mkstemp(dir=self.tests_dir)
2202+
2203+ def assert_dir_exists(self, path):
2204+ self.assertTrue(
2205+ os.path.isdir(path),
2206+ 'the directory {!r} does not exist'.format(path))
2207+
2208+ def assert_files_equal(self, expected, path):
2209+ fileset = set()
2210+ for dirpath, _, filenames in os.walk(path):
2211+ relpath = os.path.relpath(dirpath, path)
2212+ if relpath == '.':
2213+ relpath = ''
2214+ else:
2215+ fileset.add(relpath + os.path.sep)
2216+ fileset.update(os.path.join(relpath, name) for name in filenames)
2217+ self.assertEqual(expected, fileset)
2218+
2219+ def check_repository(self, repo, series):
2220+ # The repository has been created in the temp directory.
2221+ self.assertEqual(tempfile.tempdir, os.path.split(repo)[0])
2222+ self.assert_dir_exists(repo)
2223+ # The repository only contains the series directory.
2224+ self.assertEqual([series], os.listdir(repo))
2225+ series_dir = os.path.join(repo, series)
2226+ self.assert_dir_exists(series_dir)
2227+ # The series directory only contains our charm.
2228+ self.assertEqual([self.name], os.listdir(series_dir))
2229+ self.assert_dir_exists(os.path.join(series_dir, self.name))
2230+
2231+ def test_repository(self):
2232+ # The charm repository is correctly created with the default series.
2233+ repo = setup_repository(self.name, self.source)
2234+ self.check_repository(repo, 'precise')
2235+
2236+ def test_series(self):
2237+ # The charm repository is created with the given series.
2238+ repo = setup_repository(self.name, self.source, series='raring')
2239+ self.check_repository(repo, 'raring')
2240+
2241+ def test_charm_files(self):
2242+ # The charm files are correctly copied inside the repository, excluding
2243+ # unwanted directories.
2244+ repo = setup_repository(self.name, self.source)
2245+ charm_dir = os.path.join(repo, 'precise', self.name)
2246+ test_dir_name = os.path.basename(self.tests_dir)
2247+ expected = set([
2248+ os.path.basename(self.root_file),
2249+ test_dir_name + os.path.sep,
2250+ os.path.join(test_dir_name, os.path.basename(self.tests_file))
2251+ ])
2252+ self.assert_files_equal(expected, charm_dir)
2253+
2254+
2255+class TestJujuDeploy(unittest.TestCase):
2256+
2257+ unit_info = {'public-address': 'unit.example.com'}
2258+ charm = 'test-charm'
2259+ expose_call = mock.call('expose', charm)
2260+ local_charm = 'local:precise/{}'.format(charm)
2261+ repo = '/tmp/repo/'
2262+
2263+ @mock.patch('deploy.juju')
2264+ @mock.patch('deploy.wait_for_unit')
2265+ @mock.patch('deploy.setup_repository')
2266+ def call_deploy(
2267+ self, mock_setup_repository, mock_wait_for_unit, mock_juju,
2268+ options=None, force_machine=None, charm_source=None,
2269+ series='precise'):
2270+ mock_setup_repository.return_value = self.repo
2271+ mock_wait_for_unit.return_value = self.unit_info
2272+ if charm_source is None:
2273+ expected_source = os.path.join(os.path.dirname(__file__), '..')
2274+ else:
2275+ expected_source = charm_source
2276+ unit_info = juju_deploy(
2277+ self.charm, options=options, force_machine=force_machine,
2278+ charm_source=charm_source, series=series)
2279+ mock_setup_repository.assert_called_once_with(
2280+ self.charm, expected_source, series=series)
2281+ # The unit address is correctly returned.
2282+ self.assertEqual(self.unit_info, unit_info)
2283+ self.assertEqual(1, mock_wait_for_unit.call_count)
2284+ # Juju is called two times: deploy and expose.
2285+ juju_calls = mock_juju.call_args_list
2286+ self.assertEqual(2, len(juju_calls))
2287+ deploy_call, expose_call = juju_calls
2288+ self.assertEqual(self.expose_call, expose_call)
2289+ return deploy_call
2290+
2291+ def test_deployment(self):
2292+ # The function deploys and exposes the given charm.
2293+ expected_deploy_call = mock.call(
2294+ 'deploy',
2295+ '--repository', self.repo,
2296+ self.local_charm,
2297+ )
2298+ deploy_call = self.call_deploy()
2299+ self.assertEqual(expected_deploy_call, deploy_call)
2300+
2301+ def test_options(self):
2302+ # The function handles charm options.
2303+ mock_config_file = mock.Mock()
2304+ mock_config_file.name = '/tmp/config.yaml'
2305+ expected_deploy_call = mock.call(
2306+ 'deploy',
2307+ '--repository', self.repo,
2308+ '--config', mock_config_file.name,
2309+ self.local_charm,
2310+ )
2311+ with mock.patch('deploy.make_charm_config_file') as mock_callable:
2312+ mock_callable.return_value = mock_config_file
2313+ deploy_call = self.call_deploy(options={'foo': 'bar'})
2314+ self.assertEqual(expected_deploy_call, deploy_call)
2315+
2316+ def test_force_machine(self):
2317+ # The function can deploy charms in a specified machine.
2318+ expected_deploy_call = mock.call(
2319+ 'deploy',
2320+ '--repository', self.repo,
2321+ '--force-machine', '42',
2322+ self.local_charm,
2323+ )
2324+ deploy_call = self.call_deploy(force_machine=42)
2325+ self.assertEqual(expected_deploy_call, deploy_call)
2326+
2327+ def test_charm_source(self):
2328+ # The function can deploy a charm from a specific source.
2329+ expected_deploy_call = mock.call(
2330+ 'deploy',
2331+ '--repository', self.repo,
2332+ self.local_charm,
2333+ )
2334+ deploy_call = self.call_deploy(charm_source='/tmp/source/')
2335+ self.assertEqual(expected_deploy_call, deploy_call)
2336+
2337+ def test_series(self):
2338+ # The function can deploy a charm from a specific series.
2339+ expected_deploy_call = mock.call(
2340+ 'deploy',
2341+ '--repository', self.repo,
2342+ 'local:raring/{}'.format(self.charm)
2343+ )
2344+ deploy_call = self.call_deploy(series='raring')
2345+ self.assertEqual(expected_deploy_call, deploy_call)
2346
2347=== added file 'tests/test_helpers.py'
2348--- tests/test_helpers.py 1970-01-01 00:00:00 +0000
2349+++ tests/test_helpers.py 2013-07-17 21:35:43 +0000
2350@@ -0,0 +1,397 @@
2351+# This file is part of the Juju GUI, which lets users view and manage Juju
2352+# environments within a graphical interface (https://launchpad.net/juju-gui).
2353+# Copyright (C) 2012-2013 Canonical Ltd.
2354+#
2355+# This program is free software: you can redistribute it and/or modify it under
2356+# the terms of the GNU Affero General Public License version 3, as published by
2357+# the Free Software Foundation.
2358+#
2359+# This program is distributed in the hope that it will be useful, but WITHOUT
2360+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2361+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2362+# Affero General Public License for more details.
2363+#
2364+# You should have received a copy of the GNU Affero General Public License
2365+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2366+
2367+"""Juju GUI helpers tests."""
2368+
2369+import json
2370+import subprocess
2371+import unittest
2372+
2373+import mock
2374+
2375+from helpers import (
2376+ command,
2377+ juju,
2378+ juju_destroy_service,
2379+ juju_env,
2380+ juju_status,
2381+ juju_version,
2382+ ProcessError,
2383+ retry,
2384+ Version,
2385+ wait_for_unit,
2386+)
2387+
2388+
2389+class TestCommand(unittest.TestCase):
2390+
2391+ def test_simple_command(self):
2392+ # Creating a simple command (ls) works and running the command
2393+ # produces a string.
2394+ ls = command('/bin/ls')
2395+ self.assertIsInstance(ls(), str)
2396+
2397+ def test_arguments(self):
2398+ # Arguments can be passed to commands.
2399+ ls = command('/bin/ls')
2400+ self.assertIn('Usage:', ls('--help'))
2401+
2402+ def test_missing(self):
2403+ # If the command does not exist, an OSError (No such file or
2404+ # directory) is raised.
2405+ bad = command('this command does not exist')
2406+ with self.assertRaises(OSError) as info:
2407+ bad()
2408+ self.assertEqual(2, info.exception.errno)
2409+
2410+ def test_error(self):
2411+ # If the command returns a non-zero exit code, an exception is raised.
2412+ bad = command('/bin/ls', '--not a valid switch')
2413+ self.assertRaises(ProcessError, bad)
2414+
2415+ def test_baked_in_arguments(self):
2416+ # Arguments can be passed when creating the command as well as when
2417+ # executing it.
2418+ ll = command('/bin/ls', '-al')
2419+ self.assertIn('rw', ll()) # Assumes a file is r/w in the pwd.
2420+ self.assertIn('Usage:', ll('--help'))
2421+
2422+ def test_quoting(self):
2423+ # There is no need to quote special shell characters in commands.
2424+ ls = command('/bin/ls')
2425+ ls('--help', '>')
2426+
2427+
2428+@mock.patch('helpers.juju_command')
2429+class TestJuju(unittest.TestCase):
2430+
2431+ env = 'test-env'
2432+ patch_environ = mock.patch('os.environ', {'JUJU_ENV': env})
2433+ process_error = ProcessError(1, 'an error occurred', 'output', 'error')
2434+
2435+ def test_e_in_args(self, mock_juju_command):
2436+ # The command includes the environment if provided with -e.
2437+ with self.patch_environ:
2438+ juju('deploy', '-e', 'another-env', 'test-charm')
2439+ mock_juju_command.assert_called_once_with(
2440+ 'deploy', '-e', 'another-env', 'test-charm')
2441+
2442+ def test_environment_in_args(self, mock_juju_command):
2443+ # The command includes the environment if provided with --environment.
2444+ with self.patch_environ:
2445+ juju('deploy', '--environment', 'another-env', 'test-charm')
2446+ mock_juju_command.assert_called_once_with(
2447+ 'deploy', '--environment', 'another-env', 'test-charm')
2448+
2449+ def test_environment_in_context(self, mock_juju_command):
2450+ # The command includes the environment if found in the context as
2451+ # the environment variable JUJU_ENV.
2452+ with self.patch_environ:
2453+ juju('deploy', 'test-charm')
2454+ mock_juju_command.assert_called_once_with(
2455+ 'deploy', '-e', self.env, 'test-charm')
2456+
2457+ def test_environment_not_in_context(self, mock_juju_command):
2458+ # The command does not include the environment if not found in the
2459+ # context as the environment variable JUJU_ENV.
2460+ with mock.patch('os.environ', {}):
2461+ juju('deploy', 'test-charm')
2462+ mock_juju_command.assert_called_once_with('deploy', 'test-charm')
2463+
2464+ def test_handle_process_errors(self, mock_juju_command):
2465+ # The command retries several times before failing if a ProcessError is
2466+ # raised.
2467+ mock_juju_command.side_effect = ([self.process_error] * 9) + ['value']
2468+ with mock.patch('time.sleep') as mock_sleep:
2469+ with mock.patch('os.environ', {}):
2470+ result = juju('deploy', 'test-charm')
2471+ self.assertEqual('value', result)
2472+ self.assertEqual(10, mock_juju_command.call_count)
2473+ mock_juju_command.assert_called_with('deploy', 'test-charm')
2474+ self.assertEqual(9, mock_sleep.call_count)
2475+ mock_sleep.assert_called_with(1)
2476+
2477+ def test_raise_process_errors(self, mock_juju_command):
2478+ # The command raises the last ProcessError after a number of retries.
2479+ mock_juju_command.side_effect = [self.process_error] * 10
2480+ with mock.patch('time.sleep') as mock_sleep:
2481+ with mock.patch('os.environ', {}):
2482+ with self.assertRaises(ProcessError) as info:
2483+ juju('deploy', 'test-charm')
2484+ self.assertIs(self.process_error, info.exception)
2485+ self.assertEqual(10, mock_juju_command.call_count)
2486+ mock_juju_command.assert_called_with('deploy', 'test-charm')
2487+ self.assertEqual(10, mock_sleep.call_count)
2488+ mock_sleep.assert_called_with(1)
2489+
2490+
2491+@mock.patch('helpers.juju')
2492+@mock.patch('helpers.juju_status')
2493+class TestJujuDestroyService(unittest.TestCase):
2494+
2495+ service = 'test-service'
2496+
2497+ def test_service_destroyed(self, mock_juju_status, mock_juju):
2498+ # The juju destroy-service command is correctly called.
2499+ mock_juju_status.return_value = {}
2500+ juju_destroy_service(self.service)
2501+ self.assertEqual(1, mock_juju_status.call_count)
2502+ mock_juju.assert_called_once_with('destroy-service', self.service)
2503+
2504+ def test_wait_until_removed(self, mock_juju_status, mock_juju):
2505+ # The function waits for the service to be removed.
2506+ mock_juju_status.side_effect = (
2507+ {'services': {self.service: {}, 'another-service': {}}},
2508+ {'services': {'another-service': {}}},
2509+ )
2510+ juju_destroy_service(self.service)
2511+ self.assertEqual(2, mock_juju_status.call_count)
2512+ mock_juju.assert_called_once_with('destroy-service', self.service)
2513+
2514+
2515+class TestJujuEnv(unittest.TestCase):
2516+
2517+ def test_env_in_context(self):
2518+ # The function returns the juju env if found in the execution context.
2519+ with mock.patch('os.environ', {'JUJU_ENV': 'test-env'}):
2520+ self.assertEqual('test-env', juju_env())
2521+
2522+ def test_env_not_in_context(self):
2523+ # The function returns None if JUJU_ENV is not included in the context.
2524+ with mock.patch('os.environ', {}):
2525+ self.assertIsNone(juju_env())
2526+
2527+
2528+class TestJujuStatus(unittest.TestCase):
2529+
2530+ status = {
2531+ 'machines': {
2532+ '0': {'agent-state': 'running', 'dns-name': 'ec2.example.com'},
2533+ },
2534+ 'services': {
2535+ 'juju-gui': {'charm': 'cs:precise/juju-gui-48', 'exposed': True},
2536+ },
2537+ }
2538+
2539+ @mock.patch('helpers.juju')
2540+ def test_status(self, mock_juju):
2541+ # The function returns the unserialized juju status.
2542+ mock_juju.return_value = json.dumps(self.status)
2543+ status = juju_status()
2544+ self.assertEqual(self.status, status)
2545+ mock_juju.assert_called_once_with('status', '--format', 'json')
2546+
2547+
2548+@mock.patch('subprocess.check_output')
2549+class TestJujuVersion(unittest.TestCase):
2550+
2551+ error = subprocess.CalledProcessError(2, 'invalid flag', 'output')
2552+
2553+ def test_pyjuju(self, mock_check_output):
2554+ # The pyJuju version is correctly retrieved.
2555+ mock_check_output.return_value = '0.7.2'
2556+ version = juju_version()
2557+ self.assertEqual(Version(0, 7, 2), version)
2558+ mock_check_output.assert_called_once_with(
2559+ ['juju', '--version'], stderr=subprocess.STDOUT,
2560+ )
2561+
2562+ def test_juju_core(self, mock_check_output):
2563+ # The juju-core version is correctly retrieved.
2564+ mock_check_output.side_effect = (self.error, '1.12.3')
2565+ version = juju_version()
2566+ self.assertEqual(Version(1, 12, 3), version)
2567+ self.assertEqual(2, mock_check_output.call_count)
2568+ first_call, second_call = mock_check_output.call_args_list
2569+ self.assertEqual(
2570+ mock.call(['juju', '--version'], stderr=subprocess.STDOUT),
2571+ first_call,
2572+ )
2573+ self.assertEqual(mock.call(['juju', 'version']), second_call)
2574+
2575+ def test_not_semantic_versioning(self, mock_check_output):
2576+ # If the patch number is missing, it is set to zero.
2577+ mock_check_output.return_value = '0.7'
2578+ version = juju_version()
2579+ self.assertEqual(Version(0, 7, 0), version)
2580+
2581+ def test_prefix(self, mock_check_output):
2582+ # The function handles versions returned as "juju x.y.z".
2583+ mock_check_output.return_value = 'juju 0.8.3'
2584+ version = juju_version()
2585+ self.assertEqual(Version(0, 8, 3), version)
2586+
2587+ def test_suffix(self, mock_check_output):
2588+ # The function handles versions returned as "x.y.z-series-arch".
2589+ mock_check_output.return_value = '1.10.3-raring-amd64'
2590+ version = juju_version()
2591+ self.assertEqual(Version(1, 10, 3), version)
2592+
2593+ def test_all(self, mock_check_output):
2594+ # Additional information is correctly handled.
2595+ mock_check_output.side_effect = (self.error, 'juju 1.234-precise-i386')
2596+ version = juju_version()
2597+ self.assertEqual(Version(1, 234, 0), version)
2598+ self.assertEqual(2, mock_check_output.call_count)
2599+
2600+ def test_invalid_version(self, mock_check_output):
2601+ # A ValueError is raised if the returned version is not valid.
2602+ mock_check_output.return_value = '42'
2603+ with self.assertRaises(ValueError) as info:
2604+ juju_version()
2605+ self.assertEqual("invalid juju version: '42'", str(info.exception))
2606+
2607+ def test_failure(self, mock_check_output):
2608+ # A CalledProcessError is raised if the Juju version cannot be found.
2609+ mock_check_output.side_effect = (self.error, self.error)
2610+ with self.assertRaises(subprocess.CalledProcessError) as info:
2611+ juju_version()
2612+ self.assertIs(self.error, info.exception)
2613+ self.assertEqual(2, mock_check_output.call_count)
2614+
2615+
2616+class TestProcessError(unittest.TestCase):
2617+
2618+ def test_str(self):
2619+ # The string representation of the error includes required info.
2620+ err = ProcessError(1, 'mycommand', 'myoutput', 'myerror')
2621+ expected = (
2622+ "Command 'mycommand' returned non-zero exit status 1. "
2623+ "Output: 'myoutput'. Error: 'myerror'."
2624+ )
2625+ self.assertEqual(expected, str(err))
2626+
2627+
2628+@mock.patch('time.sleep')
2629+class TestRetry(unittest.TestCase):
2630+
2631+ retry_type_error = retry(TypeError, tries=10, delay=1)
2632+ result = 'my value'
2633+
2634+ def make_callable(self, side_effect):
2635+ mock_callable = mock.Mock()
2636+ mock_callable.side_effect = side_effect
2637+ mock_callable.__name__ = 'mock_callable' # Required by wraps.
2638+ decorated = retry(TypeError, tries=5, delay=1)(mock_callable)
2639+ return mock_callable, decorated
2640+
2641+ def test_immediate_success(self, mock_sleep):
2642+ # The decorated function returns without retrying if no errors occur.
2643+ mock_callable, decorated = self.make_callable([self.result])
2644+ result = decorated()
2645+ self.assertEqual(self.result, result)
2646+ self.assertEqual(1, mock_callable.call_count)
2647+ self.assertFalse(mock_sleep.called)
2648+
2649+ def test_success(self, mock_sleep):
2650+ # The decorated function returns without errors after several tries.
2651+ side_effect = ([TypeError] * 4) + [self.result]
2652+ mock_callable, decorated = self.make_callable(side_effect)
2653+ result = decorated()
2654+ self.assertEqual(self.result, result)
2655+ self.assertEqual(5, mock_callable.call_count)
2656+ self.assertEqual(4, mock_sleep.call_count)
2657+ mock_sleep.assert_called_with(1)
2658+
2659+ def test_failure(self, mock_sleep):
2660+ # The decorated function raises the last error.
2661+ mock_callable, decorated = self.make_callable([TypeError] * 5)
2662+ self.assertRaises(TypeError, decorated)
2663+ self.assertEqual(5, mock_callable.call_count)
2664+ self.assertEqual(5, mock_sleep.call_count)
2665+ mock_sleep.assert_called_with(1)
2666+
2667+
2668+@mock.patch('helpers.juju_status')
2669+class TestWaitForService(unittest.TestCase):
2670+
2671+ address = 'unit.example.com'
2672+ service = 'test-service'
2673+
2674+ def get_status(self, state='started', exposed=True, unit=0):
2675+ """Return a dict-like Juju status."""
2676+ unit_name = '{}/{}'.format(self.service, unit)
2677+ return {
2678+ 'services': {
2679+ self.service: {
2680+ 'exposed': exposed,
2681+ 'units': {
2682+ unit_name: {
2683+ 'agent-state': state,
2684+ 'public-address': self.address,
2685+ }
2686+ },
2687+ },
2688+ },
2689+ }
2690+
2691+ def test_service_not_deployed(self, mock_juju_status):
2692+ # The function waits until the service is deployed.
2693+ mock_juju_status.side_effect = (
2694+ {}, {'services': {}}, self.get_status(),
2695+ )
2696+ unit_info = wait_for_unit(self.service)
2697+ self.assertEqual(self.address, unit_info['public-address'])
2698+ self.assertEqual(3, mock_juju_status.call_count)
2699+
2700+ def test_service_not_exposed(self, mock_juju_status):
2701+ # The function waits until the service is exposed.
2702+ mock_juju_status.side_effect = (
2703+ self.get_status(exposed=False), self.get_status(),
2704+ )
2705+ unit_info = wait_for_unit(self.service)
2706+ self.assertEqual(self.address, unit_info['public-address'])
2707+ self.assertEqual(2, mock_juju_status.call_count)
2708+
2709+ def test_unit_not_ready(self, mock_juju_status):
2710+ # The function waits until the unit is created.
2711+ mock_juju_status.side_effect = (
2712+ {'services': {'juju-gui': {}}},
2713+ {'services': {'juju-gui': {'units': {}}}},
2714+ self.get_status(),
2715+ )
2716+ unit_info = wait_for_unit(self.service)
2717+ self.assertEqual(self.address, unit_info['public-address'])
2718+ self.assertEqual(3, mock_juju_status.call_count)
2719+
2720+ def test_state_error(self, mock_juju_status):
2721+ # An error is raised if the unit is in an error state.
2722+ mock_juju_status.return_value = self.get_status(state='install-error')
2723+ self.assertRaises(RuntimeError, wait_for_unit, self.service)
2724+ self.assertEqual(1, mock_juju_status.call_count)
2725+
2726+ def test_not_started(self, mock_juju_status):
2727+ # The function waits until the unit is in a started state.
2728+ mock_juju_status.side_effect = (
2729+ self.get_status(state='pending'), self.get_status(),
2730+ )
2731+ unit_info = wait_for_unit(self.service)
2732+ self.assertEqual(self.address, unit_info['public-address'])
2733+ self.assertEqual(2, mock_juju_status.call_count)
2734+
2735+ def test_unit_number(self, mock_juju_status):
2736+ # Different unit names are correctly handled.
2737+ mock_juju_status.return_value = self.get_status(unit=42)
2738+ unit_info = wait_for_unit(self.service)
2739+ self.assertEqual(self.address, unit_info['public-address'])
2740+ self.assertEqual(1, mock_juju_status.call_count)
2741+
2742+ def test_public_address(self, mock_juju_status):
2743+ # The public address is returned when the service is ready.
2744+ mock_juju_status.return_value = self.get_status()
2745+ unit_info = wait_for_unit(self.service)
2746+ self.assertEqual(self.address, unit_info['public-address'])
2747+ self.assertEqual(1, mock_juju_status.call_count)
2748
2749=== modified file 'tests/test_utils.py'
2750--- tests/test_utils.py 2013-07-11 19:10:30 +0000
2751+++ tests/test_utils.py 2013-07-17 21:35:43 +0000
2752@@ -1,14 +1,31 @@
2753-#!/usr/bin/env python2
2754+# This file is part of the Juju GUI, which lets users view and manage Juju
2755+# environments within a graphical interface (https://launchpad.net/juju-gui).
2756+# Copyright (C) 2012-2013 Canonical Ltd.
2757+#
2758+# This program is free software: you can redistribute it and/or modify it under
2759+# the terms of the GNU Affero General Public License version 3, as published by
2760+# the Free Software Foundation.
2761+#
2762+# This program is distributed in the hope that it will be useful, but WITHOUT
2763+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2764+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2765+# Affero General Public License for more details.
2766+#
2767+# You should have received a copy of the GNU Affero General Public License
2768+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2769+
2770+"""Juju GUI utils tests."""
2771
2772 from contextlib import contextmanager
2773+import json
2774 import os
2775 import shutil
2776-from simplejson import dumps
2777 from subprocess import CalledProcessError
2778 import tempfile
2779 import unittest
2780
2781 import charmhelpers
2782+from shelltoolbox import environ
2783 import tempita
2784 import yaml
2785
2786@@ -86,28 +103,70 @@
2787
2788 class TestGetApiAddress(unittest.TestCase):
2789
2790- def setUp(self):
2791- self.base_dir = tempfile.mkdtemp()
2792- self.addCleanup(shutil.rmtree, self.base_dir)
2793- self.unit_dir = tempfile.mkdtemp(dir=self.base_dir)
2794- self.machine_dir = os.path.join(self.base_dir, 'machine-1')
2795-
2796- def test_retrieving_address(self):
2797- # The API address is correctly returned.
2798- address = 'example.com:17070'
2799- os.mkdir(self.machine_dir)
2800- with open(os.path.join(self.machine_dir, 'agent.conf'), 'w') as conf:
2801- yaml.dump({'apiinfo': {'addrs': [address]}}, conf)
2802- self.assertEqual(address, get_api_address(self.unit_dir))
2803-
2804- def test_missing_file(self):
2805+ env_address = 'env.example.com:17070'
2806+ agent_address = 'agent.example.com:17070'
2807+
2808+ @contextmanager
2809+ def agent_file(self, addresses=None):
2810+ """Set up a directory structure similar to the one created by juju.
2811+
2812+ If addresses are provided, also create a machiner directory and an
2813+ agent file containing the addresses.
2814+ Remove the directory structure when exiting from the context manager.
2815+ """
2816+ base_dir = tempfile.mkdtemp()
2817+ unit_dir = tempfile.mkdtemp(dir=base_dir)
2818+ machine_dir = os.path.join(base_dir, 'machine-1')
2819+ if addresses is not None:
2820+ os.mkdir(machine_dir)
2821+ with open(os.path.join(machine_dir, 'agent.conf'), 'w') as conf:
2822+ yaml.dump({'apiinfo': {'addrs': addresses}}, conf)
2823+ try:
2824+ yield unit_dir, machine_dir
2825+ finally:
2826+ shutil.rmtree(base_dir)
2827+
2828+ def test_retrieving_address_from_env(self):
2829+ # The API address is correctly retrieved from the environment.
2830+ with environ(JUJU_API_ADDRESSES=self.env_address):
2831+ self.assertEqual(self.env_address, get_api_address())
2832+
2833+ def test_multiple_addresses_in_env(self):
2834+ # If multiple API addresses are listed in the environment variable,
2835+ # the first one is returned.
2836+ addresses = '{} foo.example.com:42'.format(self.env_address)
2837+ with environ(JUJU_API_ADDRESSES=addresses):
2838+ self.assertEqual(self.env_address, get_api_address())
2839+
2840+ def test_both_env_and_agent_file(self):
2841+ # If the API address is included in both the environment and the
2842+ # agent.conf file, the environment variable takes precedence.
2843+ with environ(JUJU_API_ADDRESSES=self.env_address):
2844+ with self.agent_file([self.agent_address]) as (unit_dir, _):
2845+ self.assertEqual(self.env_address, get_api_address(unit_dir))
2846+
2847+ def test_retrieving_address_from_agent_file(self):
2848+ # The API address is correctly retrieved from the machiner agent file.
2849+ with self.agent_file([self.agent_address]) as (unit_dir, _):
2850+ self.assertEqual(self.agent_address, get_api_address(unit_dir))
2851+
2852+ def test_multiple_addresses_in_agent_file(self):
2853+ # If multiple API addresses are listed in the agent file, the first
2854+ # one is returned.
2855+ addresses = [self.agent_address, 'foo.example.com:42']
2856+ with self.agent_file(addresses) as (unit_dir, _):
2857+ self.assertEqual(self.agent_address, get_api_address(unit_dir))
2858+
2859+ def test_missing_env_and_agent_file(self):
2860 # An IOError is raised if the agent configuration file is not found.
2861- os.mkdir(self.machine_dir)
2862- self.assertRaises(IOError, get_api_address, self.unit_dir)
2863+ with self.agent_file() as (unit_dir, machine_dir):
2864+ os.mkdir(machine_dir)
2865+ self.assertRaises(IOError, get_api_address, unit_dir)
2866
2867- def test_missing_directory(self):
2868+ def test_missing_env_and_agent_directory(self):
2869 # An IOError is raised if the machine directory is not found.
2870- self.assertRaises(IOError, get_api_address, self.unit_dir)
2871+ with self.agent_file() as (unit_dir, _):
2872+ self.assertRaises(IOError, get_api_address, unit_dir)
2873
2874
2875 class TestLegacyJuju(unittest.TestCase):
2876@@ -484,7 +543,7 @@
2877 fd, self.log_file_name = tempfile.mkstemp()
2878 os.close(fd)
2879 mock_config = {'command-log-file': self.log_file_name}
2880- charmhelpers.command = lambda *args: lambda: dumps(mock_config)
2881+ charmhelpers.command = lambda *args: lambda: json.dumps(mock_config)
2882
2883 def tearDown(self):
2884 charmhelpers.command = self.command
2885@@ -578,7 +637,7 @@
2886 start_gui(
2887 False, 'This is login help.', True, True, ssl_cert_path,
2888 charmworld_url, True, haproxy_path='haproxy',
2889- config_js_path='config')
2890+ config_js_path='config', use_analytics=True)
2891 haproxy_conf = self.files['haproxy']
2892 self.assertIn('ca-base {0}'.format(ssl_cert_path), haproxy_conf)
2893 self.assertIn('crt-base {0}'.format(ssl_cert_path), haproxy_conf)
2894@@ -596,6 +655,7 @@
2895 self.assertIn("socket_url: 'wss://", js_conf)
2896 self.assertIn('socket_protocol: "wss"', js_conf)
2897 self.assertIn('charmworldURL: "http://charmworld.example"', js_conf)
2898+ self.assertIn('useAnalytics: true', js_conf)
2899 apache_conf = self.files['juju-gui']
2900 self.assertIn('juju-gui/build-', apache_conf)
2901 self.assertIn('VirtualHost *:{0}'.format(WEB_PORT), apache_conf)
2902@@ -628,6 +688,38 @@
2903 self.assertIn('user: "admin"', js_conf)
2904 self.assertIn('password: "admin"', js_conf)
2905
2906+ def test_start_gui_no_analytics(self):
2907+ ssl_cert_path = '/tmp/certificates/'
2908+ charmworld_url = 'http://charmworld.example'
2909+ start_gui(
2910+ False, 'This is login help.', False, False, ssl_cert_path,
2911+ charmworld_url, True, haproxy_path='haproxy',
2912+ config_js_path='config', use_analytics=False)
2913+ js_conf = self.files['config']
2914+ self.assertIn('useAnalytics: false', js_conf)
2915+
2916+ def test_start_gui_fullscreen(self):
2917+ ssl_cert_path = '/tmp/certificates/'
2918+ charmworld_url = 'http://charmworld.example'
2919+ start_gui(
2920+ False, 'This is login help.', False, False, ssl_cert_path,
2921+ charmworld_url, True, haproxy_path='haproxy',
2922+ config_js_path='config', sandbox=True,
2923+ default_viewmode='fullscreen')
2924+ js_conf = self.files['config']
2925+ self.assertIn('defaultViewmode: "fullscreen"', js_conf)
2926+
2927+ def test_start_gui_with_button(self):
2928+ ssl_cert_path = '/tmp/certificates/'
2929+ charmworld_url = 'http://charmworld.example'
2930+ start_gui(
2931+ False, 'This is login help.', False, False, ssl_cert_path,
2932+ charmworld_url, True, haproxy_path='haproxy',
2933+ config_js_path='config', sandbox=True,
2934+ show_get_juju_button=True)
2935+ js_conf = self.files['config']
2936+ self.assertIn('showGetJujuButton: true', js_conf)
2937+
2938
2939 class TestNpmCache(unittest.TestCase):
2940 """To speed building from a branch we prepopulate the NPM cache."""

Subscribers

People subscribed via source and target branches