Merge lp:~andreserl/maas/trunk-fpi into lp:~maas-committers/maas/trunk

Proposed by Andres Rodriguez
Status: Merged
Approved by: Diogo Matsubara
Approved revision: no longer in the source branch.
Merged at revision: 1477
Proposed branch: lp:~andreserl/maas/trunk-fpi
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 501 lines (+231/-16)
17 files modified
contrib/maas-cluster-http.conf (+6/-0)
contrib/preseeds_v2/generic (+4/-0)
contrib/preseeds_v2/preseed_xinstall (+1/-0)
scripts/maas-import-ephemerals (+6/-1)
scripts/uec2roottar (+56/-0)
setup.py (+4/-0)
src/maasserver/api.py (+4/-1)
src/maasserver/models/nodegroup.py (+5/-0)
src/maasserver/preseed.py (+7/-1)
src/maasserver/tests/test_api.py (+3/-0)
src/maasserver/tests/test_preseed.py (+13/-5)
src/provisioningserver/kernel_opts.py (+5/-2)
src/provisioningserver/pxe/config.xinstall.template (+20/-0)
src/provisioningserver/pxe/install_image.py (+12/-2)
src/provisioningserver/pxe/tests/test_config.py (+10/-2)
src/provisioningserver/pxe/tests/test_install_image.py (+35/-0)
src/provisioningserver/tests/test_kernel_opts.py (+40/-2)
To merge this branch: bzr merge lp:~andreserl/maas/trunk-fpi
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Gavin Panella (community) Abstain
Julian Edwards (community) Approve
Review via email: mp+152039@code.launchpad.net

Commit message

Fast Path Installer

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (4.1 KiB)

<bigjools> I am trying to be constructive on the review but you're going to hate me, sorry :(
<roaksoax> hehe :)

Did you guys have any pre-implementation chat with one of us before starting this? I can't approve this as it stands for quite a few fundamental architectural reasons, and also it's an 1100+ line diff without a single test change. I did previously say I won't accept any more branches that have untested code, especially untested walls of shell :(

More below.

25 === added file 'contrib/preseeds_v2/preseed_xinstall'
26 --- contrib/preseeds_v2/preseed_xinstall 1970-01-01 00:00:00 +0000
27 +++ contrib/preseeds_v2/preseed_xinstall 2013-04-17 16:47:25 +0000
[snip]
36 +preseed = subprocess.check_output(["/usr/share/maas/xinstall/make-userdata-maas",
37 + "--apt-proxy="+proxy,
38 + "--dpkg-selections="+preseed_data,
39 + "--finished-url="+node_disable_pxe_url,
40 + "--finished-url-data="+node_disable_pxe_data,
41 + "http://"+cluster_host+"/MAAS/static/images/"+node.architecture+"/"+node.distro_series+"/xinstall/root.tar.gz",
42 + "/dev/sda"])
43 +}}
44 +{{preseed}}

This scares the bejesus out of me. This is just a workaround so that the shell script make-userdata-maas can be used instead of doing it properly in Python, and tested. It has completely sidestepped all of the code that exists already in src/maasserver/preseed.py and src/maasserver/compose_preseed.py that calculates preseed_data based on the node state.

It is extremely fragile and it is likely to break, horribly, in many ways, as soon as someone changes anything elsewhere.

At the very least, this template should not be calling out to the shell script. If you can't re-write it in Python and test it in the time available, it should be called from the existing Python code that generates preseeds and returned in preseed_data for the template. It can then be tested so we know the right preseed is getting returned for FPI.

1045 === modified file 'src/maasserver/api.py'
1061 === modified file 'src/maasserver/models/nodegroup.py'
1077 === modified file 'src/maasserver/preseed.py'
1096 === modified file 'src/provisioningserver/kernel_opts.py'
1135 === modified file 'src/provisioningserver/pxe/install_image.py'

The changes in all these files need tests. I expect some tests are broken as a result but I can't run the test suite at the moment as I get this:

$ make build
bin/buildout install database
Traceback (most recent call last):
  File "bin/buildout", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/home/ed/canonical/maas/sandbox/local/lib/python2.7/site-packages/distribute-0.6.24-py2.7.egg/pkg_resources.py", line 16, in <module>
    import sys, os, zipimport, time, re, imp, types
  File "/home/ed/canonical/maas/sandbox/lib/python2.7/re.py", line 105, in <module>
    import sre_compile
  File "/home/ed/canonical/maas/sandbox/lib/python2.7/sre_compile.py", line 14, in <module>
    import sre_parse
  File "/home/ed/canonical/maas/sandbox/lib/python2.7/sre_parse.py", line 17, in <module>
    from sre_constants import *
  File "/home/ed/canonical/maas/sandbox/lib/python2.7/sre_constants.py", line 18, in <module>
    from _sre impo...

Read more...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

So based on the conversation I just had with Andres he explained that the FPI script stuff here is also going to be used on the CD installer so they wanted to share the code.

The maas-make-userdata script is used to generate a template, and the intention is that in the future people should be able to pass extra scripts to run in preinst and postinst time in the installer.

I am strongly of the opinion that that template should be static in MAAS and any data that it requires comes from MAAS (it does now anyway - it's just indirect!). MAAS's templating system will handle this requirement with ease and vastly reduce this complexity. It is fragile and will be very very hard to debug problems.

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (32.5 KiB)

To fix the devel env, you need to merge trunk and then "make distclean".

The following tests are all broken in your branch:

======================================================================
ERROR: maasserver.tests.test_preseed.TestPreseedMethods.test_get_preseed_returns_default_preseed
----------------------------------------------------------------------
_StringException: Traceback (most recent call last):
  File "/home/ed/canonical/maas/sandbox/src/maasserver/tests/test_preseed.py", line 525, in test_get_preseed_returns_default_preseed
    preseed = get_preseed(node)
  File "/home/ed/canonical/maas/sandbox/src/maasserver/preseed.py", line 80, in get_preseed
    release=node.get_distro_series())
  File "/home/ed/canonical/maas/sandbox/src/maasserver/preseed.py", line 306, in render_preseed
    return template.substitute(**context)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 177, in substitute
    result = self._interpret_inherit(result, defs, inherit, ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 204, in _interpret_inherit
    return templ.substitute(ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 173, in substitute
    result, defs, inherit = self._interpret(ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 184, in _interpret
    self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 212, in _interpret_codes
    self._interpret_code(item, ns, out, defs)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 218, in _interpret_code
    self._exec(code[2], ns, pos)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 312, in _exec
    exec code in self.default_namespace, ns
  File "<string>", line 12, in <module>
TypeError: coercing to Unicode: need string or buffer, NoneType found at line 1 column 3 in file /home/ed/canonical/maas/sandbox/contrib/preseeds_v2/preseed_xinstall

======================================================================
ERROR: maasserver.tests.test_preseed.TestPreseedProxy.test_preseed_uses_configured_proxy
----------------------------------------------------------------------
_StringException: Traceback (most recent call last):
  File "/home/ed/canonical/maas/sandbox/src/maasserver/tests/test_preseed.py", line 513, in test_preseed_uses_configured_proxy
    factory.make_node(), PRESEED_TYPE.DEFAULT, "precise")
  File "/home/ed/canonical/maas/sandbox/src/maasserver/preseed.py", line 306, in render_preseed
    return template.substitute(**context)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 177, in substitute
    result = self._interpret_inherit(result, defs, inherit, ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 204, in _interpret_inherit
    return templ.substitute(ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 173, in substitute
    result, defs, inherit = self._interpret(ns)
  File "/usr/lib/python2.7/dist-packages/tempita/__init__.py", line 184, in _interpret
    self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
  File "/usr...

Revision history for this message
Scott Moser (smoser) wrote :
Download full text (5.5 KiB)

> <bigjools> I am trying to be constructive on the review but you're going to
> hate me, sorry :(
> <roaksoax> hehe :)
>
> Did you guys have any pre-implementation chat with one of us before starting
> this? I can't approve this as it stands for quite a few fundamental
> architectural reasons, and also it's an 1100+ line diff without a single test
> change. I did previously say I won't accept any more branches that have
> untested code, especially untested walls of shell :(
>
> More below.
>
> 25 === added file 'contrib/preseeds_v2/preseed_xinstall'
> 26 --- contrib/preseeds_v2/preseed_xinstall 1970-01-01 00:00:00
> +0000
> 27 +++ contrib/preseeds_v2/preseed_xinstall 2013-04-17 16:47:25
> +0000
> [snip]
> 36 +preseed = subprocess.check_output(["/usr/share/maas/xinstall/make-
> userdata-maas",
> 37 + "--apt-proxy="+proxy,
> 38 + "--dpkg-selections="+preseed_data,
> 39 + "--finished-url="+node_disable_pxe_url,
> 40 + "--finished-url-data="+node_disable_pxe_data,
> 41 + "http://"+cluster_host+"/MAAS/static/images/"+node.architecture+"
> /"+node.distro_series+"/xinstall/root.tar.gz",
> 42 + "/dev/sda"])
> 43 +}}
> 44 +{{preseed}}
>
> This scares the bejesus out of me. This is just a workaround so that the
> shell script make-userdata-maas can be used instead of doing it properly in
> Python, and tested. It has completely sidestepped all of the code that exists

"doing it properly in python" is a general problem that maas has. Maas knows too much. The installer (xinstall) is not maas. The proper way to do this is to not have the xinstall code in maas at all, and have maas know an interface to it. We've pushed xinstall here as a temporary landing location.

What you have above is a sane way to interface between maas an an installer.

We've explicitly put this is a preseed as these templates are sold as user-configurable. Some configuration may need to be done by an end user, and this is how they do that. Making maas invoke that code internally means a user has to change maas python code to make a change.

> already in src/maasserver/preseed.py and src/maasserver/compose_preseed.py
> that calculates preseed_data based on the node state.
>
> It is extremely fragile and it is likely to break, horribly, in many ways, as
> soon as someone changes anything elsewhere.

We have 2 components. yes, one component can break another component. A change to python can break maas. A change to glibc can break python. a change to the kernel can break glibc. Thats *good* design.

> At the very least, this template should not be calling out to the shell
> script. If you can't re-write it in Python and test it in the time available,
> it should be called from the existing Python code that generates preseeds and
> returned in preseed_data for the template. It can then be tested so we know
> the right preseed is getting returned for FPI.
>
>
> 1045 === modified file 'src/maasserver/api.py'
> 1061 === modified file 'src/maasserver/models/nodegroup.py'
> 1077 === modified file 'src/maasserver/preseed.py'
> 1096 === modified file 'src/provisionings...

Read more...

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (3.7 KiB)

Hi Scott,

On 19/04/13 03:09, Scott Moser wrote:
> "doing it properly in python" is a general problem that maas has. Maas knows too much. The installer (xinstall) is not maas. The proper way to do this is to not have the xinstall code in maas at all, and have maas know an interface to it. We've pushed xinstall here as a temporary landing location.

I don't think maas itself knows too much - all the installer specific
stuff is in templates, exactly where it should be. However I would love
for the xinstall code to not be in MAAS too. Fortunately there's an
easy solution, we just use the template feature that MAAS already has.

> What you have above is a sane way to interface between maas an an installer.

With respect, in no way do I think that is sane and you'll get the same
response from the rest of the guys in my team. I've explained already
where it's faults are and how to do it better, so I won't do so again.

> We've explicitly put this is a preseed as these templates are sold as user-configurable. Some configuration may need to be done by an end user, and this is how they do that. Making maas invoke that code internally means a user has to change maas python code to make a change.

Right now with this branch you have a situation like this:
- Maas renders a preseed template, passing in some data to it
- The template shells out to a script, passing it the data, that
generates another template

This can be vastly simplified as:
 - Maas renders a template, passing in some data to it

where the template is the one that the script previously generated.

The same configuration and flexibility is still there, without the
intermediate step. The template system we chose is very powerful.

>
>> already in src/maasserver/preseed.py and src/maasserver/compose_preseed.py
>> that calculates preseed_data based on the node state.
>>
>> It is extremely fragile and it is likely to break, horribly, in many ways, as
>> soon as someone changes anything elsewhere.
>
> We have 2 components. yes, one component can break another component. A change to python can break maas. A change to glibc can break python. a change to the kernel can break glibc. Thats *good* design.

I think that's a strawman. It's not good to have things break. You're
also falsely assuming the likelihood of Python/glibc/kernel breaking is
of the same likelihood as a change here. This change is fragile and it
will break easily.

> Its an installer. It should be separate. Please just review this as if that code was not there. We'll replace or move it at a later date.

The fact that it is here and you know it should be separate is enough of
a warning that this approach is wrong.

>> I realise that you need to get *something* in raring before FF tomorrow.
>> Last-minute rushed changes are never good, but if you can at least add tests
>> and make the change to the preseed template as I suggested then this could
>> land with a view to improving more later.
>
> I really prefer to use user-editable and modifable template files than maas python files for the interface between 'xinstall' and maas.

I was never suggesting modifiable python files. The existing preseed
setup is tot...

Read more...

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (3.3 KiB)

On 18 April 2013 18:09, Scott Moser <email address hidden> wrote:
...
> "doing it properly in python" is a general problem that maas has.
> Maas knows too much.  The installer (xinstall) is not maas.  The
> proper way to do this is to not have the xinstall code in maas at
> all, and have maas know an interface to it.  We've pushed xinstall
> here as a temporary landing location.

I say more later, but if it's not MAAS then it shouldn't land in MAAS;
it should exist as a patch in the packaging.

...
>> It is extremely fragile and it is likely to break, horribly, in
>> many ways, as soon as someone changes anything elsewhere.
>
> We have 2 components.  yes, one component can break another
> component.  A change to python can break maas.  A change to glibc
> can break python. a change to the kernel can break glibc.  Thats
> *good* design.

It's a nitpick, but that's not good design, that's just a property of
interrelating systems.

...
> Its an installer. It should be separate.  Please just review this as
> if that code was not there.  We'll replace or move it at a later
> date.

There's no one actively driving MAAS development at this very moment,
but I guess we - the Red Squad - still feel the most responsible, and
we still care about the thing we've created. Our criteria for landing
a change to any product that we're working on is: does this improve
the product? This change does not.

It satisfies a short-term goal, that of getting something to work for
Raring, but it's a horrible change: it contains a moderately complex
program written in shell-script (meaning it's now highly complex); the
script has been cargo-culted from somewhere else, and so can diverge
unchecked; paths have been hard-coded into the template; there are no
tests; and so on.

If MAAS's limitations have meant that this was all that was possible
in the time available, then more time needs to be made available:
changing MAAS *needs* to be part of the engineering effort.

This change should *not* land on trunk. Previous changes to trunk in
the same vein as this - especially those without tests - should
probably not have landed either. This change should be carried as a
patch in the packaging.

>
>> I realise that you need to get *something* in raring before FF
>> tomorrow.  Last-minute rushed changes are never good, but if you
>> can at least add tests and make the change to the preseed template
>> as I suggested then this could land with a view to improving more
>> later.
>
> I really prefer to use user-editable and modifable template files
> than maas python files for the interface between 'xinstall' and
> maas.

Fwiw, the template files are user-editable. There's meant to be an
inheritance/override mechanism so that the templates can be installed
by the package but selectively customised in, say, /etc/maas. I'm not
sure if that's effective in practice right now, but that's the
intention. Throwing that away instead of improving it - especially by
replacing it with untested shell-script! - is not doing anyone any
favours.

If you really, definitely want to land large amounts of shell-script
to MAAS, it needs to be tested. We can help by integratiing, say,
ShUnit <http://shunit.s...

Read more...

review: Disapprove
Revision history for this message
Scott Moser (smoser) wrote :

We have 2 completely separate components here:
 a.) maas
 b.) a fast path installer

There is a problem in that the fast path installer doesn't "live" anywhere. That will be addressed at a later date, and would then be addressed here by removal of any 'xinstall' reference in maas. We'll re-submit this branch with such a removal.

Maas will interface with this fast path installer via popen. Essentially running a command like:
  make-userdata-maas "--apt-proxy={{proxy}}" \
     "--dpkg-selections={{dpkg_set_selections_here}}" \
     "--finished-url={{node_disable_pxe_url}}" \
     "--finished-url-data={{node_disable_pxe_data}}" \
     "{{url_of_thing_to_install}}" "{{target_disk}}"

Having maas invoke that command itself, and making the output available in a preseed variable named 'xinstall_data' which is then rendered in a preseed wholly with '{{xinstall_data}}' is pointlessly complex and not modifiable by the end user.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

The installer should not live in MAAS upstream code, that is certain. Make a separate package for it and pull it in if you need it.

The problem with temporary fixes is that they become permanent. Especially if they go out in a release - look at how long it's taken to remove Cobbler from 12.04.

>Having maas invoke that command itself, and making the output available in a preseed variable named 'xinstall_data'
>which is then rendered in a preseed wholly with '{{xinstall_data}}' is pointlessly complex and not modifiable by the
>end user.

Yet this is exactly what you are doing via the template that shells out!

If you want FPI to land in MAAS you need to add the FPI template that it needs and populate it with data from MAAS, as both Gavin and I have said above. I don't understand why a separate script is needed.

review: Disapprove
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Much better without the xinstall stuff in here. It's obviously incomplete but I have no fundamental objections any more. Provided the xinstall preseed is sane in the future, it's looking ok.

As you said, you need tests for all the changes here. Some other comments below:

70 +Usage() {
71 +cat <<EOF
72 + Usage: ${0##*/} uec-tarball [output]
73 +EOF

Can you put a description in here so people know what this script is doing. Also, *sadface* another Wall of Shell™
:(

201 def compose_purpose_opts(params):
202 """Return the list of the purpose-specific kernel options."""
203 - if params.purpose == "commissioning":
204 + if params.purpose == "commissioning" or params.purpose == "xinstall":

Woa, really? Why is this needed?

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Hi Julian,

Thanks for the review. We will be shipping the xinstall stuff in packaging so won't really be missing anything. I'll however provide a sample preseed to be merged on trunk.

Now, as far as the latter, the 'xinstall' uses exactly the same kernel arguments as the ones the 'commissioning' step uses, with the exception of 'ds=nocloud-net', which is being added in the template file. If you believe is better to add this additional parameter on the code above, I'm also fine doing that.

224 + APPEND ds=nocloud-net {{kernel_params(arch="amd64") | kernel_command}}

Cheers.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 04/05/13 00:51, Andres Rodriguez wrote:
> Hi Julian,
>
> Thanks for the review. We will be shipping the xinstall stuff in packaging so won't really be missing anything. I'll however provide a sample preseed to be merged on trunk.

How can the "sample" one diverge from the xinstall one? And why would
they ever be different?

>
> Now, as far as the latter, the 'xinstall' uses exactly the same kernel arguments as the ones the 'commissioning' step uses, with the exception of 'ds=nocloud-net', which is being added in the template file. If you believe is better to add this additional parameter on the code above, I'm also fine doing that.
>
> 224 + APPEND ds=nocloud-net {{kernel_params(arch="amd64") | kernel_command}}
>
> Cheers.
>

How are you defining the tag that indicates to use the FPI? Because
tags support extra kernel options and that's where ds=nocloud-net should
be. Once it's in tag.kernel_opts, everything will DTRT. Having custom
code is not the right way here.

Cheers.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Hi Julian,

The ds=nocloud-net is a required kernel option for xinstall and should not be set when creating/assigning the tag. Since the 'xinstall' is another purpose, aside from commissioning and install, all the required kernel opts should be within MAAS as it is for the other purposes. I have updated the branch for it.

Thanks

Revision history for this message
Andres Rodriguez (andreserl) wrote :
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Ok fair enough on the tag. I guess you want to disassociate the notion of FPI from *how* FPI works (the xinstaller)?

Why did you add the tests in a separate branch? They should be in here!

review: Needs Fixing
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (3.3 KiB)

Thanks for the tests! Ok let's review:

154 if node.netboot:
155 - return "install"
156 + if node.should_use_traditional_installer():
157 + return "install"
158 + else:
159 + return "xinstall"

There's no test for this change, can you add one please.

171 + def get_any_interface(self):

This needs its own test please.

206 - def test_preseed_context_null_cluster_host_if_unmanaged(self):
207 + def test_preseed_context_not_null_cluster_host_if_unmanaged(self):
208 # If the nodegroup has no managed interface recorded, which is
209 # possible in the data model but would be a bit weird, the
210 # cluster_host context variable is present, but None.
211 @@ -378,7 +378,7 @@
212 interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED
213 interface.save()
214 context = get_preseed_context(release, nodegroup)
215 - self.assertIsNone(context["cluster_host"])
216 + self.assertIsNotNone(context["cluster_host"])

This should check for the actual expected interface rather than it not being None. Also, the test's comment is now wrong, fix that please!

243 + kernel_params = []
244 # These are kernel parameters read by the ephemeral environment.
245 tname = "%s:%s" % (ISCSI_TARGET_NAME_PREFIX,
246 get_ephemeral_name(params.release, params.arch))
247 - return [
248 + if params.purpose == "xinstall":
249 + kernel_params += ["ds=nocloud-net"]
250 + kernel_params += [

You can simplify this code a bit.

Remove the "kernel_params = []" bit, and assign straight to kernel_params instead of the first +=. Then you can add these lines:

    if params.purpose == "xinstall":
        kernel_params.append("ds=nocloud-net")

Much simpler!

274 + SAY Booting (amd64) under MAAS direction...
275 + SAY {{kernel_params(arch="amd64") | kernel_command}}
276 + KERNEL {{kernel_params(arch="amd64") | kernel_path }}
277 + INITRD {{kernel_params(arch="amd64") | initrd_path }}
278 + APPEND {{kernel_params(arch="amd64") | kernel_command}}
279 + IPAPPEND 2
280 +
281 +LABEL i386
282 + SAY Booting (i386) under MAAS direction...
283 + SAY {{kernel_params(arch="i386") | kernel_command}}

Since the "SAY" gets evaluated regardless of the LABEL it's executing, this will repeat 4 lots of SAY. I think we can just make it dump the kernel params as information, just the once.

341 + def test_render_pxe_config_for_xinstall(self):
342 + # The xinstall config uses an extra PXELINUX module to auto
343 + # select between i386 and amd64.
344 + get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
345 + get_ephemeral_name.return_value = factory.make_name("ephemeral")
346 + options = {
347 + "kernel_params":
348 + make_kernel_parameters(purpose="xinstall"),

.. [snip]...

Given that this new test is identical to test_render_pxe_config_for_commissioning except for one piece of data, let's introduce a test scenario into it.

I've done that for you, so just revert your changes t...

Read more...

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Hi Julian,

I have addressed all of your comments but the SAY one. SO something like?

LABEL execute
  SAY Booting under MAAS direction...
  SAY {{kernel_params() | kernel_command}}
  KERNEL ifcpu64.c32
  APPEND amd64 -- i386

Revision history for this message
Gavin Panella (allenap) wrote :

On 9 May 2013 10:04, Andres Rodriguez <email address hidden> wrote:
> Hi Julian,
>
> I have addressed all of your comments but the SAY one. SO something like?
>
> LABEL execute
> SAY Booting under MAAS direction...
> SAY {{kernel_params() | kernel_command}}

These SAY commands are processed as the file is parsed, not when the
particular label is chosen. You can't use SAY to show which label was
actually chosen.

For example:

  SAY hello
  LABEL foo
    SAY foo
  LABEL bar
    SAY bar

will echo:

  hello
  foo
  bar

regardless.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Andres, looks great! I'll echo what Gavin said about SAY. Just put them only at the TOP level like this:

SAY Booting under MAAS direction...
SAY {{kernel_params() | kernel_command}}

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

No commit message specified.

Revision history for this message
MAAS Lander (maas-lander) wrote :

Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0. Got: 1 Approve, 1 Disapprove.

Revision history for this message
Gavin Panella (allenap) :
review: Abstain
Revision history for this message
Andres Rodriguez (andreserl) :
review: Approve
Revision history for this message
Andres Rodriguez (andreserl) :
review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

The Jenkins job https://jenkins.qa.ubuntu.com/job/maas-merger-trunk/255/console reported an error when processing this lp:~andreserl/maas/trunk-fpi branch.
Not merging it.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

The branch has been rebased against latest trunk.

Revision history for this message
MAAS Lander (maas-lander) wrote :

The Jenkins job https://jenkins.qa.ubuntu.com/job/maas-merger-trunk/275/console reported an error when processing this lp:~andreserl/maas/trunk-fpi branch.
Not merging it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'contrib/maas-cluster-http.conf'
--- contrib/maas-cluster-http.conf 1970-01-01 00:00:00 +0000
+++ contrib/maas-cluster-http.conf 2013-05-11 18:43:26 +0000
@@ -0,0 +1,6 @@
1# Server static files for tftp images as FPI
2# installer needs them
3Alias /MAAS/static/images/ /var/lib/maas/tftp/
4<Directory /var/lib/maas/tftp/>
5 SetHandler None
6</Directory>
07
=== modified file 'contrib/preseeds_v2/generic'
--- contrib/preseeds_v2/generic 2012-11-14 10:32:25 +0000
+++ contrib/preseeds_v2/generic 2013-05-11 18:43:26 +0000
@@ -1,4 +1,8 @@
1{{if node.should_use_traditional_installer() }}
1{{inherit "preseed_master"}}2{{inherit "preseed_master"}}
3{{else}}
4{{inherit "preseed_xinstall"}}
5{{endif}}
26
3{{def proxy}}7{{def proxy}}
4d-i mirror/country string manual8d-i mirror/country string manual
59
=== added file 'contrib/preseeds_v2/preseed_xinstall'
--- contrib/preseeds_v2/preseed_xinstall 1970-01-01 00:00:00 +0000
+++ contrib/preseeds_v2/preseed_xinstall 2013-05-11 18:43:26 +0000
@@ -0,0 +1,1 @@
1# Disabled by default
02
=== modified file 'scripts/maas-import-ephemerals'
--- scripts/maas-import-ephemerals 2012-11-23 11:00:04 +0000
+++ scripts/maas-import-ephemerals 2013-05-11 18:43:26 +0000
@@ -239,6 +239,9 @@
239 tar -Sxzf - -C "$exdir" < "$tarball" ||239 tar -Sxzf - -C "$exdir" < "$tarball" ||
240 { error "failed to extract tarball from $furl"; return 1; }240 { error "failed to extract tarball from $furl"; return 1; }
241241
242 # Create root disk
243 uec2roottar "$tarball" || { error "failed to create root image"; return 1; }
244
242 # Look for our files in the extracted tarball.245 # Look for our files in the extracted tarball.
243 local x="" img="" kernel="" initrd=""246 local x="" img="" kernel="" initrd=""
244 for x in "$exdir/"*.img; do247 for x in "$exdir/"*.img; do
@@ -342,12 +345,14 @@
342 return 1345 return 1
343 copy_first_available "$src/initrd.gz" "$src/initrd" "$tmpdir/initrd.gz" ||346 copy_first_available "$src/initrd.gz" "$src/initrd" "$tmpdir/initrd.gz" ||
344 return 1347 return 1
348 copy_first_available "$src/dist-root.tar.gz" "" "$tmpdir/root.tar.gz" ||
349 return 1
345 fi350 fi
346351
347 local cmd out=""352 local cmd out=""
348 cmd=( maas-provision install-pxe-image353 cmd=( maas-provision install-pxe-image
349 "--arch=$arch" "--subarch=$subarch" "--release=$release"354 "--arch=$arch" "--subarch=$subarch" "--release=$release"
350 --purpose="commissioning" --image="$tmpdir" )355 --purpose="commissioning" --image="$tmpdir" --symlink="xinstall")
351 debug 2 "${cmd[@]}"356 debug 2 "${cmd[@]}"
352 out=$("${cmd[@]}" 2>&1) ||357 out=$("${cmd[@]}" 2>&1) ||
353 { error "cmd failed:" "${cmd[@]}"; error "$out"; return 1; }358 { error "cmd failed:" "${cmd[@]}"; error "$out"; return 1; }
354359
=== added file 'scripts/uec2roottar'
--- scripts/uec2roottar 1970-01-01 00:00:00 +0000
+++ scripts/uec2roottar 2013-05-11 18:43:26 +0000
@@ -0,0 +1,56 @@
1#!/bin/sh
2
3TEMP_D=""
4MP=""
5
6Usage() {
7cat <<EOF
8 ${0##*/} converts an ubuntu ephemeral image into a root
9 disk image tarball.
10
11 Usage: ${0##*/} uec-tarball [output]
12EOF
13}
14error() { echo "$(date -R):" "$@" 1>&2; }
15fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
16cleanup() {
17 [ -z "$MP" ] || umount "$MP"
18 [ -z "$TEMP_D" -o ! -d "$TEMP_D" ] || rm -Rf "${TEMP_D}";
19}
20
21[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; }
22[ $# -eq 1 -o $# -eq 2 ] || { Usage 1>&2; exit 1; }
23[ -f "$1" ] || { Usage 1>&2; error "$1: not a file"; exit 1; }
24
25[ "$(id -u)" = "0" ] || fail "must be root"
26
27TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || fail "failed mktemp"
28trap cleanup EXIT
29
30uec="$1"
31output=${2}
32
33[ -n "$output" ] || output="${uec%.tar.gz}-root.tar.gz";
34error "converting ${uec} to ${output}"
35
36error "extracting *.img from ${uec}"
37tar -C ${TEMP_D} --wildcards "*.img" -Sxvzf "$uec" ||
38 fail "failed extract $uec"
39
40img=""
41for cand in "${TEMP_D}/"*.img; do
42 [ -z "$img" ] || fail "multiple .img files in $uec"
43 [ -f "$cand" ] && img="$cand"
44done
45
46[ -n "$img" ] || fail "failed to find image in $uec"
47
48mkdir "${TEMP_D}/mp" && mount -o ro "$img" "${TEMP_D}/mp" &&
49 MP="$TEMP_D/mp" || fail "failed to mount $img"
50
51error "copying contents of ${img#${TEMP_D}/} in ${uec} to ${output}"
52tar -C "$MP" -cpSzf "${output}" --numeric-owner . ||
53 fail "failed to create ${output}"
54
55error "finished. wrote to ${output}"
56
057
=== modified file 'setup.py'
--- setup.py 2012-12-18 17:06:43 +0000
+++ setup.py 2013-05-11 18:43:26 +0000
@@ -62,6 +62,7 @@
62 'etc/maas/import_ephemerals',62 'etc/maas/import_ephemerals',
63 'etc/maas/import_pxe_files',63 'etc/maas/import_pxe_files',
64 'contrib/maas-http.conf',64 'contrib/maas-http.conf',
65 'contrib/maas-cluster-http.conf',
65 'contrib/maas_local_settings.py']),66 'contrib/maas_local_settings.py']),
66 ('/usr/share/maas',67 ('/usr/share/maas',
67 ['contrib/wsgi.py',68 ['contrib/wsgi.py',
@@ -73,10 +74,13 @@
73 'contrib/preseeds_v2/enlist',74 'contrib/preseeds_v2/enlist',
74 'contrib/preseeds_v2/generic',75 'contrib/preseeds_v2/generic',
75 'contrib/preseeds_v2/enlist_userdata',76 'contrib/preseeds_v2/enlist_userdata',
77 'contrib/preseeds_v2/preseed_xinstall',
76 'contrib/preseeds_v2/preseed_master']),78 'contrib/preseeds_v2/preseed_master']),
77 ('/usr/sbin',79 ('/usr/sbin',
78 ['scripts/maas-import-ephemerals',80 ['scripts/maas-import-ephemerals',
79 'scripts/maas-import-pxe-files']),81 'scripts/maas-import-pxe-files']),
82 ('/usr/bin',
83 ['scripts/uec2roottar']),
80 ],84 ],
8185
82 install_requires=[86 install_requires=[
8387
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2013-05-08 23:03:51 +0000
+++ src/maasserver/api.py 2013-05-11 18:43:26 +0000
@@ -1753,7 +1753,10 @@
1753 elif node.status == NODE_STATUS.ALLOCATED:1753 elif node.status == NODE_STATUS.ALLOCATED:
1754 # Install the node if netboot is enabled, otherwise boot locally.1754 # Install the node if netboot is enabled, otherwise boot locally.
1755 if node.netboot:1755 if node.netboot:
1756 return "install"1756 if node.should_use_traditional_installer():
1757 return "install"
1758 else:
1759 return "xinstall"
1757 else:1760 else:
1758 return "local" # TODO: Investigate.1761 return "local" # TODO: Investigate.
1759 else:1762 else:
17601763
=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py 2012-11-23 09:54:47 +0000
+++ src/maasserver/models/nodegroup.py 2013-05-11 18:43:26 +0000
@@ -204,6 +204,11 @@
204 self.api_key = api_token.key204 self.api_key = api_token.key
205 return super(NodeGroup, self).save(*args, **kwargs)205 return super(NodeGroup, self).save(*args, **kwargs)
206206
207 def get_any_interface(self):
208 for interface in self.nodegroupinterface_set.all():
209 return interface
210 return None
211
207 def get_managed_interface(self):212 def get_managed_interface(self):
208 """Return the interface for which MAAS managed the DHCP service.213 """Return the interface for which MAAS managed the DHCP service.
209214
210215
=== modified file 'src/maasserver/preseed.py'
--- src/maasserver/preseed.py 2013-04-08 19:56:00 +0000
+++ src/maasserver/preseed.py 2013-05-11 18:43:26 +0000
@@ -232,7 +232,13 @@
232 else:232 else:
233 base_url = nodegroup.maas_url233 base_url = nodegroup.maas_url
234 cluster_if = nodegroup.get_managed_interface()234 cluster_if = nodegroup.get_managed_interface()
235 cluster_host = None if cluster_if is None else cluster_if.ip235 any_cluster_if = nodegroup.get_any_interface()
236 cluster_host = None
237 if cluster_if is None:
238 if any_cluster_if is not None:
239 cluster_host = any_cluster_if.ip
240 else:
241 cluster_host = cluster_if.ip
236 return {242 return {
237 'main_archive_hostname': main_archive_hostname,243 'main_archive_hostname': main_archive_hostname,
238 'main_archive_directory': main_archive_directory,244 'main_archive_directory': main_archive_directory,
239245
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2013-05-08 23:03:51 +0000
+++ src/maasserver/tests/test_api.py 2013-05-11 18:43:26 +0000
@@ -3776,11 +3776,14 @@
3776 ("poweroff", {"status": NODE_STATUS.READY}),3776 ("poweroff", {"status": NODE_STATUS.READY}),
3777 ("poweroff", {"status": NODE_STATUS.RESERVED}),3777 ("poweroff", {"status": NODE_STATUS.RESERVED}),
3778 ("install", {"status": NODE_STATUS.ALLOCATED, "netboot": True}),3778 ("install", {"status": NODE_STATUS.ALLOCATED, "netboot": True}),
3779 ("xinstall", {"status": NODE_STATUS.ALLOCATED, "netboot": True}),
3779 ("local", {"status": NODE_STATUS.ALLOCATED, "netboot": False}),3780 ("local", {"status": NODE_STATUS.ALLOCATED, "netboot": False}),
3780 ("poweroff", {"status": NODE_STATUS.RETIRED}),3781 ("poweroff", {"status": NODE_STATUS.RETIRED}),
3781 ]3782 ]
3782 node = factory.make_node()3783 node = factory.make_node()
3783 for purpose, parameters in options:3784 for purpose, parameters in options:
3785 if purpose == "xinstall":
3786 node.use_fastpath_installer()
3784 for name, value in parameters.items():3787 for name, value in parameters.items():
3785 setattr(node, name, value)3788 setattr(node, name, value)
3786 self.assertEqual(purpose, api.get_boot_purpose(node))3789 self.assertEqual(purpose, api.get_boot_purpose(node))
37873790
=== modified file 'src/maasserver/tests/test_preseed.py'
--- src/maasserver/tests/test_preseed.py 2013-02-21 23:32:20 +0000
+++ src/maasserver/tests/test_preseed.py 2013-05-11 18:43:26 +0000
@@ -368,17 +368,19 @@
368 nodegroup.get_managed_interface().ip,368 nodegroup.get_managed_interface().ip,
369 context["cluster_host"])369 context["cluster_host"])
370370
371 def test_preseed_context_null_cluster_host_if_unmanaged(self):371 def test_preseed_context_cluster_host_if_unmanaged(self):
372 # If the nodegroup has no managed interface recorded, which is372 # If the nodegroup has no managed interface recorded, the cluster_host
373 # possible in the data model but would be a bit weird, the373 # context variable is still present and derived from the nodegroup.
374 # cluster_host context variable is present, but None.
375 release = factory.getRandomString()374 release = factory.getRandomString()
376 nodegroup = factory.make_node_group(maas_url=factory.getRandomString())375 nodegroup = factory.make_node_group(maas_url=factory.getRandomString())
377 for interface in nodegroup.nodegroupinterface_set.all():376 for interface in nodegroup.nodegroupinterface_set.all():
378 interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED377 interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED
379 interface.save()378 interface.save()
380 context = get_preseed_context(release, nodegroup)379 context = get_preseed_context(release, nodegroup)
381 self.assertIsNone(context["cluster_host"])380 self.assertIsNotNone(context["cluster_host"])
381 self.assertEqual(
382 nodegroup.get_any_interface().ip,
383 context["cluster_host"])
382384
383 def test_preseed_context_null_cluster_host_if_does_not_exist(self):385 def test_preseed_context_null_cluster_host_if_does_not_exist(self):
384 # If there's no nodegroup, the cluster_host context variable is386 # If there's no nodegroup, the cluster_host context variable is
@@ -525,6 +527,12 @@
525 preseed = get_preseed(node)527 preseed = get_preseed(node)
526 self.assertIn('preseed/late_command', preseed)528 self.assertIn('preseed/late_command', preseed)
527529
530 def test_get_preseed_returns_xinstall_preseed(self):
531 node = factory.make_node()
532 node.use_fastpath_installer()
533 preseed = get_preseed(node)
534 self.assertIn('# Disabled by default', preseed)
535
528 def test_get_enlist_preseed_returns_enlist_preseed(self):536 def test_get_enlist_preseed_returns_enlist_preseed(self):
529 preseed = get_enlist_preseed()537 preseed = get_enlist_preseed()
530 self.assertTrue(preseed.startswith('#cloud-config'))538 self.assertTrue(preseed.startswith('#cloud-config'))
531539
=== modified file 'src/provisioningserver/kernel_opts.py'
--- src/provisioningserver/kernel_opts.py 2013-02-01 01:07:25 +0000
+++ src/provisioningserver/kernel_opts.py 2013-05-11 18:43:26 +0000
@@ -124,11 +124,11 @@
124124
125def compose_purpose_opts(params):125def compose_purpose_opts(params):
126 """Return the list of the purpose-specific kernel options."""126 """Return the list of the purpose-specific kernel options."""
127 if params.purpose == "commissioning":127 if params.purpose == "commissioning" or params.purpose == "xinstall":
128 # These are kernel parameters read by the ephemeral environment.128 # These are kernel parameters read by the ephemeral environment.
129 tname = "%s:%s" % (ISCSI_TARGET_NAME_PREFIX,129 tname = "%s:%s" % (ISCSI_TARGET_NAME_PREFIX,
130 get_ephemeral_name(params.release, params.arch))130 get_ephemeral_name(params.release, params.arch))
131 return [131 kernel_params = [
132 # Read by the open-iscsi initramfs code.132 # Read by the open-iscsi initramfs code.
133 "iscsi_target_name=%s" % tname,133 "iscsi_target_name=%s" % tname,
134 "iscsi_target_ip=%s" % params.fs_host,134 "iscsi_target_ip=%s" % params.fs_host,
@@ -145,6 +145,9 @@
145 # Read by cloud-init.145 # Read by cloud-init.
146 "cloud-config-url=%s" % params.preseed_url,146 "cloud-config-url=%s" % params.preseed_url,
147 ]147 ]
148 if params.purpose == "xinstall":
149 kernel_params.append("ds=nocloud-net")
150 return kernel_params
148 else:151 else:
149 # These are options used by the Debian Installer.152 # These are options used by the Debian Installer.
150 return [153 return [
151154
=== added file 'src/provisioningserver/pxe/config.xinstall.template'
--- src/provisioningserver/pxe/config.xinstall.template 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/pxe/config.xinstall.template 2013-05-11 18:43:26 +0000
@@ -0,0 +1,20 @@
1DEFAULT execute
2
3SAY Booting under MAAS direction...
4SAY {{kernel_params() | kernel_command}}
5
6LABEL execute
7 KERNEL ifcpu64.c32
8 APPEND amd64 -- i386
9
10LABEL amd64
11 KERNEL {{kernel_params(arch="amd64") | kernel_path }}
12 INITRD {{kernel_params(arch="amd64") | initrd_path }}
13 APPEND {{kernel_params(arch="amd64") | kernel_command}}
14 IPAPPEND 2
15
16LABEL i386
17 KERNEL {{kernel_params(arch="i386") | kernel_path }}
18 INITRD {{kernel_params(arch="i386") | initrd_path }}
19 APPEND {{kernel_params(arch="i386") | kernel_command}}
20 IPAPPEND 2
021
=== modified file 'src/provisioningserver/pxe/install_image.py'
--- src/provisioningserver/pxe/install_image.py 2012-08-31 17:21:01 +0000
+++ src/provisioningserver/pxe/install_image.py 2013-05-11 18:43:26 +0000
@@ -70,7 +70,7 @@
70 return False70 return False
7171
7272
73def install_dir(new, old):73def install_dir(new, old, symlink=None):
74 """Install directory `new`, replacing directory `old` if it exists.74 """Install directory `new`, replacing directory `old` if it exists.
7575
76 This works as atomically as possible, but isn't entirely. Moreover,76 This works as atomically as possible, but isn't entirely. Moreover,
@@ -121,6 +121,13 @@
121 # Now delete the old image directory at leisure.121 # Now delete the old image directory at leisure.
122 rmtree('%s.old' % old, ignore_errors=True)122 rmtree('%s.old' % old, ignore_errors=True)
123123
124 # Symlink the new image directory to 'symlink'.
125 if symlink is not None:
126 sdest = "%s/%s" % (os.path.dirname(old), symlink)
127 if os.path.exists(sdest) or os.path.islink(sdest):
128 os.unlink(sdest)
129 os.symlink(old, sdest)
130
124131
125def add_arguments(parser):132def add_arguments(parser):
126 parser.add_argument(133 parser.add_argument(
@@ -138,6 +145,9 @@
138 parser.add_argument(145 parser.add_argument(
139 '--image', dest='image', default=None,146 '--image', dest='image', default=None,
140 help="Netboot image directory, containing kernel & initrd.")147 help="Netboot image directory, containing kernel & initrd.")
148 parser.add_argument(
149 '--symlink', dest='symlink', default=None,
150 help="Destination directory to symlink the installed images to.")
141151
142152
143def run(args):153def run(args):
@@ -154,5 +164,5 @@
154 tftproot, args.arch, args.subarch, args.release, args.purpose)164 tftproot, args.arch, args.subarch, args.release, args.purpose)
155 if not are_identical_dirs(destination, args.image):165 if not are_identical_dirs(destination, args.image):
156 # Image has changed. Move the new version into place.166 # Image has changed. Move the new version into place.
157 install_dir(args.image, destination)167 install_dir(args.image, destination, args.symlink)
158 rmtree(args.image, ignore_errors=True)168 rmtree(args.image, ignore_errors=True)
159169
=== modified file 'src/provisioningserver/pxe/tests/test_config.py'
--- src/provisioningserver/pxe/tests/test_config.py 2012-09-19 13:51:15 +0000
+++ src/provisioningserver/pxe/tests/test_config.py 2013-05-11 18:43:26 +0000
@@ -223,14 +223,22 @@
223 self.assertIn("chain.c32", output)223 self.assertIn("chain.c32", output)
224 self.assertNotIn("LOCALBOOT", output)224 self.assertNotIn("LOCALBOOT", output)
225225
226 def test_render_pxe_config_for_commissioning(self):226class TestRenderPXEConfigScenarios(TestCase):
227 """Tests for `provisioningserver.pxe.config.render_pxe_config`."""
228
229 scenarios = [
230 ("commissioning", dict(purpose="commissioning")),
231 ("xinstall", dict(purpose="xinstall")),
232 ]
233
234 def test_render_pxe_config_scenarios(self):
227 # The commissioning config uses an extra PXELINUX module to auto235 # The commissioning config uses an extra PXELINUX module to auto
228 # select between i386 and amd64.236 # select between i386 and amd64.
229 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")237 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
230 get_ephemeral_name.return_value = factory.make_name("ephemeral")238 get_ephemeral_name.return_value = factory.make_name("ephemeral")
231 options = {239 options = {
232 "kernel_params":240 "kernel_params":
233 make_kernel_parameters(purpose="commissioning"),241 make_kernel_parameters(purpose=self.purpose),
234 }242 }
235 output = render_pxe_config(**options)243 output = render_pxe_config(**options)
236 config = parse_pxe_config(output)244 config = parse_pxe_config(output)
237245
=== modified file 'src/provisioningserver/pxe/tests/test_install_image.py'
--- src/provisioningserver/pxe/tests/test_install_image.py 2012-08-31 15:41:18 +0000
+++ src/provisioningserver/pxe/tests/test_install_image.py 2013-05-11 18:43:26 +0000
@@ -209,3 +209,38 @@
209 self.assertEqual(209 self.assertEqual(
210 "rw-r--r--",210 "rw-r--r--",
211 target_dir.child("image").getPermissions().shorthand())211 target_dir.child("image").getPermissions().shorthand())
212
213 def test_install_dir_moves_dir_into_place_with_symlink(self):
214 download_image = os.path.join(self.make_dir(), 'download-image')
215 published_image = os.path.join(self.make_dir(), 'published-image')
216 base_path = os.path.dirname(published_image)
217 symlink_dest = 'xinstall'
218 contents = factory.getRandomString()
219 os.makedirs(download_image)
220 sample_file = factory.make_file(download_image, contents=contents)
221 install_dir(download_image, published_image, symlink_dest)
222 self.assertThat(
223 os.path.join(published_image, os.path.basename(sample_file)),
224 FileContains(contents))
225 self.assertThat(
226 os.path.join(base_path, symlink_dest, os.path.basename(sample_file)),
227 FileContains(contents))
228
229 def test_install_dir_replaces_existing_dir_with_symlink(self):
230 download_image = os.path.join(self.make_dir(), 'download-image')
231 published_image = os.path.join(self.make_dir(), 'published-image')
232 base_path = os.path.dirname(published_image)
233 symlink_dest = 'xinstall'
234 os.makedirs(download_image)
235 sample_file = factory.make_file(download_image)
236 os.makedirs(published_image)
237 os.symlink(published_image, os.path.join(base_path, symlink_dest))
238 obsolete_file = factory.make_file(published_image)
239 install_dir(download_image, published_image, symlink_dest)
240 self.assertThat(
241 os.path.join(published_image, os.path.basename(sample_file)),
242 FileExists())
243 self.assertThat(obsolete_file, Not(FileExists()))
244 self.assertThat(
245 os.path.join(base_path, symlink_dest, os.path.basename(sample_file)),
246 FileExists())
212247
=== modified file 'src/provisioningserver/tests/test_kernel_opts.py'
--- src/provisioningserver/tests/test_kernel_opts.py 2013-02-01 01:07:25 +0000
+++ src/provisioningserver/tests/test_kernel_opts.py 2013-05-11 18:43:26 +0000
@@ -118,6 +118,22 @@
118 "netcfg/choose_interface=auto",118 "netcfg/choose_interface=auto",
119 compose_kernel_command_line(params))119 compose_kernel_command_line(params))
120120
121 def test_xinstall_compose_kernel_command_line_inc_purpose_opts(self):
122 # The result of compose_kernel_command_line includes the purpose
123 # options for a non "xinstall" node.
124 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
125 get_ephemeral_name.return_value = "RELEASE-ARCH"
126 params = make_kernel_parameters(purpose="xinstall")
127 cmdline = compose_kernel_command_line(params)
128 self.assertThat(
129 cmdline,
130 ContainsAll([
131 "ds=nocloud-net",
132 "root=/dev/disk/by-path/ip-",
133 "iscsi_initiator=",
134 "overlayroot=tmpfs",
135 "ip=::::%s:BOOTIF" % params.hostname]))
136
121 def test_commissioning_compose_kernel_command_line_inc_purpose_opts(self):137 def test_commissioning_compose_kernel_command_line_inc_purpose_opts(self):
122 # The result of compose_kernel_command_line includes the purpose138 # The result of compose_kernel_command_line includes the purpose
123 # options for a non "commissioning" node.139 # options for a non "commissioning" node.
@@ -147,8 +163,8 @@
147 self.assertNotIn(cmdline, "None")163 self.assertNotIn(cmdline, "None")
148164
149 def test_compose_kernel_command_line_inc_common_opts(self):165 def test_compose_kernel_command_line_inc_common_opts(self):
150 # Test that some kernel arguments appear on both commissioning166 # Test that some kernel arguments appear on commissioning, install
151 # and install command lines.167 # and xinstall command lines.
152 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")168 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
153 get_ephemeral_name.return_value = "RELEASE-ARCH"169 get_ephemeral_name.return_value = "RELEASE-ARCH"
154 expected = ["nomodeset"]170 expected = ["nomodeset"]
@@ -159,6 +175,11 @@
159 self.assertThat(cmdline, ContainsAll(expected))175 self.assertThat(cmdline, ContainsAll(expected))
160176
161 params = make_kernel_parameters(177 params = make_kernel_parameters(
178 purpose="xinstall", arch="i386")
179 cmdline = compose_kernel_command_line(params)
180 self.assertThat(cmdline, ContainsAll(expected))
181
182 params = make_kernel_parameters(
162 purpose="install", arch="i386")183 purpose="install", arch="i386")
163 cmdline = compose_kernel_command_line(params)184 cmdline = compose_kernel_command_line(params)
164 self.assertThat(cmdline, ContainsAll(expected))185 self.assertThat(cmdline, ContainsAll(expected))
@@ -182,6 +203,23 @@
182 factory.make_file(203 factory.make_file(
183 ephemeral_dir, name='info', contents=ephemeral_info)204 ephemeral_dir, name='info', contents=ephemeral_info)
184205
206 def test_compose_kernel_command_line_inc_purpose_opts_xinstall_node(self):
207 # The result of compose_kernel_command_line includes the purpose
208 # options for a "xinstall" node.
209 ephemeral_name = factory.make_name("ephemeral")
210 params = make_kernel_parameters(purpose="xinstall")
211 self.create_ephemeral_info(
212 ephemeral_name, params.arch, params.release)
213 self.assertThat(
214 compose_kernel_command_line(params),
215 ContainsAll([
216 "ds=nocloud-net",
217 "iscsi_target_name=%s:%s" % (
218 ISCSI_TARGET_NAME_PREFIX, ephemeral_name),
219 "iscsi_target_port=3260",
220 "iscsi_target_ip=%s" % params.fs_host,
221 ]))
222
185 def test_compose_kernel_command_line_inc_purpose_opts_comm_node(self):223 def test_compose_kernel_command_line_inc_purpose_opts_comm_node(self):
186 # The result of compose_kernel_command_line includes the purpose224 # The result of compose_kernel_command_line includes the purpose
187 # options for a "commissioning" node.225 # options for a "commissioning" node.