mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-07 01:56:56 +00:00
test(provtest): assert what xCAT does rather than what the spec assumed
Running the scenarios against a live management node found nine places where they asserted something xCAT does not do. Corrected here, each with the reason in the file: - install configs carry no xcatd= or destiny=; those are written for the genesis states only. - #END OF SCRIPT is plain-transport framing; over TLS a reply ends with <serverdone>. - getcredentials is named in <arg> and answered as <data><content/> <desc/></data>, so both fields are addressable now. - currstate for an install is "install <osimage>", so the operator is starts-with. - bootparams is empty for an ordinary install, so kernel and initrd cannot be asserted from getdestiny. - a server never reached sends no <error>; assert the handshake instead. - an NXDOMAIN answer is the expected result of a removal scenario. - P-75 has to precede P-74: a withdrawn name cannot be nodeset again. Also adds netutil.hex_net: a per-network config is named by as many hex digits as the netmask covers, not by all eight.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Python leaves these next to the source it imports, and provtest is run in
|
||||
# place -- from a checkout during development and from
|
||||
# /opt/xcat/share/xcat/tools/autotest after packaging -- so they appear in the
|
||||
# tree rather than in a build directory.
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -7,10 +7,21 @@
|
||||
# never fetches them, and the day someone racks a new machine, nothing works and
|
||||
# nothing says why.
|
||||
#
|
||||
# The name is the network address in hex truncated to the netmask, because that
|
||||
# is what a loader asks for once it has failed to find a file for its own
|
||||
# address: 10.99.1.0/24 is 0A6301, six digits and not eight.
|
||||
#
|
||||
# Three loader families, three scenarios, and a cluster runs whichever of them
|
||||
# its architecture reaches: pxelinux and xnba for x86_64, grub2 for the UEFI
|
||||
# architectures mknb writes a grub2 configuration for. They are selected with
|
||||
# `-s` rather than skipped from inside, so a scenario that does not run says so
|
||||
# by its absence and never by a pass.
|
||||
#
|
||||
# provtest run \
|
||||
# --set server=10.99.1.1 --set client=10.99.1.11 \
|
||||
# --set hexnet=0A630100 --set net=10.99.1.0 \
|
||||
# --set hexnet=0A6301 --set netfile=10.99.1.0_24 \
|
||||
# --set master=10.99.1.1 --set xcatport=3001 \
|
||||
# -s pxelinux-discovery-config -s genesis-images-pxelinux \
|
||||
# conf/discovery-artefacts.conf
|
||||
|
||||
[defaults]
|
||||
@@ -29,9 +40,7 @@ type = tftp
|
||||
path = boot/grub2/grub.cfg-%(hexnet)s
|
||||
assert =
|
||||
size > 0
|
||||
# P-27: the destiny a machine with no definition must be given.
|
||||
text contains destiny=discover
|
||||
# P-28: and where to ask for the next one.
|
||||
# P-28: where to ask for the next destiny.
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
|
||||
[step httpentry]
|
||||
@@ -62,14 +71,83 @@ description = The pxelinux discovery config answers under the hex network addres
|
||||
[step pxeconfig]
|
||||
type = tftp
|
||||
path = pxelinux.cfg/%(hexnet)s
|
||||
# No `destiny=discover` here, deliberately: genesis asks xcatd what to do when
|
||||
# the kernel command line does not tell it, and a machine nobody has defined is
|
||||
# answered `discover`. The address it asks is therefore the whole content of
|
||||
# this file, and a wrong one strands the machine in genesis with no error
|
||||
# anywhere.
|
||||
assert =
|
||||
size > 0
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
text contains genesis.kernel
|
||||
|
||||
# --- P-29, the other x86_64 loader ------------------------------------------
|
||||
|
||||
[scenario xnba-discovery-config]
|
||||
description = The xnba discovery scripts for the network exist and name xcatd
|
||||
|
||||
[step gpxe]
|
||||
type = tftp
|
||||
path = xcat/xnba/nets/%(netfile)s
|
||||
assert =
|
||||
size > 0
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
|
||||
[step uefi]
|
||||
type = tftp
|
||||
path = xcat/xnba/nets/%(netfile)s.uefi
|
||||
# The UEFI script does say `destiny=discover` where the BIOS one leaves it to
|
||||
# xcatd, so this is the one place the word itself is assertable.
|
||||
assert =
|
||||
size > 0
|
||||
text contains destiny=discover
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
|
||||
# --- P-30, P-31 -------------------------------------------------------------
|
||||
|
||||
[scenario genesis-kernel-and-initrd]
|
||||
description = The genesis kernel and initrd named by the discovery config are fetchable
|
||||
[scenario genesis-images-pxelinux]
|
||||
description = The genesis kernel and initrd named by the pxelinux config are fetchable
|
||||
|
||||
[step pconfig]
|
||||
type = tftp
|
||||
path = pxelinux.cfg/%(hexnet)s
|
||||
assert =
|
||||
size > 0
|
||||
|
||||
[step pkernelpath]
|
||||
type = extract
|
||||
from = $pconfig.text
|
||||
pattern = ^\s*KERNEL\s+/?(\S+)
|
||||
assert =
|
||||
matched == yes
|
||||
|
||||
[step pkernel]
|
||||
type = tftp
|
||||
path = $pkernelpath.value
|
||||
assert =
|
||||
size > 0
|
||||
|
||||
[step pinitrdpath]
|
||||
type = extract
|
||||
from = $pconfig.text
|
||||
pattern = \binitrd=(\S+)
|
||||
assert =
|
||||
matched == yes
|
||||
|
||||
[step pinitrd]
|
||||
type = tftp
|
||||
path = $pinitrdpath.value
|
||||
# Tens of megabytes over TFTP, which is slow enough to need its own timeout.
|
||||
# It is fetched rather than merely looked for because a half-written initrd
|
||||
# from an interrupted mknb is exactly the failure this catches, and a file that
|
||||
# exists is not the same as a file that transfers.
|
||||
timeout = 120
|
||||
retries = 1
|
||||
assert =
|
||||
size > 0
|
||||
|
||||
[scenario genesis-images-grub2]
|
||||
description = The genesis kernel and initrd named by the grub2 config are fetchable
|
||||
|
||||
[step gconfig]
|
||||
type = tftp
|
||||
@@ -100,10 +178,6 @@ assert =
|
||||
[step initrd]
|
||||
type = tftp
|
||||
path = $initrdpath.value
|
||||
# Tens of megabytes over TFTP, which is slow enough to need its own timeout.
|
||||
# It is fetched rather than merely looked for because a half-written initrd
|
||||
# from an interrupted mknb is exactly the failure this catches, and a file that
|
||||
# exists is not the same as a file that transfers.
|
||||
timeout = 120
|
||||
retries = 1
|
||||
assert =
|
||||
|
||||
@@ -23,6 +23,10 @@ description = A withdrawn node resolves in neither direction
|
||||
type = dns
|
||||
name = %(node)s.%(domain)s
|
||||
rrtype = A
|
||||
# NXDOMAIN is the passing result here, and the client calls only NOERROR ok,
|
||||
# so the step has to say it expects no particular outcome and assert the
|
||||
# status itself.
|
||||
expect = any
|
||||
assert =
|
||||
status == NXDOMAIN
|
||||
count == 0
|
||||
@@ -31,6 +35,7 @@ assert =
|
||||
type = dns
|
||||
name = %(revname)s
|
||||
rrtype = PTR
|
||||
expect = any
|
||||
# A PTR left behind by a removal is the worse half of the two: the name is
|
||||
# gone, so nothing resolves the node forward, but the address still claims to
|
||||
# be it, and xcatd will act on that claim.
|
||||
|
||||
@@ -100,8 +100,10 @@ description = An undefined name in the cluster domain is refused, not forwarded
|
||||
type = dns
|
||||
name = %(missing)s.%(domain)s
|
||||
rrtype = A
|
||||
# NXDOMAIN is the answer under test, and dig reports it with rc=0, so the
|
||||
# transport still succeeded. `expect` stays at its default.
|
||||
expect = any
|
||||
# `expect = any`: NXDOMAIN *is* the answer under test. The query reached the
|
||||
# server and was answered authoritatively, which is a working resolver and not
|
||||
# a failed one, so what is asserted is the status and not the fetch.
|
||||
assert =
|
||||
status == NXDOMAIN
|
||||
count == 0
|
||||
|
||||
@@ -165,3 +165,34 @@ expect = fail
|
||||
# The control: if every port answered, the step above would prove nothing.
|
||||
assert =
|
||||
status == 0
|
||||
|
||||
# --- P-39, the other half of it ---------------------------------------------
|
||||
|
||||
[scenario default-port-the-node-was-told]
|
||||
description = A cluster on the default port names no port, and the default one answers
|
||||
|
||||
[step defaultconfig]
|
||||
type = tftp
|
||||
path = %(knownfile)s
|
||||
port = 69
|
||||
assert =
|
||||
size > 0
|
||||
|
||||
[step configroot]
|
||||
type = extract
|
||||
from = $defaultconfig.text
|
||||
pattern = set root=http,(\S+)
|
||||
# On site.httpport 80 the plugins write the address alone, and the loader uses
|
||||
# the protocol default. A port appearing here would be a node told to fetch
|
||||
# from a port nothing was configured for, which is the same failure as the
|
||||
# scenario above with the sign reversed -- so the assertion is that there is
|
||||
# no colon, not that the port is right.
|
||||
assert =
|
||||
matched == yes
|
||||
value matches ^[^:]+$
|
||||
|
||||
[step defaultserved]
|
||||
type = http
|
||||
url = http://$configroot.value/tftpboot/%(knownfile)s
|
||||
assert =
|
||||
status == 200
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
# from the outside -- the node times out -- and telling them apart means
|
||||
# watching a console during a reboot.
|
||||
#
|
||||
# P-74 needs the node's PTR removed first, and P-75 needs a second nodeset, so
|
||||
# the fixture drives them in that order and the scenarios are selected with -s
|
||||
# P-75 needs a second nodeset and P-74 needs the node's name withdrawn from
|
||||
# resolution, and that order cannot be reversed: nodeset names the config it
|
||||
# writes after the node's address, which it gets by resolving the node, so once
|
||||
# the name is gone there is no second nodeset to be had. The fixture therefore
|
||||
# drives P-75 first and P-74 last, and the scenarios are selected with -s
|
||||
# rather than all run at once.
|
||||
#
|
||||
# provtest run \
|
||||
@@ -48,8 +51,14 @@ expect = fail
|
||||
# from the console that looks identical to a protocol fault at a server that
|
||||
# is up. Here the two are told apart without booting anything.
|
||||
assert =
|
||||
handshake == no
|
||||
error present
|
||||
# No handshake and no answer, against a step that got both from the real
|
||||
# address a moment earlier. `error` is deliberately not asserted: that
|
||||
# field carries an <error> element the server sent, and a server that was
|
||||
# never reached sends nothing at all -- which is the distinction being
|
||||
# drawn, not an omission in the assertion.
|
||||
handshake == no
|
||||
ok == no
|
||||
serverdone == no
|
||||
|
||||
# --- P-74 -------------------------------------------------------------------
|
||||
# Run this only after the node's PTR has been removed.
|
||||
@@ -80,7 +89,15 @@ type = tftp
|
||||
path = boot/grub2/grub.cfg-%(hexip)s
|
||||
assert =
|
||||
size > 0
|
||||
text starts-with #%(second)s
|
||||
# P-17 for a genesis state, where it is written on the kernel command line
|
||||
# as well as on the first line: genesis is what reads it.
|
||||
text contains destiny=%(second)s
|
||||
# P-16, and this is the only place it can be asserted: xcatd= is written
|
||||
# for the genesis states and not for an OS install, because it is genesis
|
||||
# that asks xcatd what to do next. A node put into one of these states
|
||||
# with the wrong address here waits for an answer that cannot arrive.
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
|
||||
[step stale]
|
||||
type = extract
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Stage 3: TFTP, grub2. Spec P-09, P-11 to P-17, P-24, P-70, P-71.
|
||||
# Stage 3: TFTP, grub2. Spec P-09, P-11 to P-15, P-17, P-24, P-70, P-71.
|
||||
#
|
||||
# grub2 asks for three things in order: its own binary, a per-node config named
|
||||
# after the client, and whatever that config names. Each fetch is by exact
|
||||
@@ -51,13 +51,22 @@ type = tftp
|
||||
path = boot/grub2/grub.cfg-%(hexip)s
|
||||
assert =
|
||||
size > 0
|
||||
# P-17: the state nodeset was told to put the node in. xCAT writes it on
|
||||
# the first line of the file it generates, which is also where nodestat
|
||||
# reads it back from -- so a config whose first line disagrees with the
|
||||
# database is a node whose reported state is not the one it will boot.
|
||||
text starts-with #%(destiny)s
|
||||
# P-15: the installer picks its interface from BOOTIF. A node with two
|
||||
# ports that is handed the wrong one installs onto the wrong network.
|
||||
text contains BOOTIF=01-%(macdashes)s
|
||||
# P-16: where to talk xCAT to, and on which port.
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
# P-17: what nodeset was told to make it do.
|
||||
text contains destiny=%(destiny)s
|
||||
# grub2 writes $net_default_mac, the variable the loader expands to the
|
||||
# interface it booted from, rather than the MAC xCAT has on file; either
|
||||
# is a correct answer and an absent BOOTIF is not.
|
||||
text matches BOOTIF=(\$net_default_mac|01-%(macdashes)s)
|
||||
# P-16 is not asserted here: xcatd= and destiny= appear on the kernel
|
||||
# command line only for the genesis states, and an OS install is booted
|
||||
# with the distribution installer's command line. The genesis form is
|
||||
# asserted where a node is put into one -- see the ordering and discovery
|
||||
# scenarios.
|
||||
|
||||
# --- P-12 -------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -26,8 +26,16 @@ type = tftp
|
||||
path = petitboot/%(node)s
|
||||
assert =
|
||||
size > 0
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
text contains destiny=%(destiny)s
|
||||
# The state nodeset was told to write, on the first line, as for the other
|
||||
# loader families. xcatd= and destiny= are not asserted here: they are
|
||||
# written on the kernel command line only for the genesis states, and an OS
|
||||
# install is booted with the distribution installer's command line.
|
||||
text starts-with #%(destiny)s
|
||||
# petitboot reads one kernel line and one initrd line; without them the
|
||||
# firmware shows an empty boot menu and waits, which from a console looks
|
||||
# exactly like a node that never got a config at all.
|
||||
text contains kernel
|
||||
text contains initrd
|
||||
|
||||
[step byhex]
|
||||
type = tftp
|
||||
|
||||
@@ -32,8 +32,12 @@ path = pxelinux.cfg/%(node)s
|
||||
assert =
|
||||
size > 0
|
||||
text contains DEFAULT
|
||||
text contains xcatd=%(master)s:%(xcatport)s
|
||||
text contains destiny=%(destiny)s
|
||||
# The state nodeset was told to write, on the first line, as for grub2.
|
||||
text starts-with #%(destiny)s
|
||||
# pxelinux's answer to the question BOOTIF answers for grub2: IPAPPEND 2
|
||||
# makes the loader append BOOTIF itself, from the interface it booted on.
|
||||
# Without it the installer picks an interface of its own choosing.
|
||||
text contains IPAPPEND 2
|
||||
|
||||
[step byhex]
|
||||
type = tftp
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
# provtest run \
|
||||
# --set server=10.99.1.1 --set client=10.99.1.11 \
|
||||
# --set node=provtestcn --set nextserver=10.99.1.1 \
|
||||
# --set node=provtestcn --set httpport=80 \
|
||||
# conf/tftp-xnba.conf
|
||||
|
||||
[defaults]
|
||||
@@ -33,21 +33,23 @@ assert =
|
||||
[step kernelurl]
|
||||
type = extract
|
||||
from = $script.text
|
||||
pattern = imgfetch\s+(?:-n\s+\S+\s+)?(\S+)
|
||||
pattern = imgfetch\s+(?:-n\s+\S+\s+)?https?://[^/\s]+(/\S+)
|
||||
# The host is deliberately not asserted, because xnba deliberately does not
|
||||
# write one: the script says ${next-server}, the variable gpxe fills in from
|
||||
# the DHCP acknowledgement, so the node fetches from whoever booted it -- its
|
||||
# service node in a hierarchical cluster, and not necessarily the management
|
||||
# node. Which address arrives in that acknowledgement is dhcptest's subject.
|
||||
# What is assertable here is the path, and that it is served.
|
||||
assert =
|
||||
matched == yes
|
||||
# The host in the URL is the address DHCP handed out as next-server. xnba
|
||||
# builds it from the node's own attributes, so a node in a hierarchical
|
||||
# cluster that is pointed at the management node instead of its service
|
||||
# node fetches across a link that may not carry it.
|
||||
value starts-with http://%(nextserver)s
|
||||
|
||||
[step kernel]
|
||||
type = http
|
||||
url = $kernelurl.value
|
||||
# The assertion that matters: the URL the node was told to use answers. Not a
|
||||
# URL this file built out of the same parts, which would only prove that two
|
||||
# copies of the same assumption agree.
|
||||
path = $kernelurl.value
|
||||
port = %(httpport)s
|
||||
# The assertion that matters: the path the node was told to fetch answers.
|
||||
# Not a path this file built out of the same parts, which would only prove
|
||||
# that two copies of the same assumption agree.
|
||||
assert =
|
||||
status == 200
|
||||
size > 0
|
||||
|
||||
@@ -9,9 +9,15 @@
|
||||
# "a signed cluster certificate", so both halves are asserted: that it is made,
|
||||
# and that a client which cannot answer it gets nothing.
|
||||
#
|
||||
# The credential asked for is named in <arg>, exactly as getcredentials.awk
|
||||
# names it when remoteshell and xcatserver run on the installing node. Genesis
|
||||
# asks for x509cert instead and encloses a CSR; that path is not exercised here
|
||||
# because a CSR is not something a wire test can fabricate meaningfully, and
|
||||
# the callback is the same callback either way.
|
||||
#
|
||||
# provtest run \
|
||||
# --set server=10.99.1.1 --set client=10.99.1.11 \
|
||||
# --set xcatport=3001 --set credtype=xcat \
|
||||
# --set xcatport=3001 --set credtype=xcat_server_cred \
|
||||
# conf/xcatd-credentials.conf
|
||||
|
||||
[defaults]
|
||||
@@ -31,7 +37,7 @@ description = A client that answers the callback is given a signed certificate
|
||||
type = xcatreq
|
||||
command = getcredentials
|
||||
element =
|
||||
credentials = %(credtype)s
|
||||
arg = %(credtype)s
|
||||
# The listener goes up on the client's port 300 before the request is sent,
|
||||
# because the server connects back while the request is still open: a listener
|
||||
# started afterwards would be started too late.
|
||||
@@ -42,8 +48,12 @@ assert =
|
||||
# P-63: the callback arrived, and on the port the request named.
|
||||
callback_seen == yes
|
||||
callback_data contains CREDOKBYYOU
|
||||
# P-62: and the answer carries something signed.
|
||||
data present
|
||||
# P-62: and the answer carries the credential that was asked for. The
|
||||
# payload is wrapped as <data><content>..</content><desc>..</desc></data>
|
||||
# (credentials.pm:362), so `content` is where to look and `desc` says
|
||||
# which of several credentials a reply belongs to.
|
||||
desc == %(credtype)s
|
||||
content contains -----BEGIN
|
||||
error absent
|
||||
|
||||
# --- P-61 -------------------------------------------------------------------
|
||||
@@ -55,11 +65,33 @@ description = A client with no listener on the callback port is given nothing
|
||||
type = xcatreq
|
||||
command = getcredentials
|
||||
element =
|
||||
credentials = %(credtype)s
|
||||
arg = %(credtype)s
|
||||
# No callback_listen: the port is closed, the server's connection is refused,
|
||||
# and the challenge goes unanswered. Anything signed arriving here would mean
|
||||
# an address alone is enough to collect a cluster certificate.
|
||||
expect = any
|
||||
assert =
|
||||
callback_seen == no
|
||||
data absent
|
||||
content absent
|
||||
|
||||
# --- P-61 -------------------------------------------------------------------
|
||||
|
||||
[scenario credentials-nameless]
|
||||
description = A request that names no credential is refused without a callback
|
||||
|
||||
[step nameless]
|
||||
type = xcatreq
|
||||
command = getcredentials
|
||||
# No <arg>: the request is well formed as XML and says nothing about what it
|
||||
# wants. xcatd used to dereference the missing element and hand the client the
|
||||
# resulting Perl error, file and line included, over a connection anything on
|
||||
# the provisioning network can open. It must refuse instead, and must not
|
||||
# spend a callback on a request it cannot serve.
|
||||
callback_listen = 300
|
||||
callback_reply = CREDOKBYME
|
||||
callback_wait = 5
|
||||
expect = any
|
||||
assert =
|
||||
content absent
|
||||
callback_seen == no
|
||||
error absent
|
||||
|
||||
@@ -61,15 +61,18 @@ description = A known node is told its state, and everything it needs to reach i
|
||||
type = xcatreq
|
||||
command = getdestiny
|
||||
assert =
|
||||
# P-51
|
||||
destiny == %(destiny)s
|
||||
# P-52: an install destiny that names no kernel is a node that boots
|
||||
# nothing and reports success.
|
||||
kernel present
|
||||
initrd present
|
||||
# P-53
|
||||
kcmdline contains xcatd=%(master)s:%(xcatport)s
|
||||
kcmdline contains destiny=%(destiny)s
|
||||
# P-51. The state is the whole chain.currstate string, which for an OS
|
||||
# install is the word followed by the osimage nodeset resolved -- "install
|
||||
# rhels9.99-x86_64-compute" and not "install". genesis matches on the
|
||||
# first word, so that is what is asserted.
|
||||
destiny starts-with %(destiny)s
|
||||
# P-52 and P-53 are not asserted here, and the reason is worth writing
|
||||
# down. getdestiny copies kernel, initrd and kcmdline out of the
|
||||
# bootparams table (destiny.pm:968-976), and nothing in an ordinary
|
||||
# install writes a row there: the loader config under $tftpdir is what
|
||||
# names the kernel, and genesis uses the destiny field alone. bootparams
|
||||
# is populated by the iSCSI and hypervisor paths instead. Asserting the
|
||||
# three fields here would fail on every healthy cluster.
|
||||
# P-55: destiny.pm:975-987 tries noderes.tftpserver, then
|
||||
# noderes.xcatmaster, then the network's tftpserver, then site.master.
|
||||
# Four sources, no error if the wrong one wins -- the node boots and then
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
# The node fetches the script it is to run after the installer finishes, and it
|
||||
# fetches it over whichever transport it has: TLS 3001 during genesis,
|
||||
# plain 3002 from `xcatdsklspost` inside the installed system. Two paths, one
|
||||
# script, and the client reads until `#END OF SCRIPT` -- so a body that is
|
||||
# truncated without the marker is read as a hang, not as an error.
|
||||
# script, and two framings for it, which is the thing to keep straight when
|
||||
# reading the assertions below. Over 3001 the body arrives as <data> elements
|
||||
# and is ended by <serverdone>. Over 3002 there is no envelope at all, so
|
||||
# xcatd writes `#END OF SCRIPT` after the last line (xcatd:554) and the client
|
||||
# reads until it -- a body truncated without the marker is read as a hang and
|
||||
# not as an error. The marker is therefore asserted on the plain transport
|
||||
# only; asserting it on the TLS one would be asserting the wrong protocol.
|
||||
#
|
||||
# provtest run \
|
||||
# --set server=10.99.1.1 --set client=10.99.1.11 \
|
||||
@@ -21,14 +26,18 @@ retries = 2
|
||||
# --- P-57 -------------------------------------------------------------------
|
||||
|
||||
[scenario postscript-terminated]
|
||||
description = A known node's postscript arrives complete, with its end marker
|
||||
description = A known node's postscript arrives complete and is ended by the server
|
||||
|
||||
[step getpostscript]
|
||||
type = xcatreq
|
||||
port = %(xcatport)s
|
||||
command = getpostscript
|
||||
assert =
|
||||
text contains #END OF SCRIPT
|
||||
# The envelope's end marker on this transport. A response without it is a
|
||||
# connection the node is still waiting on.
|
||||
serverdone == yes
|
||||
# And the body is this node's: the script is built per node, and a node
|
||||
# that runs somebody else's sets up somebody else's network and keys.
|
||||
text contains %(node)s
|
||||
|
||||
# --- P-58 -------------------------------------------------------------------
|
||||
@@ -63,16 +72,17 @@ type = xcatreq
|
||||
port = %(xcatport)s
|
||||
command = getpostscript
|
||||
assert =
|
||||
text contains #END OF SCRIPT
|
||||
serverdone == yes
|
||||
|
||||
[step marker]
|
||||
type = extract
|
||||
from = $overtls.text
|
||||
# A line from the body that is specific to this node and to this run, used
|
||||
# below as the thing the other transport has to agree about. The two framings
|
||||
# differ -- XML elements on one side, bare lines on the other -- so a byte
|
||||
# comparison of the responses would compare the envelopes, not the script.
|
||||
pattern = ^\s*(NODE=\S+|MASTER=\S+)\s*$
|
||||
# An assignment from the body that is specific to this node, used below as the
|
||||
# thing the other transport has to agree about. The two framings differ --
|
||||
# XML elements on one side, bare lines on the other -- so a byte comparison of
|
||||
# the responses would compare the envelopes and not the script. Unanchored for
|
||||
# the same reason: on this transport the line is wrapped in <data>.
|
||||
pattern = (NODE=\S+)
|
||||
assert =
|
||||
matched == yes
|
||||
|
||||
|
||||
@@ -65,7 +65,12 @@ REPLY_FIELDS = {
|
||||
"xcatreq": frozenset(["destiny", "kernel", "initrd", "kcmdline",
|
||||
"imgserver", "name", "error", "serverdone",
|
||||
"elements", "data", "text", "handshake", "ok",
|
||||
"callback_seen", "callback_data", "raw"]),
|
||||
"callback_seen", "callback_data", "raw",
|
||||
# getcredentials wraps its payload one level deeper,
|
||||
# as <data><content/><desc/></data>, and `desc` is
|
||||
# how a reply carrying several credentials says
|
||||
# which one each part is.
|
||||
"content", "desc"]),
|
||||
"monitor": frozenset(["greeting", "lines", "raw", "text", "ok", "closed",
|
||||
"error"]),
|
||||
"flowrequest": frozenset(["replies", "count", "ok", "error", "raw"]),
|
||||
|
||||
@@ -63,6 +63,26 @@ def hex_ip(address, upper=True):
|
||||
return text if upper else text.lower()
|
||||
|
||||
|
||||
def hex_net(address, prefix, upper=True):
|
||||
"""The name a per-network configuration is written under.
|
||||
|
||||
A loader that finds no file for its own address asks for ever shorter
|
||||
prefixes of it, so the per-network file is named by as many hex digits as
|
||||
the netmask covers, rounded up to a whole digit: 10.99.1.0/24 is 0A6301,
|
||||
six digits and not eight. Anything defined for a network rather than for a
|
||||
node -- which is everything a machine nobody has defined can reach -- is
|
||||
named this way.
|
||||
"""
|
||||
text = hex_ip(address, upper=upper)
|
||||
try:
|
||||
bits = int(prefix)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("not a prefix length: %r" % (prefix,))
|
||||
if not 0 <= bits <= 32:
|
||||
raise ValueError("not a prefix length: %r" % (prefix,))
|
||||
return text[:(bits + 3) // 4]
|
||||
|
||||
|
||||
def reverse_name(address):
|
||||
"""The IN-ADDR.ARPA name a resolver is asked for a PTR by."""
|
||||
ip = parse_ip(address)
|
||||
|
||||
@@ -33,7 +33,7 @@ from .model import Reply
|
||||
#: The elements a node's own scripts read out of a response. Everything else
|
||||
#: found in the XML is still available through `elements.<name>`.
|
||||
NAMED_ELEMENTS = ("destiny", "kernel", "initrd", "kcmdline", "imgserver",
|
||||
"name", "data", "error")
|
||||
"name", "data", "error", "content", "desc")
|
||||
|
||||
ELEMENT_RE = re.compile(r"<([A-Za-z_][\w.-]*)>([^<]*)</\1>")
|
||||
|
||||
|
||||
@@ -134,9 +134,22 @@ class AddressTests(unittest.TestCase):
|
||||
self.assertEqual(netutil.hex_ip("10.99.1.11", upper=False), "0a63010b")
|
||||
|
||||
def test_hex_ip_of_a_network_address(self):
|
||||
# The per-network discovery config is named by the network's own hex.
|
||||
self.assertEqual(netutil.hex_ip("10.99.1.0"), "0A630100")
|
||||
|
||||
def test_a_per_network_config_is_named_by_the_prefix_only(self):
|
||||
# 10.99.1.0/24 is 0A6301, not 0A630100: the file is named by as many
|
||||
# digits as the netmask covers. Asking for all eight gets a machine
|
||||
# with no definition nothing at all, which is the whole failure this
|
||||
# stage exists to catch.
|
||||
self.assertEqual(netutil.hex_net("10.99.1.0", 24), "0A6301")
|
||||
self.assertEqual(netutil.hex_net("10.99.0.0", 16), "0A63")
|
||||
self.assertEqual(netutil.hex_net("10.99.1.0", 26), "0A63010")
|
||||
self.assertEqual(netutil.hex_net("10.99.1.0", 24, upper=False), "0a6301")
|
||||
|
||||
def test_a_prefix_that_is_not_one_is_refused(self):
|
||||
self.assertRaises(ValueError, netutil.hex_net, "10.99.1.0", 33)
|
||||
self.assertRaises(ValueError, netutil.hex_net, "10.99.1.0", "wide")
|
||||
|
||||
def test_hex_ip_rejects_what_is_not_an_address(self):
|
||||
self.assertRaises(ValueError, netutil.hex_ip, "provtestcn")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user